Resolving conflicts with remote trunk

This commit is contained in:
Chadtech 2020-11-22 13:54:13 -05:00
commit 385d0fda9f
209 changed files with 29256 additions and 8806 deletions

View file

@ -52,13 +52,14 @@ jobs:
args: -- -D warnings
- uses: actions-rs/cargo@v1
name: cargo test
name: cargo test -- --ignored
continue-on-error: true
with:
command: test
args: --no-fail-fast
args: -- --ignored
- uses: actions-rs/cargo@v1
name: cargo test --release
with:
command: test
args: --release --no-fail-fast
args: --release

3
.gitignore vendored
View file

@ -4,3 +4,6 @@ target
#valgrind
vgcore.*
#editors
.idea/

View file

@ -1,21 +1,55 @@
# Building the Roc compiler from source
## Installing LLVM, 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`
* a particular version of LLVM
* Python 2.7 (Windows only), `python-is-python3` (Ubuntu)
* a particular version of Zig (see below)
* a particular version of LLVM (see below)
To run the test suite (via `cargo test`), you additionally need to install:
* [`valgrind`](https://www.valgrind.org/) (needs special treatment to [install on macOS](https://stackoverflow.com/a/61359781)]
* [`valgrind`](https://www.valgrind.org/) (needs special treatment to [install on macOS](https://stackoverflow.com/a/61359781)
Alternatively, you can use `cargo test --no-fail-fast` or `cargo test -p specific_tests` to skip over the valgrind failures & tests.
Some systems may already have `libc++-dev` on them, but if not, you may need to install it. (On Ubuntu, this can be done with `sudo apt-get install libc++-dev`.) macOS systems
should already have `libunwind`, but other systems will need to install it
(e.g. with `sudo apt-get install libunwind-dev`).
### libunwind & libc++-dev
MacOS systems should already have `libunwind`, but other systems will need to install it (On Ubuntu, this can be donw with `sudo apt-get install libunwind-dev`).
Some systems may already have `libc++-dev` on them, but if not, you may need to install it. (On Ubuntu, this can be done with `sudo apt-get install libc++-dev`.)
### Zig
We use a specific version of Zig, a build off the the commit `0088efc4b`. The latest tagged version of Zig, 0.6.0, doesn't include the feature to emit LLVM ir, which is a core feature of how we use Zig. To download this specific version, you can:
* use the following commands on Debian/Ubuntu (on other distros, steps should be essentially the same):
```
cd /tmp
# download the files
wget https://ziglang.org/builds/zig-linux-x86_64-0.6.0+0088efc4b.tar.xz
# uncompress:
xz -d zig-linux-x86_64-0.6.0+0088efc4b.tar.xz
# untar:
tar xvf zig-linux-x86_64-0.6.0+0088efc4b.tar
# move the files into /opt:
sudo mkdir -p /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`).
Reload your `.bashrc` file: `source ~/.bashrc` and test that `zig` is
an available command.
* [macOS](https://ziglang.org/builds/zig-macos-x86_64-0.6.0+0088efc4b.tar.xz)
Alternatively, any recent master branch build should work. To install the latest master branch build you can use:
* `brew install zig --HEAD` (on macos)
* `snap install zig --classic --edge` (on ubunutu)
Once 0.7.0 is released, we'll switch back to installing the tagged releases and this process will get easier.
### LLVM
To see which version of LLVM you need, take a look at `Cargo.toml`, in particular the `branch` section of the `inkwell` dependency. It should have something like `llvmX-Y` where X and Y are the major and minor revisions of LLVM you need.
@ -46,7 +80,7 @@ If MacOS and using a version >= 10.15:
You may prefer to setup up the volume manually by following nix documentation.
> You my need to restart your terminal
> You may need to restart your terminal
### Usage
@ -62,6 +96,32 @@ You should be in a shell with everything needed to build already installed. Next
You should be in a repl now. Have fun!
### Editor
When you want to run the editor from Ubuntu inside nix you need to install [nixGL](https://github.com/guibou/nixGL) as well:
```bash
nix-shell
git clone https://github.com/guibou/nixGL
cd nixGL
```
If you have an Nvidia graphics card, run:
```
nix-env -f ./ -iA nixVulkanNvidia
```
If you have integrated Intel graphics, run:
```
nix-env -f ./ -iA nixVulkanIntel
```
Check the [nixGL repo](https://github.com/guibou/nixGL) for other configurations.
Now you should be able to run the editor:
```bash
cd roc
nixVulkanNvidia cargo run edit `# replace Nvidia with the config you chose in the previous step`
```
## Troubleshooting
Create an issue if you run into problems not listed here.

507
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -10,7 +10,6 @@ members = [
"compiler/types",
"compiler/uniq",
"compiler/builtins",
"compiler/builtins/bitcode",
"compiler/constrain",
"compiler/unify",
"compiler/solve",

View file

@ -60,3 +60,8 @@ wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | apt-key add -
add-apt-repository "${REPO_NAME}"
apt-get update
apt-get install -y clang-$LLVM_VERSION lldb-$LLVM_VERSION lld-$LLVM_VERSION clangd-$LLVM_VERSION libc++abi-dev libunwind-dev valgrind
# install zig - can't use apt-get since we require at least a specific commit later then the most recent tag (0.6.0)
wget -c https://ziglang.org/builds/zig-linux-x86_64-0.6.0+0088efc4b.tar.xz --no-check-certificate
tar -xf zig-linux-x86_64-0.6.0+0088efc4b.tar.xz
ln -s "$PWD/zig-linux-x86_64-0.6.0+0088efc4b/zig" /usr/local/bin/zig

View file

@ -52,12 +52,16 @@ roc_reporting = { path = "../compiler/reporting" }
roc_editor = { path = "../editor" }
# TODO switch to clap 3.0.0 once it's out. Tried adding clap = "~3.0.0-beta.1" and cargo wouldn't accept it
clap = { git = "https://github.com/rtfeldman/clap", branch = "master" }
const_format = "0.2.8"
rustyline = "6.3.0"
rustyline-derive = "0.3.1"
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"
tokio = { version = "0.2", features = ["blocking", "fs", "sync", "rt-threaded", "process", "io-driver"] }
libc = "0.2"
libloading = "0.6"
# NOTE: rtfeldman/inkwell is a fork of TheDan64/inkwell which does not change anything.
#
@ -76,7 +80,7 @@ libc = "0.2"
# commit of TheDan64/inkwell, push a new tag which points to the latest commit,
# change the tag value in this Cargo.toml to point to that tag, and `cargo update`.
# This way, GitHub Actions works and nobody's builds get broken.
inkwell = { git = "https://github.com/rtfeldman/inkwell", tag = "llvm10-0.release2" }
inkwell = { git = "https://github.com/rtfeldman/inkwell", tag = "llvm10-0.release3" }
target-lexicon = "0.10"
[dev-dependencies]
@ -88,4 +92,5 @@ quickcheck_macros = "0.8"
strip-ansi-escapes = "0.1"
serde = { version = "1.0", features = ["derive"] }
serde-xml-rs = "0.4"
serial_test = "0.5"
tempfile = "3.1.0"

View file

@ -1,5 +1,8 @@
use bumpalo::Bump;
use roc_build::{link::link, program};
use roc_build::{
link::{link, rebuild_host, LinkType},
program,
};
use roc_collections::all::MutMap;
use roc_gen::llvm::build::OptLevel;
use roc_load::file::LoadingProblem;
@ -19,8 +22,9 @@ fn report_timing(buf: &mut String, label: &str, duration: Duration) {
pub fn build_file(
target: &Triple,
src_dir: PathBuf,
filename: PathBuf,
roc_file_path: PathBuf,
opt_level: OptLevel,
link_type: LinkType,
) -> Result<PathBuf, LoadingProblem> {
let compilation_start = SystemTime::now();
let arena = Bump::new();
@ -35,15 +39,16 @@ pub fn build_file(
};
let loaded = roc_load::file::load_and_monomorphize(
&arena,
filename.clone(),
roc_file_path.clone(),
stdlib,
src_dir.as_path(),
subs_by_module,
)?;
let dest_filename = filename.with_file_name("roc_app.o");
let app_o_file = roc_file_path.with_file_name("roc_app.o");
let buf = &mut String::with_capacity(1024);
for (module_id, module_timing) in loaded.timings.iter() {
let mut it = loaded.timings.iter().peekable();
while let Some((module_id, module_timing)) = it.next() {
let module_name = loaded.interns.module_name(*module_id);
buf.push_str(" ");
@ -56,9 +61,23 @@ pub fn build_file(
report_timing(buf, "Canonicalize", module_timing.canonicalize);
report_timing(buf, "Constrain", module_timing.constrain);
report_timing(buf, "Solve", module_timing.solve);
report_timing(
buf,
"Find Specializations",
module_timing.find_specializations,
);
report_timing(
buf,
"Make Specializations",
module_timing.make_specializations,
);
report_timing(buf, "Other", module_timing.other());
buf.push('\n');
report_timing(buf, "Total", module_timing.total());
if it.peek().is_some() {
buf.push('\n');
}
}
println!(
@ -69,12 +88,14 @@ pub fn build_file(
program::gen_from_mono_module(
&arena,
loaded,
filename,
roc_file_path,
Triple::host(),
&dest_filename,
&app_o_file,
opt_level,
);
println!("\nSuccess! 🎉\n\n\t{}\n", app_o_file.display());
let compilation_end = compilation_start.elapsed().unwrap();
println!(
@ -82,36 +103,53 @@ pub fn build_file(
compilation_end.as_millis()
);
let cwd = dest_filename.parent().unwrap();
let cwd = app_o_file.parent().unwrap();
// Step 2: link the precompiled host and compiled app
let host_input_path = cwd.join("platform").join("host.o");
let binary_path = cwd.join("app"); // TODO should be app.exe on Windows
// TODO we should no longer need to do this once we have platforms on
// a package repository, as we can then get precompiled hosts from there.
let rebuild_host_start = SystemTime::now();
rebuild_host(host_input_path.as_path());
let rebuild_host_end = rebuild_host_start.elapsed().unwrap();
println!(
"Finished rebuilding the host in {} ms\n",
rebuild_host_end.as_millis()
);
// TODO try to move as much of this linking as possible to the precompiled
// host, to minimize the amount of host-application linking required.
let cmd_result = // TODO use lld
let link_start = SystemTime::now();
let (mut child, binary_path) = // TODO use lld
link(
target,
binary_path.as_path(),
host_input_path.as_path(),
dest_filename.as_path(),
binary_path,
&[host_input_path.as_path().to_str().unwrap(), app_o_file.as_path().to_str().unwrap()],
link_type
)
.map_err(|_| {
todo!("gracefully handle `rustc` failing to spawn.");
})?
.wait()
.map_err(|_| {
})?;
let cmd_result = child.wait().map_err(|_| {
todo!("gracefully handle error after `rustc` spawned");
});
let link_end = link_start.elapsed().unwrap();
println!("Finished linking in {} ms\n", link_end.as_millis());
// Clean up the leftover .o file from the Roc, if possible.
// (If cleaning it up fails, that's fine. No need to take action.)
// TODO compile the dest_filename to a tmpdir, as an extra precaution.
let _ = fs::remove_file(dest_filename);
// TODO compile the app_o_file to a tmpdir, as an extra precaution.
let _ = fs::remove_file(app_o_file);
// If the cmd errored out, return the Err.
cmd_result?;
let total_end = compilation_start.elapsed().unwrap();
println!("Finished entire process in {} ms\n", total_end.as_millis());
Ok(binary_path)
}

View file

@ -3,6 +3,7 @@ extern crate clap;
use clap::ArgMatches;
use clap::{App, Arg};
use roc_build::link::LinkType;
use roc_gen::llvm::build::OptLevel;
use std::io;
use std::path::Path;
@ -91,7 +92,7 @@ pub fn build(target: &Triple, matches: &ArgMatches, run_after_build: bool) -> io
}
});
let binary_path = build::build_file(target, src_dir, path, opt_level)
let binary_path = build::build_file(target, src_dir, path, opt_level, LinkType::Executable)
.expect("TODO gracefully handle build_file failing");
if run_after_build {

View file

@ -1,82 +1,112 @@
use bumpalo::Bump;
use inkwell::context::Context;
use inkwell::execution_engine::ExecutionEngine;
use inkwell::OptimizationLevel;
use roc_builtins::unique::uniq_stdlib;
use roc_can::constraint::Constraint;
use roc_can::expected::Expected;
use roc_can::expr::{canonicalize_expr, Expr, Output};
use roc_can::operator;
use roc_can::scope::Scope;
use roc_collections::all::{ImMap, ImSet, MutMap, MutSet, SendMap, SendSet};
use roc_constrain::expr::constrain_expr;
use roc_constrain::module::{constrain_imported_values, load_builtin_aliases, Import};
use roc_fmt::annotation::{Formattable, Newlines, Parens};
use roc_gen::llvm::build::{build_proc, build_proc_header, OptLevel};
use roc_module::ident::Ident;
use roc_module::symbol::{IdentIds, Interns, ModuleId, ModuleIds, Symbol};
use roc_parse::ast::{self, Attempting};
use roc_parse::blankspace::space0_before;
use roc_parse::parser::{loc, Fail, FailReason, Parser, State};
use roc_problem::can::Problem;
use roc_region::all::{Located, Region};
use roc_solve::solve;
use roc_types::pretty_print::{content_to_string, name_all_type_vars};
use roc_types::subs::{Content, Subs, VarStore, Variable};
use roc_types::types::Type;
use std::hash::Hash;
use std::io::{self, Write};
use std::path::{Path, PathBuf};
use std::str::from_utf8_unchecked;
use const_format::concatcp;
use gen::{gen_and_eval, ReplOutput};
use roc_gen::llvm::build::OptLevel;
use roc_parse::parser::{Fail, FailReason};
use rustyline::error::ReadlineError;
use rustyline::validate::{self, ValidationContext, ValidationResult, Validator};
use rustyline::Editor;
use rustyline_derive::{Completer, Helper, Highlighter, Hinter};
use std::io::{self};
use target_lexicon::Triple;
pub const WELCOME_MESSAGE: &str = "\n The rockin \u{001b}[36mroc repl\u{001b}[0m\n\u{001b}[35m────────────────────────\u{001b}[0m\n\n";
pub const INSTRUCTIONS: &str = "Enter an expression, or :help, or :exit.\n";
pub const PROMPT: &str = "\n\u{001b}[36m»\u{001b}[0m ";
pub const ELLIPSIS: &str = "\u{001b}[36m…\u{001b}[0m ";
const BLUE: &str = "\u{001b}[36m";
const PINK: &str = "\u{001b}[35m";
const END_COL: &str = "\u{001b}[0m";
pub const WELCOME_MESSAGE: &str = concatcp!(
"\n The rockin ",
BLUE,
"roc repl",
END_COL,
"\n",
PINK,
"────────────────────────",
END_COL,
"\n\n"
);
pub const INSTRUCTIONS: &str = "Enter an expression, or :help, or :exit/:q.\n";
pub const PROMPT: &str = concatcp!("\n", BLUE, "»", END_COL, " ");
mod eval;
mod gen;
#[derive(Completer, Helper, Hinter, Highlighter)]
struct ReplHelper {
validator: InputValidator,
pending_src: String,
}
impl ReplHelper {
pub(crate) fn new() -> ReplHelper {
ReplHelper {
validator: InputValidator::new(),
pending_src: String::new(),
}
}
}
impl Validator for ReplHelper {
fn validate(
&self,
ctx: &mut validate::ValidationContext,
) -> rustyline::Result<validate::ValidationResult> {
self.validator.validate(ctx)
}
fn validate_while_typing(&self) -> bool {
self.validator.validate_while_typing()
}
}
struct InputValidator {}
impl InputValidator {
pub(crate) fn new() -> InputValidator {
InputValidator {}
}
}
impl Validator for InputValidator {
fn validate(&self, ctx: &mut ValidationContext) -> rustyline::Result<ValidationResult> {
if ctx.input().is_empty() {
Ok(ValidationResult::Incomplete)
} else {
Ok(ValidationResult::Valid(None))
}
}
}
pub fn main() -> io::Result<()> {
use std::io::BufRead;
// To debug rustyline:
// <UNCOMMENT> env_logger::init();
// <RUN WITH:> RUST_LOG=rustyline=debug cargo run repl 2> debug.log
print!("{}{}", WELCOME_MESSAGE, INSTRUCTIONS);
// Loop
let mut pending_src = String::new();
let mut prev_line_blank = false;
let mut editor = Editor::<ReplHelper>::new();
let repl_helper = ReplHelper::new();
editor.set_helper(Some(repl_helper));
loop {
if pending_src.is_empty() {
print!("{}", PROMPT);
} else {
print!("{}", ELLIPSIS);
}
let readline = editor.readline(PROMPT);
let pending_src = &mut editor
.helper_mut()
.expect("Editor helper was not set")
.pending_src;
io::stdout().flush().unwrap();
match readline {
Ok(line) => {
//TODO rl.add_history_entry(line.as_str());
let trim_line = line.trim();
let stdin = io::stdin();
let line = stdin
.lock()
.lines()
.next()
.expect("there was no next line")
.expect("the line could not be read");
let line = line.trim();
match line.to_lowercase().as_str() {
":help" => {
println!("Use :exit to exit.");
}
match trim_line.to_lowercase().as_str() {
"" => {
if pending_src.is_empty() {
print!("\n{}", INSTRUCTIONS);
} else if prev_line_blank {
// After two blank lines in a row, give up and try parsing it
// even though it's going to fail. This way you don't get stuck.
match print_output(pending_src.as_str()) {
match eval_and_format(pending_src.as_str()) {
Ok(output) => {
println!("{}", output);
}
@ -93,17 +123,23 @@ pub fn main() -> io::Result<()> {
continue; // Skip the part where we reset prev_line_blank to false
}
}
":help" => {
println!("Use :exit or :q to exit.");
}
":exit" => {
break;
}
":q" => {
break;
}
_ => {
let result = if pending_src.is_empty() {
print_output(line)
eval_and_format(trim_line)
} else {
pending_src.push('\n');
pending_src.push_str(line);
pending_src.push_str(trim_line);
print_output(pending_src.as_str())
eval_and_format(pending_src.as_str())
};
match result {
@ -114,16 +150,7 @@ pub fn main() -> io::Result<()> {
Err(Fail {
reason: FailReason::Eof(_),
..
}) => {
// If we hit an eof, and we're allowed to keep going,
// append the str to the src we're building up and continue.
// (We only need to append it here if it was empty before;
// otherwise, we already appended it before calling print_output.)
if pending_src.is_empty() {
pending_src.push_str(line);
}
}
}) => {}
Err(fail) => {
report_parse_error(fail);
pending_src.clear();
@ -131,6 +158,27 @@ pub fn main() -> io::Result<()> {
}
}
}
}
Err(ReadlineError::Interrupted) => {
println!("CTRL-C");
break;
}
Err(ReadlineError::Eof) => {
// If we hit an eof, and we're allowed to keep going,
// append the str to the src we're building up and continue.
// (We only need to append it here if it was empty before;
// otherwise, we already appended it before calling eval_and_format.)
if pending_src.is_empty() {
pending_src.push_str("");
}
break;
}
Err(err) => {
println!("Error: {:?}", err);
break;
}
}
prev_line_blank = false;
}
@ -142,673 +190,11 @@ fn report_parse_error(fail: Fail) {
println!("TODO Gracefully report parse error in repl: {:?}", fail);
}
fn print_output(src: &str) -> Result<String, Fail> {
gen(src.as_bytes(), Triple::host(), OptLevel::Normal).map(|output| match output {
fn eval_and_format(src: &str) -> Result<String, Fail> {
gen_and_eval(src.as_bytes(), Triple::host(), OptLevel::Normal).map(|output| match output {
ReplOutput::NoProblems { expr, expr_type } => {
format!("\n{} \u{001b}[35m:\u{001b}[0m {}", expr, expr_type)
format!("\n{} {}:{} {}", expr, PINK, END_COL, expr_type)
}
ReplOutput::Problems(lines) => format!("\n{}\n", lines.join("\n\n")),
})
}
pub fn repl_home() -> ModuleId {
ModuleIds::default().get_or_insert(&"REPL".into())
}
fn promote_expr_to_module(src: &str) -> String {
let mut buffer = String::from("app Repl provides [ replOutput ] imports []\n\nreplOutput =\n");
for line in src.lines() {
// indent the body!
buffer.push_str(" ");
buffer.push_str(line);
buffer.push('\n');
}
buffer
}
fn gen(src: &[u8], target: Triple, opt_level: OptLevel) -> Result<ReplOutput, Fail> {
use roc_reporting::report::{
can_problem, mono_problem, type_problem, RocDocAllocator, DEFAULT_PALETTE,
};
let arena = Bump::new();
// SAFETY: we've already verified that this is valid UTF-8 during parsing.
let src_str: &str = unsafe { from_utf8_unchecked(src) };
let stdlib = roc_builtins::std::standard_stdlib();
let stdlib_mode = stdlib.mode;
let filename = PathBuf::from("REPL.roc");
let src_dir = Path::new("fake/test/path");
let module_src = promote_expr_to_module(src_str);
let exposed_types = MutMap::default();
let loaded = roc_load::file::load_and_monomorphize_from_str(
&arena,
filename,
&module_src,
stdlib,
src_dir,
exposed_types,
);
let 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,
..
} = loaded;
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();
// Used for reporting where an error came from.
//
// TODO: maybe Reporting should have this be an Option?
let path = PathBuf::new();
// Report problems
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 mut buf = String::new();
report.render_color_terminal(&mut buf, &alloc, &palette);
lines.push(buf);
}
for problem in type_problems.into_iter() {
let report = type_problem(&alloc, 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);
}
Ok(ReplOutput::Problems(lines))
} else {
let context = Context::create();
let module = arena.alloc(roc_gen::llvm::build::module_from_builtins(&context, "app"));
let builder = context.create_builder();
debug_assert_eq!(exposed_to_host.len(), 1);
let (main_fn_symbol, main_fn_var) = exposed_to_host.iter().next().unwrap();
let main_fn_symbol = *main_fn_symbol;
let main_fn_var = *main_fn_var;
// pretty-print the expr type string for later.
name_all_type_vars(main_fn_var, &mut subs);
let content = subs.get(main_fn_var).content;
let expr_type_str = content_to_string(content.clone(), &subs, home, &interns);
let (_, main_fn_layout) = procedures
.keys()
.find(|(s, _)| *s == main_fn_symbol)
.unwrap()
.clone();
let ptr_bytes = target.pointer_width().unwrap().bytes() as u32;
let module = arena.alloc(module);
let (module_pass, function_pass) =
roc_gen::llvm::build::construct_optimization_passes(module, opt_level);
let execution_engine = module
.create_jit_execution_engine(OptimizationLevel::None)
.expect("Error creating JIT execution engine for test");
// Without calling this, we get a linker error when building this crate
// in --release mode and then trying to eval anything in the repl.
ExecutionEngine::link_in_mc_jit();
// Compile and add all the Procs before adding main
let env = roc_gen::llvm::build::Env {
arena: &arena,
builder: &builder,
context: &context,
interns,
module,
ptr_bytes,
leak: false,
// important! we don't want any procedures to get the C calling convention
exposed_to_host: MutSet::default(),
};
let mut layout_ids = roc_gen::layout_id::LayoutIds::default();
let mut headers = Vec::with_capacity(procedures.len());
// Add all the Proc headers to the module.
// We have to do this in a separate pass first,
// because their bodies may reference each other.
let mut scope = roc_gen::llvm::build::Scope::default();
for ((symbol, layout), proc) in procedures.drain() {
let fn_val = build_proc_header(&env, &mut layout_ids, symbol, &layout, &proc);
if proc.args.is_empty() {
// this is a 0-argument thunk, i.e. a top-level constant definition
// it must be in-scope everywhere in the module!
scope.insert_top_level_thunk(symbol, layout, fn_val);
}
headers.push((proc, fn_val));
}
// Build each proc using its header info.
for (proc, fn_val) in headers {
let mut current_scope = scope.clone();
// only have top-level thunks for this proc's module in scope
// this retain is not needed for correctness, but will cause less confusion when debugging
let home = proc.name.module_id();
current_scope.retain_top_level_thunks_for_module(home);
build_proc(&env, &mut layout_ids, scope.clone(), proc, fn_val);
if fn_val.verify(true) {
function_pass.run_on(&fn_val);
} else {
use roc_builtins::std::Mode;
let mode = match stdlib_mode {
Mode::Uniqueness => "OPTIMIZED",
Mode::Standard => "NON-OPTIMIZED",
};
eprintln!(
"\n\nFunction {:?} failed LLVM verification in {} build. Its content was:\n",
fn_val.get_name().to_str().unwrap(),
mode,
);
fn_val.print_to_stderr();
panic!(
"The preceding code was from {:?}, which failed LLVM verification in {} build.",
fn_val.get_name().to_str().unwrap(),
mode,
);
}
}
let (main_fn_name, main_fn) = roc_gen::llvm::build::promote_to_main_function(
&env,
&mut layout_ids,
main_fn_symbol,
&main_fn_layout,
);
// Uncomment this to see the module's un-optimized LLVM instruction output:
// env.module.print_to_stderr();
if main_fn.verify(true) {
function_pass.run_on(&main_fn);
} else {
panic!("Main function {} failed LLVM verification in build. Uncomment things nearby to see more details.", main_fn_name);
}
module_pass.run_on(env.module);
// Verify the module
if let Err(errors) = env.module.verify() {
panic!("Errors defining module: {:?}", errors);
}
// Uncomment this to see the module's optimized LLVM instruction output:
// env.module.print_to_stderr();
let answer = unsafe {
eval::jit_to_ast(
&arena,
execution_engine,
main_fn_name,
&main_fn_layout,
&content,
&env.interns,
home,
&subs,
ptr_bytes,
)
};
let mut expr = bumpalo::collections::String::new_in(&arena);
answer.format_with_options(&mut expr, Parens::NotNeeded, Newlines::Yes, 0);
Ok(ReplOutput::NoProblems {
expr: expr.into_bump_str().to_string(),
expr_type: expr_type_str,
})
}
}
enum ReplOutput {
Problems(Vec<String>),
NoProblems { expr: String, expr_type: String },
}
pub fn infer_expr(
subs: Subs,
problems: &mut Vec<solve::TypeError>,
constraint: &Constraint,
expr_var: Variable,
) -> (Content, Subs) {
let env = solve::Env {
aliases: MutMap::default(),
vars_by_symbol: SendMap::default(),
};
let (solved, _) = solve::run(&env, problems, subs, constraint);
let content = solved.inner().get_without_compacting(expr_var).content;
(content, solved.into_inner())
}
pub fn parse_loc_with<'a>(
arena: &'a Bump,
bytes: &'a [u8],
) -> Result<Located<ast::Expr<'a>>, Fail> {
let state = State::new(&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)
}
pub fn can_expr(expr_bytes: &[u8]) -> Result<CanExprOut, Fail> {
can_expr_with(&Bump::new(), repl_home(), expr_bytes)
}
// TODO make this return a named struct instead of a big tuple
#[allow(clippy::type_complexity)]
pub fn uniq_expr(
expr_bytes: &[u8],
) -> Result<
(
Located<roc_can::expr::Expr>,
Output,
Vec<Problem>,
Subs,
Variable,
Constraint,
ModuleId,
Interns,
),
Fail,
> {
let declared_idents: &ImMap<Ident, (Symbol, Region)> = &ImMap::default();
uniq_expr_with(&Bump::new(), expr_bytes, declared_idents)
}
// TODO make this return a named struct instead of a big tuple
#[allow(clippy::type_complexity)]
pub fn uniq_expr_with(
arena: &Bump,
expr_bytes: &[u8],
declared_idents: &ImMap<Ident, (Symbol, Region)>,
) -> Result<
(
Located<roc_can::expr::Expr>,
Output,
Vec<Problem>,
Subs,
Variable,
Constraint,
ModuleId,
Interns,
),
Fail,
> {
let home = repl_home();
let CanExprOut {
loc_expr,
output,
problems,
var_store: mut old_var_store,
var,
interns,
..
} = can_expr_with(arena, home, expr_bytes)?;
// double check
let mut var_store = VarStore::new(old_var_store.fresh());
let expected2 = Expected::NoExpectation(Type::Variable(var));
let constraint = roc_constrain::uniq::constrain_declaration(
home,
&mut var_store,
Region::zero(),
&loc_expr,
declared_idents,
expected2,
);
let stdlib = uniq_stdlib();
let types = stdlib.types;
let imports: Vec<_> = types
.into_iter()
.map(|(symbol, (solved_type, region))| Import {
loc_symbol: Located::at(region, symbol),
solved_type,
})
.collect();
// load builtin values
// TODO what to do with those rigids?
let (_introduced_rigids, constraint) =
constrain_imported_values(imports, constraint, &mut var_store);
// load builtin types
let mut constraint = load_builtin_aliases(stdlib.aliases, constraint, &mut var_store);
constraint.instantiate_aliases(&mut var_store);
let subs2 = Subs::new(var_store.into());
Ok((
loc_expr, output, problems, subs2, var, constraint, home, interns,
))
}
pub struct CanExprOut {
pub loc_expr: Located<roc_can::expr::Expr>,
pub output: Output,
pub problems: Vec<Problem>,
pub home: ModuleId,
pub interns: Interns,
pub var_store: VarStore,
pub var: Variable,
pub constraint: Constraint,
}
pub fn can_expr_with(arena: &Bump, home: ModuleId, expr_bytes: &[u8]) -> Result<CanExprOut, Fail> {
let loc_expr = parse_loc_with(&arena, expr_bytes)?;
let mut var_store = VarStore::default();
let var = var_store.fresh();
let expected = Expected::NoExpectation(Type::Variable(var));
let module_ids = ModuleIds::default();
// Desugar operators (convert them to Apply calls, taking into account
// operator precedence and associativity rules), before doing other canonicalization.
//
// If we did this *during* canonicalization, then each time we
// visited a BinOp node we'd recursively try to apply this to each of its nested
// operators, and then again on *their* nested operators, ultimately applying the
// rules multiple times unnecessarily.
let loc_expr = operator::desugar_expr(arena, &loc_expr);
let mut scope = Scope::new(home);
let dep_idents = IdentIds::exposed_builtins(0);
let mut env = roc_can::env::Env::new(home, dep_idents, &module_ids, IdentIds::default());
let (loc_expr, output) = canonicalize_expr(
&mut env,
&mut var_store,
&mut scope,
Region::zero(),
&loc_expr.value,
);
// Add builtin defs (e.g. List.get) directly to the canonical Expr,
// since we aren't using modules here.
let mut with_builtins = loc_expr.value;
let builtin_defs = roc_can::builtins::builtin_defs(&mut var_store);
for (symbol, def) in builtin_defs {
if output.references.lookups.contains(&symbol) || output.references.calls.contains(&symbol)
{
with_builtins = Expr::LetNonRec(
Box::new(def),
Box::new(Located {
region: Region::zero(),
value: with_builtins,
}),
var_store.fresh(),
SendMap::default(),
);
}
}
let loc_expr = Located {
region: loc_expr.region,
value: with_builtins,
};
let constraint = constrain_expr(
&roc_constrain::expr::Env {
rigids: ImMap::default(),
home,
},
loc_expr.region,
&loc_expr.value,
expected,
);
let types = roc_builtins::std::types();
let imports: Vec<_> = types
.into_iter()
.map(|(symbol, (solved_type, region))| Import {
loc_symbol: Located::at(region, symbol),
solved_type,
})
.collect();
//load builtin values
let (_introduced_rigids, constraint) =
constrain_imported_values(imports, constraint, &mut var_store);
// TODO determine what to do with those rigids
// for var in introduced_rigids {
// output.ftv.insert(var, format!("internal_{:?}", var).into());
// }
//load builtin types
let mut constraint =
load_builtin_aliases(roc_builtins::std::aliases(), constraint, &mut var_store);
constraint.instantiate_aliases(&mut var_store);
let mut all_ident_ids = MutMap::default();
// When pretty printing types, we may need the exposed builtins,
// so include them in the Interns we'll ultimately return.
for (module_id, ident_ids) in IdentIds::exposed_builtins(0) {
all_ident_ids.insert(module_id, ident_ids);
}
all_ident_ids.insert(home, env.ident_ids);
let interns = Interns {
module_ids: env.module_ids.clone(),
all_ident_ids,
};
Ok(CanExprOut {
loc_expr,
output,
problems: env.problems,
home: env.home,
var_store,
interns,
var,
constraint,
})
}
pub fn mut_map_from_pairs<K, V, I>(pairs: I) -> MutMap<K, V>
where
I: IntoIterator<Item = (K, V)>,
K: Hash + Eq,
{
let mut answer = MutMap::default();
for (key, value) in pairs {
answer.insert(key, value);
}
answer
}
pub fn im_map_from_pairs<K, V, I>(pairs: I) -> ImMap<K, V>
where
I: IntoIterator<Item = (K, V)>,
K: Hash + Eq + Clone,
V: Clone,
{
let mut answer = ImMap::default();
for (key, value) in pairs {
answer.insert(key, value);
}
answer
}
pub fn send_set_from<V, I>(elems: I) -> SendSet<V>
where
I: IntoIterator<Item = V>,
V: Hash + Eq + Clone,
{
let mut answer = SendSet::default();
for elem in elems {
answer.insert(elem);
}
answer
}
// Check constraints
//
// Keep track of the used (in types or expectations) variables, and the declared variables (in
// flex_vars or rigid_vars fields of LetConstraint. These roc_collections should match: no duplicates
// and no variables that are used but not declared are allowed.
//
// There is one exception: the initial variable (that stores the type of the whole expression) is
// never declared, but is used.
pub fn assert_correct_variable_usage(constraint: &Constraint) {
// variables declared in constraint (flex_vars or rigid_vars)
// and variables actually used in constraints
let (declared, used) = variable_usage(constraint);
let used: ImSet<Variable> = used.into();
let mut decl: ImSet<Variable> = declared.rigid_vars.clone().into();
for var in declared.flex_vars.clone() {
decl.insert(var);
}
let diff = used.clone().relative_complement(decl);
// NOTE: this checks whether we're using variables that are not declared. For recursive type
// definitions, their rigid types are declared twice, which is correct!
if !diff.is_empty() {
println!("VARIABLE USAGE PROBLEM");
println!("used: {:?}", &used);
println!("rigids: {:?}", &declared.rigid_vars);
println!("flexs: {:?}", &declared.flex_vars);
println!("difference: {:?}", &diff);
panic!("variable usage problem (see stdout for details)");
}
}
#[derive(Default)]
pub struct SeenVariables {
pub rigid_vars: Vec<Variable>,
pub flex_vars: Vec<Variable>,
}
pub fn variable_usage(con: &Constraint) -> (SeenVariables, Vec<Variable>) {
let mut declared = SeenVariables::default();
let mut used = ImSet::default();
variable_usage_help(con, &mut declared, &mut used);
used.remove(unsafe { &Variable::unsafe_test_debug_variable(1) });
let mut used_vec: Vec<Variable> = used.into_iter().collect();
used_vec.sort();
declared.rigid_vars.sort();
declared.flex_vars.sort();
(declared, used_vec)
}
fn variable_usage_help(con: &Constraint, declared: &mut SeenVariables, used: &mut ImSet<Variable>) {
use Constraint::*;
match con {
True | SaveTheEnvironment => (),
Eq(tipe, expectation, _, _) => {
for v in tipe.variables() {
used.insert(v);
}
for v in expectation.get_type_ref().variables() {
used.insert(v);
}
}
Lookup(_, expectation, _) => {
for v in expectation.get_type_ref().variables() {
used.insert(v);
}
}
Pattern(_, _, tipe, pexpectation) => {
for v in tipe.variables() {
used.insert(v);
}
for v in pexpectation.get_type_ref().variables() {
used.insert(v);
}
}
Let(letcon) => {
declared.rigid_vars.extend(letcon.rigid_vars.clone());
declared.flex_vars.extend(letcon.flex_vars.clone());
variable_usage_help(&letcon.defs_constraint, declared, used);
variable_usage_help(&letcon.ret_constraint, declared, used);
}
And(constraints) => {
for sub in constraints {
variable_usage_help(sub, declared, used);
}
}
}
}

109
cli/src/repl/debug.rs Normal file
View file

@ -0,0 +1,109 @@
// Check constraints
//
// Keep track of the used (in types or expectations) variables, and the declared variables (in
// flex_vars or rigid_vars fields of LetConstraint. These roc_collections should match: no duplicates
// and no variables that are used but not declared are allowed.
//
// There is one exception: the initial variable (that stores the type of the whole expression) is
// never declared, but is used.
pub fn assert_correct_variable_usage(constraint: &Constraint) {
// variables declared in constraint (flex_vars or rigid_vars)
// and variables actually used in constraints
let (declared, used) = variable_usage(constraint);
let used: ImSet<Variable> = used.into();
let mut decl: ImSet<Variable> = declared.rigid_vars.clone().into();
for var in declared.flex_vars.clone() {
decl.insert(var);
}
let diff = used.clone().relative_complement(decl);
// NOTE: this checks whether we're using variables that are not declared. For recursive type
// definitions, their rigid types are declared twice, which is correct!
if !diff.is_empty() {
println!("VARIABLE USAGE PROBLEM");
println!("used: {:?}", &used);
println!("rigids: {:?}", &declared.rigid_vars);
println!("flexs: {:?}", &declared.flex_vars);
println!("difference: {:?}", &diff);
panic!("variable usage problem (see stdout for details)");
}
}
#[derive(Default)]
pub struct SeenVariables {
pub rigid_vars: Vec<Variable>,
pub flex_vars: Vec<Variable>,
}
pub fn variable_usage(con: &Constraint) -> (SeenVariables, Vec<Variable>) {
let mut declared = SeenVariables::default();
let mut used = ImSet::default();
variable_usage_help(con, &mut declared, &mut used);
used.remove(unsafe { &Variable::unsafe_test_debug_variable(1) });
let mut used_vec: Vec<Variable> = used.into_iter().collect();
used_vec.sort();
declared.rigid_vars.sort();
declared.flex_vars.sort();
(declared, used_vec)
}
fn variable_usage_help(con: &Constraint, declared: &mut SeenVariables, used: &mut ImSet<Variable>) {
use Constraint::*;
match con {
True | SaveTheEnvironment => (),
Eq(tipe, expectation, _, _) => {
for v in tipe.variables() {
used.insert(v);
}
for v in expectation.get_type_ref().variables() {
used.insert(v);
}
}
Store(tipe, var, _, _) => {
for v in tipe.variables() {
used.insert(v);
}
used.insert(*var);
}
Lookup(_, expectation, _) => {
for v in expectation.get_type_ref().variables() {
used.insert(v);
}
}
Pattern(_, _, tipe, pexpectation) => {
for v in tipe.variables() {
used.insert(v);
}
for v in pexpectation.get_type_ref().variables() {
used.insert(v);
}
}
Let(letcon) => {
declared.rigid_vars.extend(letcon.rigid_vars.clone());
declared.flex_vars.extend(letcon.flex_vars.clone());
variable_usage_help(&letcon.defs_constraint, declared, used);
variable_usage_help(&letcon.ret_constraint, declared, used);
}
And(constraints) => {
for sub in constraints {
variable_usage_help(sub, declared, used);
}
}
}
}

View file

@ -1,12 +1,12 @@
use bumpalo::collections::Vec;
use bumpalo::Bump;
use inkwell::execution_engine::ExecutionEngine;
use libloading::Library;
use roc_collections::all::MutMap;
use roc_gen::{run_jit_function, run_jit_function_dynamic_type};
use roc_module::ident::{Lowercase, TagName};
use roc_module::operator::CalledVia;
use roc_module::symbol::{Interns, ModuleId, Symbol};
use roc_mono::layout::{Builtin, Layout};
use roc_mono::layout::{union_sorted_tags_help, Builtin, Layout, UnionVariant};
use roc_parse::ast::{AssignedField, Expr, StrLiteral};
use roc_region::all::{Located, Region};
use roc_types::subs::{Content, FlatType, Subs, Variable};
@ -31,7 +31,7 @@ struct Env<'a, 'env> {
#[allow(clippy::too_many_arguments)]
pub unsafe fn jit_to_ast<'a>(
arena: &'a Bump,
execution_engine: ExecutionEngine,
lib: Library,
main_fn_name: &str,
layout: &Layout<'a>,
content: &Content,
@ -48,56 +48,82 @@ pub unsafe fn jit_to_ast<'a>(
interns,
};
jit_to_ast_help(&env, execution_engine, main_fn_name, layout, content)
jit_to_ast_help(&env, lib, main_fn_name, layout, content)
}
fn jit_to_ast_help<'a>(
env: &Env<'a, '_>,
execution_engine: ExecutionEngine,
lib: Library,
main_fn_name: &str,
layout: &Layout<'a>,
content: &Content,
) -> Expr<'a> {
match layout {
Layout::Builtin(Builtin::Int64) => run_jit_function!(
execution_engine,
main_fn_name,
i64,
|num| num_to_ast(env, i64_to_ast(env.arena, num), content)
),
Layout::Builtin(Builtin::Float64) => run_jit_function!(
execution_engine,
main_fn_name,
f64,
|num| num_to_ast(env, f64_to_ast(env.arena, num), content)
),
Layout::Builtin(Builtin::Str) | Layout::Builtin(Builtin::EmptyStr) => run_jit_function!(
execution_engine,
main_fn_name,
&'static str,
|string: &'static str| { str_to_ast(env.arena, env.arena.alloc(string)) }
),
Layout::Builtin(Builtin::EmptyList) => {
run_jit_function!(execution_engine, main_fn_name, &'static str, |_| {
Expr::List(Vec::new_in(env.arena))
Layout::Builtin(Builtin::Int1) => {
run_jit_function!(lib, main_fn_name, bool, |num| bool_to_ast(
env, num, content
))
}
Layout::Builtin(Builtin::Int8) => {
// NOTE: this is does not handle 8-bit numbers yet
run_jit_function!(lib, main_fn_name, u8, |num| byte_to_ast(env, num, content))
}
Layout::Builtin(Builtin::Int64) => {
run_jit_function!(lib, main_fn_name, i64, |num| num_to_ast(
env,
i64_to_ast(env.arena, num),
content
))
}
Layout::Builtin(Builtin::Float64) => {
run_jit_function!(lib, main_fn_name, f64, |num| num_to_ast(
env,
f64_to_ast(env.arena, num),
content
))
}
Layout::Builtin(Builtin::Str) | Layout::Builtin(Builtin::EmptyStr) => {
run_jit_function!(lib, main_fn_name, &'static str, |string: &'static str| {
str_to_ast(env.arena, env.arena.alloc(string))
})
}
Layout::Builtin(Builtin::List(_, elem_layout)) => run_jit_function!(
execution_engine,
main_fn_name,
(*const libc::c_void, usize),
|(ptr, len): (*const libc::c_void, usize)| {
list_to_ast(env, ptr, len, elem_layout, content)
Layout::Builtin(Builtin::EmptyList) => {
run_jit_function!(lib, main_fn_name, &'static str, |_| { Expr::List(&[]) })
}
Layout::Builtin(Builtin::List(_, elem_layout)) => run_jit_function!(
lib,
main_fn_name,
(*const u8, usize),
|(ptr, len): (*const u8, usize)| { list_to_ast(env, ptr, len, elem_layout, content) }
),
Layout::Builtin(other) => {
todo!("add support for rendering builtin {:?} to the REPL", other)
}
Layout::PhantomEmptyStruct => run_jit_function!(lib, main_fn_name, &u8, |_| {
Expr::Record {
update: None,
fields: &[],
final_comments: env.arena.alloc([]),
}
}),
Layout::Struct(field_layouts) => {
let ptr_to_ast = |ptr: *const libc::c_void| match content {
let ptr_to_ast = |ptr: *const u8| match content {
Content::Structure(FlatType::Record(fields, _)) => {
struct_to_ast(env, ptr, field_layouts, fields)
}
Content::Structure(FlatType::EmptyRecord) => {
struct_to_ast(env, ptr, field_layouts, &MutMap::default())
}
Content::Structure(FlatType::TagUnion(tags, _)) => {
debug_assert_eq!(tags.len(), 1);
let (tag_name, payload_vars) = tags.iter().next().unwrap();
single_tag_union_to_ast(env, ptr, field_layouts, tag_name.clone(), payload_vars)
}
other => {
unreachable!(
"Something had a Struct layout, but instead of a Record type, it had: {:?}",
"Something had a Struct layout, but instead of a Record or TagUnion type, it had: {:?}",
other
);
}
@ -106,96 +132,93 @@ fn jit_to_ast_help<'a>(
let fields = [Layout::Builtin(Builtin::Int64), layout.clone()];
let layout = Layout::Struct(&fields);
match env.ptr_bytes {
// 64-bit target (8-byte pointers, 16-byte structs)
8 => match layout.stack_size(env.ptr_bytes) {
8 => {
// just one eightbyte, returned as-is
run_jit_function!(
execution_engine,
main_fn_name,
[u8; 8],
|bytes: [u8; 8]| {
ptr_to_ast((&bytes).as_ptr() as *const libc::c_void)
}
)
}
16 => {
// two eightbytes, returned as-is
run_jit_function!(
execution_engine,
main_fn_name,
[u8; 16],
|bytes: [u8; 16]| {
ptr_to_ast((&bytes).as_ptr() as *const libc::c_void)
}
)
}
larger_size => {
// anything more than 2 eightbytes
// the return "value" is a pointer to the result
let result_stack_size = layout.stack_size(env.ptr_bytes);
run_jit_function_dynamic_type!(
execution_engine,
lib,
main_fn_name,
larger_size as usize,
|bytes: *const u8| { ptr_to_ast(bytes as *const libc::c_void) }
result_stack_size as usize,
|bytes: *const u8| { ptr_to_ast(bytes as *const u8) }
)
}
Layout::Union(union_layouts) => match content {
Content::Structure(FlatType::TagUnion(tags, _)) => {
debug_assert_eq!(union_layouts.len(), tags.len());
let tags_vec: std::vec::Vec<(TagName, std::vec::Vec<Variable>)> =
tags.iter().map(|(a, b)| (a.clone(), b.clone())).collect();
let union_variant = union_sorted_tags_help(env.arena, tags_vec, None, env.subs);
let size = layout.stack_size(env.ptr_bytes);
match union_variant {
UnionVariant::Wrapped(tags_and_layouts) => {
run_jit_function_dynamic_type!(
lib,
main_fn_name,
size as usize,
|ptr: *const u8| {
// Because this is a `Wrapped`, the first 8 bytes encode the tag ID
let tag_id = *(ptr as *const i64);
// use the tag ID as an index, to get its name and layout of any arguments
let (tag_name, arg_layouts) = &tags_and_layouts[tag_id as usize];
let tag_expr = tag_name_to_expr(env, tag_name);
let loc_tag_expr = &*env.arena.alloc(Located::at_zero(tag_expr));
let variables = &tags[tag_name];
// because the arg_layouts include the tag ID, it is one longer
debug_assert_eq!(arg_layouts.len() - 1, variables.len());
// skip forward to the start of the first element, ignoring the tag id
let ptr = ptr.offset(8);
let it = variables.iter().copied().zip(&arg_layouts[1..]);
let output = sequence_of_expr(env, ptr, it);
let output = output.into_bump_slice();
Expr::Apply(loc_tag_expr, output, CalledVia::Space)
}
)
}
_ => unreachable!("any other variant would have a different layout"),
}
}
Content::Structure(FlatType::RecursiveTagUnion(_, _, _)) => {
todo!("print recursive tag unions in the REPL")
}
other => unreachable!("Weird content for Union layout: {:?}", other),
},
// 32-bit target (4-byte pointers, 8-byte structs)
4 => {
// TODO what are valid return sizes here?
// this is just extrapolated from the 64-bit case above
// and not (yet) actually tested on a 32-bit system
match layout.stack_size(env.ptr_bytes) {
4 => {
// just one fourbyte, returned as-is
run_jit_function!(
execution_engine,
main_fn_name,
[u8; 4],
|bytes: [u8; 4]| {
ptr_to_ast((&bytes).as_ptr() as *const libc::c_void)
Layout::RecursiveUnion(_) | Layout::RecursivePointer => {
todo!("add support for rendering recursive tag unions in the REPL")
}
)
Layout::FunctionPointer(_, _) | Layout::Closure(_, _, _) => {
todo!("add support for rendering functions in the REPL")
}
8 => {
// just one fourbyte, returned as-is
run_jit_function!(
execution_engine,
main_fn_name,
[u8; 8],
|bytes: [u8; 8]| {
ptr_to_ast((&bytes).as_ptr() as *const libc::c_void)
}
)
}
larger_size => {
// anything more than 2 fourbytes
// the return "value" is a pointer to the result
run_jit_function_dynamic_type!(
execution_engine,
main_fn_name,
larger_size as usize,
|bytes: *const u8| { ptr_to_ast(bytes as *const libc::c_void) }
)
}
}
}
other => {
panic!("Unsupported target: Roc cannot currently compile to systems where pointers are {} bytes in length.", other);
}
}
}
other => {
todo!("TODO add support for rendering {:?} in the REPL", other);
Layout::Pointer(_) => todo!("add support for rendering pointers in the REPL"),
}
}
fn tag_name_to_expr<'a>(env: &Env<'a, '_>, tag_name: &TagName) -> Expr<'a> {
match tag_name {
TagName::Global(_) => Expr::GlobalTag(
env.arena
.alloc_str(&tag_name.as_string(env.interns, env.home)),
),
TagName::Private(_) => Expr::PrivateTag(
env.arena
.alloc_str(&tag_name.as_string(env.interns, env.home)),
),
TagName::Closure(_) => unreachable!("User cannot type this"),
}
}
fn ptr_to_ast<'a>(
env: &Env<'a, '_>,
ptr: *const libc::c_void,
ptr: *const u8,
layout: &Layout<'a>,
content: &Content,
) -> Expr<'a> {
@ -205,16 +228,23 @@ fn ptr_to_ast<'a>(
num_to_ast(env, i64_to_ast(env.arena, num), content)
}
Layout::Builtin(Builtin::Int1) => {
// TODO: bits are not as expected here.
// num is always false at the moment.
let num = unsafe { *(ptr as *const bool) };
bool_to_ast(env, num, content)
}
Layout::Builtin(Builtin::Float64) => {
let num = unsafe { *(ptr as *const f64) };
num_to_ast(env, f64_to_ast(env.arena, num), content)
}
Layout::Builtin(Builtin::EmptyList) => Expr::List(Vec::new_in(env.arena)),
Layout::Builtin(Builtin::EmptyList) => Expr::List(&[]),
Layout::Builtin(Builtin::List(_, elem_layout)) => {
// Turn the (ptr, len) wrapper struct into actual ptr and len values.
let len = unsafe { *(ptr.offset(env.ptr_bytes as isize) as *const usize) };
let ptr = unsafe { *(ptr as *const *const libc::c_void) };
let ptr = unsafe { *(ptr as *const *const u8) };
list_to_ast(env, ptr, len, elem_layout, content)
}
@ -228,6 +258,12 @@ fn ptr_to_ast<'a>(
Content::Structure(FlatType::Record(fields, _)) => {
struct_to_ast(env, ptr, field_layouts, fields)
}
Content::Structure(FlatType::TagUnion(tags, _)) => {
debug_assert_eq!(tags.len(), 1);
let (tag_name, payload_vars) = tags.iter().next().unwrap();
single_tag_union_to_ast(env, ptr, field_layouts, tag_name.clone(), payload_vars)
}
other => {
unreachable!(
"Something had a Struct layout, but instead of a Record type, it had: {:?}",
@ -246,7 +282,7 @@ fn ptr_to_ast<'a>(
fn list_to_ast<'a>(
env: &Env<'a, '_>,
ptr: *const libc::c_void,
ptr: *const u8,
len: usize,
elem_layout: &Layout<'a>,
content: &Content,
@ -269,11 +305,11 @@ fn list_to_ast<'a>(
let arena = env.arena;
let mut output = Vec::with_capacity_in(len, &arena);
let elem_size = elem_layout.stack_size(env.ptr_bytes);
let elem_size = elem_layout.stack_size(env.ptr_bytes) as usize;
for index in 0..(len as isize) {
let offset_bytes: isize = index * elem_size as isize;
let elem_ptr = unsafe { ptr.offset(offset_bytes) };
for index in 0..len {
let offset_bytes = index * elem_size;
let elem_ptr = unsafe { ptr.add(offset_bytes) };
let loc_expr = &*arena.alloc(Located {
value: ptr_to_ast(env, elem_ptr, elem_layout, &elem_content),
region: Region::zero(),
@ -282,13 +318,66 @@ fn list_to_ast<'a>(
output.push(loc_expr);
}
let output = output.into_bump_slice();
Expr::List(output)
}
fn single_tag_union_to_ast<'a>(
env: &Env<'a, '_>,
ptr: *const u8,
field_layouts: &'a [Layout<'a>],
tag_name: TagName,
payload_vars: &[Variable],
) -> Expr<'a> {
debug_assert_eq!(field_layouts.len(), payload_vars.len());
let arena = env.arena;
let tag_expr = tag_name_to_expr(env, &tag_name);
let loc_tag_expr = &*arena.alloc(Located::at_zero(tag_expr));
let it = payload_vars.iter().copied().zip(field_layouts);
let output = sequence_of_expr(env, ptr as *const u8, it).into_bump_slice();
Expr::Apply(loc_tag_expr, output, CalledVia::Space)
}
fn sequence_of_expr<'a, I>(
env: &Env<'a, '_>,
ptr: *const u8,
sequence: I,
) -> Vec<'a, &'a Located<Expr<'a>>>
where
I: Iterator<Item = (Variable, &'a Layout<'a>)>,
I: ExactSizeIterator<Item = (Variable, &'a Layout<'a>)>,
{
let arena = env.arena;
let subs = env.subs;
let mut output = Vec::with_capacity_in(sequence.len(), &arena);
// We'll advance this as we iterate through the fields
let mut field_ptr = ptr as *const u8;
for (var, layout) in sequence {
let content = subs.get_without_compacting(var).content;
let expr = ptr_to_ast(env, field_ptr, layout, &content);
let loc_expr = Located::at_zero(expr);
output.push(&*arena.alloc(loc_expr));
// Advance the field pointer to the next field.
field_ptr = unsafe { field_ptr.offset(layout.stack_size(env.ptr_bytes) as isize) };
}
output
}
fn struct_to_ast<'a>(
env: &Env<'a, '_>,
ptr: *const libc::c_void,
field_layouts: &[Layout<'a>],
ptr: *const u8,
field_layouts: &'a [Layout<'a>],
fields: &MutMap<Lowercase, RecordField<Variable>>,
) -> Expr<'a> {
let arena = env.arena;
@ -296,7 +385,7 @@ fn struct_to_ast<'a>(
let mut output = Vec::with_capacity_in(field_layouts.len(), &arena);
// The fields, sorted alphabetically
let sorted_fields = {
let mut sorted_fields = {
let mut vec = fields
.iter()
.collect::<std::vec::Vec<(&Lowercase, &RecordField<Variable>)>>();
@ -306,6 +395,34 @@ fn struct_to_ast<'a>(
vec
};
if sorted_fields.len() == 1 {
// this is a 1-field wrapper record around another record or 1-tag tag union
let (label, field) = sorted_fields.pop().unwrap();
let inner_content = env.subs.get_without_compacting(field.into_inner()).content;
let loc_expr = &*arena.alloc(Located {
value: ptr_to_ast(env, ptr, &Layout::Struct(field_layouts), &inner_content),
region: Region::zero(),
});
let field_name = Located {
value: &*arena.alloc_str(label.as_str()),
region: Region::zero(),
};
let loc_field = Located {
value: AssignedField::RequiredValue(field_name, &[], loc_expr),
region: Region::zero(),
};
let output = env.arena.alloc([loc_field]);
Expr::Record {
update: None,
fields: output,
final_comments: &[],
}
} else {
debug_assert_eq!(sorted_fields.len(), field_layouts.len());
// We'll advance this as we iterate through the fields
@ -330,12 +447,242 @@ fn struct_to_ast<'a>(
output.push(loc_field);
// Advance the field pointer to the next field.
field_ptr = unsafe { field_ptr.offset(field_layout.stack_size(env.ptr_bytes) as isize) };
field_ptr =
unsafe { field_ptr.offset(field_layout.stack_size(env.ptr_bytes) as isize) };
}
let output = output.into_bump_slice();
Expr::Record {
update: None,
fields: output,
final_comments: &[],
}
}
}
fn bool_to_ast<'a>(env: &Env<'a, '_>, value: bool, content: &Content) -> Expr<'a> {
use Content::*;
let arena = env.arena;
match content {
Structure(flat_type) => {
match flat_type {
FlatType::Record(fields, _) => {
debug_assert_eq!(fields.len(), 1);
let (label, field) = fields.iter().next().unwrap();
let loc_label = Located {
value: &*arena.alloc_str(label.as_str()),
region: Region::zero(),
};
let assigned_field = {
// We may be multiple levels deep in nested tag unions
// and/or records (e.g. { a: { b: { c: True } } }),
// so we need to do this recursively on the field type.
let field_var = *field.as_inner();
let field_content = env.subs.get_without_compacting(field_var).content;
let loc_expr = Located {
value: bool_to_ast(env, value, &field_content),
region: Region::zero(),
};
AssignedField::RequiredValue(loc_label, &[], arena.alloc(loc_expr))
};
let loc_assigned_field = Located {
value: assigned_field,
region: Region::zero(),
};
Expr::Record {
update: None,
fields: arena.alloc([loc_assigned_field]),
final_comments: arena.alloc([]),
}
}
FlatType::TagUnion(tags, _) if tags.len() == 1 => {
let (tag_name, payload_vars) = tags.iter().next().unwrap();
let loc_tag_expr = {
let tag_name = &tag_name.as_string(env.interns, env.home);
let tag_expr = if tag_name.starts_with('@') {
Expr::PrivateTag(arena.alloc_str(tag_name))
} else {
Expr::GlobalTag(arena.alloc_str(tag_name))
};
&*arena.alloc(Located {
value: tag_expr,
region: Region::zero(),
})
};
let payload = {
// Since this has the layout of a number, there should be
// exactly one payload in this tag.
debug_assert_eq!(payload_vars.len(), 1);
let var = *payload_vars.iter().next().unwrap();
let content = env.subs.get_without_compacting(var).content;
let loc_payload = &*arena.alloc(Located {
value: bool_to_ast(env, value, &content),
region: Region::zero(),
});
arena.alloc([loc_payload])
};
Expr::Apply(loc_tag_expr, payload, CalledVia::Space)
}
FlatType::TagUnion(tags, _) if tags.len() == 2 => {
let mut tags_iter = tags.iter();
let (tag_name_1, payload_vars_1) = tags_iter.next().unwrap();
let (tag_name_2, payload_vars_2) = tags_iter.next().unwrap();
debug_assert!(payload_vars_1.is_empty());
debug_assert!(payload_vars_2.is_empty());
let tag_name = if value {
max_by_key(tag_name_1, tag_name_2, |n| {
n.as_string(env.interns, env.home)
})
} else {
min_by_key(tag_name_1, tag_name_2, |n| {
n.as_string(env.interns, env.home)
})
};
tag_name_to_expr(env, tag_name)
}
other => {
unreachable!("Unexpected FlatType {:?} in bool_to_ast", other);
}
}
}
Alias(_, _, var) => {
let content = env.subs.get_without_compacting(*var).content;
bool_to_ast(env, value, &content)
}
other => {
unreachable!("Unexpected FlatType {:?} in bool_to_ast", other);
}
}
}
fn byte_to_ast<'a>(env: &Env<'a, '_>, value: u8, content: &Content) -> Expr<'a> {
use Content::*;
let arena = env.arena;
match content {
Structure(flat_type) => {
match flat_type {
FlatType::Record(fields, _) => {
debug_assert_eq!(fields.len(), 1);
let (label, field) = fields.iter().next().unwrap();
let loc_label = Located {
value: &*arena.alloc_str(label.as_str()),
region: Region::zero(),
};
let assigned_field = {
// We may be multiple levels deep in nested tag unions
// and/or records (e.g. { a: { b: { c: True } } }),
// so we need to do this recursively on the field type.
let field_var = *field.as_inner();
let field_content = env.subs.get_without_compacting(field_var).content;
let loc_expr = Located {
value: byte_to_ast(env, value, &field_content),
region: Region::zero(),
};
AssignedField::RequiredValue(loc_label, &[], arena.alloc(loc_expr))
};
let loc_assigned_field = Located {
value: assigned_field,
region: Region::zero(),
};
Expr::Record {
update: None,
fields: arena.alloc([loc_assigned_field]),
final_comments: &[],
}
}
FlatType::TagUnion(tags, _) if tags.len() == 1 => {
let (tag_name, payload_vars) = tags.iter().next().unwrap();
let loc_tag_expr = {
let tag_name = &tag_name.as_string(env.interns, env.home);
let tag_expr = if tag_name.starts_with('@') {
Expr::PrivateTag(arena.alloc_str(tag_name))
} else {
Expr::GlobalTag(arena.alloc_str(tag_name))
};
&*arena.alloc(Located {
value: tag_expr,
region: Region::zero(),
})
};
let payload = {
// Since this has the layout of a number, there should be
// exactly one payload in this tag.
debug_assert_eq!(payload_vars.len(), 1);
let var = *payload_vars.iter().next().unwrap();
let content = env.subs.get_without_compacting(var).content;
let loc_payload = &*arena.alloc(Located {
value: byte_to_ast(env, value, &content),
region: Region::zero(),
});
arena.alloc([loc_payload])
};
Expr::Apply(loc_tag_expr, payload, CalledVia::Space)
}
FlatType::TagUnion(tags, _) => {
// anything with fewer tags is not a byte
debug_assert!(tags.len() > 2);
let tags_vec: std::vec::Vec<(TagName, std::vec::Vec<Variable>)> =
tags.iter().map(|(a, b)| (a.clone(), b.clone())).collect();
let union_variant = union_sorted_tags_help(env.arena, tags_vec, None, env.subs);
match union_variant {
UnionVariant::ByteUnion(tagnames) => {
let tag_name = &tagnames[value as usize];
let tag_expr = tag_name_to_expr(env, tag_name);
let loc_tag_expr = Located::at_zero(tag_expr);
Expr::Apply(env.arena.alloc(loc_tag_expr), &[], CalledVia::Space)
}
_ => unreachable!("invalid union variant for a Byte!"),
}
}
other => {
unreachable!("Unexpected FlatType {:?} in bool_to_ast", other);
}
}
}
Alias(_, _, var) => {
let content = env.subs.get_without_compacting(*var).content;
byte_to_ast(env, value, &content)
}
other => {
unreachable!("Unexpected FlatType {:?} in bool_to_ast", other);
}
}
}
@ -381,7 +728,8 @@ fn num_to_ast<'a>(env: &Env<'a, '_>, num_expr: Expr<'a>, content: &Content) -> E
Expr::Record {
update: None,
fields: bumpalo::vec![in arena; loc_assigned_field],
fields: arena.alloc([loc_assigned_field]),
final_comments: arena.alloc([]),
}
}
FlatType::TagUnion(tags, _) => {
@ -423,7 +771,7 @@ fn num_to_ast<'a>(env: &Env<'a, '_>, num_expr: Expr<'a>, content: &Content) -> E
region: Region::zero(),
});
bumpalo::vec![in arena; loc_payload]
arena.alloc([loc_payload])
};
Expr::Apply(loc_tag_expr, payload, CalledVia::Space)
@ -488,3 +836,30 @@ fn str_slice_to_ast<'a>(_arena: &'a Bump, string: &'a str) -> Expr<'a> {
Expr::Str(StrLiteral::PlainLine(string))
}
}
// TODO this is currently nighly-only: use the implementation in std once it's stabilized
pub fn max_by<T, F: FnOnce(&T, &T) -> std::cmp::Ordering>(v1: T, v2: T, compare: F) -> T {
use std::cmp::Ordering;
match compare(&v1, &v2) {
Ordering::Less | Ordering::Equal => v2,
Ordering::Greater => v1,
}
}
pub fn min_by<T, F: FnOnce(&T, &T) -> std::cmp::Ordering>(v1: T, v2: T, compare: F) -> T {
use std::cmp::Ordering;
match compare(&v1, &v2) {
Ordering::Less | Ordering::Equal => v1,
Ordering::Greater => v2,
}
}
pub fn max_by_key<T, F: FnMut(&T) -> K, K: Ord>(v1: T, v2: T, mut f: F) -> T {
max_by(v1, v2, |v1, v2| f(v1).cmp(&f(v2)))
}
pub fn min_by_key<T, F: FnMut(&T) -> K, K: Ord>(v1: T, v2: T, mut f: F) -> T {
min_by(v1, v2, |v1, v2| f(v1).cmp(&f(v2)))
}

277
cli/src/repl/gen.rs Normal file
View file

@ -0,0 +1,277 @@
use crate::repl::eval;
use bumpalo::Bump;
use inkwell::context::Context;
use roc_build::link::module_to_dylib;
use roc_collections::all::{MutMap, MutSet};
use roc_fmt::annotation::Formattable;
use roc_fmt::annotation::{Newlines, Parens};
use roc_gen::llvm::build::{build_proc, build_proc_header, OptLevel};
use roc_parse::parser::Fail;
use roc_types::pretty_print::{content_to_string, name_all_type_vars};
use std::path::{Path, PathBuf};
use std::str::from_utf8_unchecked;
use target_lexicon::Triple;
pub enum ReplOutput {
Problems(Vec<String>),
NoProblems { expr: String, expr_type: String },
}
pub fn gen_and_eval(src: &[u8], target: Triple, opt_level: OptLevel) -> Result<ReplOutput, Fail> {
use roc_reporting::report::{
can_problem, mono_problem, type_problem, RocDocAllocator, DEFAULT_PALETTE,
};
let arena = Bump::new();
// SAFETY: we've already verified that this is valid UTF-8 during parsing.
let src_str: &str = unsafe { from_utf8_unchecked(src) };
let stdlib = roc_builtins::std::standard_stdlib();
let stdlib_mode = stdlib.mode;
let filename = PathBuf::from("REPL.roc");
let src_dir = Path::new("fake/test/path");
let module_src = promote_expr_to_module(src_str);
let exposed_types = MutMap::default();
let loaded = roc_load::file::load_and_monomorphize_from_str(
&arena,
filename,
&module_src,
stdlib,
src_dir,
exposed_types,
);
let mut loaded = loaded.expect("failed to load module");
use roc_load::file::MonomorphizedModule;
let MonomorphizedModule {
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 {
continue;
}
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);
for problem in can_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);
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);
lines.push(buf);
}
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();
let module = arena.alloc(roc_gen::llvm::build::module_from_builtins(&context, "app"));
let builder = context.create_builder();
debug_assert_eq!(exposed_to_host.len(), 1);
let (main_fn_symbol, main_fn_var) = exposed_to_host.iter().next().unwrap();
let main_fn_symbol = *main_fn_symbol;
let main_fn_var = *main_fn_var;
// pretty-print the expr type string for later.
name_all_type_vars(main_fn_var, &mut subs);
let content = subs.get(main_fn_var).content;
let expr_type_str = content_to_string(content.clone(), &subs, home, &interns);
let (_, main_fn_layout) = procedures
.keys()
.find(|(s, _)| *s == main_fn_symbol)
.unwrap()
.clone();
let ptr_bytes = target.pointer_width().unwrap().bytes() as u32;
let module = arena.alloc(module);
let (module_pass, function_pass) =
roc_gen::llvm::build::construct_optimization_passes(module, opt_level);
let (dibuilder, compile_unit) = roc_gen::llvm::build::Env::new_debug_info(module);
// Compile and add all the Procs before adding main
let env = roc_gen::llvm::build::Env {
arena: &arena,
builder: &builder,
dibuilder: &dibuilder,
compile_unit: &compile_unit,
context: &context,
interns,
module,
ptr_bytes,
leak: false,
// important! we don't want any procedures to get the C calling convention
exposed_to_host: MutSet::default(),
};
let mut layout_ids = roc_mono::layout::LayoutIds::default();
let mut headers = Vec::with_capacity(procedures.len());
// Add all the Proc headers to the module.
// We have to do this in a separate pass first,
// because their bodies may reference each other.
let mut scope = roc_gen::llvm::build::Scope::default();
for ((symbol, layout), proc) in procedures.drain() {
let fn_val = build_proc_header(&env, &mut layout_ids, symbol, &layout, &proc);
if proc.args.is_empty() {
// this is a 0-argument thunk, i.e. a top-level constant definition
// it must be in-scope everywhere in the module!
scope.insert_top_level_thunk(symbol, layout, fn_val);
}
headers.push((proc, fn_val));
}
// Build each proc using its header info.
for (proc, fn_val) in headers {
let mut current_scope = scope.clone();
// only have top-level thunks for this proc's module in scope
// this retain is not needed for correctness, but will cause less confusion when debugging
let home = proc.name.module_id();
current_scope.retain_top_level_thunks_for_module(home);
build_proc(&env, &mut layout_ids, scope.clone(), proc, fn_val);
// call finalize() before any code generation/verification
env.dibuilder.finalize();
if fn_val.verify(true) {
function_pass.run_on(&fn_val);
} else {
use roc_builtins::std::Mode;
let mode = match stdlib_mode {
Mode::Uniqueness => "OPTIMIZED",
Mode::Standard => "NON-OPTIMIZED",
};
eprintln!(
"\n\nFunction {:?} failed LLVM verification in {} build. Its content was:\n",
fn_val.get_name().to_str().unwrap(),
mode,
);
fn_val.print_to_stderr();
panic!(
"The preceding code was from {:?}, which failed LLVM verification in {} build.",
fn_val.get_name().to_str().unwrap(),
mode,
);
}
}
let (main_fn_name, main_fn) = roc_gen::llvm::build::promote_to_main_function(
&env,
&mut layout_ids,
main_fn_symbol,
&main_fn_layout,
);
env.dibuilder.finalize();
// Uncomment this to see the module's un-optimized LLVM instruction output:
// env.module.print_to_stderr();
if main_fn.verify(true) {
function_pass.run_on(&main_fn);
} else {
panic!("Main function {} failed LLVM verification in build. Uncomment things nearby to see more details.", main_fn_name);
}
module_pass.run_on(env.module);
// Verify the module
if let Err(errors) = env.module.verify() {
panic!("Errors defining module: {:?}", errors);
}
// Uncomment this to see the module's optimized LLVM instruction output:
// env.module.print_to_stderr();
let lib = module_to_dylib(&env.module, &target, opt_level)
.expect("Error loading compiled dylib for test");
let answer = unsafe {
eval::jit_to_ast(
&arena,
lib,
main_fn_name,
&main_fn_layout,
&content,
&env.interns,
home,
&subs,
ptr_bytes,
)
};
let mut expr = bumpalo::collections::String::new_in(&arena);
answer.format_with_options(&mut expr, Parens::NotNeeded, Newlines::Yes, 0);
Ok(ReplOutput::NoProblems {
expr: expr.into_bump_str().to_string(),
expr_type: expr_type_str,
})
}
}
fn promote_expr_to_module(src: &str) -> String {
let mut buffer = String::from("app Repl provides [ replOutput ] imports []\n\nreplOutput =\n");
for line in src.lines() {
// indent the body!
buffer.push_str(" ");
buffer.push_str(line);
buffer.push('\n');
}
buffer
}

View file

@ -12,123 +12,208 @@ mod helpers;
#[cfg(test)]
mod cli_run {
use crate::helpers::{
example_file, extract_valgrind_errors, run_cmd, run_roc, run_with_valgrind, Out,
example_file, extract_valgrind_errors, fixture_file, run_cmd, run_roc, run_with_valgrind,
};
use serial_test::serial;
use std::path::Path;
#[test]
fn run_hello_world() {
fn check_hello_world_output(out: Out) {
if !out.stderr.is_empty() {
panic!(out.stderr);
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);
}
assert!(out.status.success());
assert!(compile_out.status.success());
let out = if use_valgrind {
let (valgrind_out, raw_xml) =
run_with_valgrind(&[example_file("hello-world", "app").to_str().unwrap()]);
let ending = "Hello, World!!!!!!!!!!!!!\n";
if !&valgrind_out.stdout.ends_with(ending) {
panic!(
"expected output to end with {:?} but instead got {:?}",
ending, &valgrind_out.stdout
);
}
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);
}
assert!(valgrind_out.status.success());
}
check_hello_world_output(run_roc(&[
"build",
example_file("hello-world", "Hello.roc").to_str().unwrap(),
]));
check_hello_world_output(run_roc(&[
"build",
"--optimize",
example_file("hello-world", "Hello.roc").to_str().unwrap(),
]));
}
#[test]
fn run_quicksort() {
fn check_quicksort_output(out: Out) {
if !out.stderr.is_empty() {
panic!(out.stderr);
}
assert!(out.status.success());
// let (valgrind_out, raw_xml) =
// run_with_valgrind(&[example_file("quicksort", "app").to_str().unwrap()]);
let valgrind_out = run_cmd(example_file("quicksort", "app").to_str().unwrap(), &[]);
let ending = "[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]\n";
if !&valgrind_out.stdout.ends_with(ending) {
valgrind_out
} else {
run_cmd(file.with_file_name("app").to_str().unwrap(), &[])
};
if !&out.stdout.ends_with(expected_ending) {
panic!(
"expected output to end with {:?} but instead got {:?}",
ending, &valgrind_out.stdout
"expected output to end with {:?} but instead got {:#?}",
expected_ending, out
);
}
// let memory_errors = extract_valgrind_errors(&raw_xml);
// if !memory_errors.is_empty() {
// panic!("{:?}", memory_errors);
// }
assert!(valgrind_out.status.success());
}
// TODO: Uncomment this once we are correctly freeing the RocList even when in dev build.
/*
check_quicksort_output(run_roc(&[
"build",
example_file("quicksort", "Quicksort.roc").to_str().unwrap(),
]));
*/
check_quicksort_output(run_roc(&[
"build",
"--optimize",
example_file("quicksort", "Quicksort.roc").to_str().unwrap(),
]));
assert!(out.status.success());
}
#[test]
#[serial(hello_world)]
fn run_hello_world() {
check_output(
&example_file("hello-world", "Hello.roc"),
&[],
"Hello, World!!!!!!!!!!!!!\n",
true,
);
}
#[test]
#[serial(hello_world)]
fn run_hello_world_optimized() {
check_output(
&example_file("hello-world", "Hello.roc"),
&[],
"Hello, World!!!!!!!!!!!!!\n",
true,
);
}
#[test]
#[serial(quicksort)]
fn run_quicksort_not_optimized() {
check_output(
&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,
);
}
#[test]
#[serial(quicksort)]
fn run_quicksort_optimized() {
check_output(
&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,
);
}
#[test]
#[serial(quicksort)]
// TODO: Stop ignoring this test once we are correctly freeing the RocList even when in dev build.
#[ignore]
fn run_quicksort_valgrind() {
check_output(
&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,
);
}
#[test]
#[serial(quicksort)]
// TODO: Stop ignoring this test once valgrind supports AVX512.
#[ignore]
fn run_quicksort_optimized_valgrind() {
check_output(
&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,
);
}
#[test]
#[serial(multi_module)]
fn run_multi_module() {
fn check_muti_module_output(out: Out) {
if !out.stderr.is_empty() {
panic!(out.stderr);
}
assert!(out.status.success());
// let (valgrind_out, raw_xml) =
// run_with_valgrind(&[example_file("multi-module", "app").to_str().unwrap()]);
let valgrind_out = run_cmd(example_file("multi-module", "app").to_str().unwrap(), &[]);
let ending = "[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]\n";
if !&valgrind_out.stdout.ends_with(ending) {
panic!(
"expected output to end with {:?} but instead got {:?}",
ending, &valgrind_out.stdout
check_output(
&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,
);
}
// let memory_errors = extract_valgrind_errors(&raw_xml);
// if !memory_errors.is_empty() {
// panic!("{:?}", memory_errors);
// }
assert!(valgrind_out.status.success());
#[test]
#[serial(multi_module)]
fn run_multi_module_optimized() {
check_output(
&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,
);
}
// TODO: Uncomment this once we are correctly freeing the RocList even when in dev build.
/*
check_muti_module_output(run_roc(&[
"run",
example_file("multi-module", "Quicksort.roc")
.to_str()
.unwrap(),
]));
*/
check_muti_module_output(run_roc(&[
"run",
example_file("multi-module", "Quicksort.roc")
.to_str()
.unwrap(),
"--optimize",
]));
#[test]
#[serial(multi_module)]
// TODO: Stop ignoring this test once we are correctly freeing the RocList even when in dev build.
#[ignore]
fn run_multi_module_valgrind() {
check_output(
&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,
);
}
#[test]
#[serial(multi_module)]
// TODO: Stop ignoring this test once valgrind supports AVX512.
#[ignore]
fn run_multi_module_optimized_valgrind() {
check_output(
&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(effect)]
// fn run_effect_unoptimized() {
// check_output(
// &example_file("effect", "Main.roc"),
// &[],
// "I am Dep2.str2\n",
// true,
// );
// }
#[test]
#[serial(multi_dep_str)]
fn run_multi_dep_str_unoptimized() {
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"),
&["--optimize"],
"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_thunk)]
fn run_multi_dep_thunk_optimized() {
check_output(
&fixture_file("multi-dep-thunk", "Main.roc"),
&["--optimize"],
"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,5 @@
platform roc/quicksort
provides []
requires {}
imports []
effects Effect {}

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_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,5 @@
platform roc/quicksort
provides []
requires {}
imports []
effects Effect {}

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

@ -5,7 +5,7 @@ extern crate roc_load;
extern crate roc_module;
extern crate tempfile;
use roc_cli::repl::{INSTRUCTIONS, PROMPT, WELCOME_MESSAGE};
use roc_cli::repl::{INSTRUCTIONS, WELCOME_MESSAGE};
use serde::Deserialize;
use serde_xml_rs::from_str;
use std::env;
@ -15,6 +15,7 @@ use std::path::PathBuf;
use std::process::{Command, ExitStatus, Stdio};
use tempfile::NamedTempFile;
#[derive(Debug)]
pub struct Out {
pub stdout: String,
pub stderr: String,
@ -69,7 +70,7 @@ pub fn run_cmd(cmd_name: &str, args: &[&str]) -> Out {
let output = cmd
.output()
.expect(&format!("failed to execute cmd `{}` in CLI test", cmd_name));
.unwrap_or_else(|_| panic!("failed to execute cmd `{}` in CLI test", cmd_name));
Out {
stdout: String::from_utf8(output.stdout).unwrap(),
@ -131,6 +132,9 @@ enum ValgrindField {
Args(ValgrindDummyStruct),
Error(ValgrindError),
Status(ValgrindDummyStruct),
Stack(ValgrindDummyStruct),
#[serde(rename = "fatal_signal")]
FatalSignal(ValgrindDummyStruct),
ErrorCounts(ValgrindDummyStruct),
SuppCounts(ValgrindDummyStruct),
}
@ -203,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());
@ -225,12 +263,12 @@ pub fn repl_eval(input: &str) -> Out {
// Evaluate the expression
stdin
.write_all("\n".as_bytes())
.write_all(b"\n")
.expect("Failed to write newline to stdin");
// Gracefully exit the repl
stdin
.write_all(":exit\n".as_bytes())
.write_all(b":exit\n")
.expect("Failed to write :exit to stdin");
}
@ -240,7 +278,7 @@ pub fn repl_eval(input: &str) -> Out {
// Remove the initial instructions from the output.
let expected_instructions = format!("{}{}{}", WELCOME_MESSAGE, INSTRUCTIONS, PROMPT);
let expected_instructions = format!("{}{}", WELCOME_MESSAGE, INSTRUCTIONS);
let stdout = String::from_utf8(output.stdout).unwrap();
assert!(
@ -262,7 +300,7 @@ pub fn repl_eval(input: &str) -> Out {
panic!("repl exited unexpectedly before finishing evaluation. Exit status was {:?} and stderr was {:?}", output.status, String::from_utf8(output.stderr).unwrap());
}
} else {
let expected_after_answer = format!("\n{}", PROMPT);
let expected_after_answer = format!("\n");
assert!(
answer.ends_with(&expected_after_answer),

View file

@ -87,6 +87,80 @@ mod repl_eval {
expect_success("1.1 + 2", "3.1 : Float");
}
#[test]
fn bool_in_record() {
expect_success("{ x: 1 == 1 }", "{ x: True } : { x : Bool }");
expect_success(
"{ z: { y: { x: 1 == 1 } } }",
"{ z: { y: { x: True } } } : { z : { y : { x : Bool } } }",
);
expect_success("{ x: 1 != 1 }", "{ x: False } : { x : Bool }");
expect_success(
"{ x: 1 == 1, y: 1 != 1 }",
"{ x: True, y: False } : { x : Bool, y : Bool }",
);
}
#[test]
fn bool_basic_equality() {
expect_success("1 == 1", "True : Bool");
expect_success("1 != 1", "False : Bool");
}
#[test]
fn arbitrary_tag_unions() {
expect_success("if 1 == 1 then Red else Green", "Red : [ Green, Red ]*");
expect_success("if 1 != 1 then Red else Green", "Green : [ Green, Red ]*");
}
#[test]
fn tag_without_arguments() {
expect_success("True", "True : [ True ]*");
expect_success("False", "False : [ False ]*");
}
#[test]
fn byte_tag_union() {
expect_success(
"if 1 == 1 then Red else if 1 == 1 then Green else Blue",
"Red : [ Blue, Green, Red ]*",
);
expect_success(
"{ y: { x: if 1 == 1 then Red else if 1 == 1 then Green else Blue } }",
"{ y: { x: Red } } : { y : { x : [ Blue, Green, Red ]* } }",
);
}
#[test]
fn tag_in_record() {
expect_success(
"{ x: Foo 1 2 3, y : 4 }",
"{ x: Foo 1 2 3, y: 4 } : { x : [ Foo (Num *) (Num *) (Num *) ]*, y : Num * }",
);
expect_success(
"{ x: Foo 1 2 3 }",
"{ x: Foo 1 2 3 } : { x : [ Foo (Num *) (Num *) (Num *) ]* }",
);
expect_success("{ x: Unit }", "{ x: Unit } : { x : [ Unit ]* }");
}
#[test]
fn single_element_tag_union() {
expect_success("True 1", "True 1 : [ True (Num *) ]*");
expect_success("Foo 1 3.14", "Foo 1 3.14 : [ Foo (Num *) Float ]*");
}
#[test]
fn tag_with_arguments() {
expect_success("True 1", "True 1 : [ True (Num *) ]*");
expect_success(
"if 1 == 1 then True 3 else False 3.14",
"True 3 : [ False Float, True (Num *) ]*",
)
}
#[test]
fn literal_empty_str() {
expect_success("\"\"", "\"\" : Str");
@ -110,6 +184,11 @@ mod repl_eval {
);
}
#[test]
fn str_count_graphemes() {
expect_success("Str.countGraphemes \"å🤔\"", "2 : Int");
}
#[test]
fn literal_empty_list() {
expect_success("[]", "[] : List *");
@ -175,6 +254,25 @@ 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 list_sum() {
expect_success("List.sum []", "0 : Num *");
expect_success("List.sum [ 1, 2, 3 ]", "6 : Num *");
expect_success("List.sum [ 1.1, 2.2, 3.3 ]", "6.6 : Float");
}
#[test]
fn empty_record() {
expect_success("{}", "{} : {}");
}
#[test]
fn basic_1_field_i64_record() {
// Even though this gets unwrapped at runtime, the repl should still
@ -217,32 +315,29 @@ mod repl_eval {
);
}
// TODO uncomment this once https://github.com/rtfeldman/roc/issues/295 is done
// #[test]
// fn basic_2_field_f64_record() {
// expect_success(
// "{ foo: 4.1, bar: 2.3 }",
// "{ bar: 2.3, foo: 4.1 } : { bar : Float, foo : Float }",
// );
// }
#[test]
fn basic_2_field_f64_record() {
expect_success(
"{ foo: 4.1, bar: 2.3 }",
"{ bar: 2.3, foo: 4.1 } : { bar : Float, foo : Float }",
);
}
// #[test]
// fn basic_2_field_mixed_record() {
// expect_success(
// "{ foo: 4.1, bar: 2 }",
// "{ bar: 2, foo: 4.1 } : { bar : Num *, foo : Float }",
// );
// }
#[test]
fn basic_2_field_mixed_record() {
expect_success(
"{ foo: 4.1, bar: 2 }",
"{ bar: 2, foo: 4.1 } : { bar : Num *, foo : Float }",
);
}
// TODO uncomment this once https://github.com/rtfeldman/roc/issues/295 is done
//
// #[test]
// fn basic_3_field_record() {
// expect_success(
// "{ foo: 4.1, bar: 2, baz: 0x5 }",
// "{ foo: 4.1, bar: 2, baz: 0x5 } : { foo : Float, bar : Num *, baz : Int }",
// );
// }
#[test]
fn basic_3_field_record() {
expect_success(
"{ foo: 4.1, bar: 2, baz: 0x5 }",
"{ bar: 2, baz: 5, foo: 4.1 } : { bar : Num *, baz : Int, foo : Float }",
);
}
#[test]
fn list_of_1_field_records() {

View file

@ -26,7 +26,8 @@ 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.0"
tokio = { version = "0.2", features = ["blocking", "fs", "sync", "rt-threaded", "process", "io-driver"] }
libloading = "0.6"
tempfile = "3.1.0"
# 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
@ -44,7 +45,7 @@ tokio = { version = "0.2", features = ["blocking", "fs", "sync", "rt-threaded",
# commit of TheDan64/inkwell, push a new tag which points to the latest commit,
# change the tag value in this Cargo.toml to point to that tag, and `cargo update`.
# This way, GitHub Actions works and nobody's builds get broken.
inkwell = { git = "https://github.com/rtfeldman/inkwell", tag = "llvm10-0.release2" }
inkwell = { git = "https://github.com/rtfeldman/inkwell", tag = "llvm10-0.release3" }
target-lexicon = "0.10"
[dev-dependencies]

View file

@ -1,35 +1,44 @@
use crate::target;
use crate::target::arch_str;
use inkwell::module::Module;
use inkwell::targets::{CodeModel, FileType, RelocMode};
use libloading::{Error, Library};
use roc_gen::llvm::build::OptLevel;
use std::io;
use std::path::Path;
use std::process::{Child, Command};
use std::path::{Path, PathBuf};
use std::process::{Child, Command, Output};
use target_lexicon::{Architecture, OperatingSystem, Triple};
use tempfile::tempdir;
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum LinkType {
Executable,
Dylib,
}
/// input_paths can include the host as well as the app. e.g. &["host.o", "roc_app.o"]
pub fn link(
target: &Triple,
binary_path: &Path,
host_input_path: &Path,
dest_filename: &Path,
) -> io::Result<Child> {
// TODO we should no longer need to do this once we have platforms on
// a package repository, as we can then get precompiled hosts from there.
rebuild_host(host_input_path);
output_path: PathBuf,
input_paths: &[&str],
link_type: LinkType,
) -> io::Result<(Child, PathBuf)> {
match target {
Triple {
architecture: Architecture::X86_64,
operating_system: OperatingSystem::Linux,
..
} => link_linux(target, binary_path, host_input_path, dest_filename),
} => link_linux(target, output_path, input_paths, link_type),
Triple {
architecture: Architecture::X86_64,
operating_system: OperatingSystem::Darwin,
..
} => link_macos(target, binary_path, host_input_path, dest_filename),
} => link_macos(target, output_path, input_paths, link_type),
_ => panic!("TODO gracefully handle unsupported target: {:?}", target),
}
}
fn rebuild_host(host_input_path: &Path) {
pub fn rebuild_host(host_input_path: &Path) {
let c_host_src = host_input_path.with_file_name("host.c");
let c_host_dest = host_input_path.with_file_name("c_host.o");
let rust_host_src = host_input_path.with_file_name("host.rs");
@ -38,7 +47,7 @@ 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",
@ -49,18 +58,22 @@ 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",
@ -73,9 +86,11 @@ 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",
@ -84,7 +99,9 @@ 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",
@ -96,8 +113,10 @@ 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",
@ -106,27 +125,32 @@ 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);
}
}
fn link_linux(
target: &Triple,
binary_path: &Path,
host_input_path: &Path,
dest_filename: &Path,
) -> io::Result<Child> {
output_path: PathBuf,
input_paths: &[&str],
link_type: LinkType,
) -> io::Result<(Child, PathBuf)> {
let libcrt_path = if Path::new("/usr/lib/x86_64-linux-gnu").exists() {
Path::new("/usr/lib/x86_64-linux-gnu")
} else {
Path::new("/usr/lib")
};
let libgcc_path = if Path::new("/lib/x86_64-linux-gnu/libgcc_s.so.1").exists() {
Path::new("/lib/x86_64-linux-gnu/libgcc_s.so.1")
} else if Path::new("/usr/lib/x86_64-linux-gnu/libgcc_s.so.1").exists() {
@ -134,8 +158,45 @@ fn link_linux(
} else {
Path::new("/usr/lib/libgcc_s.so.1")
};
let mut soname;
let (base_args, output_path) = match link_type {
LinkType::Executable => (
// Presumably this S stands for Static, since if we include Scrt1.o
// in the linking for dynamic builds, linking fails.
vec![libcrt_path.join("Scrt1.o").to_str().unwrap().to_string()],
output_path,
),
LinkType::Dylib => {
// TODO: do we acually need the version number on this?
// Do we even need the "-soname" argument?
//
// See https://software.intel.com/content/www/us/en/develop/articles/create-a-unix-including-linux-shared-library.html
soname = output_path.clone();
soname.set_extension("so.1");
let mut output_path = output_path;
output_path.set_extension("so.1.0");
(
// TODO: find a way to avoid using a vec! here - should theoretically be
// able to do this somehow using &[] but the borrow checker isn't having it.
// Also find a way to have these be string slices instead of Strings.
vec![
"-shared".to_string(),
"-soname".to_string(),
soname.as_path().to_str().unwrap().to_string(),
],
output_path,
)
}
};
// NOTE: order of arguments to `ld` matters here!
// The `-l` flags should go after the `.o` arguments
Ok((
Command::new("ld")
// Don't allow LD_ env vars to affect this
.env_clear()
@ -144,12 +205,11 @@ fn link_linux(
arch_str(target),
libcrt_path.join("crti.o").to_str().unwrap(),
libcrt_path.join("crtn.o").to_str().unwrap(),
libcrt_path.join("Scrt1.o").to_str().unwrap(),
"-dynamic-linker",
"/lib64/ld-linux-x86-64.so.2",
// Inputs
host_input_path.to_str().unwrap(), // host.o
dest_filename.to_str().unwrap(), // app.o
])
.args(&base_args)
.args(&["-dynamic-linker", "/lib64/ld-linux-x86-64.so.2"])
.args(input_paths)
.args(&[
// Libraries - see https://github.com/rtfeldman/roc/pull/554#discussion_r496365925
// for discussion and further references
"-lc",
@ -164,30 +224,46 @@ fn link_linux(
libgcc_path.to_str().unwrap(),
// Output
"-o",
binary_path.to_str().unwrap(), // app
output_path.as_path().to_str().unwrap(), // app (or app.so or app.dylib etc.)
])
.spawn()
.spawn()?,
output_path,
))
}
fn link_macos(
target: &Triple,
binary_path: &Path,
host_input_path: &Path,
dest_filename: &Path,
) -> io::Result<Child> {
output_path: PathBuf,
input_paths: &[&str],
link_type: LinkType,
) -> io::Result<(Child, PathBuf)> {
let (link_type_arg, output_path) = match link_type {
LinkType::Executable => ("-execute", output_path),
LinkType::Dylib => {
let mut output_path = output_path;
output_path.set_extension("dylib");
("-dylib", output_path)
}
};
Ok((
// NOTE: order of arguments to `ld` matters here!
// The `-l` flags should go after the `.o` arguments
Command::new("ld")
// Don't allow LD_ env vars to affect this
.env_clear()
.args(&[
link_type_arg,
"-arch",
target.architecture.to_string().as_str(),
// Inputs
host_input_path.to_str().unwrap(), // host.o
dest_filename.to_str().unwrap(), // roc_app.o
])
.args(input_paths)
.args(&[
// Libraries - see https://github.com/rtfeldman/roc/pull/554#discussion_r496392274
// for discussion and further references
"-L/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/lib",
"-lSystem",
"-lresolv",
"-lpthread",
@ -198,7 +274,62 @@ fn link_macos(
"-lc++", // TODO shouldn't we need this?
// Output
"-o",
binary_path.to_str().unwrap(), // app
output_path.to_str().unwrap(), // app
])
.spawn()
.spawn()?,
output_path,
))
}
pub fn module_to_dylib(
module: &Module,
target: &Triple,
opt_level: OptLevel,
) -> Result<Library, Error> {
let dir = tempdir().unwrap();
let filename = PathBuf::from("Test.roc");
let file_path = dir.path().join(filename);
let mut app_o_file = file_path;
app_o_file.set_file_name("app.o");
// Emit the .o file using position-indepedent code (PIC) - needed for dylibs
let reloc = RelocMode::PIC;
let model = CodeModel::Default;
let target_machine = target::target_machine(target, opt_level.into(), reloc, model).unwrap();
target_machine
.write_to_file(module, FileType::Object, &app_o_file)
.expect("Writing .o file failed");
// Link app.o into a dylib - e.g. app.so or app.dylib
let (mut child, dylib_path) = link(
&Triple::host(),
app_o_file.clone(),
&[app_o_file.to_str().unwrap()],
LinkType::Dylib,
)
.unwrap();
child.wait().unwrap();
// Load the dylib
let path = dylib_path.as_path().to_str().unwrap();
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

@ -2,10 +2,9 @@ use crate::target;
use bumpalo::Bump;
use inkwell::context::Context;
use inkwell::targets::{CodeModel, FileType, RelocMode};
use inkwell::OptimizationLevel;
use roc_gen::layout_id::LayoutIds;
use roc_gen::llvm::build::{build_proc, build_proc_header, module_from_builtins, OptLevel, Scope};
use roc_load::file::MonomorphizedModule;
use roc_mono::layout::LayoutIds;
use std::path::{Path, PathBuf};
use target_lexicon::Triple;
@ -15,24 +14,26 @@ use target_lexicon::Triple;
#[allow(clippy::cognitive_complexity)]
pub fn gen_from_mono_module(
arena: &Bump,
loaded: MonomorphizedModule,
filename: PathBuf,
mut loaded: MonomorphizedModule,
_file_path: PathBuf,
target: Triple,
dest_filename: &Path,
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, filename.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);
@ -40,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, filename.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);
@ -49,11 +51,27 @@ 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();
let module = arena.alloc(module_from_builtins(&context, "app"));
// strip Zig debug stuff
// module.strip_debug_info();
let builder = context.create_builder();
let (dibuilder, compile_unit) = roc_gen::llvm::build::Env::new_debug_info(module);
let (mpm, fpm) = roc_gen::llvm::build::construct_optimization_passes(module, opt_level);
let ptr_bytes = target.pointer_width().unwrap().bytes() as u32;
@ -62,6 +80,8 @@ pub fn gen_from_mono_module(
let env = roc_gen::llvm::build::Env {
arena: &arena,
builder: &builder,
dibuilder: &dibuilder,
compile_unit: &compile_unit,
context: &context,
interns: loaded.interns,
module,
@ -99,6 +119,9 @@ pub fn gen_from_mono_module(
// println!("\n\nBuilding and then verifying function {:?}\n\n", proc);
build_proc(&env, &mut layout_ids, scope.clone(), proc, fn_val);
// call finalize() before any code generation/verification
env.dibuilder.finalize();
if fn_val.verify(true) {
fpm.run_on(&fn_val);
} else {
@ -111,6 +134,8 @@ pub fn gen_from_mono_module(
}
}
env.dibuilder.finalize();
// Uncomment this to see the module's optimized LLVM instruction output:
// env.module.print_to_stderr();
@ -126,14 +151,11 @@ pub fn gen_from_mono_module(
// Emit the .o file
let opt = OptimizationLevel::Aggressive;
let reloc = RelocMode::Default;
let model = CodeModel::Default;
let target_machine = target::target_machine(&target, opt, reloc, model).unwrap();
let target_machine = target::target_machine(&target, opt_level.into(), reloc, model).unwrap();
target_machine
.write_to_file(&env.module, FileType::Object, &dest_filename)
.write_to_file(&env.module, FileType::Object, &app_o_file)
.expect("Writing .o file failed");
println!("\nSuccess! 🎉\n\n\t{}\n", dest_filename.display());
}

View file

@ -68,7 +68,7 @@ pub fn target_machine(
Target::from_name(arch).unwrap().create_target_machine(
&TargetTriple::create(target_triple_str(target)),
arch,
"+avx2", // TODO this string was used uncritically from an example, and should be reexamined
"", // TODO: this probably should be TargetMachine::get_host_cpu_features() to enable all features.
opt,
reloc,
model,

View file

@ -58,12 +58,44 @@ This is where bottom-level functions that need to be written as LLVM are created
### builtins/src/std.rs
Its one thing to actually write these functions, its _another_ thing to let the Roc compiler know they exist as part of the standard library. You have to tell the compiler "Hey, this function exists, and it has this type signature". That happens in `std.rs`.
## Specifying how we pass args to the function
### builtins/mono/src/borrow.rs
After we have all of this, we need to specify if the arguements we're passing are owned, borrowed or irrelvant. Towards the bottom of this file, add a new case for you builtin and specify each arg. Be sure to read the comment, as it explains this in more detail.
## Specifying the uniqueness of a function
### builtins/src/unique.rs
One of the cool things about Roc is that it evaluates if a value in memory is shared between scopes or if it is used in just one place. If the value is used in one place then it is 'unique', and it therefore can be mutated in place. For a value created by a function, the uniqueness of the output is determined in part by the uniqueness of the input arguments. For example `List.single : elem -> List elem` can return a unique list if the `elem` is also unique.
We have to define the uniqueness constraints of a function just like we have to define a type signature. That is what happens in `unique.rs`. This can be tricky so it would be a good step to ask for help on if it is confusing.
## Testing it
### solve/tests/solve_expr.rs
To make sure that Roc is properly inferring the type of the new builtin, add a test to this file simlar to:
```
#[test]
fn atan() {
infer_eq_without_problem(
indoc!(
r#"
Num.atan
"#
),
"Float -> Float",
);
}
```
But replace `Num.atan` and the type signature with the new builtin.
### gen/test/*.rs
In this directory, there are a couple files like `gen_num.rs`, `gen_str.rs`, etc. For the `Str` module builtins, put the test in `gen_str.rs`, etc. Find the one for the new builtin, and add a test like:
```
#[test]
fn atan() {
assert_evals_to!("Num.atan 10", 1.4711276743037347, f64);
}
```
But replace `Num.atan`, the return value, and the return type with your new builtin.
# Mistakes that are easy to make!!
When implementing a new builtin, it is often easy to copy and paste the implementation for an existing builtin. This can take you quite far since many builtins are very similar, but it also risks forgetting to change one small part of what you copy and pasted and losing a lot of time later on when you cant figure out why things dont work. So, speaking from experience, even if you are copying an existing builtin, try and implement it manually without copying and pasting. Two recent instances of this (as of September 7th, 2020):

4
compiler/builtins/bitcode/.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
zig-cache
src/zig-cache
builtins.ll
builtins.bc

View file

@ -1,9 +0,0 @@
[package]
name = "roc_builtins_bitcode"
version = "0.1.0"
authors = ["Richard Feldman <oss@rtfeldman.com>"]
repository = "https://github.com/rtfeldman/roc"
readme = "README.md"
edition = "2018"
description = "Generate LLVM bitcode for Roc builtins"
license = "Apache-2.0"

View file

@ -1,57 +1,35 @@
# Bitcode for Builtins
## Adding a bitcode builtin
To add a builtin:
1. Add the function to the relevent module. For `Num` builtin use it in `src/num.zig`, for `Str` builtins use `src/str.zig`, and so on. **For anything you add, you must add tests for it!** Not only does to make the builtins more maintainable, it's the the easiest way to test these functions on Zig. To run the test, run: `zig build test`
2. Make sure the function is public with the `pub` keyword and uses the C calling convention. This is really easy, just add `pub` and `callconv(.C)` to the function declaration like so: `pub fn atan(num: f64) callconv(.C) f64 { ... }`
3. In `src/main.zig`, export the function. This is also organized by module. For example, for a `Num` function find the `Num` section and add: `comptime { exportNumFn(num.atan, "atan"); }`. The first arguement is the function, the second is the name of it in LLVM.
4. In `compiler/builtins/src/bitcode.rs`, add a constant for the new function. This is how we use it in Rust. Once again, this is organized by module, so just find the relevent area and add your new function.
5. You can now your function in Rust using `call_bitcode_fn` in `llvm/src/build.rs`!
## How it works
Roc's builtins are implemented in the compiler using LLVM only.
When their implementations are simple enough (e.g. addition), they
can be implemented directly in Inkwell.
When their implementations are complex enough, it's nicer to
implement them in a higher-level language like Rust, compile the
result to LLVM bitcode, and import that bitcode into the compiler.
implement them in a higher-level language like Zig, then compile
the result to LLVM bitcode, and import that bitcode into the compiler.
Here is the process for doing that.
Compiling the bitcode happens automatically in a Rust build script at `compiler/builtins/build.rs`.
Then `builtins/src/bitcode/rs` staticlly imports the compiled bitcode for use in the compiler.
## Building the bitcode
You can find the compiled bitcode in `target/debug/build/roc_builtins-[some random characters]/out/builtins.bc`.
There will be two directories like `roc_builtins-[some random characters]`, look for the one that has an
`out` directory as a child.
The source we'll use to generate the bitcode is in `src/lib.rs` in this directory.
To generate the bitcode, `cd` into `compiler/builtins/bitcode/` and run:
```bash
$ ./regenerate.sh
```
> If you want to take a look at the human-readable LLVM IR rather than the
> bitcode, run this instead and look for a `.ll` file instead of a `.bc` file:
>
> ```bash
> $ cargo rustc --release --lib -- --emit=llvm-ir
> ```
>
> Then look in the root `roc` source directory under `target/release/deps/` for a file
> with a name like `roc_builtins_bitcode-8da0901c58a73ebf.bc` - except
> probably with a different hash before the `.bc`. If there's more than one
> `*.bc` file in that directory, delete the whole `deps/` directory and re-run
> the `cargo rustc` command above to regenerate it.
**Note**: In order to be able to address the bitcode functions by name, they need to be defined with the `#[no_mangle]` attribute.
The bitcode is a bunch of bytes that aren't particularly human-readable.
Since Roc is designed to be distributed as a single binary, these bytes
need to be included in the raw source somewhere.
The `llvm/src/build.rs` file statically imports these raw bytes
using the [`include_bytes!` macro](https://doc.rust-lang.org/std/macro.include_bytes.html).
The current `.bc` file is located at:
```
compiler/gen/src/llvm/builtins.bc
```
The script will automatically replace this `.bc` file with the new one.
Once that's done, `git status` should show that the `builtins.bc` file
has been changed. Commit that change and you're done!
> The bitcode is a bunch of bytes that aren't particularly human-readable.
> If you want to take a look at the human-readable LLVM IR, look at
> `target/debug/build/roc_builtins-[some random characters]/out/builtins.ll`
## Calling bitcode functions
Use the `call_bitcode_fn` function defined in `llvm/src/build.rs` to call bitcode funcitons.
use the `call_bitcode_fn` function defined in `llvm/src/build.rs` to call bitcode funcitons.

View file

@ -0,0 +1,59 @@
const builtin = @import("builtin");
const std = @import("std");
const mem = std.mem;
const Builder = std.build.Builder;
pub fn build(b: *Builder) void {
b.setPreferredReleaseMode(builtin.Mode.ReleaseFast);
const mode = b.standardReleaseOptions();
// Options
const fallback_main_path = "./src/main.zig";
const main_path_desc = b.fmt("Override path to main.zig. Used by \"ir\", \"bc\", and \"test\". Defaults to \"{}\". ", .{fallback_main_path});
const main_path = b.option([]const u8, "main-path", main_path_desc) orelse fallback_main_path;
const fallback_bitcode_path = "./builtins.bc";
const bitcode_path_desc = b.fmt("Override path to generated bitcode file. Used by \"ir\" and \"bc\". Defaults to \"{}\". ", .{fallback_bitcode_path});
const bitcode_path = b.option([]const u8, "bc-path", bitcode_path_desc) orelse fallback_bitcode_path;
// Tests
var main_tests = b.addTest(main_path);
main_tests.setBuildMode(mode);
const test_step = b.step("test", "Run tests");
test_step.dependOn(&main_tests.step);
// Lib
const obj_name = "builtins";
const obj = b.addObject(obj_name, main_path);
obj.setBuildMode(mode);
obj.strip = true;
obj.emit_llvm_ir = true;
obj.emit_bin = false;
const ir = b.step("ir", "Build LLVM ir");
ir.dependOn(&obj.step);
// IR to Bitcode
const bitcode_path_arg = b.fmt("-o={}", .{bitcode_path});
const ir_out_file = b.fmt("{}.ll", .{obj_name});
const ir_to_bitcode = b.addSystemCommand(&[_][]const u8{
"llvm-as-10",
ir_out_file,
bitcode_path_arg
});
const bicode = b.step("bc", "Build LLVM ir and convert to bitcode");
bicode.dependOn(ir);
bicode.dependOn(&ir_to_bitcode.step);
b.default_step = ir;
removeInstallSteps(b);
}
fn removeInstallSteps(b: *Builder) void {
for (b.top_level_steps.items) |top_level_step, i| {
if (mem.eql(u8, top_level_step.step.name, "install") or mem.eql(u8, top_level_step.step.name, "uninstall")) {
const name = top_level_step.step.name;
_ = b.top_level_steps.swapRemove(i);
}
}
}

View file

@ -1,19 +0,0 @@
#!/bin/bash
set -euxo pipefail
# Clear out any existing output files. Sometimes if these are there, rustc
# doesn't generate the .bc file - or we can end up with more than one .bc
rm -rf ../../../target/release/deps/
# Regenerate the .bc file
cargo rustc --release --lib -- --emit=llvm-bc
bc_files=$(ls ../../../target/release/deps/*.bc | wc -l)
if [[ $bc_files != 1 ]]; then
echo "More than one .bc file was emitted somehow."
exit 1;
fi
cp ../../../target/release/deps/*.bc ../../gen/src/llvm/builtins.bc

File diff suppressed because it is too large Load diff

View file

@ -1,54 +0,0 @@
// NOTE: Editing this file on its own does nothing! The procedure for
// incorporating changes here is in this crate' README.
#![crate_type = "lib"]
#![no_std]
mod libm;
/// TODO this is no longer used. Feel free to delete it the next time
/// we need to rebuild builtins.bc!
#[no_mangle]
pub fn i64_to_f64_(num: i64) -> f64 {
num as f64
}
/// Adapted from Rust's core::num module, by the Rust core team,
/// licensed under the Apache License, version 2.0 - https://www.apache.org/licenses/LICENSE-2.0
///
/// Thank you, Rust core team!
#[no_mangle]
pub fn pow_int_(mut base: i64, mut exp: i64) -> i64 {
let mut acc = 1;
while exp > 1 {
if (exp & 1) == 1 {
acc *= base;
}
exp /= 2;
base *= base;
}
// Deal with the final bit of the exponent separately, since
// squaring the base afterwards is not necessary and may cause a
// needless overflow.
if exp == 1 {
acc *= base;
}
acc
}
/// Adapted from Rust's core::num module, by the Rust core team,
/// licensed under the Apache License, version 2.0 - https://www.apache.org/licenses/LICENSE-2.0
///
/// Thank you, Rust core team!
#[no_mangle]
pub fn is_finite_(num: f64) -> bool {
f64::is_finite(num)
}
#[no_mangle]
pub fn atan_(x: f64) -> f64 {
libm::atan(x)
}

View file

@ -1,199 +0,0 @@
/// Adapted from Rust's libm module, by the Rust core team,
/// licensed under the Apache License, version 2.0 - https://www.apache.org/licenses/LICENSE-2.0
/// https://github.com/rust-lang/libm/blob/master/LICENSE-APACHE
///
/// Thank you, Rust core team!
// From https://github.com/rust-lang/libm/blob/master/src/math/mod.rs
#[cfg(not(debug_assertions))]
macro_rules! i {
($array:expr, $index:expr) => {
unsafe { *$array.get_unchecked($index) }
};
($array:expr, $index:expr, = , $rhs:expr) => {
unsafe {
*$array.get_unchecked_mut($index) = $rhs;
}
};
($array:expr, $index:expr, += , $rhs:expr) => {
unsafe {
*$array.get_unchecked_mut($index) += $rhs;
}
};
($array:expr, $index:expr, -= , $rhs:expr) => {
unsafe {
*$array.get_unchecked_mut($index) -= $rhs;
}
};
($array:expr, $index:expr, &= , $rhs:expr) => {
unsafe {
*$array.get_unchecked_mut($index) &= $rhs;
}
};
($array:expr, $index:expr, == , $rhs:expr) => {
unsafe { *$array.get_unchecked_mut($index) == $rhs }
};
}
#[cfg(debug_assertions)]
macro_rules! i {
($array:expr, $index:expr) => {
*$array.get($index).unwrap()
};
($array:expr, $index:expr, = , $rhs:expr) => {
*$array.get_mut($index).unwrap() = $rhs;
};
($array:expr, $index:expr, -= , $rhs:expr) => {
*$array.get_mut($index).unwrap() -= $rhs;
};
($array:expr, $index:expr, += , $rhs:expr) => {
*$array.get_mut($index).unwrap() += $rhs;
};
($array:expr, $index:expr, &= , $rhs:expr) => {
*$array.get_mut($index).unwrap() &= $rhs;
};
($array:expr, $index:expr, == , $rhs:expr) => {
*$array.get_mut($index).unwrap() == $rhs
};
}
macro_rules! llvm_intrinsically_optimized {
(#[cfg($($clause:tt)*)] $e:expr) => {
#[cfg(all(feature = "unstable", $($clause)*))]
{
if true { // thwart the dead code lint
$e
}
}
};
}
macro_rules! force_eval {
($e:expr) => {
unsafe {
::core::ptr::read_volatile(&$e);
}
};
}
// From https://github.com/rust-lang/libm/blob/master/src/math/atan.rs
// Clippy fails CI if we don't include overrides below. Since this is copied
// straight from the libm crate, I figure they had a reason to include
// the extra percision, so we just silence this warning.
#[allow(clippy::excessive_precision)]
const ATANHI: [f64; 4] = [
4.63647609000806093515e-01, /* atan(0.5)hi 0x3FDDAC67, 0x0561BB4F */
7.85398163397448278999e-01, /* atan(1.0)hi 0x3FE921FB, 0x54442D18 */
9.82793723247329054082e-01, /* atan(1.5)hi 0x3FEF730B, 0xD281F69B */
1.57079632679489655800e+00, /* atan(inf)hi 0x3FF921FB, 0x54442D18 */
];
#[allow(clippy::excessive_precision)]
const ATANLO: [f64; 4] = [
2.26987774529616870924e-17, /* atan(0.5)lo 0x3C7A2B7F, 0x222F65E2 */
3.06161699786838301793e-17, /* atan(1.0)lo 0x3C81A626, 0x33145C07 */
1.39033110312309984516e-17, /* atan(1.5)lo 0x3C700788, 0x7AF0CBBD */
6.12323399573676603587e-17, /* atan(inf)lo 0x3C91A626, 0x33145C07 */
];
#[allow(clippy::excessive_precision)]
const AT: [f64; 11] = [
3.33333333333329318027e-01, /* 0x3FD55555, 0x5555550D */
-1.99999999998764832476e-01, /* 0xBFC99999, 0x9998EBC4 */
1.42857142725034663711e-01, /* 0x3FC24924, 0x920083FF */
-1.11111104054623557880e-01, /* 0xBFBC71C6, 0xFE231671 */
9.09088713343650656196e-02, /* 0x3FB745CD, 0xC54C206E */
-7.69187620504482999495e-02, /* 0xBFB3B0F2, 0xAF749A6D */
6.66107313738753120669e-02, /* 0x3FB10D66, 0xA0D03D51 */
-5.83357013379057348645e-02, /* 0xBFADDE2D, 0x52DEFD9A */
4.97687799461593236017e-02, /* 0x3FA97B4B, 0x24760DEB */
-3.65315727442169155270e-02, /* 0xBFA2B444, 0x2C6A6C2F */
1.62858201153657823623e-02, /* 0x3F90AD3A, 0xE322DA11 */
];
#[cfg_attr(all(test, assert_no_panic), no_panic::no_panic)]
pub fn fabs(x: f64) -> f64 {
// On wasm32 we know that LLVM's intrinsic will compile to an optimized
// `f64.abs` native instruction, so we can leverage this for both code size
// and speed.
llvm_intrinsically_optimized! {
#[cfg(target_arch = "wasm32")] {
return unsafe { ::core::intrinsics::fabsf64(x) }
}
}
f64::from_bits(x.to_bits() & (u64::MAX / 2))
}
#[inline(always)]
#[cfg_attr(all(test, assert_no_panic), no_panic::no_panic)]
pub fn atan(x: f64) -> f64 {
let mut x = x;
let mut ix = (x.to_bits() >> 32) as u32;
let sign = ix >> 31;
ix &= 0x7fff_ffff;
if ix >= 0x4410_0000 {
if x.is_nan() {
return x;
}
let z = ATANHI[3] + f64::from_bits(0x0380_0000); // 0x1p-120f
return if sign != 0 { -z } else { z };
}
let id = if ix < 0x3fdc_0000 {
/* |x| < 0.4375 */
if ix < 0x3e40_0000 {
/* |x| < 2^-27 */
if ix < 0x0010_0000 {
/* raise underflow for subnormal x */
force_eval!(x as f32);
}
return x;
}
-1
} else {
x = fabs(x);
if ix < 0x3ff30000 {
/* |x| < 1.1875 */
if ix < 0x3fe60000 {
/* 7/16 <= |x| < 11/16 */
x = (2. * x - 1.) / (2. + x);
0
} else {
/* 11/16 <= |x| < 19/16 */
x = (x - 1.) / (x + 1.);
1
}
} else if ix < 0x40038000 {
/* |x| < 2.4375 */
x = (x - 1.5) / (1. + 1.5 * x);
2
} else {
/* 2.4375 <= |x| < 2^66 */
x = -1. / x;
3
}
};
let z = x * x;
let w = z * z;
/* break sum from i=0 to 10 AT[i]z**(i+1) into odd and even poly */
let s1 = z * (AT[0] + w * (AT[2] + w * (AT[4] + w * (AT[6] + w * (AT[8] + w * AT[10])))));
let s2 = w * (AT[1] + w * (AT[3] + w * (AT[5] + w * (AT[7] + w * AT[9]))));
if id < 0 {
return x - x * (s1 + s2);
}
let z = i!(ATANHI, id as usize) - (x * (s1 + s2) - i!(ATANLO, id as usize) - x);
if sign != 0 {
-z
} else {
z
}
}

View file

@ -0,0 +1,34 @@
const builtin = @import("builtin");
const std = @import("std");
const testing = std.testing;
// Num Module
const num = @import("num.zig");
comptime { exportNumFn(num.atan, "atan"); }
comptime { exportNumFn(num.isFinite, "is_finite"); }
comptime { exportNumFn(num.powInt, "pow_int"); }
comptime { exportNumFn(num.acos, "acos"); }
comptime { exportNumFn(num.asin, "asin"); }
// Str Module
const str = @import("str.zig");
comptime { exportStrFn(str.strSplitInPlace, "str_split_in_place"); }
comptime { exportStrFn(str.countSegments, "count_segments"); }
comptime { exportStrFn(str.countGraphemeClusters, "count_grapheme_clusters"); }
// Export helpers - Must be run inside a comptime
fn exportBuiltinFn(comptime fn_target: anytype, comptime fn_name: []const u8) void {
@export(fn_target, .{ .name = "roc_builtins." ++ fn_name, .linkage = .Strong });
}
fn exportNumFn(comptime fn_target: anytype, comptime fn_name: []const u8) void {
exportBuiltinFn(fn_target, "num." ++ fn_name);
}
fn exportStrFn(comptime fn_target: anytype, comptime fn_name: []const u8) void {
exportBuiltinFn(fn_target, "str." ++ fn_name);
}
// Run all tests in imported modules
// https://github.com/ziglang/zig/blob/master/lib/std/std.zig#L94
test "" {
testing.refAllDecls(@This());
}

View file

@ -0,0 +1,22 @@
const std = @import("std");
const math = std.math;
pub fn atan(num: f64) callconv(.C) f64 {
return math.atan(num);
}
pub fn isFinite(num: f64) callconv(.C) bool {
return math.isFinite(num);
}
pub fn powInt(base: i64, exp: i64) callconv(.C) i64 {
return math.pow(i64, base, exp);
}
pub fn acos(num: f64) callconv(.C) f64 {
return math.acos(num);
}
pub fn asin(num: f64) callconv(.C) f64 {
return math.asin(num);
}

View file

@ -0,0 +1,524 @@
const std = @import("std");
const unicode = std.unicode;
const testing = std.testing;
const expectEqual = testing.expectEqual;
const expect = testing.expect;
const RocStr = struct {
str_bytes_ptrs: [*]u8,
str_len: usize,
pub fn get_small_str_ptr(self: *RocStr) *u8 {
const small_str_ptr = @ptrCast(*u8, self);
return small_str_ptr;
}
pub fn empty() RocStr {
return RocStr {
.str_len = 0,
.str_bytes_ptrs = undefined
};
}
pub fn init(bytes: [*]u8, len: usize) RocStr {
const rocStrSize = @sizeOf(RocStr);
if (len < rocStrSize) {
var ret_small_str = RocStr.empty();
const target_ptr = @ptrToInt(ret_small_str.get_small_str_ptr());
var index : u8 = 0;
// Zero out the data, just to be safe
while (index < rocStrSize) {
var offset_ptr = @intToPtr(*u8, target_ptr + index);
offset_ptr.* = 0;
index += 1;
}
index = 0;
while (index < len) {
var offset_ptr = @intToPtr(*u8, target_ptr + index);
offset_ptr.* = bytes[index];
index += 1;
}
// set the final byte to be the length
const final_byte_ptr = @intToPtr(*u8, target_ptr + rocStrSize - 1);
final_byte_ptr.* = @truncate(u8, len) ^ 0b10000000;
return ret_small_str;
} else {
return RocStr {
.str_bytes_ptrs = bytes,
.str_len = len
};
}
}
pub fn eq(self: *RocStr, other: RocStr) bool {
if (self.str_len != other.str_len) {
return false;
}
var areEq: bool = true;
var index: usize = 0;
while (index < self.str_len and areEq) {
areEq = areEq and self.str_bytes_ptrs[index] == other.str_bytes_ptrs[index];
index = index + 1;
}
return areEq;
}
test "RocStr.eq: equal" {
const str1_len = 3;
var str1: [str1_len]u8 = "abc".*;
const str1_ptr: [*]u8 = &str1;
var roc_str1 = RocStr.init(str1_ptr, str1_len);
const str2_len = 3;
var str2: [str2_len]u8 = "abc".*;
const str2_ptr: [*]u8 = &str2;
var roc_str2 = RocStr.init(str2_ptr, str2_len);
expect(roc_str1.eq(roc_str2));
}
test "RocStr.eq: not equal different length" {
const str1_len = 4;
var str1: [str1_len]u8 = "abcd".*;
const str1_ptr: [*]u8 = &str1;
var roc_str1 = RocStr.init(str1_ptr, str1_len);
const str2_len = 3;
var str2: [str2_len]u8 = "abc".*;
const str2_ptr: [*]u8 = &str2;
var roc_str2 = RocStr.init(str2_ptr, str2_len);
expect(!roc_str1.eq(roc_str2));
}
test "RocStr.eq: not equal same length" {
const str1_len = 3;
var str1: [str1_len]u8 = "acb".*;
const str1_ptr: [*]u8 = &str1;
var roc_str1 = RocStr.init(str1_ptr, str1_len);
const str2_len = 3;
var str2: [str2_len]u8 = "abc".*;
const str2_ptr: [*]u8 = &str2;
var roc_str2 = RocStr.init(str2_ptr, str2_len);
expect(!roc_str1.eq(roc_str2));
}
};
// Str.split
pub fn strSplitInPlace(
array: [*]RocStr,
array_len: usize,
str_bytes_ptrs: [*]u8,
str_len: usize,
delimiter_bytes_ptrs: [*]u8,
delimiter_len: usize
) callconv(.C) void {
var ret_array_index : usize = 0;
var sliceStart_index : usize = 0;
var str_index : usize = 0;
if (str_len > delimiter_len) {
const end_index : usize = str_len - delimiter_len + 1;
while (str_index <= end_index) {
var delimiter_index : usize = 0;
var matches_delimiter = true;
while (delimiter_index < delimiter_len) {
var delimiterChar = delimiter_bytes_ptrs[delimiter_index];
var strChar = str_bytes_ptrs[str_index + delimiter_index];
if (delimiterChar != strChar) {
matches_delimiter = false;
break;
}
delimiter_index += 1;
}
if (matches_delimiter) {
const segment_len : usize = str_index - sliceStart_index;
array[ret_array_index] = RocStr.init(str_bytes_ptrs + sliceStart_index, segment_len);
sliceStart_index = str_index + delimiter_len;
ret_array_index += 1;
str_index += delimiter_len;
} else {
str_index += 1;
}
}
}
array[ret_array_index] = RocStr.init(str_bytes_ptrs + sliceStart_index, str_len - sliceStart_index);
}
test "strSplitInPlace: no delimiter" {
// Str.split "abc" "!" == [ "abc" ]
var str: [3]u8 = "abc".*;
const str_ptr: [*]u8 = &str;
var delimiter: [1]u8 = "!".*;
const delimiter_ptr: [*]u8 = &delimiter;
var array: [1]RocStr = undefined;
const array_ptr: [*]RocStr = &array;
strSplitInPlace(
@ptrCast([*]u128, array_ptr),
1,
str_ptr,
3,
delimiter_ptr,
1
);
var expected = [1]RocStr{
RocStr.init(str_ptr, 3),
};
expectEqual(array.len, expected.len);
expect(array[0].eq(expected[0]));
}
test "strSplitInPlace: empty end" {
const str_len: usize = 50;
var str: [str_len]u8 = "1---- ---- ---- ---- ----2---- ---- ---- ---- ----".*;
const str_ptr: [*]u8 = &str;
const delimiter_len = 24;
const delimiter: [delimiter_len]u8 = "---- ---- ---- ---- ----";
const delimiter_ptr: [*]u8 = &delimiter;
const array_len : usize = 3;
var array: [array_len]RocStr = [_]RocStr {
undefined,
undefined,
undefined,
};
const array_ptr: [*]RocStr = &array;
strSplitInPlace(
array_ptr,
array_len,
str_ptr,
str_len,
delimiter_ptr,
delimiter_len
);
const first_expected_str_len: usize = 1;
var first_expected_str: [first_expected_str_len]u8 = "1".*;
const first_expected_str_ptr: [*]u8 = &first_expected_str;
var firstExpectedRocStr = RocStr.init(first_expected_str_ptr, first_expected_str_len);
const second_expected_str_len: usize = 1;
var second_expected_str: [second_expected_str_len]u8 = "2".*;
const second_expected_str_ptr: [*]u8 = &second_expected_str;
var secondExpectedRocStr = RocStr.init(second_expected_str_ptr, second_expected_str_len);
expectEqual(array.len, 3);
expectEqual(array[0].str_len, 0);
expect(array[0].eq(firstExpectedRocStr));
expect(array[1].eq(secondExpectedRocStr));
expectEqual(array[2].str_len, 0);
}
test "strSplitInPlace: delimiter on sides" {
// Str.split "tttghittt" "ttt" == [ "", "ghi", "" ]
const str_len: usize = 9;
var str: [str_len]u8 = "tttghittt".*;
const str_ptr: [*]u8 = &str;
const delimiter_len = 3;
var delimiter: [delimiter_len]u8 = "ttt".*;
const delimiter_ptr: [*]u8 = &delimiter;
const array_len : usize = 3;
var array: [array_len]RocStr = [_]RocStr{
undefined ,
undefined,
undefined,
};
const array_ptr: [*]RocStr = &array;
strSplitInPlace(
array_ptr,
array_len,
str_ptr,
str_len,
delimiter_ptr,
delimiter_len
);
const expected_str_len: usize = 3;
var expected_str: [expected_str_len]u8 = "ghi".*;
const expected_str_ptr: [*]u8 = &expected_str;
var expectedRocStr = RocStr.init(expected_str_ptr, expected_str_len);
expectEqual(array.len, 3);
expectEqual(array[0].str_len, 0);
expect(array[1].eq(expectedRocStr));
expectEqual(array[2].str_len, 0);
}
test "strSplitInPlace: three pieces" {
// Str.split "a!b!c" "!" == [ "a", "b", "c" ]
const str_len: usize = 5;
var str: [str_len]u8 = "a!b!c".*;
const str_ptr: [*]u8 = &str;
const delimiter_len = 1;
var delimiter: [delimiter_len]u8 = "!".*;
const delimiter_ptr: [*]u8 = &delimiter;
const array_len : usize = 3;
var array: [array_len]RocStr = undefined;
const array_ptr: [*]RocStr = &array;
strSplitInPlace(
array_ptr,
array_len,
str_ptr,
str_len,
delimiter_ptr,
delimiter_len
);
var a: [1]u8 = "a".*;
const a_ptr: [*]u8 = &a;
var b: [1]u8 = "b".*;
const b_ptr: [*]u8 = &b;
var c: [1]u8 = "c".*;
const c_ptr: [*]u8 = &c;
var expected_array = [array_len]RocStr{
RocStr{
.str_bytes_ptrs = a_ptr,
.str_len = 1,
},
RocStr{
.str_bytes_ptrs = b_ptr,
.str_len = 1,
},
RocStr{
.str_bytes_ptrs = c_ptr,
.str_len = 1,
}
};
expectEqual(expected_array.len, array.len);
expect(array[0].eq(expected_array[0]));
expect(array[1].eq(expected_array[1]));
expect(array[2].eq(expected_array[2]));
}
// This is used for `Str.split : Str, Str -> Array Str
// It is used to count how many segments the input `_str`
// needs to be broken into, so that we can allocate a array
// of that size. It always returns at least 1.
pub fn countSegments(
str_bytes_ptrs: [*]u8,
str_len: usize,
delimiter_bytes_ptrs: [*]u8,
delimiter_len: usize
) callconv(.C) usize {
var count: usize = 1;
if (str_len > delimiter_len) {
var str_index: usize = 0;
const end_cond: usize = str_len - delimiter_len + 1;
while (str_index < end_cond) {
var delimiter_index: usize = 0;
var matches_delimiter = true;
while (delimiter_index < delimiter_len) {
const delimiterChar = delimiter_bytes_ptrs[delimiter_index];
const strChar = str_bytes_ptrs[str_index + delimiter_index];
if (delimiterChar != strChar) {
matches_delimiter = false;
break;
}
delimiter_index += 1;
}
if (matches_delimiter) {
count += 1;
}
str_index += 1;
}
}
return count;
}
test "countSegments: long delimiter" {
// Str.split "str" "delimiter" == [ "str" ]
// 1 segment
const str_len: usize = 3;
var str: [str_len]u8 = "str".*;
const str_ptr: [*]u8 = &str;
const delimiter_len = 9;
var delimiter: [delimiter_len]u8 = "delimiter".*;
const delimiter_ptr: [*]u8 = &delimiter;
const segments_count = countSegments(
str_ptr,
str_len,
delimiter_ptr,
delimiter_len
);
expectEqual(segments_count, 1);
}
test "countSegments: delimiter at start" {
// Str.split "hello there" "hello" == [ "", " there" ]
// 2 segments
const str_len: usize = 11;
var str: [str_len]u8 = "hello there".*;
const str_ptr: [*]u8 = &str;
const delimiter_len = 5;
var delimiter: [delimiter_len]u8 = "hello".*;
const delimiter_ptr: [*]u8 = &delimiter;
const segments_count = countSegments(
str_ptr,
str_len,
delimiter_ptr,
delimiter_len
);
expectEqual(segments_count, 2);
}
test "countSegments: delimiter interspered" {
// Str.split "a!b!c" "!" == [ "a", "b", "c" ]
// 3 segments
const str_len: usize = 5;
var str: [str_len]u8 = "a!b!c".*;
const str_ptr: [*]u8 = &str;
const delimiter_len = 1;
var delimiter: [delimiter_len]u8 = "!".*;
const delimiter_ptr: [*]u8 = &delimiter;
const segments_count = countSegments(
str_ptr,
str_len,
delimiter_ptr,
delimiter_len
);
expectEqual(segments_count, 3);
}
// Str.countGraphemeClusters
const grapheme = @import("helpers/grapheme.zig");
pub fn countGraphemeClusters(bytes_ptr: [*]u8, bytes_len: usize) callconv(.C) usize {
var bytes = bytes_ptr[0..bytes_len];
var iter = (unicode.Utf8View.init(bytes) catch unreachable).iterator();
var count: usize = 0;
var grapheme_break_state: ?grapheme.BoundClass = null;
var grapheme_break_state_ptr = &grapheme_break_state;
var opt_last_codepoint: ?u21 = null;
while (iter.nextCodepoint()) |cur_codepoint| {
if (opt_last_codepoint) |last_codepoint| {
var did_break = grapheme.isGraphemeBreak(
last_codepoint,
cur_codepoint,
grapheme_break_state_ptr
);
if (did_break) {
count += 1;
grapheme_break_state = null;
}
}
opt_last_codepoint = cur_codepoint;
}
// If there are no breaks, but the str is not empty, then there
// must be a single grapheme
if (bytes_len != 0) {
count += 1;
}
return count;
}
test "countGraphemeClusters: empty string" {
var bytes_arr = "".*;
var bytes_len = bytes_arr.len;
var bytes_ptr: [*]u8 = &bytes_arr;
var count = countGraphemeClusters(bytes_ptr, bytes_len);
expectEqual(count, 0);
}
test "countGraphemeClusters: ascii characters" {
var bytes_arr = "abcd".*;
var bytes_len = bytes_arr.len;
var bytes_ptr: [*]u8 = &bytes_arr;
var count = countGraphemeClusters(bytes_ptr, bytes_len);
expectEqual(count, 4);
}
test "countGraphemeClusters: utf8 characters" {
var bytes_arr = "ãxā".*;
var bytes_len = bytes_arr.len;
var bytes_ptr: [*]u8 = &bytes_arr;
var count = countGraphemeClusters(bytes_ptr, bytes_len);
expectEqual(count, 3);
}
test "countGraphemeClusters: emojis" {
var bytes_arr = "🤔🤔🤔".*;
var bytes_len = bytes_arr.len;
var bytes_ptr: [*]u8 = &bytes_arr;
var count = countGraphemeClusters(bytes_ptr, bytes_len);
expectEqual(count, 3);
}
test "countGraphemeClusters: emojis and ut8 characters" {
var bytes_arr = "🤔å🤔¥🤔ç".*;
var bytes_len = bytes_arr.len;
var bytes_ptr: [*]u8 = &bytes_arr;
var count = countGraphemeClusters(bytes_ptr, bytes_len);
expectEqual(count, 6);
}
test "countGraphemeClusters: emojis, ut8, and ascii characters" {
var bytes_arr = "6🤔å🤔e¥🤔çpp".*;
var bytes_len = bytes_arr.len;
var bytes_ptr: [*]u8 = &bytes_arr;
var count = countGraphemeClusters(bytes_ptr, bytes_len);
expectEqual(count, 10);
}

View file

@ -0,0 +1,64 @@
#[macro_use]
extern crate pretty_assertions;
#[cfg(test)]
mod bitcode {
use roc_builtins_bitcode::{count_segments_, str_split_};
#[test]
fn count_segments() {
assert_eq!(
count_segments_((&"hello there").as_bytes(), (&"hello").as_bytes()),
2
);
assert_eq!(
count_segments_((&"a\nb\nc").as_bytes(), (&"\n").as_bytes()),
3
);
assert_eq!(
count_segments_((&"str").as_bytes(), (&"delimiter").as_bytes()),
1
);
}
#[test]
fn str_split() {
fn splits_to(string: &str, delimiter: &str, expectation: &[&[u8]]) {
assert_eq!(
str_split_(
&mut [(&"").as_bytes()].repeat(expectation.len()),
&string.as_bytes(),
&delimiter.as_bytes()
),
expectation
);
}
splits_to(
"a!b!c",
"!",
&[(&"a").as_bytes(), (&"b").as_bytes(), (&"c").as_bytes()],
);
splits_to(
"a!?b!?c!?",
"!?",
&[
(&"a").as_bytes(),
(&"b").as_bytes(),
(&"c").as_bytes(),
(&"").as_bytes(),
],
);
splits_to("abc", "!", &[(&"abc").as_bytes()]);
splits_to(
"tttttghittttt",
"ttttt",
&[(&"").as_bytes(), (&"ghi").as_bytes(), (&"").as_bytes()],
);
splits_to("def", "!!!!!!", &[(&"def").as_bytes()]);
}
}

View file

@ -0,0 +1,75 @@
use std::convert::AsRef;
use std::env;
use std::ffi::OsStr;
use std::path::Path;
use std::process::Command;
use std::str;
// TODO: Use zig build system command instead
fn main() {
let out_dir = env::var_os("OUT_DIR").unwrap();
// "." is relative to where "build.rs" is
let src_path = Path::new(".").join("bitcode").join("src");
let main_zig_path = src_path.join("main.zig");
let src_path_str = src_path.to_str().expect("Invalid src path");
println!("Building main.zig from: {}", src_path_str);
let zig_cache_dir = Path::new(&out_dir).join("zig-cache");
let zig_cache_dir_str = zig_cache_dir.to_str().expect("Invalid zig cache dir");
println!("Setting zig cache to: {}", zig_cache_dir_str);
let dest_ll_path = Path::new(&out_dir).join("builtins.ll");
let dest_ll = dest_ll_path.to_str().expect("Invalid dest ir path");
let emit_ir_arg = "-femit-llvm-ir=".to_owned() + dest_ll;
println!("Compiling zig llvm-ir to: {}", dest_ll);
run_command(
"zig",
&[
"build-obj",
main_zig_path.to_str().unwrap(),
&emit_ir_arg,
"-fno-emit-bin",
"--strip",
"-O",
"ReleaseFast",
"--cache-dir",
zig_cache_dir_str,
],
);
let dest_bc_path = Path::new(&out_dir).join("builtins.bc");
let dest_bc = dest_bc_path.to_str().expect("Invalid dest bc path");
println!("Compiling bitcode to: {}", dest_bc);
run_command("llvm-as-10", &[dest_ll, "-o", dest_bc]);
// TODO: Recursivly search zig src dir to watch for each file
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-changed={}", src_path_str);
println!("cargo:rustc-env=BUILTINS_BC={}", dest_bc);
}
fn run_command<S, I>(command: &str, args: I)
where
I: IntoIterator<Item = S>,
S: AsRef<OsStr>,
{
let output_result = Command::new(OsStr::new(&command)).args(args).output();
match output_result {
Ok(output) => match output.status.success() {
true => (),
false => {
let error_str = match str::from_utf8(&output.stderr) {
Ok(stderr) => stderr.to_string(),
Err(_) => format!("Failed to run \"{}\"", command),
};
panic!("{} failed: {}", command, error_str);
}
},
Err(reason) => panic!("{} failed: {}", command, reason),
}
}

View file

@ -0,0 +1,28 @@
use std::fs::File;
use std::io::prelude::Read;
use std::vec::Vec;
pub fn get_bytes() -> Vec<u8> {
// In the build script for the builtins module, we compile the builtins bitcode and set
// BUILTINS_BC to the path to the compiled output.
let path: &'static str = env!(
"BUILTINS_BC",
"Env var BUILTINS_BC not found. Is there a problem with the build script?"
);
let mut builtins_bitcode = File::open(path).expect("Unable to find builtins bitcode source");
let mut buffer = Vec::new();
builtins_bitcode
.read_to_end(&mut buffer)
.expect("Unable to read builtins bitcode");
buffer
}
pub const NUM_ASIN: &str = "roc_builtins.num.asin";
pub const NUM_ACOS: &str = "roc_builtins.num.acos";
pub const NUM_ATAN: &str = "roc_builtins.num.atan";
pub const NUM_IS_FINITE: &str = "roc_builtins.num.is_finite";
pub const NUM_POW_INT: &str = "roc_builtins.num.pow_int";
pub const STR_COUNT_SEGMENTS: &str = "roc_builtins.str.count_segments";
pub const STR_STR_SPLIT_IN_PLACE: &str = "roc_builtins.str.str_split_in_place";
pub const STR_COUNT_GRAPEHEME_CLUSTERS: &str = "roc_builtins.str.count_grapheme_clusters";

View file

@ -10,5 +10,6 @@
// and encouraging shortcuts here creates bad incentives. I would rather temporarily
// re-enable this when working on performance optimizations than have it block PRs.
#![allow(clippy::large_enum_variant)]
pub mod bitcode;
pub mod std;
pub mod unique;

View file

@ -1,8 +1,12 @@
use roc_collections::all::{default_hasher, MutMap, MutSet};
use roc_module::ident::TagName;
use roc_module::symbol::Symbol;
use roc_region::all::{Located, Region};
use roc_types::solved_types::{BuiltinAlias, SolvedType};
use roc_region::all::Region;
use roc_types::builtin_aliases::{
bool_type, float_type, int_type, list_type, map_type, num_type, ordering_type, result_type,
set_type, str_type,
};
use roc_types::solved_types::SolvedType;
use roc_types::subs::VarId;
use std::collections::HashMap;
@ -16,7 +20,6 @@ pub enum Mode {
pub struct StdLib {
pub mode: Mode,
pub types: MutMap<Symbol, (SolvedType, Region)>,
pub aliases: MutMap<Symbol, BuiltinAlias>,
pub applies: MutSet<Symbol>,
}
@ -24,7 +27,6 @@ pub fn standard_stdlib() -> StdLib {
StdLib {
mode: Mode::Standard,
types: types(),
aliases: aliases(),
applies: vec![
Symbol::LIST_LIST,
Symbol::SET_SET,
@ -46,123 +48,6 @@ const TVAR3: VarId = VarId::from_u32(3);
const TVAR4: VarId = VarId::from_u32(4);
const TOP_LEVEL_CLOSURE_VAR: VarId = VarId::from_u32(5);
pub fn aliases() -> MutMap<Symbol, BuiltinAlias> {
let mut aliases = HashMap::with_capacity_and_hasher(NUM_BUILTIN_IMPORTS, default_hasher());
let mut add_alias = |symbol, alias| {
debug_assert!(
!aliases.contains_key(&symbol),
"Duplicate alias definition for {:?}",
symbol
);
// TODO instead of using Region::zero for all of these,
// instead use the Region where they were defined in their
// source .roc files! This can give nicer error messages.
aliases.insert(symbol, alias);
};
let single_private_tag = |symbol, targs| {
SolvedType::TagUnion(
vec![(TagName::Private(symbol), targs)],
Box::new(SolvedType::EmptyTagUnion),
)
};
// Num range : [ @Num range ]
add_alias(
Symbol::NUM_NUM,
BuiltinAlias {
region: Region::zero(),
vars: vec![Located::at(Region::zero(), "range".into())],
typ: single_private_tag(Symbol::NUM_AT_NUM, vec![flex(TVAR1)]),
},
);
// Integer : [ @Integer ]
add_alias(
Symbol::NUM_INTEGER,
BuiltinAlias {
region: Region::zero(),
vars: Vec::new(),
typ: single_private_tag(Symbol::NUM_AT_INTEGER, Vec::new()),
},
);
// Int : Num Integer
add_alias(
Symbol::NUM_INT,
BuiltinAlias {
region: Region::zero(),
vars: Vec::new(),
typ: SolvedType::Apply(
Symbol::NUM_NUM,
vec![SolvedType::Apply(Symbol::NUM_INTEGER, Vec::new())],
),
},
);
// FloatingPoint : [ @FloatingPoint ]
add_alias(
Symbol::NUM_FLOATINGPOINT,
BuiltinAlias {
region: Region::zero(),
vars: Vec::new(),
typ: single_private_tag(Symbol::NUM_AT_FLOATINGPOINT, Vec::new()),
},
);
// Float : Num FloatingPoint
add_alias(
Symbol::NUM_FLOAT,
BuiltinAlias {
region: Region::zero(),
vars: Vec::new(),
typ: SolvedType::Apply(
Symbol::NUM_NUM,
vec![SolvedType::Apply(Symbol::NUM_FLOATINGPOINT, Vec::new())],
),
},
);
// Bool : [ True, False ]
add_alias(
Symbol::BOOL_BOOL,
BuiltinAlias {
region: Region::zero(),
vars: Vec::new(),
typ: SolvedType::TagUnion(
vec![
(TagName::Global("True".into()), Vec::new()),
(TagName::Global("False".into()), Vec::new()),
],
Box::new(SolvedType::EmptyTagUnion),
),
},
);
// Result a e : [ Ok a, Err e ]
add_alias(
Symbol::RESULT_RESULT,
BuiltinAlias {
region: Region::zero(),
vars: vec![
Located::at(Region::zero(), "a".into()),
Located::at(Region::zero(), "e".into()),
],
typ: SolvedType::TagUnion(
vec![
(TagName::Global("Ok".into()), vec![flex(TVAR1)]),
(TagName::Global("Err".into()), vec![flex(TVAR2)]),
],
Box::new(SolvedType::EmptyTagUnion),
),
},
);
aliases
}
pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
let mut types = HashMap::with_capacity_and_hasher(NUM_BUILTIN_IMPORTS, default_hasher());
@ -465,6 +350,18 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
top_level_function(vec![float_type()], Box::new(float_type())),
);
// acos : Float -> Float
add_type(
Symbol::NUM_ACOS,
top_level_function(vec![float_type()], Box::new(float_type())),
);
// asin : Float -> Float
add_type(
Symbol::NUM_ASIN,
top_level_function(vec![float_type()], Box::new(float_type())),
);
// Bool module
// and : Bool, Bool -> Bool
@ -493,6 +390,15 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
// Str module
// Str.split : Str, Str -> List Str
add_type(
Symbol::STR_SPLIT,
top_level_function(
vec![str_type(), str_type()],
Box::new(list_type(str_type())),
),
);
// Str.concat : Str, Str -> Str
add_type(
Symbol::STR_CONCAT,
@ -511,6 +417,12 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
SolvedType::Func(vec![str_type(), str_type()], Box::new(bool_type())),
);
// countGraphemes : Str -> Int
add_type(
Symbol::STR_COUNT_GRAPHEMES,
top_level_function(vec![str_type()], Box::new(int_type())),
);
// List module
// get : List elem, Int -> Result elem [ OutOfBounds ]*
@ -559,6 +471,24 @@ 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()),
),
);
// sum : List (Num a) -> Num a
add_type(
Symbol::LIST_SUM,
top_level_function(
vec![list_type(num_type(flex(TVAR1)))],
Box::new(num_type(flex(TVAR1))),
),
);
// walkRight : List elem, (elem -> accum -> accum), accum -> accum
add_type(
Symbol::LIST_WALK_RIGHT,
@ -788,61 +718,3 @@ fn top_level_function(arguments: Vec<SolvedType>, ret: Box<SolvedType>) -> Solve
fn closure(arguments: Vec<SolvedType>, closure_var: VarId, ret: Box<SolvedType>) -> SolvedType {
SolvedType::Func(arguments, Box::new(SolvedType::Flex(closure_var)), ret)
}
#[inline(always)]
fn float_type() -> SolvedType {
SolvedType::Apply(Symbol::NUM_FLOAT, Vec::new())
}
#[inline(always)]
fn int_type() -> SolvedType {
SolvedType::Apply(Symbol::NUM_INT, Vec::new())
}
#[inline(always)]
fn bool_type() -> SolvedType {
SolvedType::Apply(Symbol::BOOL_BOOL, Vec::new())
}
#[inline(always)]
fn ordering_type() -> SolvedType {
// [ LT, EQ, GT ]
SolvedType::TagUnion(
vec![
(TagName::Global("GT".into()), vec![]),
(TagName::Global("EQ".into()), vec![]),
(TagName::Global("LT".into()), vec![]),
],
Box::new(SolvedType::EmptyTagUnion),
)
}
#[inline(always)]
fn str_type() -> SolvedType {
SolvedType::Apply(Symbol::STR_STR, Vec::new())
}
#[inline(always)]
fn num_type(a: SolvedType) -> SolvedType {
SolvedType::Apply(Symbol::NUM_NUM, vec![a])
}
#[inline(always)]
fn result_type(a: SolvedType, e: SolvedType) -> SolvedType {
SolvedType::Apply(Symbol::RESULT_RESULT, vec![a, e])
}
#[inline(always)]
fn list_type(a: SolvedType) -> SolvedType {
SolvedType::Apply(Symbol::LIST_LIST, vec![a])
}
#[inline(always)]
fn set_type(a: SolvedType) -> SolvedType {
SolvedType::Apply(Symbol::SET_SET, vec![a])
}
#[inline(always)]
fn map_type(key: SolvedType, value: SolvedType) -> SolvedType {
SolvedType::Apply(Symbol::MAP_MAP, vec![key, value])
}

View file

@ -2,8 +2,9 @@ use crate::std::StdLib;
use roc_collections::all::{default_hasher, MutMap};
use roc_module::ident::TagName;
use roc_module::symbol::Symbol;
use roc_region::all::{Located, Region};
use roc_types::solved_types::{BuiltinAlias, SolvedBool, SolvedType};
use roc_region::all::Region;
use roc_types::builtin_aliases;
use roc_types::solved_types::{SolvedBool, SolvedType};
use roc_types::subs::VarId;
use std::collections::HashMap;
@ -58,7 +59,6 @@ pub fn uniq_stdlib() -> StdLib {
use crate::std::Mode;
let types = types();
let aliases = aliases();
/*
debug_assert!({
@ -103,7 +103,6 @@ pub fn uniq_stdlib() -> StdLib {
StdLib {
mode: Mode::Uniqueness,
types,
aliases,
applies: vec![
Symbol::ATTR_ATTR,
Symbol::LIST_LIST,
@ -116,132 +115,6 @@ pub fn uniq_stdlib() -> StdLib {
}
}
pub fn aliases() -> MutMap<Symbol, BuiltinAlias> {
let mut aliases = MutMap::default();
let mut add_alias = |symbol, alias| {
debug_assert!(
!aliases.contains_key(&symbol),
"Duplicate alias definition for {:?}",
symbol
);
// TODO instead of using Region::zero for all of these,
// instead use the Region where they were defined in their
// source .roc files! This can give nicer error messages.
aliases.insert(symbol, alias);
};
let single_private_tag = |symbol, targs| {
SolvedType::TagUnion(
vec![(TagName::Private(symbol), targs)],
Box::new(SolvedType::EmptyTagUnion),
)
};
// NOTE: `a` must be the first variable bound here!
let_tvars! { a, err, star };
// Num : Num Integer
add_alias(
Symbol::NUM_NUM,
BuiltinAlias {
region: Region::zero(),
vars: vec![Located::at(Region::zero(), "a".into())],
typ: single_private_tag(Symbol::NUM_AT_NUM, vec![flex(a)]),
},
);
// Integer : [ @Integer ]
add_alias(
Symbol::NUM_INTEGER,
BuiltinAlias {
region: Region::zero(),
vars: Vec::new(),
typ: single_private_tag(Symbol::NUM_AT_INTEGER, Vec::new()),
},
);
// FloatingPoint : [ @FloatingPoint ]
add_alias(
Symbol::NUM_FLOATINGPOINT,
BuiltinAlias {
region: Region::zero(),
vars: Vec::new(),
typ: single_private_tag(Symbol::NUM_AT_FLOATINGPOINT, Vec::new()),
},
);
// Int : Num Integer
add_alias(
Symbol::NUM_INT,
BuiltinAlias {
region: Region::zero(),
vars: Vec::new(),
typ: SolvedType::Apply(
Symbol::NUM_NUM,
vec![lift(
star,
SolvedType::Apply(Symbol::NUM_INTEGER, Vec::new()),
)],
),
},
);
// Float : Num FloatingPoint
add_alias(
Symbol::NUM_FLOAT,
BuiltinAlias {
region: Region::zero(),
vars: Vec::new(),
typ: SolvedType::Apply(
Symbol::NUM_NUM,
vec![lift(
star,
SolvedType::Apply(Symbol::NUM_FLOATINGPOINT, Vec::new()),
)],
),
},
);
// Bool : [ True, False ]
add_alias(
Symbol::BOOL_BOOL,
BuiltinAlias {
region: Region::zero(),
vars: Vec::new(),
typ: SolvedType::TagUnion(
vec![
(TagName::Global("True".into()), Vec::new()),
(TagName::Global("False".into()), Vec::new()),
],
Box::new(SolvedType::EmptyTagUnion),
),
},
);
// Result a e : [ Ok a, Err e ]
add_alias(
Symbol::RESULT_RESULT,
BuiltinAlias {
region: Region::zero(),
vars: vec![
Located::at(Region::zero(), "a".into()),
Located::at(Region::zero(), "e".into()),
],
typ: SolvedType::TagUnion(
vec![
(TagName::Global("Ok".into()), vec![flex(a)]),
(TagName::Global("Err".into()), vec![flex(err)]),
],
Box::new(SolvedType::EmptyTagUnion),
),
},
);
aliases
}
pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
let mut types = HashMap::with_capacity_and_hasher(NUM_BUILTIN_IMPORTS, default_hasher());
@ -502,6 +375,18 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
unique_function(vec![float_type(star1)], float_type(star2))
});
// acos : Float -> Float
add_type(Symbol::NUM_ACOS, {
let_tvars! { star1, star2 };
unique_function(vec![float_type(star1)], float_type(star2))
});
// asin : Float -> Float
add_type(Symbol::NUM_ASIN, {
let_tvars! { star1, star2 };
unique_function(vec![float_type(star1)], float_type(star2))
});
// Bool module
// isEq or (==) : Attr * a, Attr * a -> Attr * Bool
@ -789,6 +674,32 @@ 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))
});
// sum : Attr * (List (Attr u (Num (Attr u num))))
// -> Attr v (Num (Attr v num))
add_type(Symbol::LIST_SUM, {
let_tvars! { star1, u, v, num };
unique_function(
vec![SolvedType::Apply(
Symbol::ATTR_ATTR,
vec![
flex(star1),
SolvedType::Apply(Symbol::LIST_LIST, vec![num_type(u, num)]),
],
)],
num_type(v, num),
)
});
// join : Attr * (List (Attr * (List a)))
// -> Attr * (List a)
add_type(Symbol::LIST_JOIN, {
@ -1122,6 +1033,24 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
// Str module
// Str.split :
// Attr * Str,
// Attr * Str
// -> Attr * (List (Attr * Str))
add_type(Symbol::STR_SPLIT, {
let_tvars! { star1, star2, star3, star4 };
unique_function(
vec![str_type(star1), str_type(star2)],
SolvedType::Apply(
Symbol::ATTR_ATTR,
vec![
flex(star3),
SolvedType::Apply(Symbol::LIST_LIST, vec![str_type(star4)]),
],
),
)
});
// isEmpty : Attr * Str -> Attr * Bool
add_type(Symbol::STR_IS_EMPTY, {
let_tvars! { star1, star2 };
@ -1140,6 +1069,12 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
unique_function(vec![str_type(star1), str_type(star2)], bool_type(star3))
});
// Str.countGraphemes : Attr * Str, -> Attr * Int
add_type(Symbol::STR_COUNT_GRAPHEMES, {
let_tvars! { star1, star2 };
unique_function(vec![str_type(star1)], int_type(star2))
});
// Result module
// map : Attr * (Result (Attr a e))
@ -1212,7 +1147,17 @@ fn lift(u: VarId, a: SolvedType) -> SolvedType {
fn float_type(u: VarId) -> SolvedType {
SolvedType::Apply(
Symbol::ATTR_ATTR,
vec![flex(u), SolvedType::Apply(Symbol::NUM_FLOAT, Vec::new())],
vec![
flex(u),
SolvedType::Alias(
Symbol::NUM_FLOAT,
Vec::new(),
Box::new(builtin_aliases::num_type(SolvedType::Apply(
Symbol::ATTR_ATTR,
vec![flex(u), builtin_aliases::floatingpoint_type()],
))),
),
],
)
}
@ -1220,7 +1165,17 @@ fn float_type(u: VarId) -> SolvedType {
fn int_type(u: VarId) -> SolvedType {
SolvedType::Apply(
Symbol::ATTR_ATTR,
vec![flex(u), SolvedType::Apply(Symbol::NUM_INT, Vec::new())],
vec![
flex(u),
SolvedType::Alias(
Symbol::NUM_INT,
Vec::new(),
Box::new(builtin_aliases::num_type(SolvedType::Apply(
Symbol::ATTR_ATTR,
vec![flex(u), builtin_aliases::integer_type()],
))),
),
],
)
}
@ -1228,7 +1183,7 @@ fn int_type(u: VarId) -> SolvedType {
fn bool_type(u: VarId) -> SolvedType {
SolvedType::Apply(
Symbol::ATTR_ATTR,
vec![flex(u), SolvedType::Apply(Symbol::BOOL_BOOL, Vec::new())],
vec![flex(u), builtin_aliases::bool_type()],
)
}
@ -1244,10 +1199,7 @@ fn str_type(u: VarId) -> SolvedType {
fn num_type(u: VarId, a: VarId) -> SolvedType {
SolvedType::Apply(
Symbol::ATTR_ATTR,
vec![
flex(u),
SolvedType::Apply(Symbol::NUM_NUM, vec![attr_type(u, a)]),
],
vec![flex(u), builtin_aliases::num_type(attr_type(u, a))],
)
}
@ -1255,10 +1207,7 @@ fn num_type(u: VarId, a: VarId) -> SolvedType {
fn result_type(u: VarId, a: SolvedType, e: SolvedType) -> SolvedType {
SolvedType::Apply(
Symbol::ATTR_ATTR,
vec![
flex(u),
SolvedType::Apply(Symbol::RESULT_RESULT, vec![a, e]),
],
vec![flex(u), builtin_aliases::result_type(a, e)],
)
}

View file

@ -1,6 +1,6 @@
use crate::env::Env;
use crate::scope::Scope;
use roc_collections::all::{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> {
@ -66,8 +73,8 @@ pub fn canonicalize_annotation(
var_store: &mut VarStore,
) -> Annotation {
let mut introduced_variables = IntroducedVariables::default();
let mut aliases = SendMap::default();
let mut references = MutSet::default();
let mut aliases = SendMap::default();
let typ = can_annotation_help(
env,
annotation,
@ -180,8 +187,72 @@ fn can_annotation_help(
args.push(arg_ann);
}
match scope.lookup_alias(symbol) {
Some(alias) => {
// use a known alias
let mut actual = alias.typ.clone();
let mut substitutions = ImMap::default();
let mut vars = Vec::new();
if alias.vars.len() != args.len() {
let error = Type::Erroneous(Problem::BadTypeArguments {
symbol,
region,
alias_needs: alias.vars.len() as u8,
type_got: args.len() as u8,
});
return error;
}
for (loc_var, arg_ann) in alias.vars.iter().zip(args.into_iter()) {
let name = loc_var.value.0.clone();
let var = loc_var.value.1;
substitutions.insert(var, arg_ann.clone());
vars.push((name.clone(), arg_ann));
}
// make sure the recursion variable is freshly instantiated
if let Type::RecursiveTagUnion(rvar, _, _) = &mut actual {
let new = var_store.fresh();
substitutions.insert(*rvar, Type::Variable(new));
*rvar = new;
}
// make sure hidden variables are freshly instantiated
for var in alias.hidden_variables.iter() {
substitutions.insert(*var, Type::Variable(var_store.fresh()));
}
// instantiate variables
actual.substitute(&substitutions);
Type::Alias(symbol, vars, Box::new(actual))
}
None => {
let mut args = Vec::new();
references.insert(symbol);
for arg in *type_arguments {
let arg_ann = can_annotation_help(
env,
&arg.value,
region,
scope,
var_store,
introduced_variables,
local_aliases,
references,
);
args.push(arg_ann);
}
Type::Apply(symbol, args)
}
}
}
BoundVariable(v) => {
let name = Lowercase::from(*v);
@ -276,18 +347,32 @@ fn can_annotation_help(
inner_type
};
let alias = Alias {
region,
vars: lowercase_vars,
uniqueness: None,
typ: alias_actual,
};
local_aliases.insert(symbol, alias);
let mut hidden_variables = MutSet::default();
hidden_variables.extend(alias_actual.variables());
// We turn this 'inline' alias into an Apply. This will later get de-aliased again,
// but this approach is easier wrt. instantiation of uniqueness variables.
let args = vars.into_iter().map(|(_, b)| b).collect();
Type::Apply(symbol, args)
for loc_var in lowercase_vars.iter() {
hidden_variables.remove(&loc_var.value.1);
}
scope.add_alias(symbol, region, lowercase_vars, alias_actual);
let alias = scope.lookup_alias(symbol).unwrap();
local_aliases.insert(symbol, alias.clone());
// Type::Alias(symbol, vars, Box::new(alias.typ.clone()))
if vars.is_empty() && env.home == symbol.module_id() {
let actual_var = var_store.fresh();
introduced_variables.insert_host_exposed_alias(symbol, actual_var);
Type::HostExposedAlias {
name: symbol,
arguments: vars,
actual: Box::new(alias.typ.clone()),
actual_var,
}
} else {
Type::Alias(symbol, vars, Box::new(alias.typ.clone()))
}
}
_ => {
// This is a syntactically invalid type alias.
@ -295,7 +380,7 @@ fn can_annotation_help(
}
},
Record { fields, ext } => {
Record { fields, ext, .. } => {
let field_types = can_assigned_fields(
env,
fields,
@ -323,7 +408,7 @@ fn can_annotation_help(
Type::Record(field_types, Box::new(ext_type))
}
TagUnion { tags, ext } => {
TagUnion { tags, ext, .. } => {
let tag_types = can_tags(
env,
tags,

View file

@ -51,8 +51,10 @@ pub fn builtin_defs(var_store: &mut VarStore) -> MutMap<Symbol, Def> {
Symbol::BOOL_OR => bool_or,
Symbol::BOOL_NOT => bool_not,
Symbol::STR_CONCAT => str_concat,
Symbol::STR_SPLIT => str_split,
Symbol::STR_IS_EMPTY => str_is_empty,
Symbol::STR_STARTS_WITH => str_starts_with,
Symbol::STR_COUNT_GRAPHEMES => str_count_graphemes,
Symbol::LIST_LEN => list_len,
Symbol::LIST_GET => list_get,
Symbol::LIST_SET => list_set,
@ -63,6 +65,8 @@ 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_SUM => list_sum,
Symbol::LIST_PREPEND => list_prepend,
Symbol::LIST_JOIN => list_join,
Symbol::LIST_MAP => list_map,
@ -99,6 +103,38 @@ pub fn builtin_defs(var_store: &mut VarStore) -> MutMap<Symbol, Def> {
Symbol::NUM_POW_INT => num_pow_int,
Symbol::NUM_FLOOR => num_floor,
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(),
}
}
@ -218,8 +254,8 @@ fn num_binop(symbol: Symbol, var_store: &mut VarStore, op: LowLevel) -> Def {
)
}
/// Num a, Num a -> Bool
fn num_bool_binop(symbol: Symbol, var_store: &mut VarStore, op: LowLevel) -> Def {
/// Num a, Num a -> b
fn num_num_other_binop(symbol: Symbol, var_store: &mut VarStore, op: LowLevel) -> Def {
let num_var = var_store.fresh();
let bool_var = var_store.fresh();
let body = RunLowLevel {
@ -325,12 +361,7 @@ fn num_add_checked(symbol: Symbol, var_store: &mut VarStore) -> Def {
annotation: None,
};
let body = LetNonRec(
Box::new(def),
Box::new(no_region(cont)),
ret_var,
SendMap::default(),
);
let body = LetNonRec(Box::new(def), Box::new(no_region(cont)), ret_var);
defn(
symbol,
@ -353,27 +384,27 @@ fn num_mul(symbol: Symbol, var_store: &mut VarStore) -> Def {
/// Num.isGt : Num a, Num a -> Bool
fn num_gt(symbol: Symbol, var_store: &mut VarStore) -> Def {
num_bool_binop(symbol, var_store, LowLevel::NumGt)
num_num_other_binop(symbol, var_store, LowLevel::NumGt)
}
/// Num.isGte : Num a, Num a -> Bool
fn num_gte(symbol: Symbol, var_store: &mut VarStore) -> Def {
num_bool_binop(symbol, var_store, LowLevel::NumGte)
num_num_other_binop(symbol, var_store, LowLevel::NumGte)
}
/// Num.isLt : Num a, Num a -> Bool
fn num_lt(symbol: Symbol, var_store: &mut VarStore) -> Def {
num_bool_binop(symbol, var_store, LowLevel::NumLt)
num_num_other_binop(symbol, var_store, LowLevel::NumLt)
}
/// Num.isLte : Num a, Num a -> Num a
/// Num.isLte : Num a, Num a -> Bool
fn num_lte(symbol: Symbol, var_store: &mut VarStore) -> Def {
num_bool_binop(symbol, var_store, LowLevel::NumLte)
num_num_other_binop(symbol, var_store, LowLevel::NumLte)
}
/// Num.compare : Num a, Num a -> [ LT, EQ, GT ]
fn num_compare(symbol: Symbol, var_store: &mut VarStore) -> Def {
num_bool_binop(symbol, var_store, LowLevel::NumCompare)
num_num_other_binop(symbol, var_store, LowLevel::NumCompare)
}
/// Num.sin : Float -> Float
@ -787,6 +818,46 @@ fn num_atan(symbol: Symbol, var_store: &mut VarStore) -> Def {
)
}
/// Num.acos : Float -> Float
fn num_acos(symbol: Symbol, var_store: &mut VarStore) -> Def {
let arg_float_var = var_store.fresh();
let ret_float_var = var_store.fresh();
let body = RunLowLevel {
op: LowLevel::NumAcos,
args: vec![(arg_float_var, Var(Symbol::ARG_1))],
ret_var: ret_float_var,
};
defn(
symbol,
vec![(arg_float_var, Symbol::ARG_1)],
var_store,
body,
ret_float_var,
)
}
/// Num.asin : Float -> Float
fn num_asin(symbol: Symbol, var_store: &mut VarStore) -> Def {
let arg_float_var = var_store.fresh();
let ret_float_var = var_store.fresh();
let body = RunLowLevel {
op: LowLevel::NumAsin,
args: vec![(arg_float_var, Var(Symbol::ARG_1))],
ret_var: ret_float_var,
};
defn(
symbol,
vec![(arg_float_var, Symbol::ARG_1)],
var_store,
body,
ret_float_var,
)
}
/// List.isEmpty : List * -> Bool
fn list_is_empty(symbol: Symbol, var_store: &mut VarStore) -> Def {
let list_var = var_store.fresh();
@ -838,6 +909,26 @@ fn list_reverse(symbol: Symbol, var_store: &mut VarStore) -> Def {
)
}
/// Str.split : Str, Str -> List Str
fn str_split(symbol: Symbol, var_store: &mut VarStore) -> Def {
let str_var = var_store.fresh();
let ret_list_var = var_store.fresh();
let body = RunLowLevel {
op: LowLevel::StrSplit,
args: vec![(str_var, Var(Symbol::ARG_1)), (str_var, Var(Symbol::ARG_2))],
ret_var: ret_list_var,
};
defn(
symbol,
vec![(str_var, Symbol::ARG_1), (str_var, Symbol::ARG_2)],
var_store,
body,
ret_list_var,
)
}
/// Str.concat : Str, Str -> Str
fn str_concat(symbol: Symbol, var_store: &mut VarStore) -> Def {
let str_var = var_store.fresh();
@ -857,7 +948,7 @@ fn str_concat(symbol: Symbol, var_store: &mut VarStore) -> Def {
)
}
/// Str.isEmpty : List * -> Bool
/// Str.isEmpty : Str -> Bool
fn str_is_empty(symbol: Symbol, var_store: &mut VarStore) -> Def {
let str_var = var_store.fresh();
let bool_var = var_store.fresh();
@ -897,6 +988,26 @@ fn str_starts_with(symbol: Symbol, var_store: &mut VarStore) -> Def {
)
}
/// Str.countGraphemes : Str -> Int
fn str_count_graphemes(symbol: Symbol, var_store: &mut VarStore) -> Def {
let str_var = var_store.fresh();
let int_var = var_store.fresh();
let body = RunLowLevel {
op: LowLevel::StrCountGraphemes,
args: vec![(str_var, Var(Symbol::ARG_1))],
ret_var: int_var,
};
defn(
symbol,
vec![(str_var, Symbol::ARG_1)],
var_store,
body,
int_var,
)
}
/// List.concat : List elem, List elem -> List elem
fn list_concat(symbol: Symbol, var_store: &mut VarStore) -> Def {
let list_var = var_store.fresh();
@ -1231,6 +1342,26 @@ fn list_walk_right(symbol: Symbol, var_store: &mut VarStore) -> Def {
)
}
/// List.sum : List (Num a) -> Num a
fn list_sum(symbol: Symbol, var_store: &mut VarStore) -> Def {
let list_var = var_store.fresh();
let result_var = var_store.fresh();
let body = RunLowLevel {
op: LowLevel::ListSum,
args: vec![(list_var, Var(Symbol::ARG_1))],
ret_var: result_var,
};
defn(
symbol,
vec![(list_var, Symbol::ARG_1)],
var_store,
body,
result_var,
)
}
/// List.keepIf : List elem, (elem -> Bool) -> List elem
fn list_keep_if(symbol: Symbol, var_store: &mut VarStore) -> Def {
let list_var = var_store.fresh();
@ -1254,6 +1385,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

@ -1,13 +1,14 @@
use crate::expected::{Expected, PExpected};
use roc_collections::all::{ImMap, ImSet, SendMap};
use roc_collections::all::SendMap;
use roc_module::symbol::Symbol;
use roc_region::all::{Located, Region};
use roc_types::subs::{VarStore, Variable};
use roc_types::types::{Alias, Category, PatternCategory, Type};
use roc_types::subs::Variable;
use roc_types::types::{Category, PatternCategory, Type};
#[derive(Debug, Clone, PartialEq)]
pub enum Constraint {
Eq(Type, Expected<Type>, Category, Region),
Store(Type, Variable, &'static str, u32),
Lookup(Symbol, Expected<Type>, Region),
Pattern(Region, PatternCategory, Type, PExpected<Type>),
True, // Used for things that always unify, e.g. blanks and runtime errors
@ -16,90 +17,11 @@ pub enum Constraint {
And(Vec<Constraint>),
}
impl Constraint {
pub fn instantiate_aliases(&mut self, var_store: &mut VarStore) {
Self::instantiate_aliases_help(self, &ImMap::default(), var_store, &mut ImSet::default())
}
fn instantiate_aliases_help(
&mut self,
aliases: &ImMap<Symbol, Alias>,
var_store: &mut VarStore,
introduced: &mut ImSet<Variable>,
) {
use Constraint::*;
match self {
True | SaveTheEnvironment => {}
Eq(typ, expected, _, region) => {
let expected_region = expected.get_annotation_region().unwrap_or(*region);
expected.get_type_mut_ref().instantiate_aliases(
expected_region,
aliases,
var_store,
introduced,
);
typ.instantiate_aliases(*region, aliases, var_store, introduced);
}
Lookup(_, expected, region) => {
let expected_region = expected.get_annotation_region().unwrap_or(*region);
expected.get_type_mut_ref().instantiate_aliases(
expected_region,
aliases,
var_store,
introduced,
);
}
Pattern(region, _, typ, pexpected) => {
pexpected
.get_type_mut_ref()
.instantiate_aliases(*region, aliases, var_store, introduced);
typ.instantiate_aliases(*region, aliases, var_store, introduced);
}
And(nested) => {
for c in nested.iter_mut() {
c.instantiate_aliases_help(aliases, var_store, introduced);
}
}
Let(letcon) => {
let mut new_aliases = aliases.clone();
for (k, v) in letcon.def_aliases.iter() {
new_aliases.insert(*k, v.clone());
}
let mut introduced = ImSet::default();
for Located { region, value: typ } in letcon.def_types.iter_mut() {
typ.instantiate_aliases(*region, &new_aliases, var_store, &mut introduced);
}
letcon.defs_constraint.instantiate_aliases_help(
&new_aliases,
var_store,
&mut introduced,
);
letcon.ret_constraint.instantiate_aliases_help(
&new_aliases,
var_store,
&mut introduced,
);
letcon.flex_vars.extend(introduced);
}
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct LetConstraint {
pub rigid_vars: Vec<Variable>,
pub flex_vars: Vec<Variable>,
pub def_types: SendMap<Symbol, Located<Type>>,
pub def_aliases: SendMap<Symbol, Alias>,
pub defs_constraint: Constraint,
pub ret_constraint: Constraint,
}

View file

@ -110,7 +110,7 @@ pub fn canonicalize_defs<'a>(
mut output: Output,
var_store: &mut VarStore,
original_scope: &Scope,
loc_defs: &'a bumpalo::collections::Vec<'a, &'a Located<ast::Def<'a>>>,
loc_defs: &'a [&'a Located<ast::Def<'a>>],
pattern_type: PatternType,
) -> (CanDefs, Scope, Output, MutMap<Symbol, Region>) {
// Canonicalizing defs while detecting shadowing involves a multi-step process:
@ -130,8 +130,6 @@ pub fn canonicalize_defs<'a>(
// This naturally handles recursion too, because a given expr which refers
// to itself won't be processed until after its def has been added to scope.
use roc_parse::ast::Def::*;
// Record both the original and final idents from the scope,
// so we can diff them while detecting unused defs.
let mut scope = original_scope.clone();
@ -139,77 +137,37 @@ pub fn canonicalize_defs<'a>(
let mut refs_by_symbol = MutMap::default();
let mut can_defs_by_symbol = HashMap::with_capacity_and_hasher(num_defs, default_hasher());
let mut pending = Vec::with_capacity(num_defs); // TODO bump allocate this!
let mut iter = loc_defs.iter().peekable();
// Canonicalize all the patterns, record shadowing problems, and store
// the ast::Expr values in pending_exprs for further canonicalization
// once we've finished assembling the entire scope.
while let Some(loc_def) = iter.next() {
// Any time we have an Annotation followed immediately by a Body,
// check to see if their patterns are equivalent. If they are,
// turn it into a TypedBody. Otherwise, give an error.
let (new_output, pending_def) = match &loc_def.value {
Annotation(pattern, annotation) | Nested(Annotation(pattern, annotation)) => {
match iter.peek() {
Some(Located {
value: Body(body_pattern, body_expr),
region: body_region,
}) => {
if pattern.value.equivalent(&body_pattern.value) {
iter.next();
pending_typed_body(
env,
body_pattern,
annotation,
body_expr,
var_store,
&mut scope,
pattern_type,
)
} else if loc_def.region.lines_between(body_region) > 1 {
// there is a line of whitespace between the annotation and the body
// treat annotation and body separately
to_pending_def(env, var_store, &loc_def.value, &mut scope, pattern_type)
} else {
// the pattern of the annotation does not match the pattern of the body directly below it
env.problems.push(Problem::SignatureDefMismatch {
annotation_pattern: pattern.region,
def_pattern: body_pattern.region,
});
// both the annotation and definition are skipped!
iter.next();
continue;
}
}
_ => to_pending_def(env, var_store, &loc_def.value, &mut scope, pattern_type),
}
}
_ => to_pending_def(env, var_store, &loc_def.value, &mut scope, pattern_type),
};
output.union(new_output);
for loc_def in loc_defs {
match to_pending_def(env, var_store, &loc_def.value, &mut scope, pattern_type) {
None => (),
Some((new_output, pending_def)) => {
// store the top-level defs, used to ensure that closures won't capture them
if let PatternType::TopLevelDef = pattern_type {
match &pending_def {
PendingDef::AnnotationOnly(_, loc_can_pattern, _)
| PendingDef::Body(_, loc_can_pattern, _)
| PendingDef::TypedBody(_, loc_can_pattern, _, _) => env.top_level_symbols.extend(
| PendingDef::TypedBody(_, loc_can_pattern, _, _) => {
env.top_level_symbols.extend(
bindings_from_patterns(std::iter::once(loc_can_pattern))
.iter()
.map(|t| t.0),
),
)
}
PendingDef::Alias { .. } | PendingDef::InvalidAlias => {}
}
}
// Record the ast::Expr for later. We'll do another pass through these
// once we have the entire scope assembled. If we were to canonicalize
// the exprs right now, they wouldn't have symbols in scope from defs
// that get would have gotten added later in the defs list!
pending.push(pending_def);
output.union(new_output);
}
}
}
if cfg!(debug_assertions) {
@ -226,17 +184,11 @@ pub fn canonicalize_defs<'a>(
let mut can_ann =
canonicalize_annotation(env, &mut scope, &ann.value, ann.region, var_store);
// all referenced symbols in an alias must be symbols
output
.references
.referenced_aliases
.extend(can_ann.aliases.keys().copied());
// if an alias definition uses an alias, the used alias is referenced
output
.references
.lookups
.extend(can_ann.aliases.keys().copied());
// Record all the annotation's references in output.references.lookups
for symbol in can_ann.references {
output.references.lookups.insert(symbol);
output.references.referenced_aliases.insert(symbol);
}
let mut can_vars: Vec<Located<(Lowercase, Variable)>> =
Vec::with_capacity(vars.len());
@ -280,13 +232,9 @@ pub fn canonicalize_defs<'a>(
);
}
let alias = roc_types::types::Alias {
region: ann.region,
vars: can_vars,
uniqueness: None,
typ: can_ann.typ,
};
aliases.insert(symbol, alias);
scope.add_alias(symbol, ann.region, can_vars.clone(), can_ann.typ.clone());
let alias = scope.lookup_alias(symbol).expect("alias is added to scope");
aliases.insert(symbol, alias.clone());
}
other => value_defs.push(other),
}
@ -522,14 +470,17 @@ pub fn sort_can_defs(
// TODO also do the same `addDirects` check elm/compiler does, so we can
// report an error if a recursive definition can't possibly terminate!
match topological_sort_into_groups(defined_symbols.as_slice(), all_successors_without_self) {
match ven_graph::topological_sort_into_groups(
defined_symbols.as_slice(),
all_successors_without_self,
) {
Ok(groups) => {
let mut declarations = Vec::new();
// groups are in reversed order
for group in groups.into_iter().rev() {
group_to_declaration(
group,
&group,
&env.closures,
&mut all_successors_with_self,
&can_defs_by_symbol,
@ -539,21 +490,10 @@ pub fn sort_can_defs(
(Ok(declarations), output)
}
Err((groups, nodes_in_cycle)) => {
Err((mut groups, nodes_in_cycle)) => {
let mut declarations = Vec::new();
let mut problems = Vec::new();
// groups are in reversed order
for group in groups.into_iter().rev() {
group_to_declaration(
group,
&env.closures,
&mut all_successors_with_self,
&can_defs_by_symbol,
&mut declarations,
);
}
// nodes_in_cycle are symbols that form a syntactic cycle. That isn't always a problem,
// and in general it's impossible to decide whether it is. So we use a crude heuristic:
//
@ -623,8 +563,50 @@ pub fn sort_can_defs(
declarations.push(Declaration::InvalidCycle(symbols_in_cycle, regions));
} else {
// slightly inefficient, because we know this becomes exactly one DeclareRec already
groups.push(cycle);
}
}
// now we have a collection of groups whose dependencies are not cyclic.
// They are however not yet topologically sorted. Here we have to get a bit
// creative to get all the definitions in the correct sorted order.
let mut group_ids = Vec::with_capacity(groups.len());
let mut symbol_to_group_index = MutMap::default();
for (i, group) in groups.iter().enumerate() {
for symbol in group {
symbol_to_group_index.insert(*symbol, i);
}
group_ids.push(i);
}
let successors_of_group = |group_id: &usize| {
let mut result = ImSet::default();
// for each symbol in this group
for symbol in &groups[*group_id] {
// find its successors
for succ in all_successors_without_self(symbol) {
// and add its group to the result
result.insert(symbol_to_group_index[&succ]);
}
}
// don't introduce any cycles to self
result.remove(group_id);
result
};
match ven_graph::topological_sort_into_groups(&group_ids, successors_of_group) {
Ok(sorted_group_ids) => {
for sorted_group in sorted_group_ids.iter().rev() {
for group_id in sorted_group.iter().rev() {
let group = &groups[*group_id];
group_to_declaration(
cycle,
group,
&env.closures,
&mut all_successors_with_self,
&can_defs_by_symbol,
@ -632,6 +614,9 @@ pub fn sort_can_defs(
);
}
}
}
Err(_) => unreachable!("there should be no cycles now!"),
}
for problem in problems {
env.problem(problem);
@ -643,7 +628,7 @@ pub fn sort_can_defs(
}
fn group_to_declaration(
group: Vec<Symbol>,
group: &[Symbol],
closures: &MutMap<Symbol, References>,
successors: &mut dyn FnMut(&Symbol) -> ImSet<Symbol>,
can_defs_by_symbol: &MutMap<Symbol, Def>,
@ -1243,7 +1228,7 @@ pub fn can_defs_with_return<'a>(
env: &mut Env<'a>,
var_store: &mut VarStore,
scope: Scope,
loc_defs: &'a bumpalo::collections::Vec<'a, &'a Located<ast::Def<'a>>>,
loc_defs: &'a [&'a Located<ast::Def<'a>>],
loc_ret: &'a Located<ast::Expr<'a>>,
) -> (Expr, Output) {
let (unsorted, mut scope, defs_output, symbols_introduced) = canonicalize_defs(
@ -1282,7 +1267,7 @@ pub fn can_defs_with_return<'a>(
for declaration in decls.into_iter().rev() {
loc_expr = Located {
region: Region::zero(),
value: decl_to_let(var_store, declaration, loc_expr, output.aliases.clone()),
value: decl_to_let(var_store, declaration, loc_expr),
};
}
@ -1292,19 +1277,12 @@ pub fn can_defs_with_return<'a>(
}
}
fn decl_to_let(
var_store: &mut VarStore,
decl: Declaration,
loc_ret: Located<Expr>,
aliases: SendMap<Symbol, Alias>,
) -> Expr {
fn decl_to_let(var_store: &mut VarStore, decl: Declaration, loc_ret: Located<Expr>) -> Expr {
match decl {
Declaration::Declare(def) => {
Expr::LetNonRec(Box::new(def), Box::new(loc_ret), var_store.fresh(), aliases)
}
Declaration::DeclareRec(defs) => {
Expr::LetRec(defs, Box::new(loc_ret), var_store.fresh(), aliases)
Expr::LetNonRec(Box::new(def), Box::new(loc_ret), var_store.fresh())
}
Declaration::DeclareRec(defs) => Expr::LetRec(defs, Box::new(loc_ret), var_store.fresh()),
Declaration::InvalidCycle(symbols, regions) => {
Expr::RuntimeError(RuntimeError::CircularDef(symbols, regions))
}
@ -1355,7 +1333,7 @@ fn to_pending_def<'a>(
def: &'a ast::Def<'a>,
scope: &mut Scope,
pattern_type: PatternType,
) -> (Output, PendingDef<'a>) {
) -> Option<(Output, PendingDef<'a>)> {
use roc_parse::ast::Def::*;
match def {
@ -1370,10 +1348,10 @@ fn to_pending_def<'a>(
loc_pattern.region,
);
(
Some((
output,
PendingDef::AnnotationOnly(loc_pattern, loc_can_pattern, loc_ann),
)
))
}
Body(loc_pattern, loc_expr) => {
// This takes care of checking for shadowing and adding idents to scope.
@ -1386,10 +1364,46 @@ fn to_pending_def<'a>(
loc_pattern.region,
);
(
Some((
output,
PendingDef::Body(loc_pattern, loc_can_pattern, loc_expr),
)
))
}
AnnotatedBody {
ann_pattern,
ann_type,
comment: _,
body_pattern,
body_expr,
} => {
if ann_pattern.value.equivalent(&body_pattern.value) {
// NOTE: Pick the body pattern, picking the annotation one is
// incorrect in the presence of optional record fields!
//
// { x, y } : { x : Int, y ? Bool }*
// { x, y ? False } = rec
Some(pending_typed_body(
env,
body_pattern,
ann_type,
body_expr,
var_store,
scope,
pattern_type,
))
} else {
// the pattern of the annotation does not match the pattern of the body direc
env.problems.push(Problem::SignatureDefMismatch {
annotation_pattern: ann_pattern.region,
def_pattern: body_pattern.region,
});
// TODO: Should we instead build some PendingDef::InvalidAnnotatedBody ? This would
// remove the `Option` on this function (and be probably more reliable for further
// problem/error reporting)
None
}
}
Alias { name, vars, ann } => {
@ -1422,12 +1436,12 @@ fn to_pending_def<'a>(
region: loc_var.region,
});
return (Output::default(), PendingDef::InvalidAlias);
return Some((Output::default(), PendingDef::InvalidAlias));
}
}
}
(
Some((
Output::default(),
PendingDef::Alias {
name: Located {
@ -1437,7 +1451,7 @@ fn to_pending_def<'a>(
vars: can_rigids,
ann,
},
)
))
}
Err((original_region, loc_shadowed_symbol)) => {
@ -1446,7 +1460,7 @@ fn to_pending_def<'a>(
shadow: loc_shadowed_symbol,
});
(Output::default(), PendingDef::InvalidAlias)
Some((Output::default(), PendingDef::InvalidAlias))
}
}
}
@ -1454,6 +1468,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),
}
}
@ -1591,6 +1607,7 @@ fn make_tag_union_recursive<'a>(
*typ = Type::RecursiveTagUnion(rec_var, tags.to_vec(), ext.clone());
typ.substitute_alias(symbol, &Type::Variable(rec_var));
}
Type::RecursiveTagUnion(_, _, _) => {}
Type::Alias(_, _, actual) => make_tag_union_recursive(
env,
symbol,

View file

@ -11,7 +11,7 @@ use crate::procedure::References;
use crate::scope::Scope;
use inlinable_string::InlinableString;
use roc_collections::all::{ImSet, MutMap, MutSet, SendMap};
use roc_module::ident::{Lowercase, TagName};
use roc_module::ident::{ForeignSymbol, Lowercase, TagName};
use roc_module::low_level::LowLevel;
use roc_module::operator::CalledVia;
use roc_module::symbol::Symbol;
@ -83,8 +83,8 @@ pub enum Expr {
},
// Let
LetRec(Vec<Def>, Box<Located<Expr>>, Variable, Aliases),
LetNonRec(Box<Def>, Box<Located<Expr>>, Variable, Aliases),
LetRec(Vec<Def>, Box<Located<Expr>>, Variable),
LetNonRec(Box<Def>, Box<Located<Expr>>, Variable),
/// This is *only* for calling functions, not for tag application.
/// The Tag variant contains any applied values inside it.
@ -98,6 +98,11 @@ pub enum Expr {
args: Vec<(Variable, Expr)>,
ret_var: Variable,
},
ForeignCall {
foreign_symbol: ForeignSymbol,
args: Vec<(Variable, Expr)>,
ret_var: Variable,
},
Closure {
function_type: Variable,
@ -157,8 +162,6 @@ pub enum Expr {
RuntimeError(RuntimeError),
}
type Aliases = SendMap<Symbol, Alias>;
#[derive(Clone, Debug, PartialEq)]
pub struct Field {
pub var: Variable,
@ -205,13 +208,13 @@ pub fn canonicalize_expr<'a>(
ast::Expr::Record {
fields,
update: Some(loc_update),
final_comments: _,
} => {
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 {
@ -222,6 +225,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
@ -239,20 +256,32 @@ pub fn canonicalize_expr<'a>(
ast::Expr::Record {
fields,
update: None,
final_comments: _,
} => {
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),
@ -309,7 +338,7 @@ pub fn canonicalize_expr<'a>(
let mut args = Vec::new();
let mut outputs = Vec::new();
for loc_arg in loc_args {
for loc_arg in loc_args.iter() {
let (arg_expr, arg_out) =
canonicalize_expr(env, var_store, scope, loc_arg.region, &loc_arg.value);
@ -320,6 +349,10 @@ pub fn canonicalize_expr<'a>(
// Default: We're not tail-calling a symbol (by name), we're tail-calling a function value.
output.tail_call = None;
for arg_out in outputs {
output.references = output.references.union(arg_out.references);
}
let expr = match fn_expr.value {
Var(symbol) => {
output.references.calls.insert(symbol);
@ -371,10 +404,6 @@ pub fn canonicalize_expr<'a>(
}
};
for arg_out in outputs {
output.references = output.references.union(arg_out.references);
}
(expr, output)
}
ast::Expr::Var { module_name, ident } => {
@ -450,7 +479,7 @@ pub fn canonicalize_expr<'a>(
let mut bound_by_argument_patterns = MutSet::default();
for loc_pattern in loc_arg_patterns.into_iter() {
for loc_pattern in loc_arg_patterns.iter() {
let (new_output, can_arg) = canonicalize_pattern(
env,
var_store,
@ -562,7 +591,7 @@ pub fn canonicalize_expr<'a>(
let mut can_branches = Vec::with_capacity(branches.len());
for branch in branches {
for branch in branches.iter() {
let (can_when_branch, branch_references) =
canonicalize_when_branch(env, var_store, scope, region, *branch, &mut output);
@ -698,10 +727,11 @@ pub fn canonicalize_expr<'a>(
}
ast::Expr::MalformedIdent(name) => {
use roc_problem::can::RuntimeError::*;
(
RuntimeError(MalformedIdentifier((*name).into(), region)),
Output::default(),
)
let problem = MalformedIdentifier((*name).into(), region);
env.problem(Problem::RuntimeError(problem.clone()));
(RuntimeError(problem), Output::default())
}
ast::Expr::Nested(sub_expr) => {
let (answer, output) = canonicalize_expr(env, var_store, scope, region, sub_expr);
@ -721,34 +751,34 @@ pub fn canonicalize_expr<'a>(
}
// Below this point, we shouln't see any of these nodes anymore because
// operator desugaring should have removed them!
ast::Expr::ParensAround(sub_expr) => {
bad_expr @ ast::Expr::ParensAround(_) => {
panic!(
"A ParensAround did not get removed during operator desugaring somehow: {:?}",
sub_expr
"A ParensAround did not get removed during operator desugaring somehow: {:#?}",
bad_expr
);
}
ast::Expr::SpaceBefore(sub_expr, _spaces) => {
bad_expr @ ast::Expr::SpaceBefore(_, _) => {
panic!(
"A SpaceBefore did not get removed during operator desugaring somehow: {:?}",
sub_expr
"A SpaceBefore did not get removed during operator desugaring somehow: {:#?}",
bad_expr
);
}
ast::Expr::SpaceAfter(sub_expr, _spaces) => {
bad_expr @ ast::Expr::SpaceAfter(_, _) => {
panic!(
"A SpaceAfter did not get removed during operator desugaring somehow: {:?}",
sub_expr
"A SpaceAfter did not get removed during operator desugaring somehow: {:#?}",
bad_expr
);
}
ast::Expr::BinOp((_, loc_op, _)) => {
bad_expr @ ast::Expr::BinOp(_) => {
panic!(
"A binary operator did not get desugared somehow: {:?}",
loc_op
"A binary operator did not get desugared somehow: {:#?}",
bad_expr
);
}
ast::Expr::UnaryOp(_, loc_op) => {
bad_expr @ ast::Expr::UnaryOp(_, _) => {
panic!(
"A unary operator did not get desugared somehow: {:?}",
loc_op
"A unary operator did not get desugared somehow: {:#?}",
bad_expr
);
}
};
@ -788,7 +818,7 @@ fn canonicalize_when_branch<'a>(
let mut scope = original_scope.clone();
// TODO report symbols not bound in all patterns
for loc_pattern in &branch.patterns {
for loc_pattern in branch.patterns.iter() {
let (new_output, can_pattern) = canonicalize_pattern(
env,
var_store,
@ -973,20 +1003,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,
@ -1006,17 +1042,40 @@ fn canonicalize_fields<'a>(
output.references = output.references.union(field_out.references);
}
Err(CanonicalizeFieldProblem::InvalidOptionalValue {
field_name,
field_region,
}) => {
env.problems.push(Problem::InvalidOptionalValue {
field_name: field_name.clone(),
field_region,
record_region: region,
});
return Err(CanonicalizeRecordProblem::InvalidOptionalValue {
field_name,
field_region,
record_region: region,
});
}
}
}
(can_fields, output)
Ok((can_fields, output))
}
enum CanonicalizeFieldProblem {
InvalidOptionalValue {
field_name: Lowercase,
field_region: Region,
},
}
fn canonicalize_field<'a>(
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 {
@ -1026,17 +1085,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(_) => {
@ -1118,7 +1178,8 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
| other @ Accessor { .. }
| other @ Update { .. }
| other @ Var(_)
| other @ RunLowLevel { .. } => other,
| other @ RunLowLevel { .. }
| other @ ForeignCall { .. } => other,
List {
list_var,
@ -1221,7 +1282,7 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
}
}
LetRec(defs, loc_expr, var, aliases) => {
LetRec(defs, loc_expr, var) => {
let mut new_defs = Vec::with_capacity(defs.len());
for def in defs {
@ -1242,10 +1303,10 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
value: inline_calls(var_store, scope, loc_expr.value),
};
LetRec(new_defs, Box::new(loc_expr), var, aliases)
LetRec(new_defs, Box::new(loc_expr), var)
}
LetNonRec(def, loc_expr, var, aliases) => {
LetNonRec(def, loc_expr, var) => {
let def = Def {
loc_pattern: def.loc_pattern,
loc_expr: Located {
@ -1262,7 +1323,7 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
value: inline_calls(var_store, scope, loc_expr.value),
};
LetNonRec(Box::new(def), Box::new(loc_expr), var, aliases)
LetNonRec(Box::new(def), Box::new(loc_expr), var)
}
Closure {
@ -1367,9 +1428,6 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
// Not sure if param_var should be involved.
let pattern_vars = SendMap::default();
// TODO get the actual correct aliases
let aliases = SendMap::default();
let def = Def {
loc_pattern,
loc_expr,
@ -1384,7 +1442,6 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
Box::new(def),
Box::new(loc_answer),
var_store.fresh(),
aliases,
),
};
}

View file

@ -1,3 +1,4 @@
use crate::builtins;
use crate::def::{canonicalize_defs, sort_can_defs, Declaration};
use crate::env::Env;
use crate::expr::Output;
@ -42,19 +43,24 @@ pub struct ModuleOutput {
#[allow(clippy::too_many_arguments)]
pub fn canonicalize_module_defs<'a>(
arena: &Bump,
loc_defs: bumpalo::collections::Vec<'a, Located<ast::Def<'a>>>,
loc_defs: &'a [Located<ast::Def<'a>>],
home: ModuleId,
module_ids: &ModuleIds,
exposed_ident_ids: IdentIds,
dep_idents: MutMap<ModuleId, IdentIds>,
aliases: MutMap<Symbol, Alias>,
exposed_imports: MutMap<Ident, (Symbol, Region)>,
mut exposed_symbols: MutSet<Symbol>,
var_store: &mut VarStore,
) -> Result<ModuleOutput, RuntimeError> {
let mut can_exposed_imports = MutMap::default();
let mut scope = Scope::new(home);
let mut scope = Scope::new(home, var_store);
let num_deps = dep_idents.len();
for (name, alias) in aliases.into_iter() {
scope.add_alias(name, alias.region, alias.vars, alias.typ);
}
// Desugar operators (convert them to Apply calls, taking into account
// operator precedence and associativity rules), before doing other canonicalization.
//
@ -65,9 +71,9 @@ pub fn canonicalize_module_defs<'a>(
let mut desugared =
bumpalo::collections::Vec::with_capacity_in(loc_defs.len() + num_deps, arena);
for loc_def in loc_defs {
for loc_def in loc_defs.iter() {
desugared.push(&*arena.alloc(Located {
value: desugar_def(arena, arena.alloc(loc_def.value)),
value: desugar_def(arena, &loc_def.value),
region: loc_def.region,
}));
}
@ -259,6 +265,15 @@ pub fn canonicalize_module_defs<'a>(
}
}
// Add builtin defs (e.g. List.get) to the module's defs
let builtin_defs = builtins::builtin_defs(var_store);
for (symbol, def) in builtin_defs {
if references.contains(&symbol) {
declarations.push(Declaration::Builtin(def));
}
}
Ok(ModuleOutput {
aliases,
rigid_variables,
@ -289,6 +304,13 @@ fn fix_values_captured_in_closure_defs(
defs: &mut Vec<crate::def::Def>,
no_capture_symbols: &mut MutSet<Symbol>,
) {
// recursive defs cannot capture each other
for def in defs.iter() {
no_capture_symbols.extend(crate::pattern::symbols_from_pattern(&def.loc_pattern.value));
}
// TODO mutually recursive functions should both capture the union of both their capture sets
for def in defs.iter_mut() {
fix_values_captured_in_closure_def(def, no_capture_symbols);
}
@ -343,12 +365,12 @@ fn fix_values_captured_in_closure_expr(
use crate::expr::Expr::*;
match expr {
LetNonRec(def, loc_expr, _, _) => {
LetNonRec(def, loc_expr, _) => {
// LetNonRec(Box<Def>, Box<Located<Expr>>, Variable, Aliases),
fix_values_captured_in_closure_def(def, no_capture_symbols);
fix_values_captured_in_closure_expr(&mut loc_expr.value, no_capture_symbols);
}
LetRec(defs, loc_expr, _, _) => {
LetRec(defs, loc_expr, _) => {
// LetRec(Vec<Def>, Box<Located<Expr>>, Variable, Aliases),
fix_values_captured_in_closure_defs(defs, no_capture_symbols);
fix_values_captured_in_closure_expr(&mut loc_expr.value, no_capture_symbols);
@ -430,7 +452,7 @@ fn fix_values_captured_in_closure_expr(
fix_values_captured_in_closure_expr(&mut loc_arg.value, no_capture_symbols);
}
}
RunLowLevel { args, .. } => {
RunLowLevel { args, .. } | ForeignCall { args, .. } => {
for (_, arg) in args.iter_mut() {
fix_values_captured_in_closure_expr(arg, no_capture_symbols);
}

View file

@ -47,6 +47,28 @@ 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),
AnnotatedBody {
ann_pattern,
ann_type,
comment,
body_pattern,
body_expr,
}
| Nested(AnnotatedBody {
ann_pattern,
ann_type,
comment,
body_pattern,
body_expr,
}) => AnnotatedBody {
ann_pattern,
ann_type,
comment: *comment,
body_pattern: *body_pattern,
body_expr: desugar_expr(arena, body_expr),
},
Nested(NotYetImplemented(s)) => todo!("{}", s),
NotYetImplemented(s) => todo!("{}", s),
}
}
@ -90,9 +112,10 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located<Expr<'a>>) -> &'a
List(elems) | Nested(List(elems)) => {
let mut new_elems = Vec::with_capacity_in(elems.len(), arena);
for elem in elems {
for elem in elems.iter() {
new_elems.push(desugar_expr(arena, elem));
}
let new_elems = new_elems.into_bump_slice();
let value: Expr<'a> = List(new_elems);
arena.alloc(Located {
@ -100,10 +123,19 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located<Expr<'a>>) -> &'a
value,
})
}
Record { fields, update } | Nested(Record { fields, update }) => {
Record {
fields,
update,
final_comments,
}
| Nested(Record {
fields,
update,
final_comments,
}) => {
let mut new_fields = Vec::with_capacity_in(fields.len(), arena);
for field in fields {
for field in fields.iter() {
let value = desugar_field(arena, &field.value);
new_fields.push(Located {
@ -112,11 +144,14 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located<Expr<'a>>) -> &'a
});
}
let new_fields = new_fields.into_bump_slice();
arena.alloc(Located {
region: loc_expr.region,
value: Record {
update: *update,
fields: new_fields,
final_comments,
},
})
}
@ -130,7 +165,7 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located<Expr<'a>>) -> &'a
Defs(defs, loc_ret) | Nested(Defs(defs, loc_ret)) => {
let mut desugared_defs = Vec::with_capacity_in(defs.len(), arena);
for loc_def in defs.into_iter() {
for loc_def in defs.iter() {
let loc_def = Located {
value: desugar_def(arena, &loc_def.value),
region: loc_def.region,
@ -139,6 +174,8 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located<Expr<'a>>) -> &'a
desugared_defs.push(&*arena.alloc(loc_def));
}
let desugared_defs = desugared_defs.into_bump_slice();
arena.alloc(Located {
value: Defs(desugared_defs, desugar_expr(arena, loc_ret)),
region: loc_expr.region,
@ -147,10 +184,12 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located<Expr<'a>>) -> &'a
Apply(loc_fn, loc_args, called_via) | Nested(Apply(loc_fn, loc_args, called_via)) => {
let mut desugared_args = Vec::with_capacity_in(loc_args.len(), arena);
for loc_arg in loc_args {
for loc_arg in loc_args.iter() {
desugared_args.push(desugar_expr(arena, loc_arg));
}
let desugared_args = desugared_args.into_bump_slice();
arena.alloc(Located {
value: Apply(desugar_expr(arena, loc_fn), desugared_args, *called_via),
region: loc_expr.region,
@ -160,11 +199,11 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located<Expr<'a>>) -> &'a
let loc_desugared_cond = &*arena.alloc(desugar_expr(arena, &loc_cond_expr));
let mut desugared_branches = Vec::with_capacity_in(branches.len(), arena);
for branch in branches.into_iter() {
for branch in branches.iter() {
let desugared = desugar_expr(arena, &branch.value);
let mut alternatives = Vec::with_capacity_in(branch.patterns.len(), arena);
for loc_pattern in &branch.patterns {
for loc_pattern in branch.patterns.iter() {
alternatives.push(Located {
region: loc_pattern.region,
value: Pattern::Nested(&loc_pattern.value),
@ -177,6 +216,8 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located<Expr<'a>>) -> &'a
None
};
let alternatives = alternatives.into_bump_slice();
desugared_branches.push(&*arena.alloc(WhenBranch {
patterns: alternatives,
value: Located {
@ -187,6 +228,8 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located<Expr<'a>>) -> &'a
}));
}
let desugared_branches = desugared_branches.into_bump_slice();
arena.alloc(Located {
value: When(loc_desugared_cond, desugared_branches),
region: loc_expr.region,
@ -213,6 +256,8 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located<Expr<'a>>) -> &'a
let loc_fn_var = arena.alloc(Located { region, value });
let desugared_args = bumpalo::vec![in arena; desugar_expr(arena, loc_arg)];
let desugared_args = desugared_args.into_bump_slice();
arena.alloc(Located {
value: Apply(loc_fn_var, desugared_args, CalledVia::UnaryOp(op)),
region: loc_expr.region,
@ -293,8 +338,8 @@ fn desugar_field<'a>(
desugar_expr(arena, arena.alloc(loc_expr)),
)
}
SpaceBefore(field, spaces) => SpaceBefore(arena.alloc(desugar_field(arena, field)), spaces),
SpaceAfter(field, spaces) => SpaceAfter(arena.alloc(desugar_field(arena, field)), spaces),
SpaceBefore(field, _spaces) => desugar_field(arena, field),
SpaceAfter(field, _spaces) => desugar_field(arena, field),
Malformed(string) => Malformed(string),
}
@ -463,10 +508,12 @@ fn desugar_bin_op<'a>(arena: &'a Bump, loc_expr: &'a Located<Expr<'_>>) -> &'a L
args.push(left);
for arg in arguments {
for arg in arguments.iter() {
args.push(arg);
}
let args = args.into_bump_slice();
Apply(function, args, CalledVia::BinOp(Pizza))
}
expr => {
@ -480,6 +527,8 @@ fn desugar_bin_op<'a>(arena: &'a Bump, loc_expr: &'a Located<Expr<'_>>) -> &'a L
region: right.region,
});
let args = args.into_bump_slice();
Apply(function, args, CalledVia::BinOp(Pizza))
}
}
@ -498,6 +547,8 @@ fn desugar_bin_op<'a>(arena: &'a Bump, loc_expr: &'a Located<Expr<'_>>) -> &'a L
region: loc_op.region,
});
let args = args.into_bump_slice();
Apply(loc_expr, args, CalledVia::BinOp(binop))
}
};

View file

@ -197,7 +197,7 @@ pub fn canonicalize_pattern<'a>(
ptype => unsupported_pattern(env, ptype, region),
},
Underscore => match pattern_type {
Underscore(_) => match pattern_type {
WhenBranch | FunctionArg => Pattern::Underscore,
ptype => unsupported_pattern(env, ptype, region),
},
@ -361,7 +361,7 @@ pub fn canonicalize_pattern<'a>(
// If we encountered an erroneous pattern (e.g. one with shadowing),
// use the resulting RuntimeError. Otherwise, return a successful record destructure.
opt_erroneous.unwrap_or_else(|| Pattern::RecordDestructure {
opt_erroneous.unwrap_or(Pattern::RecordDestructure {
whole_var,
ext_var,
destructs,

View file

@ -1,9 +1,9 @@
use roc_collections::all::ImMap;
use roc_collections::all::{ImMap, MutSet};
use roc_module::ident::{Ident, Lowercase};
use roc_module::symbol::{IdentIds, ModuleId, Symbol};
use roc_problem::can::RuntimeError;
use roc_region::all::{Located, Region};
use roc_types::subs::Variable;
use roc_types::subs::{VarStore, Variable};
use roc_types::types::{Alias, Type};
#[derive(Clone, Debug, PartialEq)]
@ -25,12 +25,41 @@ pub struct Scope {
}
impl Scope {
pub fn new(home: ModuleId) -> Scope {
pub fn new(home: ModuleId, var_store: &mut VarStore) -> Scope {
use roc_types::solved_types::{BuiltinAlias, FreeVars};
let solved_aliases = roc_types::builtin_aliases::aliases();
let mut aliases = ImMap::default();
for (symbol, builtin_alias) in solved_aliases {
let BuiltinAlias { region, vars, typ } = builtin_alias;
let mut free_vars = FreeVars::default();
let typ = roc_types::solved_types::to_type(&typ, &mut free_vars, var_store);
let mut variables = Vec::new();
// make sure to sort these variables to make them line up with the type arguments
let mut type_variables: Vec<_> = free_vars.unnamed_vars.into_iter().collect();
type_variables.sort();
for (loc_name, (_, var)) in vars.iter().zip(type_variables) {
variables.push(Located::at(loc_name.region, (loc_name.value.clone(), var)));
}
let alias = Alias {
region,
typ,
hidden_variables: MutSet::default(),
vars: variables,
uniqueness: None,
};
aliases.insert(symbol, alias);
}
Scope {
home,
idents: Symbol::default_in_scope(),
symbols: ImMap::default(),
aliases: ImMap::default(),
aliases,
}
}
@ -146,14 +175,21 @@ impl Scope {
vars: Vec<Located<(Lowercase, Variable)>>,
typ: Type,
) {
self.aliases.insert(
name,
Alias {
let mut hidden_variables = MutSet::default();
hidden_variables.extend(typ.variables());
for loc_var in vars.iter() {
hidden_variables.remove(&loc_var.value.1);
}
let alias = Alias {
region,
vars,
hidden_variables,
uniqueness: None,
typ,
},
);
};
self.aliases.insert(name, alias);
}
}

View file

@ -21,7 +21,7 @@ mod can_inline {
fn assert_inlines_to(input: &str, expected: Expr, var_store: &mut VarStore) {
let arena = Bump::new();
let scope = &mut Scope::new(test_home());
let scope = &mut Scope::new(test_home(), var_store);
let actual_out = can_expr_with(&arena, test_home(), input);
let actual = inline_calls(var_store, scope, actual_out.loc_expr.value);

View file

@ -73,7 +73,7 @@ pub fn can_expr_with(arena: &Bump, home: ModuleId, expr_str: &str) -> CanExprOut
// rules multiple times unnecessarily.
let loc_expr = operator::desugar_expr(arena, &loc_expr);
let mut scope = Scope::new(home);
let mut scope = Scope::new(home, &mut var_store);
let dep_idents = IdentIds::exposed_builtins(0);
let mut env = Env::new(home, dep_idents, &module_ids, IdentIds::default());
let (loc_expr, output) = canonicalize_expr(

View file

@ -244,6 +244,300 @@ mod test_can {
assert_can_int("-0b11", -0b11);
}
// ANNOTATIONS
#[test]
fn correct_annotated_body() {
let src = indoc!(
r#"
f : Int -> Int
f = \ a -> a
f
"#
);
let arena = Bump::new();
let CanExprOut { problems, .. } = can_expr_with(&arena, test_home(), src);
assert_eq!(problems, Vec::new());
}
#[test]
fn correct_annotated_body_with_comments() {
let src = indoc!(
r#"
f : Int -> Int # comment
f = \ a -> a
f
"#
);
let arena = Bump::new();
let CanExprOut { problems, .. } = can_expr_with(&arena, test_home(), src);
assert_eq!(problems, Vec::new());
}
#[test]
fn name_mismatch_annotated_body() {
let src = indoc!(
r#"
f : Int -> Int
g = \ a -> a
g
"#
);
let arena = Bump::new();
let CanExprOut { problems, .. } = can_expr_with(&arena, test_home(), src);
// Here we have 2 issues:
// 1. `g` doesn't match the previous annotation named `f`, so we
// have a `SignatureDefMismatch`.
// 2. Thus, `g` is not defined then final reference to it is a
// `LookupNotInScope`.
assert_eq!(problems.len(), 2);
assert!(problems.iter().all(|problem| match problem {
Problem::SignatureDefMismatch { .. } => true,
Problem::RuntimeError(RuntimeError::LookupNotInScope(_, _)) => true,
_ => false,
}));
}
#[test]
fn name_mismatch_annotated_body_with_comment() {
let src = indoc!(
r#"
f : Int -> Int # comment
g = \ a -> a
g
"#
);
let arena = Bump::new();
let CanExprOut { problems, .. } = can_expr_with(&arena, test_home(), src);
// Here we have 2 issues:
// 1. `g` doesn't match the previous annotation named `f`, so we
// have a `SignatureDefMismatch`.
// 2. Thus, `g` is not defined then final reference to it is a
// `LookupNotInScope`.
assert_eq!(problems.len(), 2);
assert!(problems.iter().all(|problem| match problem {
Problem::SignatureDefMismatch { .. } => true,
Problem::RuntimeError(RuntimeError::LookupNotInScope(_, _)) => true,
_ => false,
}));
}
#[test]
fn separated_annotated_body() {
let src = indoc!(
r#"
f : Int -> Int
f = \ a -> a
f 42
"#
);
let arena = Bump::new();
let CanExprOut { problems, .. } = can_expr_with(&arena, test_home(), src);
assert_eq!(problems.len(), 1);
assert!(problems.iter().all(|problem| match problem {
Problem::RuntimeError(RuntimeError::Shadowing { .. }) => true,
_ => false,
}));
}
#[test]
fn separated_annotated_body_with_comment() {
let src = indoc!(
r#"
f : Int -> Int
# comment
f = \ a -> a
f 42
"#
);
let arena = Bump::new();
let CanExprOut { problems, .. } = can_expr_with(&arena, test_home(), src);
assert_eq!(problems.len(), 1);
assert!(problems.iter().all(|problem| match problem {
Problem::RuntimeError(RuntimeError::Shadowing { .. }) => true,
_ => false,
}));
}
#[test]
fn shadowed_annotation() {
let src = indoc!(
r#"
f : Int -> Int
f : Int -> Int
f
"#
);
let arena = Bump::new();
let CanExprOut { problems, .. } = can_expr_with(&arena, test_home(), src);
assert_eq!(problems.len(), 1);
println!("{:#?}", problems);
assert!(problems.iter().all(|problem| match problem {
Problem::RuntimeError(RuntimeError::Shadowing { .. }) => true,
_ => false,
}));
}
#[test]
fn correct_nested_unannotated_body() {
let src = indoc!(
r#"
f : Int
f =
g = 42
g + 1
f
"#
);
let arena = Bump::new();
let CanExprOut { problems, .. } = can_expr_with(&arena, test_home(), src);
assert_eq!(problems, Vec::new());
}
#[test]
fn correct_nested_annotated_body() {
let src = indoc!(
r#"
f : Int
f =
g : Int
g = 42
g + 1
f
"#
);
let arena = Bump::new();
let CanExprOut { problems, .. } = can_expr_with(&arena, test_home(), src);
assert_eq!(problems, Vec::new());
}
#[test]
fn correct_nested_body_annotated_multiple_lines() {
let src = indoc!(
r#"
f : Int
f =
g : Int
g = 42
h : Int
h = 5
z = 4
g + h + z
f
"#
);
let arena = Bump::new();
let CanExprOut { problems, .. } = can_expr_with(&arena, test_home(), src);
assert_eq!(problems, Vec::new());
}
#[test]
fn correct_nested_body_unannotated_multiple_lines() {
let src = indoc!(
r#"
f : Int
f =
g = 42
h : Int
h = 5
z = 4
g + h + z
f
"#
);
let arena = Bump::new();
let CanExprOut { problems, .. } = can_expr_with(&arena, test_home(), src);
assert_eq!(problems, Vec::new());
}
#[test]
fn correct_double_nested_body() {
let src = indoc!(
r#"
f : Int
f =
g =
h = 42
h + 1
g + 1
f
"#
);
let arena = Bump::new();
let CanExprOut { problems, .. } = can_expr_with(&arena, test_home(), src);
assert_eq!(problems, Vec::new());
}
#[test]
fn annotation_followed_with_unrelated_affectation() {
let src = indoc!(
r#"
F : Int
x = 1
x
"#
);
let arena = Bump::new();
let CanExprOut { problems, .. } = can_expr_with(&arena, test_home(), src);
assert_eq!(problems.len(), 1);
assert!(problems.iter().all(|problem| match problem {
Problem::UnusedDef(_, _) => true,
_ => false,
}));
}
#[test]
fn two_annotations_followed_with_unrelated_affectation() {
let src = indoc!(
r#"
G : Int
F : Int
x = 1
x
"#
);
let arena = Bump::new();
let CanExprOut { problems, .. } = can_expr_with(&arena, test_home(), src);
assert_eq!(problems.len(), 2);
assert!(problems.iter().all(|problem| match problem {
Problem::UnusedDef(_, _) => true,
_ => false,
}));
}
// LOCALS
// TODO rewrite this test to check only for UnusedDef reports
@ -305,9 +599,35 @@ 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, _, _) => {
LetRec(assignments, body, _) => {
match &assignments.get(i).map(|def| &def.loc_expr.value) {
Some(Closure {
recursive: recursion,
@ -325,7 +645,7 @@ mod test_can {
}
}
}
LetNonRec(def, body, _, _) => {
LetNonRec(def, body, _) => {
if i > 0 {
// recurse in the body (not the def!)
get_closure(&body.value, i - 1)

View file

@ -2,6 +2,7 @@ use roc_can::constraint::Constraint::{self, *};
use roc_can::constraint::LetConstraint;
use roc_can::expected::Expected::{self, *};
use roc_collections::all::SendMap;
use roc_module::ident::TagName;
use roc_module::symbol::Symbol;
use roc_region::all::Region;
use roc_types::subs::Variable;
@ -13,7 +14,7 @@ use roc_types::types::Type::{self, *};
pub fn int_literal(num_var: Variable, expected: Expected<Type>, region: Region) -> Constraint {
let num_type = Variable(num_var);
let reason = Reason::IntLiteral;
let expected_literal = ForReason(reason, Type::Apply(Symbol::NUM_INT, vec![]), region);
let expected_literal = ForReason(reason, num_int(), region);
exists(
vec![num_var],
@ -28,7 +29,7 @@ pub fn int_literal(num_var: Variable, expected: Expected<Type>, region: Region)
pub fn float_literal(num_var: Variable, expected: Expected<Type>, region: Region) -> Constraint {
let num_type = Variable(num_var);
let reason = Reason::FloatLiteral;
let expected_literal = ForReason(reason, Type::Apply(Symbol::NUM_FLOAT, vec![]), region);
let expected_literal = ForReason(reason, num_float(), region);
exists(
vec![num_var],
@ -45,7 +46,6 @@ pub fn exists(flex_vars: Vec<Variable>, constraint: Constraint) -> Constraint {
rigid_vars: Vec::new(),
flex_vars,
def_types: SendMap::default(),
def_aliases: SendMap::default(),
defs_constraint: constraint,
ret_constraint: Constraint::True,
}))
@ -70,3 +70,51 @@ pub fn list_type(typ: Type) -> Type {
pub fn str_type() -> Type {
builtin_type(Symbol::STR_STR, Vec::new())
}
#[inline(always)]
pub fn num_float() -> Type {
Type::Alias(
Symbol::NUM_FLOAT,
vec![],
Box::new(num_num(num_floatingpoint())),
)
}
#[inline(always)]
pub fn num_floatingpoint() -> Type {
let alias_content = Type::TagUnion(
vec![(TagName::Private(Symbol::NUM_AT_FLOATINGPOINT), vec![])],
Box::new(Type::EmptyTagUnion),
);
Type::Alias(Symbol::NUM_FLOATINGPOINT, vec![], Box::new(alias_content))
}
#[inline(always)]
pub fn num_int() -> Type {
Type::Alias(Symbol::NUM_INT, vec![], Box::new(num_num(num_integer())))
}
#[inline(always)]
pub fn num_integer() -> Type {
let alias_content = Type::TagUnion(
vec![(TagName::Private(Symbol::NUM_AT_INTEGER), vec![])],
Box::new(Type::EmptyTagUnion),
);
Type::Alias(Symbol::NUM_INTEGER, vec![], Box::new(alias_content))
}
#[inline(always)]
pub fn num_num(typ: Type) -> Type {
let alias_content = Type::TagUnion(
vec![(TagName::Private(Symbol::NUM_AT_NUM), vec![typ.clone()])],
Box::new(Type::EmptyTagUnion),
);
Type::Alias(
Symbol::NUM_NUM,
vec![("range".into(), typ)],
Box::new(alias_content),
)
}

View file

@ -16,7 +16,7 @@ use roc_region::all::{Located, Region};
use roc_types::subs::Variable;
use roc_types::types::AnnotationSource::{self, *};
use roc_types::types::Type::{self, *};
use roc_types::types::{Alias, Category, PReason, Reason, RecordField};
use roc_types::types::{Category, PReason, Reason, RecordField};
/// This is for constraining Defs
#[derive(Default, Debug)]
@ -42,23 +42,6 @@ pub fn exists(flex_vars: Vec<Variable>, constraint: Constraint) -> Constraint {
rigid_vars: Vec::new(),
flex_vars,
def_types: SendMap::default(),
def_aliases: SendMap::default(),
defs_constraint: constraint,
ret_constraint: Constraint::True,
}))
}
#[inline(always)]
pub fn exists_with_aliases(
def_aliases: SendMap<Symbol, Alias>,
flex_vars: Vec<Variable>,
constraint: Constraint,
) -> Constraint {
Let(Box::new(LetConstraint {
rigid_vars: Vec::new(),
flex_vars,
def_types: SendMap::default(),
def_aliases,
defs_constraint: constraint,
ret_constraint: Constraint::True,
}))
@ -72,6 +55,40 @@ pub struct Env {
pub home: ModuleId,
}
fn constrain_untyped_args(
env: &Env,
arguments: &[(Variable, Located<Pattern>)],
closure_type: Type,
return_type: Type,
) -> (Vec<Variable>, PatternState, Type) {
let mut vars = Vec::with_capacity(arguments.len());
let mut pattern_types = Vec::with_capacity(arguments.len());
let mut pattern_state = PatternState::default();
for (pattern_var, loc_pattern) in arguments {
let pattern_type = Type::Variable(*pattern_var);
let pattern_expected = PExpected::NoExpectation(pattern_type.clone());
pattern_types.push(pattern_type);
constrain_pattern(
env,
&loc_pattern.value,
loc_pattern.region,
pattern_expected,
&mut pattern_state,
);
vars.push(*pattern_var);
}
let function_type =
Type::Function(pattern_types, Box::new(closure_type), Box::new(return_type));
(vars, pattern_state, function_type)
}
pub fn constrain_expr(
env: &Env,
region: Region,
@ -83,7 +100,7 @@ pub fn constrain_expr(
Num(var, _) => exists(
vec![*var],
Eq(
Type::Apply(Symbol::NUM_NUM, vec![Type::Variable(*var)]),
crate::builtins::num_num(Type::Variable(*var)),
expected,
Category::Num,
region,
@ -129,7 +146,7 @@ pub fn constrain_expr(
let stored_con = Eq(
Type::Variable(*record_var),
expected,
Category::Storage,
Category::Storage(std::file!(), std::line!()),
region,
);
@ -313,7 +330,10 @@ pub fn constrain_expr(
]),
)
}
Var(symbol) => Lookup(*symbol, expected, region),
Var(symbol) => {
// make lookup constraint to lookup this symbol's type in the environment
Lookup(*symbol, expected, region)
}
Closure {
function_type: fn_var,
closure_type: closure_var,
@ -327,46 +347,25 @@ pub fn constrain_expr(
} => {
// NOTE defs are treated somewhere else!
let loc_body_expr = &**boxed;
let mut state = PatternState {
headers: SendMap::default(),
vars: Vec::with_capacity(arguments.len()),
constraints: Vec::with_capacity(1),
};
let mut vars = Vec::with_capacity(state.vars.capacity() + 1);
let mut pattern_types = Vec::with_capacity(state.vars.capacity());
let ret_var = *ret_var;
let closure_var = *closure_var;
let closure_ext_var = *closure_ext_var;
let ret_type = Type::Variable(ret_var);
let closure_type = Type::Variable(closure_var);
let return_type = Type::Variable(ret_var);
let (mut vars, pattern_state, function_type) =
constrain_untyped_args(env, arguments, closure_type, return_type.clone());
vars.push(ret_var);
vars.push(closure_var);
vars.push(closure_ext_var);
vars.push(*fn_var);
for (pattern_var, loc_pattern) in arguments {
let pattern_type = Type::Variable(*pattern_var);
let pattern_expected = PExpected::NoExpectation(pattern_type.clone());
pattern_types.push(pattern_type);
constrain_pattern(
env,
&loc_pattern.value,
loc_pattern.region,
pattern_expected,
&mut state,
);
vars.push(*pattern_var);
}
let body_type = NoExpectation(ret_type.clone());
let body_type = NoExpectation(return_type);
let ret_constraint =
constrain_expr(env, loc_body_expr.region, &loc_body_expr.value, body_type);
vars.push(*fn_var);
let defs_constraint = And(state.constraints);
// make sure the captured symbols are sorted!
debug_assert_eq!(captured_symbols.clone(), {
let mut copy = captured_symbols.clone();
@ -383,30 +382,23 @@ pub fn constrain_expr(
&mut vars,
);
let fn_type = Type::Function(
pattern_types,
Box::new(Type::Variable(closure_var)),
Box::new(ret_type),
);
exists(
vars,
And(vec![
Let(Box::new(LetConstraint {
rigid_vars: Vec::new(),
flex_vars: state.vars,
def_types: state.headers,
def_aliases: SendMap::default(),
defs_constraint,
flex_vars: pattern_state.vars,
def_types: pattern_state.headers,
defs_constraint: And(pattern_state.constraints),
ret_constraint,
})),
// "the closure's type is equal to expected type"
Eq(fn_type.clone(), expected, Category::Lambda, region),
Eq(function_type.clone(), expected, Category::Lambda, region),
// "fn_var is equal to the closure's type" - fn_var is used in code gen
Eq(
Type::Variable(*fn_var),
NoExpectation(fn_type),
Category::Storage,
NoExpectation(function_type),
Category::Storage(std::file!(), std::line!()),
region,
),
closure_constraint,
@ -439,6 +431,7 @@ pub fn constrain_expr(
match expected {
FromAnnotation(name, arity, _, tipe) => {
let num_branches = branches.len() + 1;
for (index, (loc_cond, loc_body)) in branches.iter().enumerate() {
let cond_con = constrain_expr(
env,
@ -456,7 +449,7 @@ pub fn constrain_expr(
arity,
AnnotationSource::TypedIfBranch {
index: Index::zero_based(index),
num_branches: branches.len(),
num_branches,
},
tipe.clone(),
),
@ -465,6 +458,7 @@ pub fn constrain_expr(
branch_cons.push(cond_con);
branch_cons.push(then_con);
}
let else_con = constrain_expr(
env,
final_else.region,
@ -474,7 +468,7 @@ pub fn constrain_expr(
arity,
AnnotationSource::TypedIfBranch {
index: Index::zero_based(branches.len()),
num_branches: branches.len(),
num_branches,
},
tipe.clone(),
),
@ -483,7 +477,7 @@ pub fn constrain_expr(
let ast_con = Eq(
Type::Variable(*branch_var),
NoExpectation(tipe),
Category::Storage,
Category::Storage(std::file!(), std::line!()),
region,
);
@ -535,7 +529,7 @@ pub fn constrain_expr(
branch_cons.push(Eq(
Type::Variable(*branch_var),
expected,
Category::Storage,
Category::Storage(std::file!(), std::line!()),
region,
));
branch_cons.push(else_con);
@ -565,15 +559,12 @@ pub fn constrain_expr(
constraints.push(expr_con);
match &expected {
FromAnnotation(name, arity, _, typ) => {
// record the type of the whole expression in the AST
let ast_con = Eq(
Type::Variable(*expr_var),
expected.clone(),
Category::Storage,
region,
);
constraints.push(ast_con);
FromAnnotation(name, arity, _, _typ) => {
// NOTE deviation from elm.
//
// in elm, `_typ` is used, but because we have this `expr_var` too
// and need to constrain it, this is what works and gives better error messages
let typ = Type::Variable(*expr_var);
for (index, when_branch) in branches.iter().enumerate() {
let pattern_region =
@ -602,6 +593,10 @@ pub fn constrain_expr(
constraints.push(branch_con);
}
constraints.push(Eq(typ, expected, Category::When, region));
return exists(vec![cond_var, *expr_var], And(constraints));
}
_ => {
@ -745,11 +740,10 @@ pub fn constrain_expr(
]),
)
}
LetRec(defs, loc_ret, var, aliases) => {
LetRec(defs, loc_ret, var) => {
let body_con = constrain_expr(env, loc_ret.region, &loc_ret.value, expected.clone());
exists_with_aliases(
aliases.clone(),
exists(
vec![*var],
And(vec![
constrain_recursive_defs(env, defs, body_con),
@ -758,26 +752,25 @@ pub fn constrain_expr(
Eq(
Type::Variable(*var),
expected,
Category::Storage,
Category::Storage(std::file!(), std::line!()),
loc_ret.region,
),
]),
)
}
LetNonRec(def, loc_ret, var, aliases) => {
LetNonRec(def, loc_ret, var) => {
let body_con = constrain_expr(env, loc_ret.region, &loc_ret.value, expected.clone());
exists_with_aliases(
aliases.clone(),
exists(
vec![*var],
And(vec![
constrain_def(env, def, body_con),
// Record the type of tne entire def-expression in the variable.
// Record the type of the entire def-expression in the variable.
// Code gen will need that later!
Eq(
Type::Variable(*var),
expected,
Category::Storage,
Category::Storage(std::file!(), std::line!()),
loc_ret.region,
),
]),
@ -821,7 +814,7 @@ pub fn constrain_expr(
let ast_con = Eq(
Type::Variable(*variant_var),
expected,
Category::Storage,
Category::Storage(std::file!(), std::line!()),
region,
);
@ -874,6 +867,52 @@ pub fn constrain_expr(
]),
)
}
ForeignCall {
args,
ret_var,
foreign_symbol,
} => {
// This is a modified version of what we do for function calls.
// The operation's return type
let ret_type = Variable(*ret_var);
// This will be used in the occurs check
let mut vars = Vec::with_capacity(1 + args.len());
vars.push(*ret_var);
let mut arg_types = Vec::with_capacity(args.len());
let mut arg_cons = Vec::with_capacity(args.len());
let mut add_arg = |index, arg_type: Type, arg| {
let reason = Reason::ForeignCallArg {
foreign_symbol: foreign_symbol.clone(),
arg_index: Index::zero_based(index),
};
let expected_arg = ForReason(reason, arg_type.clone(), Region::zero());
let arg_con = constrain_expr(env, Region::zero(), arg, expected_arg);
arg_types.push(arg_type);
arg_cons.push(arg_con);
};
for (index, (arg_var, arg)) in args.iter().enumerate() {
vars.push(*arg_var);
add_arg(index, Variable(*arg_var), arg);
}
let category = Category::ForeignCall;
exists(
vars,
And(vec![
And(arg_cons),
Eq(ret_type, expected, category, region),
]),
)
}
RuntimeError(_) => {
// Runtime Errors have no constraints because they're going to crash.
True
@ -926,13 +965,11 @@ fn constrain_when_branch(
rigid_vars: Vec::new(),
flex_vars: state.vars,
def_types: state.headers,
def_aliases: SendMap::default(),
defs_constraint: Constraint::And(state.constraints),
ret_constraint: Constraint::Let(Box::new(LetConstraint {
rigid_vars: Vec::new(),
flex_vars: Vec::new(),
def_types: SendMap::default(),
def_aliases: SendMap::default(),
defs_constraint: guard_constraint,
ret_constraint,
})),
@ -942,7 +979,6 @@ fn constrain_when_branch(
rigid_vars: Vec::new(),
flex_vars: state.vars,
def_types: state.headers,
def_aliases: SendMap::default(),
defs_constraint: Constraint::And(state.constraints),
ret_constraint,
}))
@ -964,11 +1000,7 @@ fn constrain_empty_record(region: Region, expected: Expected<Type>) -> Constrain
/// Constrain top-level module declarations
#[inline(always)]
pub fn constrain_decls(
home: ModuleId,
decls: &[Declaration],
aliases: SendMap<Symbol, Alias>,
) -> Constraint {
pub fn constrain_decls(home: ModuleId, decls: &[Declaration]) -> Constraint {
let mut constraint = Constraint::SaveTheEnvironment;
let mut env = Env {
@ -983,18 +1015,10 @@ pub fn constrain_decls(
match decl {
Declaration::Declare(def) | Declaration::Builtin(def) => {
constraint = exists_with_aliases(
aliases.clone(),
Vec::new(),
constrain_def(&env, def, constraint),
);
constraint = constrain_def(&env, def, constraint);
}
Declaration::DeclareRec(defs) => {
constraint = exists_with_aliases(
aliases.clone(),
Vec::new(),
constrain_recursive_defs(&env, defs, constraint),
);
constraint = constrain_recursive_defs(&env, defs, constraint);
}
Declaration::InvalidCycle(_, _) => {
// invalid cycles give a canonicalization error. we skip them here.
@ -1037,17 +1061,14 @@ fn constrain_def(env: &Env, def: &Def, body_con: Constraint) -> Constraint {
let expr_var = def.expr_var;
let expr_type = Type::Variable(expr_var);
let mut pattern_state = constrain_def_pattern(env, &def.loc_pattern, expr_type.clone());
let mut def_pattern_state = constrain_def_pattern(env, &def.loc_pattern, expr_type.clone());
pattern_state.vars.push(expr_var);
def_pattern_state.vars.push(expr_var);
let mut def_aliases = SendMap::default();
let mut new_rigids = Vec::new();
let expr_con = match &def.annotation {
Some(annotation) => {
def_aliases = annotation.aliases.clone();
let arity = annotation.signature.arity();
let rigids = &env.rigids;
let mut ftv = rigids.clone();
@ -1058,7 +1079,7 @@ fn constrain_def(env: &Env, def: &Def, body_con: Constraint) -> Constraint {
&mut new_rigids,
&mut ftv,
&def.loc_pattern,
&mut pattern_state.headers,
&mut def_pattern_state.headers,
);
let env = &Env {
@ -1075,10 +1096,10 @@ fn constrain_def(env: &Env, def: &Def, body_con: Constraint) -> Constraint {
signature.clone(),
);
pattern_state.constraints.push(Eq(
def_pattern_state.constraints.push(Eq(
expr_type,
annotation_expected.clone(),
Category::Storage,
Category::Storage(std::file!(), std::line!()),
Region::span_across(&annotation.region, &def.loc_expr.region),
));
@ -1100,9 +1121,11 @@ fn constrain_def(env: &Env, def: &Def, body_con: Constraint) -> Constraint {
name,
..
},
Type::Function(arg_types, _, _),
Type::Function(arg_types, _closure_type, ret_type),
) => {
let expected = annotation_expected;
// NOTE if we ever have problems with the closure, the ignored `_closure_type`
// is probably a good place to start the investigation!
let region = def.loc_expr.region;
let loc_body_expr = &**loc_body;
@ -1116,7 +1139,7 @@ fn constrain_def(env: &Env, def: &Def, body_con: Constraint) -> Constraint {
let ret_var = *ret_var;
let closure_var = *closure_var;
let closure_ext_var = *closure_ext_var;
let ret_type = Type::Variable(ret_var);
let ret_type = *ret_type.clone();
vars.push(ret_var);
vars.push(closure_var);
@ -1153,19 +1176,19 @@ fn constrain_def(env: &Env, def: &Def, body_con: Constraint) -> Constraint {
}
{
// record the correct type in pattern_var
let pattern_type = Type::Variable(*pattern_var);
pattern_types.push(pattern_type.clone());
// NOTE: because we perform an equality with part of the signature
// this constraint must be to the def_pattern_state's constraints
def_pattern_state.vars.push(*pattern_var);
pattern_types.push(Type::Variable(*pattern_var));
state.vars.push(*pattern_var);
state.constraints.push(Constraint::Eq(
pattern_type.clone(),
let pattern_con = Eq(
Type::Variable(*pattern_var),
Expected::NoExpectation(loc_ann.clone()),
Category::Storage,
Category::Storage(std::file!(), std::line!()),
loc_pattern.region,
));
);
vars.push(*pattern_var);
def_pattern_state.constraints.push(pattern_con);
}
}
@ -1178,12 +1201,15 @@ fn constrain_def(env: &Env, def: &Def, body_con: Constraint) -> Constraint {
&mut vars,
);
let fn_type = Type::Function(
pattern_types,
Box::new(Type::Variable(closure_var)),
Box::new(ret_type.clone()),
let body_type = FromAnnotation(
def.loc_pattern.clone(),
arguments.len(),
AnnotationSource::TypedBody {
region: annotation.region,
},
ret_type.clone(),
);
let body_type = NoExpectation(ret_type);
let ret_constraint =
constrain_expr(env, loc_body_expr.region, &loc_body_expr.value, body_type);
@ -1197,51 +1223,58 @@ fn constrain_def(env: &Env, def: &Def, body_con: Constraint) -> Constraint {
rigid_vars: Vec::new(),
flex_vars: state.vars,
def_types: state.headers,
def_aliases: SendMap::default(),
defs_constraint,
ret_constraint,
})),
// "the closure's type is equal to expected type"
Eq(fn_type.clone(), expected, Category::Lambda, region),
// "fn_var is equal to the closure's type" - fn_var is used in code gen
Eq(
Type::Variable(*fn_var),
NoExpectation(fn_type),
Category::Storage,
region,
),
Store(signature.clone(), *fn_var, std::file!(), std::line!()),
Store(signature, expr_var, std::file!(), std::line!()),
Store(ret_type, ret_var, std::file!(), std::line!()),
closure_constraint,
]),
)
}
_ => constrain_expr(
&env,
def.loc_expr.region,
&def.loc_expr.value,
annotation_expected,
),
_ => {
let expected = annotation_expected;
let ret_constraint =
constrain_expr(env, def.loc_expr.region, &def.loc_expr.value, expected);
And(vec![
Let(Box::new(LetConstraint {
rigid_vars: Vec::new(),
flex_vars: vec![],
def_types: SendMap::default(),
defs_constraint: True,
ret_constraint,
})),
// Store type into AST vars. We use Store so errors aren't reported twice
Store(signature, expr_var, std::file!(), std::line!()),
])
}
}
None => constrain_expr(
}
None => {
// no annotation, so no extra work with rigids
constrain_expr(
env,
def.loc_expr.region,
&def.loc_expr.value,
NoExpectation(expr_type),
),
)
}
};
Let(Box::new(LetConstraint {
rigid_vars: new_rigids,
flex_vars: pattern_state.vars,
def_types: pattern_state.headers,
def_aliases,
flex_vars: def_pattern_state.vars,
def_types: def_pattern_state.headers,
defs_constraint: Let(Box::new(LetConstraint {
rigid_vars: Vec::new(), // always empty
flex_vars: Vec::new(), // empty, because our functions have no arguments
def_types: SendMap::default(), // empty, because our functions have no arguments!
def_aliases: SendMap::default(),
defs_constraint: And(pattern_state.constraints),
defs_constraint: And(def_pattern_state.constraints),
ret_constraint: expr_con,
})),
ret_constraint: body_con,
@ -1352,29 +1385,13 @@ pub fn rec_defs_help(
mut rigid_info: Info,
mut flex_info: Info,
) -> Constraint {
let mut def_aliases = SendMap::default();
for def in defs {
let expr_var = def.expr_var;
let expr_type = Type::Variable(expr_var);
let pattern_expected = PExpected::NoExpectation(expr_type.clone());
let mut def_pattern_state = constrain_def_pattern(env, &def.loc_pattern, expr_type.clone());
let mut pattern_state = PatternState {
headers: SendMap::default(),
vars: flex_info.vars.clone(),
constraints: Vec::with_capacity(1),
};
constrain_pattern(
env,
&def.loc_pattern.value,
def.loc_pattern.region,
pattern_expected,
&mut pattern_state,
);
pattern_state.vars.push(expr_var);
def_pattern_state.vars.push(expr_var);
let mut new_rigids = Vec::new();
match &def.annotation {
@ -1391,21 +1408,16 @@ pub fn rec_defs_help(
rigid_vars: Vec::new(),
flex_vars: Vec::new(), // empty because Roc function defs have no args
def_types: SendMap::default(), // empty because Roc function defs have no args
def_aliases: SendMap::default(),
defs_constraint: True, // I think this is correct, once again because there are no args
ret_constraint: expr_con,
}));
flex_info.vars = pattern_state.vars;
flex_info.vars = def_pattern_state.vars;
flex_info.constraints.push(def_con);
flex_info.def_types.extend(pattern_state.headers);
flex_info.def_types.extend(def_pattern_state.headers);
}
Some(annotation) => {
for (symbol, alias) in annotation.aliases.clone() {
def_aliases.insert(symbol, alias);
}
let arity = annotation.signature.arity();
let mut ftv = env.rigids.clone();
@ -1415,7 +1427,7 @@ pub fn rec_defs_help(
&mut new_rigids,
&mut ftv,
&def.loc_pattern,
&mut pattern_state.headers,
&mut def_pattern_state.headers,
);
let annotation_expected = FromAnnotation(
@ -1426,46 +1438,154 @@ pub fn rec_defs_help(
},
signature.clone(),
);
let expr_con = constrain_expr(
&Env {
rigids: ftv,
home: env.home,
// when a def is annotated, and it's body is a closure, treat this
// as a named function (in elm terms) for error messages.
//
// This means we get errors like "the first argument of `f` is weird"
// instead of the more generic "something is wrong with the body of `f`"
match (&def.loc_expr.value, &signature) {
(
Closure {
function_type: fn_var,
closure_type: closure_var,
closure_ext_var,
return_type: ret_var,
captured_symbols,
arguments,
loc_body,
name,
..
},
def.loc_expr.region,
&def.loc_expr.value,
NoExpectation(expr_type.clone()),
Type::Function(arg_types, _closure_type, ret_type),
) => {
// NOTE if we ever have trouble with closure type unification, the ignored
// `_closure_type` here is a good place to start investigating
let expected = annotation_expected;
let region = def.loc_expr.region;
let loc_body_expr = &**loc_body;
let mut state = PatternState {
headers: SendMap::default(),
vars: Vec::with_capacity(arguments.len()),
constraints: Vec::with_capacity(1),
};
let mut vars = Vec::with_capacity(state.vars.capacity() + 1);
let mut pattern_types = Vec::with_capacity(state.vars.capacity());
let ret_var = *ret_var;
let closure_var = *closure_var;
let closure_ext_var = *closure_ext_var;
let ret_type = *ret_type.clone();
vars.push(ret_var);
vars.push(closure_var);
vars.push(closure_ext_var);
let it = arguments.iter().zip(arg_types.iter()).enumerate();
for (index, ((pattern_var, loc_pattern), loc_ann)) in it {
{
// ensure type matches the one in the annotation
let opt_label =
if let Pattern::Identifier(label) = def.loc_pattern.value {
Some(label)
} else {
None
};
let pattern_type: &Type = loc_ann;
let pattern_expected = PExpected::ForReason(
PReason::TypedArg {
index: Index::zero_based(index),
opt_name: opt_label,
},
pattern_type.clone(),
loc_pattern.region,
);
// ensure expected type unifies with annotated type
rigid_info.constraints.push(Eq(
expr_type,
annotation_expected.clone(),
Category::Storage,
def.loc_expr.region,
));
constrain_pattern(
env,
&loc_pattern.value,
loc_pattern.region,
pattern_expected,
&mut state,
);
}
// TODO investigate if this let can be safely removed
let def_con = Let(Box::new(LetConstraint {
{
// NOTE: because we perform an equality with part of the signature
// this constraint must be to the def_pattern_state's constraints
def_pattern_state.vars.push(*pattern_var);
pattern_types.push(Type::Variable(*pattern_var));
let pattern_con = Eq(
Type::Variable(*pattern_var),
Expected::NoExpectation(loc_ann.clone()),
Category::Storage(std::file!(), std::line!()),
loc_pattern.region,
);
def_pattern_state.constraints.push(pattern_con);
}
}
let closure_constraint = constrain_closure_size(
*name,
region,
captured_symbols,
closure_var,
closure_ext_var,
&mut vars,
);
let fn_type = Type::Function(
pattern_types,
Box::new(Type::Variable(closure_var)),
Box::new(ret_type.clone()),
);
let body_type = NoExpectation(ret_type.clone());
let expr_con = constrain_expr(
env,
loc_body_expr.region,
&loc_body_expr.value,
body_type,
);
vars.push(*fn_var);
let def_con = exists(
vars,
And(vec![
Let(Box::new(LetConstraint {
rigid_vars: Vec::new(),
flex_vars: Vec::new(), // empty because Roc function defs have no args
def_types: SendMap::default(), // empty because Roc function defs have no args
def_aliases: SendMap::default(),
defs_constraint: True, // I think this is correct, once again because there are no args
flex_vars: state.vars,
def_types: state.headers,
defs_constraint: And(state.constraints),
ret_constraint: expr_con,
}));
})),
Eq(fn_type.clone(), expected.clone(), Category::Lambda, region),
// "fn_var is equal to the closure's type" - fn_var is used in code gen
// Store type into AST vars. We use Store so errors aren't reported twice
Store(signature.clone(), *fn_var, std::file!(), std::line!()),
Store(signature, expr_var, std::file!(), std::line!()),
Store(ret_type, ret_var, std::file!(), std::line!()),
closure_constraint,
]),
);
rigid_info.vars.extend(&new_rigids);
// because of how in Roc headers point to variables, we must include the pattern var here
rigid_info.vars.extend(pattern_state.vars);
rigid_info.constraints.push(Let(Box::new(LetConstraint {
rigid_vars: new_rigids,
flex_vars: Vec::new(), // no flex vars introduced
flex_vars: def_pattern_state.vars,
def_types: SendMap::default(), // no headers introduced (at this level)
def_aliases: SendMap::default(),
defs_constraint: def_con,
ret_constraint: True,
})));
rigid_info.def_types.extend(pattern_state.headers);
rigid_info.def_types.extend(def_pattern_state.headers);
}
_ => todo!(),
}
}
}
}
@ -1474,18 +1594,15 @@ pub fn rec_defs_help(
rigid_vars: rigid_info.vars,
flex_vars: Vec::new(),
def_types: rigid_info.def_types,
def_aliases,
defs_constraint: True,
ret_constraint: Let(Box::new(LetConstraint {
rigid_vars: Vec::new(),
flex_vars: flex_info.vars,
def_types: flex_info.def_types.clone(),
def_aliases: SendMap::default(),
defs_constraint: Let(Box::new(LetConstraint {
rigid_vars: Vec::new(),
flex_vars: Vec::new(),
def_types: flex_info.def_types,
def_aliases: SendMap::default(),
defs_constraint: True,
ret_constraint: And(flex_info.constraints),
})),

View file

@ -2,14 +2,12 @@ use crate::expr::constrain_decls;
use roc_builtins::std::{Mode, StdLib};
use roc_can::constraint::{Constraint, LetConstraint};
use roc_can::module::ModuleOutput;
use roc_collections::all::{ImMap, MutMap, MutSet, SendMap};
use roc_module::ident::Lowercase;
use roc_collections::all::{MutMap, MutSet, SendMap};
use roc_module::symbol::{ModuleId, Symbol};
use roc_region::all::{Located, Region};
use roc_types::boolean_algebra::Bool;
use roc_types::solved_types::{BuiltinAlias, SolvedBool, SolvedType};
use roc_types::subs::{VarId, VarStore, Variable};
use roc_types::types::{Alias, Problem, RecordField, Type};
use roc_types::solved_types::{FreeVars, SolvedType};
use roc_types::subs::{VarStore, Variable};
use roc_types::types::{Alias, Problem};
pub type SubsByModule = MutMap<ModuleId, ExposedModuleTypes>;
@ -41,8 +39,8 @@ pub fn constrain_module(
let decls = &module.declarations;
match mode {
Standard => constrain_decls(home, decls, send_aliases),
Uniqueness => crate::uniq::constrain_decls(home, decls, send_aliases, var_store),
Standard => constrain_decls(home, decls),
Uniqueness => crate::uniq::constrain_decls(home, decls, var_store),
}
}
@ -71,7 +69,11 @@ pub fn constrain_imported_values(
// do nothing, in the future the alias definitions should not be in the list of imported values
}
_ => {
let typ = to_type(&import.solved_type, &mut free_vars, var_store);
let typ = roc_types::solved_types::to_type(
&import.solved_type,
&mut free_vars,
var_store,
);
def_types.insert(
loc_symbol.value,
@ -104,269 +106,15 @@ pub fn constrain_imported_values(
rigid_vars,
flex_vars: Vec::new(),
def_types,
def_aliases: SendMap::default(),
defs_constraint: True,
ret_constraint: body_con,
})),
)
}
pub fn load_builtin_aliases<I>(
aliases: I,
body_con: Constraint,
var_store: &mut VarStore,
) -> Constraint
where
I: IntoIterator<Item = (Symbol, BuiltinAlias)>,
{
use Constraint::*;
// Load all the given builtin aliases.
let mut def_aliases = SendMap::default();
for (symbol, builtin_alias) in aliases {
let mut free_vars = FreeVars::default();
let actual = to_type(&builtin_alias.typ, &mut free_vars, var_store);
let mut vars = Vec::with_capacity(builtin_alias.vars.len());
for (loc_lowercase, index) in builtin_alias.vars.iter().zip(1..) {
let var = if let Some(result) = free_vars.unnamed_vars.get(&VarId::from_u32(index)) {
result
} else {
panic!(
"var_id {:?} was not instantiated in the body of {:?} : {:?} (is it phantom?)",
index, symbol, &builtin_alias
)
};
vars.push(Located::at(
loc_lowercase.region,
(loc_lowercase.value.clone(), *var),
));
}
let alias = Alias {
vars,
region: builtin_alias.region,
uniqueness: None,
typ: actual,
};
def_aliases.insert(symbol, alias);
}
Let(Box::new(LetConstraint {
rigid_vars: Vec::new(),
flex_vars: Vec::new(),
def_types: SendMap::default(),
def_aliases,
defs_constraint: True,
ret_constraint: body_con,
}))
}
#[derive(Debug, Clone, Default)]
pub struct FreeVars {
pub named_vars: ImMap<Lowercase, Variable>,
pub unnamed_vars: ImMap<VarId, Variable>,
pub wildcards: Vec<Variable>,
}
pub fn to_type(
solved_type: &SolvedType,
free_vars: &mut FreeVars,
var_store: &mut VarStore,
) -> Type {
use roc_types::solved_types::SolvedType::*;
match solved_type {
Func(args, closure, ret) => {
let mut new_args = Vec::with_capacity(args.len());
for arg in args {
new_args.push(to_type(&arg, free_vars, var_store));
}
let new_ret = to_type(&ret, free_vars, var_store);
let new_closure = to_type(&closure, free_vars, var_store);
Type::Function(new_args, Box::new(new_closure), Box::new(new_ret))
}
Apply(symbol, args) => {
let mut new_args = Vec::with_capacity(args.len());
for arg in args {
new_args.push(to_type(&arg, free_vars, var_store));
}
Type::Apply(*symbol, new_args)
}
Rigid(lowercase) => {
if let Some(var) = free_vars.named_vars.get(&lowercase) {
Type::Variable(*var)
} else {
let var = var_store.fresh();
free_vars.named_vars.insert(lowercase.clone(), var);
Type::Variable(var)
}
}
Flex(var_id) => Type::Variable(var_id_to_flex_var(*var_id, free_vars, var_store)),
Wildcard => {
let var = var_store.fresh();
free_vars.wildcards.push(var);
Type::Variable(var)
}
Record { fields, ext } => {
use RecordField::*;
let mut new_fields = SendMap::default();
for (label, field) in fields {
let field_val = match field {
Required(typ) => Required(to_type(&typ, free_vars, var_store)),
Optional(typ) => Optional(to_type(&typ, free_vars, var_store)),
Demanded(typ) => Demanded(to_type(&typ, free_vars, var_store)),
};
new_fields.insert(label.clone(), field_val);
}
Type::Record(new_fields, Box::new(to_type(ext, free_vars, var_store)))
}
EmptyRecord => Type::EmptyRec,
EmptyTagUnion => Type::EmptyTagUnion,
TagUnion(tags, ext) => {
let mut new_tags = Vec::with_capacity(tags.len());
for (tag_name, args) in tags {
let mut new_args = Vec::with_capacity(args.len());
for arg in args.iter() {
new_args.push(to_type(arg, free_vars, var_store));
}
new_tags.push((tag_name.clone(), new_args));
}
Type::TagUnion(new_tags, Box::new(to_type(ext, free_vars, var_store)))
}
RecursiveTagUnion(rec_var_id, tags, ext) => {
let mut new_tags = Vec::with_capacity(tags.len());
for (tag_name, args) in tags {
let mut new_args = Vec::with_capacity(args.len());
for arg in args.iter() {
new_args.push(to_type(arg, free_vars, var_store));
}
new_tags.push((tag_name.clone(), new_args));
}
let rec_var = free_vars
.unnamed_vars
.get(rec_var_id)
.expect("rec var not in unnamed vars");
Type::RecursiveTagUnion(
*rec_var,
new_tags,
Box::new(to_type(ext, free_vars, var_store)),
)
}
Boolean(SolvedBool::SolvedShared) => Type::Boolean(Bool::Shared),
Boolean(SolvedBool::SolvedContainer(solved_cvar, solved_mvars)) => {
let cvar = var_id_to_flex_var(*solved_cvar, free_vars, var_store);
let mvars = solved_mvars
.iter()
.map(|var_id| var_id_to_flex_var(*var_id, free_vars, var_store));
Type::Boolean(Bool::container(cvar, mvars))
}
Alias(symbol, solved_type_variables, 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::Alias(*symbol, type_variables, Box::new(actual))
}
Error => Type::Erroneous(Problem::SolvedTypeError),
Erroneous(problem) => Type::Erroneous(problem.clone()),
}
}
fn var_id_to_flex_var(
var_id: VarId,
free_vars: &mut FreeVars,
var_store: &mut VarStore,
) -> Variable {
if let Some(var) = free_vars.unnamed_vars.get(&var_id) {
*var
} else {
let var = var_store.fresh();
free_vars.unnamed_vars.insert(var_id, var);
var
}
}
pub fn constrain_imported_aliases(
aliases: MutMap<Symbol, Alias>,
body_con: Constraint,
var_store: &mut VarStore,
) -> Constraint {
use Constraint::*;
let mut def_aliases = SendMap::default();
for (symbol, imported_alias) in aliases {
let mut vars = Vec::with_capacity(imported_alias.vars.len());
let mut substitution = ImMap::default();
for Located {
region,
value: (lowercase, old_var),
} in &imported_alias.vars
{
let new_var = var_store.fresh();
vars.push(Located::at(*region, (lowercase.clone(), new_var)));
substitution.insert(*old_var, Type::Variable(new_var));
}
let mut actual = imported_alias.typ.clone();
actual.substitute(&substitution);
let alias = Alias {
vars,
region: imported_alias.region,
uniqueness: imported_alias.uniqueness,
typ: actual,
};
def_aliases.insert(symbol, alias);
}
Let(Box::new(LetConstraint {
rigid_vars: Vec::new(),
flex_vars: Vec::new(),
def_types: SendMap::default(),
def_aliases,
defs_constraint: True,
ret_constraint: body_con,
}))
}
/// Run pre_constrain_imports to get imported_symbols and imported_aliases.
pub fn constrain_imports(
imported_symbols: Vec<Import>,
imported_aliases: MutMap<Symbol, Alias>,
constraint: Constraint,
var_store: &mut VarStore,
) -> Constraint {
@ -378,7 +126,7 @@ pub fn constrain_imports(
// output.ftv.insert(var, format!("internal_{:?}", var).into());
// }
constrain_imported_aliases(imported_aliases, constraint, var_store)
constraint
}
pub struct ConstrainableImports {
@ -432,12 +180,12 @@ pub fn pre_constrain_imports(
None => {
let is_valid_alias = stdlib.applies.contains(&symbol)
// This wasn't a builtin value or Apply; maybe it was a builtin alias.
|| stdlib.aliases.contains_key(&symbol);
|| roc_types::builtin_aliases::aliases().contains_key(&symbol);
if !is_valid_alias {
panic!(
"Could not find {:?} in builtin types {:?} or aliases {:?}",
symbol, stdlib.types, stdlib.aliases
"Could not find {:?} in builtin types {:?} or builtin aliases",
symbol, stdlib.types,
);
}
}

View file

@ -11,6 +11,7 @@ use roc_region::all::{Located, Region};
use roc_types::subs::Variable;
use roc_types::types::{Category, PReason, PatternCategory, Reason, RecordField, Type};
#[derive(Default)]
pub struct PatternState {
pub headers: SendMap<Symbol, Located<Type>>,
pub vars: Vec<Variable>,
@ -148,7 +149,7 @@ pub fn constrain_pattern(
state.constraints.push(Constraint::Pattern(
region,
PatternCategory::Num,
builtins::builtin_type(Symbol::NUM_NUM, vec![Type::Variable(*var)]),
builtins::num_num(Type::Variable(*var)),
expected,
));
}
@ -157,7 +158,7 @@ pub fn constrain_pattern(
state.constraints.push(Constraint::Pattern(
region,
PatternCategory::Float,
builtins::builtin_type(Symbol::NUM_INT, vec![]),
builtins::num_int(),
expected,
));
}
@ -166,7 +167,7 @@ pub fn constrain_pattern(
state.constraints.push(Constraint::Pattern(
region,
PatternCategory::Float,
builtins::builtin_type(Symbol::NUM_FLOAT, vec![]),
builtins::num_float(),
expected,
));
}
@ -271,7 +272,7 @@ pub fn constrain_pattern(
let whole_con = Constraint::Eq(
Type::Variable(*whole_var),
Expected::NoExpectation(record_type),
Category::Storage,
Category::Storage(std::file!(), std::line!()),
region,
);
@ -315,7 +316,7 @@ pub fn constrain_pattern(
vec![(tag_name.clone(), argument_types)],
Box::new(Type::Variable(*ext_var)),
)),
Category::Storage,
Category::Storage(std::file!(), std::line!()),
region,
);

View file

@ -1,4 +1,5 @@
use crate::expr::{exists, exists_with_aliases, Info};
use crate::builtins::{num_floatingpoint, num_integer, num_num};
use crate::expr::{exists, Info};
use roc_can::annotation::IntroducedVariables;
use roc_can::constraint::Constraint::{self, *};
use roc_can::constraint::LetConstraint;
@ -14,7 +15,7 @@ use roc_types::boolean_algebra::Bool;
use roc_types::subs::{VarStore, Variable};
use roc_types::types::AnnotationSource::{self, *};
use roc_types::types::Type::{self, *};
use roc_types::types::{Alias, Category, PReason, Reason, RecordField};
use roc_types::types::{Category, PReason, Reason, RecordField};
use roc_uniq::builtins::{attr_type, empty_list_type, list_type, str_type};
use roc_uniq::sharing::{self, FieldAccess, Mark, Usage, VarUsage};
@ -60,7 +61,6 @@ pub fn constrain_declaration(
pub fn constrain_decls(
home: ModuleId,
decls: &[Declaration],
mut aliases: SendMap<Symbol, Alias>,
var_store: &mut VarStore,
) -> Constraint {
let mut constraint = Constraint::SaveTheEnvironment;
@ -87,8 +87,6 @@ pub fn constrain_decls(
}
}
aliases_to_attr_type(var_store, &mut aliases);
let mut env = Env {
home,
rigids: ImMap::default(),
@ -101,8 +99,7 @@ pub fn constrain_decls(
match decl {
Declaration::Declare(def) | Declaration::Builtin(def) => {
constraint = exists_with_aliases(
aliases.clone(),
constraint = exists(
Vec::new(),
constrain_def(
&env,
@ -115,8 +112,7 @@ pub fn constrain_decls(
);
}
Declaration::DeclareRec(defs) => {
constraint = exists_with_aliases(
aliases.clone(),
constraint = exists(
Vec::new(),
constrain_recursive_defs(
&env,
@ -321,7 +317,7 @@ fn constrain_pattern(
let whole_con = Constraint::Eq(
Type::Variable(*whole_var),
Expected::NoExpectation(record_type),
Category::Storage,
Category::Storage(std::file!(), std::line!()),
region,
);
@ -385,7 +381,7 @@ fn constrain_pattern(
let whole_con = Constraint::Eq(
Type::Variable(*whole_var),
Expected::NoExpectation(union_type),
Category::Storage,
Category::Storage(std::file!(), std::line!()),
region,
);
@ -420,31 +416,30 @@ fn unique_unbound_num(
let val_type = Type::Variable(inner_var);
let val_utype = attr_type(Bool::variable(val_uvar), val_type);
let num_utype = Type::Apply(Symbol::NUM_NUM, vec![val_utype]);
let num_utype = num_num(val_utype);
let num_type = attr_type(Bool::variable(num_uvar), num_utype);
(num_uvar, val_uvar, num_type, num_var)
}
fn unique_num(var_store: &mut VarStore, symbol: Symbol) -> (Variable, Variable, Type) {
fn unique_num(var_store: &mut VarStore, val_type: Type) -> (Variable, Variable, Type) {
let num_uvar = var_store.fresh();
let val_uvar = var_store.fresh();
let val_type = Type::Apply(symbol, Vec::new());
let val_utype = attr_type(Bool::variable(val_uvar), val_type);
let num_utype = Type::Apply(Symbol::NUM_NUM, vec![val_utype]);
let num_utype = num_num(val_utype);
let num_type = attr_type(Bool::variable(num_uvar), num_utype);
(num_uvar, val_uvar, num_type)
}
fn unique_int(var_store: &mut VarStore) -> (Variable, Variable, Type) {
unique_num(var_store, Symbol::NUM_INTEGER)
unique_num(var_store, num_integer())
}
fn unique_float(var_store: &mut VarStore) -> (Variable, Variable, Type) {
unique_num(var_store, Symbol::NUM_FLOATINGPOINT)
unique_num(var_store, num_floatingpoint())
}
pub fn constrain_expr(
@ -691,7 +686,12 @@ pub fn constrain_expr(
constraints.push(Eq(inferred, expected.clone(), Category::List, region));
let stored = Type::Variable(*list_var);
constraints.push(Eq(stored, expected, Category::Storage, region));
constraints.push(Eq(
stored,
expected,
Category::Storage(std::file!(), std::line!()),
region,
));
exists(vec![*elem_var, *list_var, uniq_var], And(constraints))
}
@ -807,7 +807,6 @@ pub fn constrain_expr(
rigid_vars: Vec::new(),
flex_vars: state.vars,
def_types: state.headers,
def_aliases: SendMap::default(),
defs_constraint,
ret_constraint,
})),
@ -967,7 +966,61 @@ pub fn constrain_expr(
]),
)
}
LetRec(defs, loc_ret, var, unlifted_aliases) => {
ForeignCall {
foreign_symbol,
args,
ret_var,
} => {
// This is a modified version of what we do for function calls.
let ret_type = Variable(*ret_var);
let mut vars = Vec::with_capacity(1 + args.len());
vars.push(*ret_var);
// Canonicalize the function expression and its arguments
let mut arg_types = Vec::with_capacity(args.len());
let mut arg_cons = Vec::with_capacity(args.len());
for (index, (arg_var, arg_expr)) in args.iter().enumerate() {
let arg_type = Variable(*arg_var);
let reason = Reason::ForeignCallArg {
foreign_symbol: foreign_symbol.clone(),
arg_index: Index::zero_based(index),
};
let expected_arg = Expected::ForReason(reason, arg_type.clone(), region);
let arg_con = constrain_expr(
env,
var_store,
var_usage,
applied_usage_constraint,
Region::zero(),
arg_expr,
expected_arg,
);
vars.push(*arg_var);
arg_types.push(arg_type);
arg_cons.push(arg_con);
}
let expected_uniq_type = var_store.fresh();
vars.push(expected_uniq_type);
let category = Category::ForeignCall;
exists(
vars,
And(vec![
And(arg_cons),
Eq(ret_type, expected, category, region),
]),
)
}
LetRec(defs, loc_ret, var) => {
// NOTE doesn't currently unregister bound symbols
// may be a problem when symbols are not globally unique
let body_con = constrain_expr(
@ -980,11 +1033,7 @@ pub fn constrain_expr(
expected.clone(),
);
let mut aliases = unlifted_aliases.clone();
aliases_to_attr_type(var_store, &mut aliases);
exists_with_aliases(
aliases,
exists(
vec![*var],
And(vec![
constrain_recursive_defs(
@ -1000,13 +1049,13 @@ pub fn constrain_expr(
Eq(
Type::Variable(*var),
expected,
Category::Storage,
Category::Storage(std::file!(), std::line!()),
loc_ret.region,
),
]),
)
}
LetNonRec(def, loc_ret, var, unlifted_aliases) => {
LetNonRec(def, loc_ret, var) => {
// NOTE doesn't currently unregister bound symbols
// may be a problem when symbols are not globally unique
let body_con = constrain_expr(
@ -1019,11 +1068,7 @@ pub fn constrain_expr(
expected.clone(),
);
let mut aliases = unlifted_aliases.clone();
aliases_to_attr_type(var_store, &mut aliases);
exists_with_aliases(
aliases,
exists(
vec![*var],
And(vec![
constrain_def(
@ -1039,7 +1084,7 @@ pub fn constrain_expr(
Eq(
Type::Variable(*var),
expected,
Category::Storage,
Category::Storage(std::file!(), std::line!()),
loc_ret.region,
),
]),
@ -1135,7 +1180,7 @@ pub fn constrain_expr(
let ast_con = Eq(
Type::Variable(*branch_var),
Expected::NoExpectation(tipe),
Category::Storage,
Category::Storage(std::file!(), std::line!()),
region,
);
@ -1842,13 +1887,11 @@ fn constrain_when_branch(
rigid_vars: Vec::new(),
flex_vars: state.vars,
def_types: state.headers,
def_aliases: SendMap::default(),
defs_constraint: Constraint::And(state.constraints),
ret_constraint: Constraint::Let(Box::new(LetConstraint {
rigid_vars: Vec::new(),
flex_vars: vec![guard_uniq_var],
def_types: SendMap::default(),
def_aliases: SendMap::default(),
defs_constraint: guard_constraint,
ret_constraint,
})),
@ -1858,7 +1901,6 @@ fn constrain_when_branch(
rigid_vars: Vec::new(),
flex_vars: state.vars,
def_types: state.headers,
def_aliases: SendMap::default(),
defs_constraint: Constraint::And(state.constraints),
ret_constraint,
}))
@ -2122,6 +2164,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]),
@ -2150,39 +2232,6 @@ fn annotation_to_attr_type_many(
})
}
fn aliases_to_attr_type(var_store: &mut VarStore, aliases: &mut SendMap<Symbol, Alias>) {
for alias in aliases.iter_mut() {
// ensure
//
// Identity a : [ Identity a ]
//
// does not turn into
//
// Identity a : [ Identity (Attr u a) ]
//
// That would give a double attr wrapper on the type arguments.
// The `change_var_kind` flag set to false ensures type variables remain of kind *
let (_, new) = annotation_to_attr_type(var_store, &alias.typ, &mut ImSet::default(), false);
// remove the outer Attr, because when this occurs in a signature it'll already be wrapped in one
match new {
Type::Apply(Symbol::ATTR_ATTR, args) => {
alias.typ = args[1].clone();
if let Type::Boolean(b) = args[0].clone() {
alias.uniqueness = Some(b);
}
}
_ => unreachable!("`annotation_to_attr_type` always gives back an Attr"),
}
// Check that if the alias is a recursive tag union, all structures containing the
// recursion variable get the same uniqueness as the recursion variable (and thus as the
// recursive tag union itself)
if let Some(b) = &alias.uniqueness {
fix_mutual_recursive_alias(&mut alias.typ, b);
}
}
}
fn constrain_def(
env: &Env,
var_store: &mut VarStore,
@ -2205,12 +2254,10 @@ fn constrain_def(
pattern_state.vars.push(expr_var);
let mut def_aliases = SendMap::default();
let mut new_rigids = Vec::new();
let expr_con = match &def.annotation {
Some(annotation) => {
def_aliases = annotation.aliases.clone();
let arity = annotation.signature.arity();
let mut ftv = env.rigids.clone();
@ -2236,7 +2283,7 @@ fn constrain_def(
pattern_state.constraints.push(Eq(
expr_type,
annotation_expected.clone(),
Category::Storage,
Category::Storage(std::file!(), std::line!()),
Region::zero(),
));
@ -2264,19 +2311,14 @@ fn constrain_def(
),
};
// Lift aliases to Attr types
aliases_to_attr_type(var_store, &mut def_aliases);
Let(Box::new(LetConstraint {
rigid_vars: new_rigids,
flex_vars: pattern_state.vars,
def_types: pattern_state.headers,
def_aliases,
defs_constraint: Let(Box::new(LetConstraint {
rigid_vars: Vec::new(), // always empty
flex_vars: Vec::new(), // empty, because our functions have no arguments
def_types: SendMap::default(), // empty, because our functions have no arguments!
def_aliases: SendMap::default(),
defs_constraint: And(pattern_state.constraints),
ret_constraint: expr_con,
})),
@ -2426,7 +2468,6 @@ pub fn rec_defs_help(
mut rigid_info: Info,
mut flex_info: Info,
) -> Constraint {
let mut def_aliases = SendMap::default();
for def in defs {
let expr_var = def.expr_var;
let expr_type = Type::Variable(expr_var);
@ -2470,7 +2511,6 @@ pub fn rec_defs_help(
rigid_vars: Vec::new(),
flex_vars: Vec::new(), // empty because Roc function defs have no args
def_types: SendMap::default(), // empty because Roc function defs have no args
def_aliases: SendMap::default(),
defs_constraint: True, // I think this is correct, once again because there are no args
ret_constraint: expr_con,
}));
@ -2481,9 +2521,6 @@ pub fn rec_defs_help(
}
Some(annotation) => {
for (symbol, alias) in annotation.aliases.clone() {
def_aliases.insert(symbol, alias);
}
let arity = annotation.signature.arity();
let mut ftv = env.rigids.clone();
let signature = instantiate_rigids(
@ -2513,35 +2550,31 @@ pub fn rec_defs_help(
applied_usage_constraint,
def.loc_expr.region,
&def.loc_expr.value,
Expected::NoExpectation(expr_type.clone()),
annotation_expected.clone(),
);
// ensure expected type unifies with annotated type
rigid_info.constraints.push(Eq(
let storage_con = Eq(
expr_type,
annotation_expected.clone(),
Category::Storage,
annotation_expected,
Category::Storage(std::file!(), std::line!()),
def.loc_expr.region,
));
);
// TODO investigate if this let can be safely removed
let def_con = Let(Box::new(LetConstraint {
rigid_vars: Vec::new(),
flex_vars: Vec::new(), // empty because Roc function defs have no args
def_types: SendMap::default(), // empty because Roc function defs have no args
def_aliases: SendMap::default(),
defs_constraint: True, // I think this is correct, once again because there are no args
defs_constraint: storage_con,
ret_constraint: expr_con,
}));
rigid_info.vars.extend(&new_rigids);
// because of how in Roc headers point to variables, we must include the pattern var here
rigid_info.vars.extend(pattern_state.vars);
rigid_info.constraints.push(Let(Box::new(LetConstraint {
rigid_vars: new_rigids,
flex_vars: Vec::new(), // no flex vars introduced
flex_vars: pattern_state.vars,
def_types: SendMap::default(), // no headers introduced (at this level)
def_aliases: SendMap::default(),
defs_constraint: def_con,
ret_constraint: True,
})));
@ -2550,25 +2583,19 @@ pub fn rec_defs_help(
}
}
// list aliases to Attr types
aliases_to_attr_type(var_store, &mut def_aliases);
Let(Box::new(LetConstraint {
rigid_vars: rigid_info.vars,
flex_vars: Vec::new(),
def_types: rigid_info.def_types,
def_aliases,
defs_constraint: True,
ret_constraint: Let(Box::new(LetConstraint {
rigid_vars: Vec::new(),
flex_vars: flex_info.vars,
def_types: flex_info.def_types.clone(),
def_aliases: SendMap::default(),
defs_constraint: Let(Box::new(LetConstraint {
rigid_vars: Vec::new(),
flex_vars: Vec::new(),
def_types: flex_info.def_types,
def_aliases: SendMap::default(),
defs_constraint: True,
ret_constraint: And(flex_info.constraints),
})),
@ -2604,93 +2631,3 @@ fn constrain_field_update(
(var, field_type, con)
}
/// Fix uniqueness attributes on mutually recursive type aliases.
/// Given aliases
///
/// > ListA a b : [ Cons a (ListB b a), Nil ]
/// > ListB a b : [ Cons a (ListA b a), Nil ]
///
/// We get the lifted alias:
///
/// > `Test.ListB`: Alias {
/// > ...,
/// > uniqueness: Some(
/// > Container(
/// > 118,
/// > {},
/// > ),
/// > ),
/// > typ: [ Global('Cons') <9> (`#Attr.Attr` Container(119, {}) Alias `Test.ListA` <10> <9>[ but actually [ Global('Cons') <10> (`#Attr.Attr` Container(118, {}) <13>), Global('Nil') ] ]), Global('Nil') ] as <13>,
/// > },
///
/// Note that the alias will get uniqueness variable <118>, but the contained `ListA` gets variable
/// <119>. But, 119 is contained in 118, and 118 in 119, so we need <119> >= <118> >= <119> >= <118> ...
/// That can only be true if they are the same. Type inference will not find that, so we must do it
/// ourselves in user-defined aliases.
fn fix_mutual_recursive_alias(typ: &mut Type, attribute: &Bool) {
use Type::*;
if let RecursiveTagUnion(rec, tags, _ext) = typ {
for (_, args) in tags {
for mut arg in args {
fix_mutual_recursive_alias_help(*rec, &Type::Boolean(attribute.clone()), &mut arg);
}
}
}
}
fn fix_mutual_recursive_alias_help(rec_var: Variable, attribute: &Type, into_type: &mut Type) {
if into_type.contains_variable(rec_var) {
if let Type::Apply(Symbol::ATTR_ATTR, args) = into_type {
args[0] = attribute.clone();
fix_mutual_recursive_alias_help_help(rec_var, attribute, &mut args[1]);
}
}
}
#[inline(always)]
fn fix_mutual_recursive_alias_help_help(rec_var: Variable, attribute: &Type, into_type: &mut Type) {
use Type::*;
match into_type {
Function(args, closure, ret) => {
fix_mutual_recursive_alias_help(rec_var, attribute, ret);
fix_mutual_recursive_alias_help(rec_var, attribute, closure);
args.iter_mut()
.for_each(|arg| fix_mutual_recursive_alias_help(rec_var, attribute, arg));
}
RecursiveTagUnion(_, tags, ext) | TagUnion(tags, ext) => {
fix_mutual_recursive_alias_help(rec_var, attribute, ext);
for (_tag, args) in tags.iter_mut() {
for arg in args.iter_mut() {
fix_mutual_recursive_alias_help(rec_var, attribute, arg);
}
}
}
Record(fields, ext) => {
fix_mutual_recursive_alias_help(rec_var, attribute, ext);
for field in fields.iter_mut() {
let arg = match field {
RecordField::Required(arg) => arg,
RecordField::Optional(arg) => arg,
RecordField::Demanded(arg) => arg,
};
fix_mutual_recursive_alias_help(rec_var, attribute, arg);
}
}
Alias(_, _, actual_type) => {
// call help_help, because actual_type is not wrapped in ATTR
fix_mutual_recursive_alias_help_help(rec_var, attribute, actual_type);
}
Apply(_, args) => {
args.iter_mut()
.for_each(|arg| fix_mutual_recursive_alias_help(rec_var, attribute, arg));
}
EmptyRec | EmptyTagUnion | Erroneous(_) | Variable(_) | Boolean(_) => {}
}
}

View file

@ -1,4 +1,4 @@
use crate::spaces::{fmt_comments_only, fmt_condition_spaces, fmt_spaces, newline, INDENT};
use crate::spaces::{fmt_comments_only, fmt_spaces, newline, NewlineAt, INDENT};
use bumpalo::collections::String;
use roc_parse::ast::{AssignedField, Expr, Tag, TypeAnnotation};
use roc_region::all::Located;
@ -84,6 +84,83 @@ where
}
}
macro_rules! format_sequence {
($buf: expr, $indent:expr, $start:expr, $end:expr, $items:expr, $t:ident) => {
// is it a multiline type annotation?
if $items.iter().any(|item| item.value.is_multiline()) {
let braces_indent = $indent + INDENT;
let item_indent = braces_indent + INDENT;
newline($buf, braces_indent);
$buf.push($start);
for item in $items.iter() {
match item.value {
$t::SpaceBefore(expr_below, spaces_above_expr) => {
newline($buf, item_indent);
fmt_comments_only(
$buf,
spaces_above_expr.iter(),
NewlineAt::Bottom,
item_indent,
);
match &expr_below {
$t::SpaceAfter(expr_above, spaces_below_expr) => {
expr_above.format($buf, item_indent);
$buf.push(',');
fmt_comments_only(
$buf,
spaces_below_expr.iter(),
NewlineAt::Top,
item_indent,
);
}
_ => {
expr_below.format($buf, item_indent);
$buf.push(',');
}
}
}
$t::SpaceAfter(sub_expr, spaces) => {
newline($buf, item_indent);
sub_expr.format($buf, item_indent);
$buf.push(',');
fmt_comments_only($buf, spaces.iter(), NewlineAt::Top, item_indent);
}
_ => {
newline($buf, item_indent);
item.format($buf, item_indent);
$buf.push(',');
}
}
}
newline($buf, braces_indent);
$buf.push($end);
} else {
// is_multiline == false
$buf.push($start);
let mut iter = $items.iter().peekable();
while let Some(item) = iter.next() {
$buf.push(' ');
item.format($buf, $indent);
if iter.peek().is_some() {
$buf.push(',');
}
}
if !$items.is_empty() {
$buf.push(' ');
}
$buf.push($end);
}
};
}
impl<'a> Formattable<'a> for TypeAnnotation<'a> {
fn is_multiline(&self) -> bool {
use roc_parse::ast::TypeAnnotation::*;
@ -105,7 +182,11 @@ impl<'a> Formattable<'a> for TypeAnnotation<'a> {
Apply(_, _, args) => args.iter().any(|loc_arg| loc_arg.value.is_multiline()),
As(lhs, _, rhs) => lhs.value.is_multiline() || rhs.value.is_multiline(),
Record { fields, ext } => {
Record {
fields,
ext,
final_comments: _,
} => {
match ext {
Some(ann) if ann.value.is_multiline() => return true,
_ => {}
@ -114,7 +195,11 @@ impl<'a> Formattable<'a> for TypeAnnotation<'a> {
fields.iter().any(|field| field.value.is_multiline())
}
TagUnion { tags, ext } => {
TagUnion {
tags,
ext,
final_comments: _,
} => {
match ext {
Some(ann) if ann.value.is_multiline() => return true,
_ => {}
@ -197,16 +282,24 @@ impl<'a> Formattable<'a> for TypeAnnotation<'a> {
BoundVariable(v) => buf.push_str(v),
Wildcard => buf.push('*'),
TagUnion { tags, ext } => {
tags.format(buf, indent);
TagUnion {
tags,
ext,
final_comments: _,
} => {
format_sequence!(buf, indent, '[', ']', tags, Tag);
if let Some(loc_ext_ann) = *ext {
loc_ext_ann.value.format(buf, indent);
}
}
Record { fields, ext } => {
fields.format(buf, indent);
Record {
fields,
ext,
final_comments: _,
} => {
format_sequence!(buf, indent, '{', '}', fields, AssignedField);
if let Some(loc_ext_ann) = *ext {
loc_ext_ann.value.format(buf, indent);
@ -333,7 +426,7 @@ fn format_assigned_field_help<'a, T>(
buf.push_str(name.value);
}
AssignedField::SpaceBefore(sub_field, spaces) => {
fmt_comments_only(buf, spaces.iter(), indent);
fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent);
format_assigned_field_help(
sub_field,
buf,
@ -352,7 +445,7 @@ fn format_assigned_field_help<'a, T>(
separator_prefix,
is_multiline,
);
fmt_comments_only(buf, spaces.iter(), indent);
fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent);
}
Malformed(raw) => {
buf.push_str(raw);
@ -421,110 +514,3 @@ impl<'a> Formattable<'a> for Tag<'a> {
}
}
}
macro_rules! implement_format_sequence {
($start:expr, $end:expr, $t:ident) => {
fn format_with_options(
&self,
buf: &mut String<'a>,
_parens: Parens,
_newlines: Newlines,
indent: u16,
) {
buf.push($start);
let mut iter = self.iter().peekable();
let is_multiline = self.is_multiline();
let item_indent = if is_multiline {
indent + INDENT
} else {
indent
};
while let Some(item) = iter.next() {
if is_multiline {
match &item.value {
$t::SpaceBefore(expr_below, spaces_above_expr) => {
newline(buf, item_indent);
fmt_comments_only(buf, spaces_above_expr.iter(), item_indent);
match &expr_below {
$t::SpaceAfter(expr_above, spaces_below_expr) => {
expr_above.format(buf, item_indent);
if iter.peek().is_some() {
buf.push(',');
}
fmt_condition_spaces(
buf,
spaces_below_expr.iter(),
item_indent,
);
}
_ => {
expr_below.format(buf, item_indent);
if iter.peek().is_some() {
buf.push(',');
}
}
}
}
$t::SpaceAfter(sub_expr, spaces) => {
newline(buf, item_indent);
sub_expr.format(buf, item_indent);
if iter.peek().is_some() {
buf.push(',');
}
fmt_condition_spaces(buf, spaces.iter(), item_indent);
}
_ => {
newline(buf, item_indent);
item.format(buf, item_indent);
if iter.peek().is_some() {
buf.push(',');
}
}
}
} else {
buf.push(' ');
item.format(buf, item_indent);
if iter.peek().is_some() {
buf.push(',');
}
}
}
if is_multiline {
newline(buf, indent);
}
if !self.is_empty() && !is_multiline {
buf.push(' ');
}
buf.push($end);
}
};
}
impl<'a> Formattable<'a> for &'a [Located<Tag<'a>>] {
fn is_multiline(&self) -> bool {
self.iter().any(|t| t.value.is_multiline())
}
implement_format_sequence!('[', ']', Tag);
}
impl<'a> Formattable<'a> for &'a [Located<AssignedField<'a, TypeAnnotation<'a>>>] {
fn is_multiline(&self) -> bool {
self.iter().any(|f| f.value.is_multiline())
}
implement_format_sequence!('{', '}', AssignedField);
}

View file

@ -1,6 +1,6 @@
use crate::annotation::{Formattable, Newlines, Parens};
use crate::pattern::fmt_pattern;
use crate::spaces::{fmt_spaces, is_comment, newline, INDENT};
use crate::spaces::{fmt_spaces, newline, INDENT};
use bumpalo::collections::String;
use roc_parse::ast::{Def, Expr, Pattern};
@ -15,11 +15,12 @@ impl<'a> Formattable<'a> for Def<'a> {
loc_pattern.is_multiline() || loc_annotation.is_multiline()
}
Body(loc_pattern, loc_expr) => loc_pattern.is_multiline() || loc_expr.is_multiline(),
AnnotatedBody { .. } => true,
SpaceBefore(sub_def, spaces) | SpaceAfter(sub_def, spaces) => {
spaces.iter().any(|s| is_comment(s)) || sub_def.is_multiline()
spaces.iter().any(|s| s.is_comment()) || sub_def.is_multiline()
}
Nested(def) => def.is_multiline(),
NotYetImplemented(s) => todo!("{}", s),
}
}
@ -57,6 +58,24 @@ impl<'a> Formattable<'a> for Def<'a> {
Body(loc_pattern, loc_expr) => {
fmt_body(buf, &loc_pattern.value, &loc_expr.value, indent);
}
AnnotatedBody {
ann_pattern,
ann_type,
comment,
body_pattern,
body_expr,
} => {
ann_pattern.format(buf, indent);
buf.push_str(" : ");
ann_type.format(buf, indent);
if let Some(comment_str) = comment {
buf.push_str(" # ");
buf.push_str(comment_str.trim());
}
buf.push_str("\n");
fmt_body(buf, &body_pattern.value, &body_expr.value, indent);
}
SpaceBefore(sub_def, spaces) => {
fmt_spaces(buf, spaces.iter(), indent);
sub_def.format(buf, indent);
@ -66,6 +85,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

@ -1,10 +1,8 @@
use crate::annotation::{Formattable, Newlines, Parens};
use crate::def::fmt_def;
use crate::pattern::fmt_pattern;
use crate::spaces::{
add_spaces, fmt_comments_only, fmt_condition_spaces, fmt_spaces, newline, INDENT,
};
use bumpalo::collections::{String, Vec};
use crate::spaces::{add_spaces, fmt_comments_only, fmt_spaces, newline, NewlineAt, INDENT};
use bumpalo::collections::String;
use roc_module::operator::{self, BinOp};
use roc_parse::ast::StrSegment;
use roc_parse::ast::{AssignedField, Base, CommentOrNewline, Expr, Pattern, WhenBranch};
@ -108,7 +106,7 @@ impl<'a> Formattable<'a> for Expr<'a> {
if format_newlines {
fmt_spaces(buf, spaces.iter(), indent);
} else {
fmt_comments_only(buf, spaces.iter(), indent);
fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent);
}
sub_expr.format_with_options(buf, parens, newlines, indent);
}
@ -117,7 +115,7 @@ impl<'a> Formattable<'a> for Expr<'a> {
if format_newlines {
fmt_spaces(buf, spaces.iter(), indent);
} else {
fmt_comments_only(buf, spaces.iter(), indent);
fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent);
}
}
ParensAround(sub_expr) => {
@ -194,12 +192,12 @@ impl<'a> Formattable<'a> for Expr<'a> {
if multiline_args {
let arg_indent = indent + INDENT;
for loc_arg in loc_args {
for loc_arg in loc_args.iter() {
newline(buf, arg_indent);
loc_arg.format_with_options(buf, Parens::InApply, Newlines::No, arg_indent);
}
} else {
for loc_arg in loc_args {
for loc_arg in loc_args.iter() {
buf.push(' ');
loc_arg.format_with_options(buf, Parens::InApply, Newlines::Yes, indent);
}
@ -230,8 +228,12 @@ impl<'a> Formattable<'a> for Expr<'a> {
buf.push_str(string);
}
Record { fields, update } => {
fmt_record(buf, *update, fields, indent);
Record {
fields,
update,
final_comments,
} => {
fmt_record(buf, *update, fields, final_comments, indent);
}
Closure(loc_patterns, loc_ret) => {
fmt_closure(buf, loc_patterns, loc_ret, indent);
@ -412,7 +414,12 @@ pub fn fmt_list<'a>(buf: &mut String<'a>, loc_items: &[&Located<Expr<'a>>], inde
match &item.value {
Expr::SpaceBefore(expr_below, spaces_above_expr) => {
newline(buf, item_indent);
fmt_comments_only(buf, spaces_above_expr.iter(), item_indent);
fmt_comments_only(
buf,
spaces_above_expr.iter(),
NewlineAt::Bottom,
item_indent,
);
match &expr_below {
Expr::SpaceAfter(expr_above, spaces_below_expr) => {
@ -422,7 +429,12 @@ pub fn fmt_list<'a>(buf: &mut String<'a>, loc_items: &[&Located<Expr<'a>>], inde
buf.push(',');
}
fmt_condition_spaces(buf, spaces_below_expr.iter(), item_indent);
fmt_comments_only(
buf,
spaces_below_expr.iter(),
NewlineAt::Top,
item_indent,
);
}
_ => {
expr_below.format(buf, item_indent);
@ -442,7 +454,7 @@ pub fn fmt_list<'a>(buf: &mut String<'a>, loc_items: &[&Located<Expr<'a>>], inde
buf.push(',');
}
fmt_condition_spaces(buf, spaces.iter(), item_indent);
fmt_comments_only(buf, spaces.iter(), NewlineAt::Top, item_indent);
}
_ => {
@ -517,12 +529,22 @@ fn fmt_when<'a>(
match &loc_condition.value {
Expr::SpaceBefore(expr_below, spaces_above_expr) => {
fmt_condition_spaces(buf, spaces_above_expr.iter(), condition_indent);
fmt_comments_only(
buf,
spaces_above_expr.iter(),
NewlineAt::Top,
condition_indent,
);
newline(buf, condition_indent);
match &expr_below {
Expr::SpaceAfter(expr_above, spaces_below_expr) => {
expr_above.format(buf, condition_indent);
fmt_condition_spaces(buf, spaces_below_expr.iter(), condition_indent);
fmt_comments_only(
buf,
spaces_below_expr.iter(),
NewlineAt::Top,
condition_indent,
);
newline(buf, indent);
}
_ => {
@ -581,7 +603,7 @@ fn fmt_when<'a>(
add_spaces(buf, indent + (INDENT * 2));
match expr.value {
Expr::SpaceBefore(nested, spaces) => {
fmt_comments_only(buf, spaces.iter(), indent + (INDENT * 2));
fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent + (INDENT * 2));
nested.format_with_options(
buf,
Parens::NotNeeded,
@ -629,13 +651,18 @@ fn fmt_if<'a>(
if is_multiline_condition {
match &loc_condition.value {
Expr::SpaceBefore(expr_below, spaces_above_expr) => {
fmt_condition_spaces(buf, spaces_above_expr.iter(), return_indent);
fmt_comments_only(buf, spaces_above_expr.iter(), NewlineAt::Top, return_indent);
newline(buf, return_indent);
match &expr_below {
Expr::SpaceAfter(expr_above, spaces_below_expr) => {
expr_above.format(buf, return_indent);
fmt_condition_spaces(buf, spaces_below_expr.iter(), return_indent);
fmt_comments_only(
buf,
spaces_below_expr.iter(),
NewlineAt::Top,
return_indent,
);
newline(buf, indent);
}
@ -648,7 +675,7 @@ fn fmt_if<'a>(
Expr::SpaceAfter(expr_above, spaces_below_expr) => {
newline(buf, return_indent);
expr_above.format(buf, return_indent);
fmt_condition_spaces(buf, spaces_below_expr.iter(), return_indent);
fmt_comments_only(buf, spaces_below_expr.iter(), NewlineAt::Top, return_indent);
newline(buf, indent);
}
@ -671,13 +698,13 @@ fn fmt_if<'a>(
Expr::SpaceBefore(expr_below, spaces_below) => {
// we want exactly one newline, user-inserted extra newlines are ignored.
newline(buf, return_indent);
fmt_comments_only(buf, spaces_below.iter(), return_indent);
fmt_comments_only(buf, spaces_below.iter(), NewlineAt::Bottom, return_indent);
match &expr_below {
Expr::SpaceAfter(expr_above, spaces_above) => {
expr_above.format(buf, return_indent);
fmt_condition_spaces(buf, spaces_above.iter(), return_indent);
fmt_comments_only(buf, spaces_above.iter(), NewlineAt::Top, return_indent);
newline(buf, indent);
}
@ -707,7 +734,7 @@ fn fmt_if<'a>(
pub fn fmt_closure<'a>(
buf: &mut String<'a>,
loc_patterns: &'a Vec<'a, Located<Pattern<'a>>>,
loc_patterns: &'a [Located<Pattern<'a>>],
loc_ret: &'a Located<Expr<'a>>,
indent: u16,
) {
@ -779,8 +806,12 @@ pub fn fmt_record<'a>(
buf: &mut String<'a>,
update: Option<&'a Located<Expr<'a>>>,
loc_fields: &[Located<AssignedField<'a, Expr<'a>>>],
final_comments: &'a [CommentOrNewline<'a>],
indent: u16,
) {
if loc_fields.is_empty() {
buf.push_str("{}");
} else {
buf.push('{');
match update {
@ -796,43 +827,116 @@ pub fn fmt_record<'a>(
}
}
let is_multiline = loc_fields.iter().any(|loc_field| loc_field.is_multiline());
let mut iter = loc_fields.iter().peekable();
let field_indent = if is_multiline {
indent + INDENT
} else {
if !loc_fields.is_empty() {
buf.push(' ');
}
indent
};
// we abuse the `Newlines` type to decide between multiline or single-line layout
let newlines = if is_multiline {
Newlines::Yes
} else {
Newlines::No
};
while let Some(field) = iter.next() {
field.format_with_options(buf, Parens::NotNeeded, newlines, field_indent);
if iter.peek().is_some() {
buf.push(',');
if !is_multiline {
buf.push(' ');
}
}
}
let is_multiline = loc_fields.iter().any(|loc_field| loc_field.is_multiline())
|| !final_comments.is_empty();
if is_multiline {
newline(buf, indent)
} else if !loc_fields.is_empty() {
buf.push(' ');
let field_indent = indent + INDENT;
for field in loc_fields.iter() {
// comma addition is handled by the `format_field_multiline` function
// since we can have stuff like:
// { x # comment
// , y
// }
// In this case, we have to move the comma before the comment.
format_field_multiline(buf, &field.value, field_indent, "");
}
fmt_comments_only(buf, final_comments.iter(), NewlineAt::Top, field_indent);
newline(buf, indent);
} else {
// is_multiline == false */
buf.push(' ');
let field_indent = indent;
let mut iter = loc_fields.iter().peekable();
while let Some(field) = iter.next() {
field.format_with_options(buf, Parens::NotNeeded, Newlines::No, field_indent);
if iter.peek().is_some() {
buf.push_str(", ");
}
}
buf.push(' ');
// if we are here, that means that `final_comments` is empty, thus we don't have
// to add a comment. Anyway, it is not possible to have a single line record with
// a comment in it.
};
// closes the initial bracket
buf.push('}');
}
}
fn format_field_multiline<'a, T>(
buf: &mut String<'a>,
field: &AssignedField<'a, T>,
indent: u16,
separator_prefix: &str,
) where
T: Formattable<'a>,
{
use self::AssignedField::*;
match field {
RequiredValue(name, spaces, ann) => {
newline(buf, indent);
buf.push_str(name.value);
if !spaces.is_empty() {
fmt_spaces(buf, spaces.iter(), indent);
}
buf.push_str(separator_prefix);
buf.push_str(": ");
ann.value.format(buf, indent);
buf.push(',');
}
OptionalValue(name, spaces, ann) => {
newline(buf, indent);
buf.push_str(name.value);
if !spaces.is_empty() {
fmt_spaces(buf, spaces.iter(), indent);
}
buf.push_str(separator_prefix);
buf.push_str("? ");
ann.value.format(buf, indent);
buf.push(',');
}
LabelOnly(name) => {
newline(buf, indent);
buf.push_str(name.value);
buf.push(',');
}
AssignedField::SpaceBefore(sub_field, spaces) => {
// We have something like that:
// ```
// # comment
// field,
// ```
// we'd like to preserve this
fmt_comments_only(buf, spaces.iter(), NewlineAt::Top, indent);
format_field_multiline(buf, sub_field, indent, separator_prefix);
}
AssignedField::SpaceAfter(sub_field, spaces) => {
// We have somethig like that:
// ```
// field # comment
// , otherfield
// ```
// we'd like to transform it into:
// ```
// field,
// # comment
// otherfield
// ```
format_field_multiline(buf, sub_field, indent, separator_prefix);
fmt_comments_only(buf, spaces.iter(), NewlineAt::Top, indent);
}
Malformed(raw) => {
buf.push_str(raw);
}
}
}

View file

@ -1,5 +1,5 @@
use crate::annotation::{Formattable, Newlines, Parens};
use crate::spaces::{fmt_comments_only, fmt_spaces, is_comment};
use crate::spaces::{fmt_comments_only, fmt_spaces, NewlineAt};
use bumpalo::collections::String;
use roc_parse::ast::{Base, Pattern};
@ -19,7 +19,7 @@ impl<'a> Formattable<'a> for Pattern<'a> {
Pattern::SpaceBefore(_, spaces) | Pattern::SpaceAfter(_, spaces) => {
debug_assert!(!spaces.is_empty());
spaces.iter().any(|s| is_comment(s))
spaces.iter().any(|s| s.is_comment())
}
Pattern::Nested(nested_pat) => nested_pat.is_multiline(),
@ -37,7 +37,7 @@ impl<'a> Formattable<'a> for Pattern<'a> {
| Pattern::NonBase10Literal { .. }
| Pattern::FloatLiteral(_)
| Pattern::StrLiteral(_)
| Pattern::Underscore
| Pattern::Underscore(_)
| Pattern::Malformed(_)
| Pattern::QualifiedIdentifier { .. } => false,
}
@ -128,12 +128,15 @@ impl<'a> Formattable<'a> for Pattern<'a> {
StrLiteral(literal) => {
todo!("Format string literal: {:?}", literal);
}
Underscore => buf.push('_'),
Underscore(name) => {
buf.push('_');
buf.push_str(name);
}
// Space
SpaceBefore(sub_pattern, spaces) => {
if !sub_pattern.is_multiline() {
fmt_comments_only(buf, spaces.iter(), indent)
fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent)
} else {
fmt_spaces(buf, spaces.iter(), indent);
}
@ -143,7 +146,7 @@ impl<'a> Formattable<'a> for Pattern<'a> {
sub_pattern.format_with_options(buf, parens, newlines, indent);
// if only_comments {
if !sub_pattern.is_multiline() {
fmt_comments_only(buf, spaces.iter(), indent)
fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent)
} else {
fmt_spaces(buf, spaces.iter(), indent);
}

View file

@ -45,12 +45,14 @@ where
}
}
LineComment(comment) => {
fmt_comment(buf, comment, indent);
fmt_comment(buf, comment);
newline(buf, indent);
encountered_comment = true;
}
DocComment(docs) => {
fmt_docs(buf, docs, indent);
fmt_docs(buf, docs);
newline(buf, indent);
encountered_comment = true;
}
@ -58,78 +60,67 @@ where
}
}
/// Similar to fmt_comments_only, but does not finish with a newline()
/// Used to format when and if statement conditions
pub fn fmt_condition_spaces<'a, I>(buf: &mut String<'a>, spaces: I, indent: u16)
where
I: Iterator<Item = &'a CommentOrNewline<'a>>,
{
use self::CommentOrNewline::*;
let mut iter = spaces.peekable();
while let Some(space) = iter.next() {
match space {
Newline => {}
LineComment(comment) => {
buf.push('#');
buf.push_str(comment);
}
DocComment(docs) => {
buf.push_str("##");
buf.push_str(docs);
}
}
match iter.peek() {
None => {}
Some(next_space) => match next_space {
Newline => {}
LineComment(_) | DocComment(_) => {
newline(buf, indent);
}
},
}
}
#[derive(Eq, PartialEq, Debug)]
pub enum NewlineAt {
Top,
Bottom,
Both,
None,
}
/// Like format_spaces, but remove newlines and keep only comments.
pub fn fmt_comments_only<'a, I>(buf: &mut String<'a>, spaces: I, indent: u16)
where
/// The `new_line_at` argument describes how new lines should be inserted
/// at the beginning or at the end of the block
/// in the case of there is some comment in the `spaces` argument.
pub fn fmt_comments_only<'a, I>(
buf: &mut String<'a>,
spaces: I,
new_line_at: NewlineAt,
indent: u16,
) where
I: Iterator<Item = &'a CommentOrNewline<'a>>,
{
use self::CommentOrNewline::*;
use NewlineAt::*;
let mut comment_seen = false;
for space in spaces {
match space {
Newline => {}
LineComment(comment) => {
fmt_comment(buf, comment, indent);
if comment_seen || new_line_at == Top || new_line_at == Both {
newline(buf, indent);
}
fmt_comment(buf, comment);
comment_seen = true;
}
DocComment(docs) => {
fmt_docs(buf, docs, indent);
if comment_seen || new_line_at == Top || new_line_at == Both {
newline(buf, indent);
}
fmt_docs(buf, docs);
comment_seen = true;
}
}
}
if comment_seen && (new_line_at == Bottom || new_line_at == Both) {
newline(buf, indent);
}
}
fn fmt_comment<'a>(buf: &mut String<'a>, comment: &'a str, indent: u16) {
fn fmt_comment<'a>(buf: &mut String<'a>, comment: &'a str) {
buf.push('#');
buf.push_str(comment);
newline(buf, indent);
}
fn fmt_docs<'a>(buf: &mut String<'a>, docs: &'a str, indent: u16) {
buf.push_str("##");
buf.push_str(docs);
newline(buf, indent);
}
pub fn is_comment<'a>(space: &'a CommentOrNewline<'a>) -> bool {
match space {
CommentOrNewline::Newline => false,
CommentOrNewline::LineComment(_) => true,
CommentOrNewline::DocComment(_) => true,
if !comment.starts_with(' ') {
buf.push(' ');
}
buf.push_str(comment);
}
fn fmt_docs<'a>(buf: &mut String<'a>, docs: &'a str) {
buf.push_str("##");
if !docs.starts_with(' ') {
buf.push(' ');
}
buf.push_str(docs);
}

View file

@ -129,6 +129,24 @@ mod test_fmt {
);
}
#[test]
fn force_space_at_begining_of_comment() {
expr_formats_to(
indoc!(
r#"
#comment
f
"#
),
indoc!(
r#"
# comment
f
"#
),
);
}
#[test]
fn func_def() {
expr_formats_same(indoc!(
@ -592,7 +610,7 @@ mod test_fmt {
r#"
{ shoes &
rightShoe: newRightShoe,
leftShoe: newLeftShoe
leftShoe: newLeftShoe,
}
"#
));
@ -609,13 +627,180 @@ mod test_fmt {
r#"
{ shoes &
rightShoe: bareFoot,
leftShoe: bareFoot
leftShoe: bareFoot,
}
"#
),
);
}
#[test]
fn final_comments_in_records() {
expr_formats_same(indoc!(
r#"
{
x: 42,
# comment
}"#
));
expr_formats_same(indoc!(
r#"
{
x: 42,
# comment
# other comment
}"#
));
}
#[test]
fn final_comments_without_comma_in_records() {
expr_formats_to(
indoc!(
r#"
{
y: 41,
# comment 1
x: 42 # comment 2
}"#
),
indoc!(
r#"
{
y: 41,
# comment 1
x: 42,
# comment 2
}"#
),
);
}
#[test]
fn multiple_final_comments_without_comma_in_records() {
expr_formats_to(
indoc!(
r#"
{
y: 41,
x: 42 # comment 1
# comment 2
}"#
),
indoc!(
r#"
{
y: 41,
x: 42,
# comment 1
# comment 2
}"#
),
);
}
#[test]
fn comments_with_newlines_in_records() {
expr_formats_to(
indoc!(
r#"
{
z: 44 #comment 0
,
y: 41, # comment 1
# comment 2
x: 42
# comment 3
# comment 4
}"#
),
indoc!(
r#"
{
z: 44,
# comment 0
y: 41,
# comment 1
# comment 2
x: 42,
# comment 3
# comment 4
}"#
),
);
}
#[test]
fn multiple_final_comments_with_comma_in_records() {
expr_formats_to(
indoc!(
r#"
{
y: 41,
x: 42, # comment 1
# comment 2
}"#
),
indoc!(
r#"
{
y: 41,
x: 42,
# comment 1
# comment 2
}"#
),
);
}
#[test]
fn trailing_comma_in_record_annotation() {
expr_formats_to(
indoc!(
r#"
f: { y : Int,
x : Int ,
}
f"#
),
indoc!(
r#"
f :
{
y : Int,
x : Int,
}
f"#
),
);
}
// // TODO This raises a parse error:
// // NotYetImplemented("TODO the : in this declaration seems outdented")
// #[test]
// fn comments_in_record_annotation() {
// expr_formats_to(
// indoc!(
// r#"
// f :
// {}
// f"#
// ),
// indoc!(
// r#"
// f : b {}
// f"#
// ),
// );
// }
#[test]
fn def_closure() {
expr_formats_same(indoc!(
@ -1002,7 +1187,7 @@ mod test_fmt {
r#"
{
x: 4,
y: 42
y: 42,
}
"#
));
@ -1028,7 +1213,7 @@ mod test_fmt {
r#"
pos = {
x: 5,
y: 10
y: 10,
}
pos
@ -1039,7 +1224,7 @@ mod test_fmt {
pos =
{
x: 5,
y: 10
y: 10,
}
pos
@ -1062,7 +1247,7 @@ mod test_fmt {
r#"
{
x: 4,
y: 42
y: 42,
}
"#
),
@ -2122,6 +2307,23 @@ mod test_fmt {
));
}
// TODO This raises a parse error:
// NotYetImplemented("TODO the : in this declaration seems outdented")
// #[test]
// fn multiline_tag_union_annotation() {
// expr_formats_same(indoc!(
// r#"
// b :
// [
// True,
// False,
// ]
// b
// "#
// ));
// }
#[test]
fn tag_union() {
expr_formats_same(indoc!(

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
@ -38,14 +39,17 @@ inlinable_string = "0.1"
# commit of TheDan64/inkwell, push a new tag which points to the latest commit,
# change the tag value in this Cargo.toml to point to that tag, and `cargo update`.
# This way, GitHub Actions works and nobody's builds get broken.
inkwell = { git = "https://github.com/rtfeldman/inkwell", tag = "llvm10-0.release2" }
inkwell = { git = "https://github.com/rtfeldman/inkwell", tag = "llvm10-0.release3" }
target-lexicon = "0.10"
libloading = "0.6"
[dev-dependencies]
roc_can = { path = "../can" }
roc_parse = { path = "../parse" }
roc_load = { path = "../load" }
roc_reporting = { path = "../reporting" }
roc_build = { path = "../build" }
roc_std = { path = "../../roc_std" }
pretty_assertions = "0.5.1"
maplit = "1.0.1"
indoc = "0.3.3"
@ -54,4 +58,3 @@ quickcheck_macros = "0.8"
tokio = { version = "0.2", features = ["blocking", "fs", "sync", "rt-threaded"] }
bumpalo = { version = "3.2", features = ["collections"] }
libc = "0.2"
roc_std = { path = "../../roc_std" }

View file

@ -1,51 +0,0 @@
use roc_collections::all::{default_hasher, MutMap};
use roc_module::symbol::{Interns, Symbol};
use roc_mono::layout::Layout;
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct LayoutId(u32);
impl LayoutId {
// Returns something like "foo#1" when given a symbol that interns to "foo"
// and a LayoutId of 1.
pub fn to_symbol_string(self, symbol: Symbol, interns: &Interns) -> String {
format!("{}_{}", symbol.ident_string(interns), self.0)
}
}
struct IdsByLayout<'a> {
by_id: MutMap<Layout<'a>, u32>,
next_id: u32,
}
#[derive(Default)]
pub struct LayoutIds<'a> {
by_symbol: MutMap<Symbol, IdsByLayout<'a>>,
}
impl<'a> LayoutIds<'a> {
/// Returns a LayoutId which is unique for the given symbol and layout.
/// If given the same symbol and same layout, returns the same LayoutId.
pub fn get(&mut self, symbol: Symbol, layout: &Layout<'a>) -> LayoutId {
// Note: this function does some weird stuff to satisfy the borrow checker.
// There's probably a nicer way to write it that still works.
let ids = self.by_symbol.entry(symbol).or_insert_with(|| IdsByLayout {
by_id: HashMap::with_capacity_and_hasher(1, default_hasher()),
next_id: 1,
});
// Get the id associated with this layout, or default to next_id.
let answer = ids.by_id.get(layout).copied().unwrap_or(ids.next_id);
// If we had to default to next_id, it must not have been found;
// store the ID we're going to return and increment next_id.
if answer == ids.next_id {
ids.by_id.insert(layout.clone(), ids.next_id);
ids.next_id += 1;
}
LayoutId(answer)
}
}

View file

@ -11,7 +11,6 @@
// re-enable this when working on performance optimizations than have it block PRs.
#![allow(clippy::large_enum_variant)]
pub mod layout_id;
pub mod llvm;
pub mod run_roc;

View file

@ -0,0 +1,21 @@
use inkwell::types::BasicTypeEnum;
use roc_module::low_level::LowLevel;
pub fn call_bitcode_fn<'a, 'ctx, 'env>(
op: LowLevel,
env: &Env<'a, 'ctx, 'env>,
args: &[BasicValueEnum<'ctx>],
fn_name: &str,
) -> BasicValueEnum<'ctx> {
let fn_val = env
.module
.get_function(fn_name)
.unwrap_or_else(|| panic!("Unrecognized builtin function: {:?} - if you're working on the Roc compiler, do you need to rebuild the bitcode? See compiler/builtins/bitcode/README.md", fn_name));
let call = env.builder.build_call(fn_val, args, "call_builtin");
call.set_call_convention(fn_val.get_call_conventions());
call.try_as_basic_value()
.left()
.unwrap_or_else(|| panic!("LLVM error: Invalid call for low-level op {:?}", op))
}

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,8 @@
use crate::llvm::build::{Env, InPlace};
use crate::llvm::convert::{basic_type_from_layout, collection, get_ptr_type, ptr_int};
use crate::llvm::build::{
allocate_with_refcount_help, build_num_binop, cast_basic_basic, Env, InPlace,
};
use crate::llvm::compare::build_eq;
use crate::llvm::convert::{basic_type_from_layout, collection, get_ptr_type};
use inkwell::builder::Builder;
use inkwell::context::Context;
use inkwell::types::{BasicTypeEnum, PointerType};
@ -184,7 +187,9 @@ pub fn list_prepend<'a, 'ctx, 'env>(
// one we just malloc'd.
//
// TODO how do we decide when to do the small memcpy vs the normal one?
builder.build_memcpy(index_1_ptr, ptr_bytes, list_ptr, ptr_bytes, list_size);
builder
.build_memcpy(index_1_ptr, ptr_bytes, list_ptr, ptr_bytes, list_size)
.unwrap();
} else {
panic!("TODO Cranelift currently only knows how to clone list elements that are Copy.");
}
@ -625,7 +630,9 @@ pub fn list_append<'a, 'ctx, 'env>(
// one we just malloc'd.
//
// TODO how do we decide when to do the small memcpy vs the normal one?
builder.build_memcpy(clone_ptr, ptr_bytes, list_ptr, ptr_bytes, list_size);
builder
.build_memcpy(clone_ptr, ptr_bytes, list_ptr, ptr_bytes, list_size)
.unwrap();
} else {
panic!("TODO Cranelift currently only knows how to clone list elements that are Copy.");
}
@ -727,6 +734,81 @@ pub fn list_len<'ctx>(
.into_int_value()
}
/// List.sum : List (Num a) -> Num a
pub fn list_sum<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
parent: FunctionValue<'ctx>,
list: BasicValueEnum<'ctx>,
default_layout: &Layout<'a>,
) -> BasicValueEnum<'ctx> {
let ctx = env.context;
let builder = env.builder;
let list_wrapper = list.into_struct_value();
let len = list_len(env.builder, list_wrapper);
let accum_type = basic_type_from_layout(env.arena, ctx, default_layout, env.ptr_bytes);
let accum_alloca = builder.build_alloca(accum_type, "alloca_walk_right_accum");
let default: BasicValueEnum = match accum_type {
BasicTypeEnum::IntType(int_type) => int_type.const_zero().into(),
BasicTypeEnum::FloatType(float_type) => float_type.const_zero().into(),
_ => unreachable!(""),
};
builder.build_store(accum_alloca, default);
let then_block = ctx.append_basic_block(parent, "then");
let cont_block = ctx.append_basic_block(parent, "branchcont");
let condition = builder.build_int_compare(
IntPredicate::UGT,
len,
ctx.i64_type().const_zero(),
"list_non_empty",
);
builder.build_conditional_branch(condition, then_block, cont_block);
builder.position_at_end(then_block);
let elem_ptr_type = get_ptr_type(&accum_type, AddressSpace::Generic);
let list_ptr = load_list_ptr(builder, list_wrapper, elem_ptr_type);
let walk_right_loop = |_, elem: BasicValueEnum<'ctx>| {
// load current accumulator
let current = builder.build_load(accum_alloca, "retrieve_accum");
let new_current = build_num_binop(
env,
parent,
current,
default_layout,
elem,
default_layout,
roc_module::low_level::LowLevel::NumAdd,
);
builder.build_store(accum_alloca, new_current);
};
incrementing_elem_loop(
builder,
ctx,
parent,
list_ptr,
len,
"#index",
walk_right_loop,
);
builder.build_unconditional_branch(cont_block);
builder.position_at_end(cont_block);
builder.build_load(accum_alloca, "load_final_acum")
}
/// List.walkRight : List elem, (elem -> accum -> accum), accum -> accum
#[allow(clippy::too_many_arguments)]
pub fn list_walk_right<'a, 'ctx, 'env>(
@ -788,8 +870,7 @@ pub fn list_walk_right<'a, 'ctx, 'env>(
let new_current = call_site_value
.try_as_basic_value()
.left()
.unwrap_or_else(|| panic!("LLVM error: Invalid call by pointer."))
.into_int_value();
.unwrap_or_else(|| panic!("LLVM error: Invalid call by pointer."));
builder.build_store(accum_alloca, new_current);
};
@ -820,6 +901,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>,
@ -850,7 +1041,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);
@ -1399,11 +1590,6 @@ pub fn incrementing_index_loop<'ctx, LoopFn>(
parent: FunctionValue<'ctx>,
end: IntValue<'ctx>,
index_name: &str,
// allocating memory for an index is costly, so sometimes
// we want to reuse an index if multiple loops happen in a
// series, such as the case in List.concat. A memory
// allocation cab be passed in to be used, and the memory
// allocation that _is_ used is the return value.
mut loop_fn: LoopFn,
) -> PointerValue<'ctx>
where
@ -1575,12 +1761,7 @@ pub fn load_list<'ctx>(
wrapper_struct: StructValue<'ctx>,
ptr_type: PointerType<'ctx>,
) -> (IntValue<'ctx>, PointerValue<'ctx>) {
let ptr_as_int = builder
.build_extract_value(wrapper_struct, Builtin::WRAPPER_PTR, "read_list_ptr")
.unwrap()
.into_int_value();
let ptr = builder.build_int_to_ptr(ptr_as_int, ptr_type, "list_cast_ptr");
let ptr = load_list_ptr(builder, wrapper_struct, ptr_type);
let length = builder
.build_extract_value(wrapper_struct, Builtin::WRAPPER_LEN, "list_len")
@ -1595,12 +1776,14 @@ pub fn load_list_ptr<'ctx>(
wrapper_struct: StructValue<'ctx>,
ptr_type: PointerType<'ctx>,
) -> PointerValue<'ctx> {
let ptr_as_int = builder
// a `*mut u8` pointer
let generic_ptr = builder
.build_extract_value(wrapper_struct, Builtin::WRAPPER_PTR, "read_list_ptr")
.unwrap()
.into_int_value();
.into_pointer_value();
builder.build_int_to_ptr(ptr_as_int, ptr_type, "list_cast_ptr")
// cast to the expected pointer type
cast_basic_basic(builder, generic_ptr.into(), ptr_type.into()).into_pointer_value()
}
pub fn clone_nonempty_list<'a, 'ctx, 'env>(
@ -1625,9 +1808,6 @@ pub fn clone_nonempty_list<'a, 'ctx, 'env>(
// Allocate space for the new array that we'll copy into.
let clone_ptr = allocate_list(env, inplace, elem_layout, list_len);
let int_type = ptr_int(ctx, ptr_bytes);
let ptr_as_int = builder.build_ptr_to_int(clone_ptr, int_type, "list_cast_ptr");
// TODO check if malloc returned null; if so, runtime error for OOM!
// Either memcpy or deep clone the array elements
@ -1636,12 +1816,17 @@ pub fn clone_nonempty_list<'a, 'ctx, 'env>(
// one we just malloc'd.
//
// TODO how do we decide when to do the small memcpy vs the normal one?
builder.build_memcpy(clone_ptr, ptr_bytes, elems_ptr, ptr_bytes, size);
builder
.build_memcpy(clone_ptr, ptr_bytes, elems_ptr, ptr_bytes, size)
.unwrap();
} else {
panic!("TODO Cranelift currently only knows how to clone list elements that are Copy.");
}
// Create a fresh wrapper struct for the newly populated array
let u8_ptr_type = ctx.i8_type().ptr_type(AddressSpace::Generic);
let generic_ptr = cast_basic_basic(builder, clone_ptr.into(), u8_ptr_type.into());
let struct_type = collection(ctx, env.ptr_bytes);
let mut struct_val;
@ -1649,9 +1834,9 @@ pub fn clone_nonempty_list<'a, 'ctx, 'env>(
struct_val = builder
.build_insert_value(
struct_type.get_undef(),
ptr_as_int,
generic_ptr,
Builtin::WRAPPER_PTR,
"insert_ptr",
"insert_ptr_clone_nonempty_list",
)
.unwrap();
@ -1692,7 +1877,9 @@ pub fn clone_list<'a, 'ctx, 'env>(
);
// copy old elements in
builder.build_memcpy(new_ptr, ptr_bytes, old_ptr, ptr_bytes, bytes);
builder
.build_memcpy(new_ptr, ptr_bytes, old_ptr, ptr_bytes, bytes)
.unwrap();
new_ptr
}
@ -1706,53 +1893,13 @@ pub fn allocate_list<'a, 'ctx, 'env>(
let builder = env.builder;
let ctx = env.context;
let elem_type = basic_type_from_layout(env.arena, ctx, elem_layout, env.ptr_bytes);
let elem_bytes = elem_layout.stack_size(env.ptr_bytes) as u64;
let len_type = env.ptr_int();
// bytes per element
let bytes_len = len_type.const_int(elem_bytes, false);
let offset = (env.ptr_bytes as u64).max(elem_bytes);
let elem_bytes = elem_layout.stack_size(env.ptr_bytes) as u64;
let bytes_per_element = len_type.const_int(elem_bytes, false);
let ptr = {
let len = builder.build_int_mul(bytes_len, length, "data_length");
let len =
builder.build_int_add(len, len_type.const_int(offset, false), "add_refcount_space");
let number_of_data_bytes = builder.build_int_mul(bytes_per_element, length, "data_length");
env.builder
.build_array_malloc(ctx.i8_type(), len, "create_list_ptr")
.unwrap()
// TODO check if malloc returned null; if so, runtime error for OOM!
};
// We must return a pointer to the first element:
let ptr_bytes = env.ptr_bytes;
let int_type = ptr_int(ctx, ptr_bytes);
let ptr_as_int = builder.build_ptr_to_int(ptr, int_type, "list_cast_ptr");
let incremented = builder.build_int_add(
ptr_as_int,
ctx.i64_type().const_int(offset, false),
"increment_list_ptr",
);
let ptr_type = get_ptr_type(&elem_type, AddressSpace::Generic);
let list_element_ptr = builder.build_int_to_ptr(incremented, ptr_type, "list_cast_ptr");
// subtract ptr_size, to access the refcount
let refcount_ptr = builder.build_int_sub(
incremented,
ctx.i64_type().const_int(env.ptr_bytes as u64, false),
"refcount_ptr",
);
let refcount_ptr = builder.build_int_to_ptr(
refcount_ptr,
int_type.ptr_type(AddressSpace::Generic),
"make ptr",
);
let ref_count_one = match inplace {
let rc1 = match inplace {
InPlace::InPlace => length,
InPlace::Clone => {
// the refcount of a new list is initially 1
@ -1761,33 +1908,33 @@ pub fn allocate_list<'a, 'ctx, 'env>(
}
};
builder.build_store(refcount_ptr, ref_count_one);
list_element_ptr
allocate_with_refcount_help(env, elem_layout, number_of_data_bytes, rc1)
}
pub fn store_list<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
list_ptr: PointerValue<'ctx>,
pointer_to_first_element: PointerValue<'ctx>,
len: IntValue<'ctx>,
) -> BasicValueEnum<'ctx> {
let ctx = env.context;
let builder = env.builder;
let ptr_bytes = env.ptr_bytes;
let int_type = ptr_int(ctx, ptr_bytes);
let ptr_as_int = builder.build_ptr_to_int(list_ptr, int_type, "list_cast_ptr");
let struct_type = collection(ctx, ptr_bytes);
let u8_ptr_type = ctx.i8_type().ptr_type(AddressSpace::Generic);
let generic_ptr =
cast_basic_basic(builder, pointer_to_first_element.into(), u8_ptr_type.into());
let mut struct_val;
// Store the pointer
struct_val = builder
.build_insert_value(
struct_type.get_undef(),
ptr_as_int,
generic_ptr,
Builtin::WRAPPER_PTR,
"insert_ptr",
"insert_ptr_store_list",
)
.unwrap();

View file

@ -1,18 +1,96 @@
use crate::llvm::build::{ptr_from_symbol, Env, InPlace, Scope};
use crate::llvm::build::{
call_bitcode_fn, call_void_bitcode_fn, ptr_from_symbol, Env, InPlace, Scope,
};
use crate::llvm::build_list::{
allocate_list, build_basic_phi2, empty_list, incrementing_elem_loop, incrementing_index_loop,
load_list_ptr, store_list,
};
use crate::llvm::convert::{collection, ptr_int};
use crate::llvm::convert::collection;
use inkwell::builder::Builder;
use inkwell::types::BasicTypeEnum;
use inkwell::values::{BasicValueEnum, FunctionValue, IntValue, PointerValue, StructValue};
use inkwell::{AddressSpace, IntPredicate};
use roc_builtins::bitcode;
use roc_module::symbol::Symbol;
use roc_mono::layout::{Builtin, Layout};
pub static CHAR_LAYOUT: Layout = Layout::Builtin(Builtin::Int8);
/// Str.split : Str, Str -> List Str
pub fn str_split<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
scope: &Scope<'a, 'ctx>,
parent: FunctionValue<'ctx>,
inplace: InPlace,
str_symbol: Symbol,
delimiter_symbol: Symbol,
) -> BasicValueEnum<'ctx> {
let builder = env.builder;
let ctx = env.context;
let str_ptr = ptr_from_symbol(scope, str_symbol);
let delimiter_ptr = ptr_from_symbol(scope, delimiter_symbol);
let str_wrapper_type = BasicTypeEnum::StructType(collection(ctx, env.ptr_bytes));
load_str(
env,
parent,
*str_ptr,
str_wrapper_type,
|str_bytes_ptr, str_len, _str_smallness| {
load_str(
env,
parent,
*delimiter_ptr,
str_wrapper_type,
|delimiter_bytes_ptr, delimiter_len, _delimiter_smallness| {
let segment_count = call_bitcode_fn(
env,
&[
BasicValueEnum::PointerValue(str_bytes_ptr),
BasicValueEnum::IntValue(str_len),
BasicValueEnum::PointerValue(delimiter_bytes_ptr),
BasicValueEnum::IntValue(delimiter_len),
],
&bitcode::STR_COUNT_SEGMENTS,
)
.into_int_value();
// a pointer to the elements
let ret_list_ptr =
allocate_list(env, inplace, &Layout::Builtin(Builtin::Str), segment_count);
// get the RocStr type defined by zig
let roc_str_type = env.module.get_struct_type("str.RocStr").unwrap();
// convert `*mut { *mut u8, i64 }` to `*mut RocStr`
let ret_list_ptr_zig_rocstr = builder.build_bitcast(
ret_list_ptr,
roc_str_type.ptr_type(AddressSpace::Generic),
"convert_to_zig_rocstr",
);
call_void_bitcode_fn(
env,
&[
ret_list_ptr_zig_rocstr,
BasicValueEnum::IntValue(segment_count),
BasicValueEnum::PointerValue(str_bytes_ptr),
BasicValueEnum::IntValue(str_len),
BasicValueEnum::PointerValue(delimiter_bytes_ptr),
BasicValueEnum::IntValue(delimiter_len),
],
&bitcode::STR_STR_SPLIT_IN_PLACE,
);
store_list(env, ret_list_ptr, segment_count)
},
)
},
)
}
/// Str.concat : Str, Str -> Str
pub fn str_concat<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
@ -28,19 +106,19 @@ pub fn str_concat<'a, 'ctx, 'env>(
let second_str_ptr = ptr_from_symbol(scope, second_str_symbol);
let first_str_ptr = ptr_from_symbol(scope, first_str_symbol);
let str_wrapper_type = BasicTypeEnum::StructType(collection(ctx, env.ptr_bytes));
let ret_type = BasicTypeEnum::StructType(collection(ctx, env.ptr_bytes));
load_str(
env,
parent,
*second_str_ptr,
str_wrapper_type,
ret_type,
|second_str_ptr, second_str_len, second_str_smallness| {
load_str(
env,
parent,
*first_str_ptr,
str_wrapper_type,
ret_type,
|first_str_ptr, first_str_len, first_str_smallness| {
// first_str_len > 0
// We do this check to avoid allocating memory. If the first input
@ -73,7 +151,7 @@ pub fn str_concat<'a, 'ctx, 'env>(
second_str_length_comparison,
if_second_str_is_nonempty,
if_second_str_is_empty,
str_wrapper_type,
ret_type,
)
};
@ -458,14 +536,14 @@ fn clone_nonempty_str<'a, 'ctx, 'env>(
}
Smallness::Big => {
let clone_ptr = allocate_list(env, inplace, &CHAR_LAYOUT, len);
let int_type = ptr_int(ctx, ptr_bytes);
let ptr_as_int = builder.build_ptr_to_int(clone_ptr, int_type, "list_cast_ptr");
// TODO check if malloc returned null; if so, runtime error for OOM!
// Copy the bytes from the original array into the new
// one we just malloc'd.
builder.build_memcpy(clone_ptr, ptr_bytes, bytes_ptr, ptr_bytes, len);
builder
.build_memcpy(clone_ptr, ptr_bytes, bytes_ptr, ptr_bytes, len)
.unwrap();
// Create a fresh wrapper struct for the newly populated array
let struct_type = collection(ctx, env.ptr_bytes);
@ -475,7 +553,7 @@ fn clone_nonempty_str<'a, 'ctx, 'env>(
struct_val = builder
.build_insert_value(
struct_type.get_undef(),
ptr_as_int,
clone_ptr,
Builtin::WRAPPER_PTR,
"insert_ptr",
)
@ -703,3 +781,33 @@ pub fn str_starts_with<'a, 'ctx, 'env>(
},
)
}
/// Str.countGraphemes : Str -> Int
pub fn str_count_graphemes<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
scope: &Scope<'a, 'ctx>,
parent: FunctionValue<'ctx>,
str_symbol: Symbol,
) -> BasicValueEnum<'ctx> {
let ctx = env.context;
let sym_str_ptr = ptr_from_symbol(scope, str_symbol);
let ret_type = BasicTypeEnum::IntType(ctx.i64_type());
load_str(
env,
parent,
*sym_str_ptr,
ret_type,
|str_ptr, str_len, _str_smallness| {
call_bitcode_fn(
env,
&[
BasicValueEnum::PointerValue(str_ptr),
BasicValueEnum::IntValue(str_len),
],
&bitcode::STR_COUNT_GRAPEHEME_CLUSTERS,
)
},
)
}

Binary file not shown.

View file

@ -131,6 +131,7 @@ pub fn basic_type_from_layout<'ctx>(
Pointer(layout) => basic_type_from_layout(arena, context, &layout, ptr_bytes)
.ptr_type(AddressSpace::Generic)
.into(),
PhantomEmptyStruct => context.struct_type(&[], false).into(),
Struct(sorted_fields) => basic_type_from_record(arena, context, sorted_fields, ptr_bytes),
Union(tags) if tags.len() == 1 => {
let sorted_fields = tags.iter().next().unwrap();
@ -202,26 +203,13 @@ pub fn block_of_memory<'ctx>(
/// Two usize values. Could be a wrapper for a List or a Str.
///
/// It would be nicer if we could store this as a tuple containing one usize
/// and one pointer. However, if we do that, we run into a problem with the
/// empty list: it doesn't know what pointer type it should initailize to,
/// so it can only create an empty (usize, usize) struct.
///
/// This way, we always initialize it to (usize, usize), and then if there's
/// actually a pointer, we use build_int_to_ptr and build_ptr_to_int to convert
/// the field when necessary. (It's not allowed to cast the entire struct from
/// (usize, usize) to (usize, ptr) or vice versa.)
/// This way, we always initialize it to (*mut u8, usize), and may have to cast the pointer type
/// for lists.
pub fn collection(ctx: &Context, ptr_bytes: u32) -> StructType<'_> {
let int_type = BasicTypeEnum::IntType(ptr_int(ctx, ptr_bytes));
let usize_type = ptr_int(ctx, ptr_bytes);
let u8_ptr = ctx.i8_type().ptr_type(AddressSpace::Generic);
ctx.struct_type(&[int_type, int_type], false)
}
/// Two usize values.
pub fn collection_int_wrapper(ctx: &Context, ptr_bytes: u32) -> StructType<'_> {
let usize_type = BasicTypeEnum::IntType(ptr_int(ctx, ptr_bytes));
ctx.struct_type(&[usize_type, usize_type], false)
ctx.struct_type(&[u8_ptr.into(), usize_type.into()], false)
}
pub fn ptr_int(ctx: &Context, ptr_bytes: u32) -> IntType<'_> {

File diff suppressed because it is too large Load diff

View file

@ -28,24 +28,28 @@ impl<T: Sized> Into<Result<T, String>> for RocCallResult<T> {
#[macro_export]
macro_rules! run_jit_function {
($execution_engine: expr, $main_fn_name: expr, $ty:ty, $transform:expr) => {{
($lib: expr, $main_fn_name: expr, $ty:ty, $transform:expr) => {{
let v: std::vec::Vec<roc_problem::can::Problem> = std::vec::Vec::new();
run_jit_function!($execution_engine, $main_fn_name, $ty, $transform, v)
run_jit_function!($lib, $main_fn_name, $ty, $transform, v)
}};
($execution_engine: expr, $main_fn_name: expr, $ty:ty, $transform:expr, $errors:expr) => {{
($lib: expr, $main_fn_name: expr, $ty:ty, $transform:expr, $errors:expr) => {{
use inkwell::context::Context;
use inkwell::execution_engine::JitFunction;
use roc_gen::run_roc::RocCallResult;
use std::mem::MaybeUninit;
unsafe {
let main: JitFunction<unsafe extern "C" fn() -> RocCallResult<$ty>> = $execution_engine
.get_function($main_fn_name)
let main: libloading::Symbol<unsafe extern "C" fn(*mut RocCallResult<$ty>) -> ()> =
$lib.get($main_fn_name.as_bytes())
.ok()
.ok_or(format!("Unable to JIT compile `{}`", $main_fn_name))
.expect("errored");
match main.call().into() {
let mut result = MaybeUninit::uninit();
main(result.as_mut_ptr());
match result.assume_init().into() {
Ok(success) => {
// only if there are no exceptions thrown, check for errors
assert_eq!(
@ -68,26 +72,25 @@ macro_rules! run_jit_function {
/// It explicitly allocates a buffer that the roc main function can write its result into.
#[macro_export]
macro_rules! run_jit_function_dynamic_type {
($execution_engine: expr, $main_fn_name: expr, $bytes:expr, $transform:expr) => {{
($lib: expr, $main_fn_name: expr, $bytes:expr, $transform:expr) => {{
let v: std::vec::Vec<roc_problem::can::Problem> = std::vec::Vec::new();
run_jit_function_dynamic_type!($execution_engine, $main_fn_name, $bytes, $transform, v)
run_jit_function_dynamic_type!($lib, $main_fn_name, $bytes, $transform, v)
}};
($execution_engine: expr, $main_fn_name: expr, $bytes:expr, $transform:expr, $errors:expr) => {{
($lib: expr, $main_fn_name: expr, $bytes:expr, $transform:expr, $errors:expr) => {{
use inkwell::context::Context;
use inkwell::execution_engine::JitFunction;
use roc_gen::run_roc::RocCallResult;
unsafe {
let main: JitFunction<unsafe extern "C" fn(*const u8)> = $execution_engine
.get_function($main_fn_name)
let main: libloading::Symbol<unsafe extern "C" fn(*const u8)> = $lib
.get($main_fn_name.as_bytes())
.ok()
.ok_or(format!("Unable to JIT compile `{}`", $main_fn_name))
.expect("errored");
let layout = std::alloc::Layout::array::<u8>($bytes).unwrap();
let result = std::alloc::alloc(layout);
main.call(result);
main(result);
let flag = *result;

View file

@ -14,8 +14,7 @@ mod helpers;
#[cfg(test)]
mod gen_list {
use crate::helpers::with_larger_debug_stack;
//use roc_std::roclist;
use roc_std::RocList;
use roc_std::{RocList, RocStr};
#[test]
fn roc_list_construction() {
@ -264,6 +263,48 @@ mod gen_list {
);
}
#[test]
fn list_walk_right_with_str() {
assert_evals_to!(
r#"List.walkRight [ "x", "y", "z" ] Str.concat "<""#,
RocStr::from("zyx<"),
RocStr
);
assert_evals_to!(
r#"List.walkRight [ "Third", "Second", "First" ] Str.concat "Fourth""#,
RocStr::from("FirstSecondThirdFourth"),
RocStr
);
}
#[test]
fn list_walk_right_with_record() {
assert_evals_to!(
indoc!(
r#"
Bit : [ Zero, One ]
byte : List Bit
byte = [ Zero, One, Zero, One, Zero, Zero, One, Zero ]
initialCounts = { zeroes: 0, ones: 0 }
acc = \b, r ->
when b is
Zero -> { r & zeroes: r.zeroes + 1 }
One -> { r & ones: r.ones + 1 }
finalCounts = List.walkRight byte acc initialCounts
finalCounts.ones * 10 + finalCounts.zeroes
"#
),
35,
i64
);
}
#[test]
fn list_keep_if_empty_list_of_int() {
assert_evals_to!(
@ -273,7 +314,7 @@ mod gen_list {
empty =
[]
List.keepIf empty (\x -> True)
List.keepIf empty (\_ -> True)
"#
),
RocList::from_slice(&[]),
@ -305,7 +346,7 @@ mod gen_list {
indoc!(
r#"
alwaysTrue : Int -> Bool
alwaysTrue = \i ->
alwaysTrue = \_ ->
True
oneThroughEight : List Int
@ -326,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
@ -1562,4 +1603,20 @@ 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);
}
#[test]
fn list_sum() {
assert_evals_to!("List.sum []", 0, i64);
assert_evals_to!("List.sum [ 1, 2, 3 ]", 6, i64);
assert_evals_to!("List.sum [ 1.1, 2.2, 3.3 ]", 6.6, f64);
}
}

View file

@ -13,15 +13,7 @@ mod helpers;
#[cfg(test)]
mod gen_num {
/*
use inkwell::passes::PassManager;
use inkwell::types::BasicType;
use inkwell::OptimizationLevel;
use roc_gen::llvm::build::{build_proc, build_proc_header};
use roc_gen::llvm::convert::basic_type_from_layout;
use roc_mono::layout::Layout;
use roc_types::subs::Subs;
*/
use roc_std::RocOrder;
#[test]
fn f64_sqrt() {
@ -557,7 +549,7 @@ mod gen_num {
indoc!(
r#"
always42 : Num.Num Num.Integer -> Num.Num Num.Integer
always42 = \num -> 42
always42 = \_ -> 42
always42 5
"#
@ -584,86 +576,16 @@ mod gen_num {
#[test]
fn int_compare() {
assert_evals_to!(
indoc!(
r#"
when Num.compare 0 1 is
LT -> 0
EQ -> 1
GT -> 2
"#
),
0,
i64
);
assert_evals_to!(
indoc!(
r#"
when Num.compare 1 1 is
LT -> 0
EQ -> 1
GT -> 2
"#
),
1,
i64
);
assert_evals_to!(
indoc!(
r#"
when Num.compare 1 0 is
LT -> 0
EQ -> 1
GT -> 2
"#
),
2,
i64
);
assert_evals_to!("Num.compare 0 1", RocOrder::Lt, RocOrder);
assert_evals_to!("Num.compare 1 1", RocOrder::Eq, RocOrder);
assert_evals_to!("Num.compare 1 0", RocOrder::Gt, RocOrder);
}
#[test]
fn float_compare() {
assert_evals_to!(
indoc!(
r#"
when Num.compare 0 3.14 is
LT -> 0
EQ -> 1
GT -> 2
"#
),
0,
i64
);
assert_evals_to!(
indoc!(
r#"
when Num.compare 3.14 3.14 is
LT -> 0
EQ -> 1
GT -> 2
"#
),
1,
i64
);
assert_evals_to!(
indoc!(
r#"
when Num.compare 3.14 0 is
LT -> 0
EQ -> 1
GT -> 2
"#
),
2,
i64
);
assert_evals_to!("Num.compare 0.01 3.14", RocOrder::Lt, RocOrder);
assert_evals_to!("Num.compare 3.14 3.14", RocOrder::Eq, RocOrder);
assert_evals_to!("Num.compare 3.14 0.01", RocOrder::Gt, RocOrder);
}
#[test]
@ -691,19 +613,19 @@ mod gen_num {
assert_evals_to!("Num.atan 10", 1.4711276743037347, f64);
}
#[test]
#[should_panic(expected = r#"Roc failed with message: "integer addition overflowed!"#)]
fn int_overflow() {
assert_evals_to!(
indoc!(
r#"
9_223_372_036_854_775_807 + 1
"#
),
0,
i64
);
}
// #[test]
// #[should_panic(expected = r#"Roc failed with message: "integer addition overflowed!"#)]
// fn int_overflow() {
// assert_evals_to!(
// indoc!(
// r#"
// 9_223_372_036_854_775_807 + 1
// "#
// ),
// 0,
// i64
// );
// }
#[test]
fn int_add_checked() {
@ -775,17 +697,43 @@ mod gen_num {
);
}
// #[test]
// #[should_panic(expected = r#"Roc failed with message: "float addition overflowed!"#)]
// fn float_overflow() {
// assert_evals_to!(
// indoc!(
// r#"
// 1.7976931348623157e308 + 1.7976931348623157e308
// "#
// ),
// 0.0,
// f64
// );
// }
#[test]
#[should_panic(expected = r#"Roc failed with message: "float addition overflowed!"#)]
fn float_overflow() {
fn num_max_int() {
assert_evals_to!(
indoc!(
r#"
1.7976931348623157e308 + 1.7976931348623157e308
Num.maxInt
"#
),
0.0,
f64
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() {
@ -489,7 +491,7 @@ mod gen_primitives {
#[test]
fn peano1() {
assert_evals_to!(
assert_non_opt_evals_to!(
indoc!(
r#"
Peano : [ S Peano, Z ]
@ -509,7 +511,7 @@ mod gen_primitives {
#[test]
fn peano2() {
assert_evals_to!(
assert_non_opt_evals_to!(
indoc!(
r#"
Peano : [ S Peano, Z ]
@ -548,25 +550,24 @@ mod gen_primitives {
#[test]
fn linked_list_len_0() {
assert_evals_to!(
assert_non_opt_evals_to!(
indoc!(
r#"
app LinkedListLen0 provides [ main ] imports []
app Test provides [ main ] imports []
LinkedList a : [ Nil, Cons a (LinkedList a) ]
len : LinkedList a -> Int
len = \list ->
when list is
Nil -> 0
Cons _ rest -> 1 + len rest
main =
nil : LinkedList Int
nil = Nil
length : LinkedList a -> Int
length = \list ->
when list is
Nil -> 0
Cons _ rest -> 1 + length rest
main =
length nil
len nil
"#
),
0,
@ -576,7 +577,7 @@ mod gen_primitives {
#[test]
fn linked_list_len_twice_0() {
assert_evals_to!(
assert_non_opt_evals_to!(
indoc!(
r#"
app LinkedListLenTwice0 provides [ main ] imports []
@ -603,7 +604,7 @@ mod gen_primitives {
#[test]
fn linked_list_len_1() {
assert_evals_to!(
assert_non_opt_evals_to!(
indoc!(
r#"
app Test provides [ main ] imports []
@ -630,7 +631,7 @@ mod gen_primitives {
#[test]
fn linked_list_len_twice_1() {
assert_evals_to!(
assert_non_opt_evals_to!(
indoc!(
r#"
app Test provides [ main ] imports []
@ -657,7 +658,7 @@ mod gen_primitives {
#[test]
fn linked_list_len_3() {
assert_evals_to!(
assert_non_opt_evals_to!(
indoc!(
r#"
app Test provides [ main ] imports []
@ -685,7 +686,7 @@ mod gen_primitives {
#[test]
fn linked_list_sum_num_a() {
assert_evals_to!(
assert_non_opt_evals_to!(
indoc!(
r#"
app Test provides [ main ] imports []
@ -713,7 +714,7 @@ mod gen_primitives {
#[test]
fn linked_list_sum_int() {
assert_evals_to!(
assert_non_opt_evals_to!(
indoc!(
r#"
app Test provides [ main ] imports []
@ -740,7 +741,7 @@ mod gen_primitives {
#[test]
fn linked_list_map() {
assert_evals_to!(
assert_non_opt_evals_to!(
indoc!(
r#"
app Test provides [ main ] imports []
@ -829,7 +830,7 @@ mod gen_primitives {
#[test]
fn when_peano() {
assert_evals_to!(
assert_non_opt_evals_to!(
indoc!(
r#"
Peano : [ S Peano, Z ]
@ -847,7 +848,7 @@ mod gen_primitives {
i64
);
assert_evals_to!(
assert_non_opt_evals_to!(
indoc!(
r#"
Peano : [ S Peano, Z ]
@ -865,7 +866,7 @@ mod gen_primitives {
i64
);
assert_evals_to!(
assert_non_opt_evals_to!(
indoc!(
r#"
Peano : [ S Peano, Z ]
@ -884,22 +885,22 @@ mod gen_primitives {
);
}
#[test]
#[should_panic(expected = "Roc failed with message: ")]
fn exception() {
assert_evals_to!(
indoc!(
r#"
if True then
x + z
else
y + z
"#
),
3,
i64
);
}
// #[test]
// #[should_panic(expected = "Roc failed with message: ")]
// fn exception() {
// assert_evals_to!(
// indoc!(
// r#"
// if True then
// x + z
// else
// y + z
// "#
// ),
// 3,
// i64
// );
// }
#[test]
fn closure() {
@ -1002,20 +1003,20 @@ mod gen_primitives {
#[test]
fn io_poc_effect() {
assert_evals_to!(
assert_non_opt_evals_to!(
indoc!(
r#"
app Test provides [ main ] imports []
Effect a : [ @Effect ({} -> a) ]
# succeed : a -> Effect a
succeed : a -> Effect a
succeed = \x -> @Effect \{} -> x
# runEffect : Effect a -> a
runEffect : Effect a -> a
runEffect = \@Effect thunk -> thunk {}
# foo : Effect Float
foo : Effect Float
foo =
succeed 3.14
@ -1056,4 +1057,518 @@ mod gen_primitives {
f64
);
}
#[test]
fn return_wrapped_function_pointer() {
assert_non_opt_evals_to!(
indoc!(
r#"
app Test provides [ main ] imports []
Effect a : [ @Effect ({} -> a) ]
foo : Effect {}
foo = @Effect \{} -> {}
main : Effect {}
main = foo
"#
),
1,
i64,
|_| 1
);
}
#[test]
fn return_wrapped_closure() {
assert_non_opt_evals_to!(
indoc!(
r#"
app Test provides [ main ] imports []
Effect a : [ @Effect ({} -> a) ]
foo : Effect {}
foo =
x = 5
@Effect (\{} -> if x > 3 then {} else {})
main : Effect {}
main = foo
"#
),
1,
i64,
|_| 1
);
}
#[test]
fn linked_list_is_empty_1() {
assert_non_opt_evals_to!(
indoc!(
r#"
app Test provides [ main ] imports []
ConsList a : [ Cons a (ConsList a), Nil ]
empty : ConsList a
empty = Nil
isEmpty : ConsList a -> Bool
isEmpty = \list ->
when list is
Cons _ _ ->
False
Nil ->
True
main : Bool
main =
myList : ConsList Int
myList = empty
isEmpty myList
"#
),
true,
bool
);
}
#[test]
fn linked_list_is_empty_2() {
assert_non_opt_evals_to!(
indoc!(
r#"
app Test provides [ main ] imports []
ConsList a : [ Cons a (ConsList a), Nil ]
isEmpty : ConsList a -> Bool
isEmpty = \list ->
when list is
Cons _ _ ->
False
Nil ->
True
main : Bool
main =
myList : ConsList Int
myList = Cons 0x1 Nil
isEmpty myList
"#
),
false,
bool
);
}
#[test]
fn recursive_functon_with_rigid() {
assert_non_opt_evals_to!(
indoc!(
r#"
app Test provides [ main ] imports []
State a : { count : Int, x : a }
foo : State a -> Int
foo = \state ->
if state.count == 0 then
0
else
1 + foo { count: state.count - 1, x: state.x }
main : Int
main =
foo { count: 3, x: {} }
"#
),
3,
i64
);
}
#[test]
#[ignore]
fn rbtree_insert() {
assert_non_opt_evals_to!(
indoc!(
r#"
app Test provides [ main ] imports []
NodeColor : [ Red, Black ]
Dict k v : [ Node NodeColor k v (Dict k v) (Dict k v), Empty ]
Key k : Num k
insert : Key k, v, Dict (Key k) v -> Dict (Key k) v
insert = \key, value, dict ->
when insertHelp key value dict is
Node Red k v l r ->
Node Black k v l r
x ->
x
insertHelp : (Key k), v, Dict (Key k) v -> Dict (Key k) v
insertHelp = \key, value, dict ->
when dict is
Empty ->
# New nodes are always red. If it violates the rules, it will be fixed
# when balancing.
Node Red key value Empty Empty
Node nColor nKey nValue nLeft nRight ->
when Num.compare key nKey is
LT ->
balance nColor nKey nValue (insertHelp key value nLeft) nRight
EQ ->
Node nColor nKey value nLeft nRight
GT ->
balance nColor nKey nValue nLeft (insertHelp key value nRight)
balance : NodeColor, k, v, Dict k v, Dict k v -> Dict k v
balance = \color, key, value, left, right ->
when right is
Node Red rK rV rLeft rRight ->
when left is
Node Red lK lV lLeft lRight ->
Node
Red
key
value
(Node Black lK lV lLeft lRight)
(Node Black rK rV rLeft rRight)
_ ->
Node color rK rV (Node Red key value left rLeft) rRight
_ ->
when left is
Node Red lK lV (Node Red llK llV llLeft llRight) lRight ->
Node
Red
lK
lV
(Node Black llK llV llLeft llRight)
(Node Black key value lRight right)
_ ->
Node color key value left right
main : Dict Int {}
main =
insert 0 {} Empty
"#
),
1,
i64
);
}
#[test]
#[ignore]
fn rbtree_balance_inc_dec() {
// TODO does not define a variable correctly, but all is well with the type signature
assert_non_opt_evals_to!(
indoc!(
r#"
app Test provides [ main ] imports []
NodeColor : [ Red, Black ]
Dict k : [ Node NodeColor k (Dict k) (Dict k), Empty ]
# balance : NodeColor, k, Dict k, Dict k -> Dict k
balance = \color, key, left, right ->
when right is
Node Red rK rLeft rRight ->
when left is
Node Red _ _ _ ->
Node
Red
key
Empty
Empty
_ ->
Node color rK (Node Red key left rLeft) rRight
_ ->
Empty
main : Dict Int
main =
balance Red 0 Empty Empty
"#
),
0,
i64
);
}
#[test]
fn rbtree_balance_3() {
assert_non_opt_evals_to!(
indoc!(
r#"
app Test provides [ main ] imports []
Dict k : [ Node k (Dict k) (Dict k), Empty ]
balance : k, Dict k -> Dict k
balance = \key, left ->
Node key left Empty
main : Dict Int
main =
balance 0 Empty
"#
),
1,
i64
);
}
#[test]
#[ignore]
fn rbtree_balance_mono_problem() {
// because of how the function is written, only `Red` is used and so in the function's
// type, the first argument is a unit and dropped. Apparently something is weird with
// constraint generation where the specialization required by `main` does not fix the
// problem. As a result, the first argument is dropped and we run into issues down the line
//
// concretely, the `rRight` symbol will not be defined
assert_non_opt_evals_to!(
indoc!(
r#"
app Test provides [ main ] imports []
NodeColor : [ Red, Black ]
Dict k v : [ Node NodeColor k v (Dict k v) (Dict k v), Empty ]
# balance : NodeColor, k, v, Dict k v, Dict k v -> Dict k v
balance = \color, key, value, left, right ->
when right is
Node Red rK rV rLeft rRight ->
when left is
Node Red lK lV lLeft lRight ->
Node
Red
key
value
(Node Black lK lV lLeft lRight)
(Node Black rK rV rLeft rRight)
_ ->
Node color rK rV (Node Red key value left rLeft) rRight
_ ->
Empty
main : Dict Int Int
main =
balance Red 0 0 Empty Empty
"#
),
1,
i64
);
}
#[test]
fn rbtree_balance_full() {
assert_non_opt_evals_to!(
indoc!(
r#"
app Test provides [ main ] imports []
NodeColor : [ Red, Black ]
Dict k v : [ Node NodeColor k v (Dict k v) (Dict k v), Empty ]
balance : NodeColor, k, v, Dict k v, Dict k v -> Dict k v
balance = \color, key, value, left, right ->
when right is
Node Red rK rV rLeft rRight ->
when left is
Node Red lK lV lLeft lRight ->
Node
Red
key
value
(Node Black lK lV lLeft lRight)
(Node Black rK rV rLeft rRight)
_ ->
Node color rK rV (Node Red key value left rLeft) rRight
_ ->
when left is
Node Red lK lV (Node Red llK llV llLeft llRight) lRight ->
Node
Red
lK
lV
(Node Black llK llV llLeft llRight)
(Node Black key value lRight right)
_ ->
Node color key value left right
main : Dict Int Int
main =
balance Red 0 0 Empty Empty
"#
),
1,
i64
);
}
#[test]
fn nested_pattern_match_two_ways() {
// exposed an issue in the ordering of pattern match checks when ran with `--release` mode
assert_non_opt_evals_to!(
indoc!(
r#"
app Test provides [ main ] imports []
ConsList a : [ Cons a (ConsList a), Nil ]
balance : ConsList Int -> Int
balance = \right ->
when right is
Cons 1 foo ->
when foo is
Cons 1 _ -> 3
_ -> 3
_ -> 3
main : Int
main =
when balance Nil is
_ -> 3
"#
),
3,
i64
);
assert_non_opt_evals_to!(
indoc!(
r#"
app Test provides [ main ] imports []
ConsList a : [ Cons a (ConsList a), Nil ]
balance : ConsList Int -> Int
balance = \right ->
when right is
Cons 1 (Cons 1 _) -> 3
_ -> 3
main : Int
main =
when balance Nil is
_ -> 3
"#
),
3,
i64
);
}
#[test]
fn linked_list_guarded_double_pattern_match() {
// the important part here is that the first case (with the nested Cons) does not match
// TODO this also has undefined behavior
assert_non_opt_evals_to!(
indoc!(
r#"
app Test provides [ main ] imports []
ConsList a : [ Cons a (ConsList a), Nil ]
balance : ConsList Int -> Int
balance = \right ->
when right is
Cons 1 foo ->
when foo is
Cons 1 _ -> 3
_ -> 3
_ -> 3
main : Int
main =
when balance Nil is
_ -> 3
"#
),
3,
i64
);
}
#[test]
fn linked_list_double_pattern_match() {
assert_non_opt_evals_to!(
indoc!(
r#"
app Test provides [ main ] imports []
ConsList a : [ Cons a (ConsList a), Nil ]
foo : ConsList Int -> Int
foo = \list ->
when list is
Cons _ (Cons x _) -> x
_ -> 0
main : Int
main =
foo (Cons 1 (Cons 32 Nil))
"#
),
32,
i64
);
}
#[test]
fn binary_tree_double_pattern_match() {
assert_non_opt_evals_to!(
indoc!(
r#"
app Test provides [ main ] imports []
BTree : [ Node BTree BTree, Leaf Int ]
foo : BTree -> Int
foo = \btree ->
when btree is
Node (Node (Leaf x) _) _ -> x
_ -> 0
main : Int
main =
foo (Node (Node (Leaf 32) (Leaf 0)) (Leaf 0))
"#
),
32,
i64
);
}
}

View file

@ -254,11 +254,11 @@ mod gen_records {
r#"
v = {}
1
v
"#
),
1,
i64
(),
()
);
}
#[test]
@ -820,4 +820,28 @@ mod gen_records {
i64
);
}
#[test]
fn booleans_in_record() {
assert_evals_to!(
indoc!("{ x: 1 == 1, y: 1 == 1 }"),
(true, true),
(bool, bool)
);
assert_evals_to!(
indoc!("{ x: 1 != 1, y: 1 == 1 }"),
(false, true),
(bool, bool)
);
assert_evals_to!(
indoc!("{ x: 1 == 1, y: 1 != 1 }"),
(true, false),
(bool, bool)
);
assert_evals_to!(
indoc!("{ x: 1 != 1, y: 1 != 1 }"),
(false, false),
(bool, bool)
);
}
}

View file

@ -8,11 +8,232 @@ extern crate inkwell;
extern crate libc;
extern crate roc_gen;
use core;
use roc_std::RocStr;
#[macro_use]
mod helpers;
const ROC_STR_MEM_SIZE: usize = core::mem::size_of::<RocStr>();
#[cfg(test)]
mod gen_str {
use crate::ROC_STR_MEM_SIZE;
use std::cmp::min;
fn small_str(str: &str) -> [u8; ROC_STR_MEM_SIZE] {
let mut bytes: [u8; ROC_STR_MEM_SIZE] = Default::default();
let mut index: usize = 0;
while index < ROC_STR_MEM_SIZE {
bytes[index] = 0;
index += 1;
}
let str_bytes = str.as_bytes();
let output_len: usize = min(str_bytes.len(), ROC_STR_MEM_SIZE);
index = 0;
while index < output_len {
bytes[index] = str_bytes[index];
index += 1;
}
bytes[ROC_STR_MEM_SIZE - 1] = 0b1000_0000 ^ (output_len as u8);
bytes
}
#[test]
fn str_split_bigger_delimiter_small_str() {
assert_evals_to!(
indoc!(
r#"
List.len (Str.split "hello" "JJJJ there")
"#
),
1,
i64
);
assert_evals_to!(
indoc!(
r#"
when List.first (Str.split "JJJ" "JJJJ there") is
Ok str ->
Str.countGraphemes str
_ ->
-1
"#
),
3,
i64
);
}
#[test]
fn str_split_str_concat_repeated() {
assert_evals_to!(
indoc!(
r#"
when List.first (Str.split "JJJJJ" "JJJJ there") is
Ok str ->
str
|> Str.concat str
|> Str.concat str
|> Str.concat str
|> Str.concat str
_ ->
"Not Str!"
"#
),
"JJJJJJJJJJJJJJJJJJJJJJJJJ",
&'static str
);
}
#[test]
fn str_split_small_str_bigger_delimiter() {
assert_evals_to!(
indoc!(
r#"
when
List.first
(Str.split "JJJ" "0123456789abcdefghi")
is
Ok str -> str
_ -> ""
"#
),
small_str("JJJ"),
[u8; ROC_STR_MEM_SIZE]
);
}
#[test]
fn str_split_big_str_small_delimiter() {
assert_evals_to!(
indoc!(
r#"
Str.split "01234567789abcdefghi?01234567789abcdefghi" "?"
"#
),
&["01234567789abcdefghi", "01234567789abcdefghi"],
&'static [&'static str]
);
assert_evals_to!(
indoc!(
r#"
Str.split "01234567789abcdefghi 3ch 01234567789abcdefghi" "3ch"
"#
),
&["01234567789abcdefghi ", " 01234567789abcdefghi"],
&'static [&'static str]
);
}
#[test]
fn str_split_small_str_small_delimiter() {
assert_evals_to!(
indoc!(
r#"
Str.split "J!J!J" "!"
"#
),
&[small_str("J"), small_str("J"), small_str("J")],
&'static [[u8; ROC_STR_MEM_SIZE]]
);
}
#[test]
fn str_split_bigger_delimiter_big_strs() {
assert_evals_to!(
indoc!(
r#"
Str.split
"string to split is shorter"
"than the delimiter which happens to be very very long"
"#
),
&["string to split is shorter"],
&'static [&'static str]
);
}
#[test]
fn str_split_empty_strs() {
assert_evals_to!(
indoc!(
r#"
Str.split "" ""
"#
),
&[small_str("")],
&'static [[u8; ROC_STR_MEM_SIZE]]
)
}
#[test]
fn str_split_minimal_example() {
assert_evals_to!(
indoc!(
r#"
Str.split "a," ","
"#
),
&[small_str("a"), small_str("")],
&'static [[u8; ROC_STR_MEM_SIZE]]
)
}
#[test]
fn str_split_small_str_big_delimiter() {
assert_evals_to!(
indoc!(
r#"
Str.split
"1---- ---- ---- ---- ----2---- ---- ---- ---- ----"
"---- ---- ---- ---- ----"
|> List.len
"#
),
3,
i64
);
assert_evals_to!(
indoc!(
r#"
Str.split
"1---- ---- ---- ---- ----2---- ---- ---- ---- ----"
"---- ---- ---- ---- ----"
"#
),
&[small_str("1"), small_str("2"), small_str("")],
&'static [[u8; ROC_STR_MEM_SIZE]]
);
}
#[test]
fn str_split_small_str_20_char_delimiter() {
assert_evals_to!(
indoc!(
r#"
Str.split
"3|-- -- -- -- -- -- |4|-- -- -- -- -- -- |"
"|-- -- -- -- -- -- |"
"#
),
&[small_str("3"), small_str("4"), small_str("")],
&'static [[u8; ROC_STR_MEM_SIZE]]
);
}
#[test]
fn str_concat_big_to_big() {
assert_evals_to!(
@ -211,4 +432,23 @@ mod gen_str {
assert_evals_to!(r#"Str.startsWith "hell" "hello world""#, true, bool);
assert_evals_to!(r#"Str.startsWith "" "hello world""#, true, bool);
}
#[test]
fn str_count_graphemes_small_str() {
assert_evals_to!(r#"Str.countGraphemes "å🤔""#, 2, usize);
}
#[test]
fn str_count_graphemes_three_js() {
assert_evals_to!(r#"Str.countGraphemes "JJJ""#, 3, usize);
}
#[test]
fn str_count_graphemes_big_str() {
assert_evals_to!(
r#"Str.countGraphemes "6🤔å🤔e¥🤔çppkd🙃1jdal🦯asdfa∆ltråø˚waia8918.,🏅jjc""#,
45,
usize
);
}
}

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,16 +79,16 @@ mod gen_tags {
y : Maybe Int
y = Just 0x4
0x1
y
"#
),
1,
i64
(0, 0x4),
(i64, i64)
);
}
#[test]
fn applied_tag_just_unit() {
fn applied_tag_just_enum() {
assert_evals_to!(
indoc!(
r#"
@ -99,11 +101,11 @@ mod gen_tags {
y : Maybe Fruit
y = Just orange
0x1
y
"#
),
1,
i64
(0, 2),
(i64, u8)
);
}
@ -234,7 +236,6 @@ mod gen_tags {
// }
#[test]
#[ignore]
fn even_odd() {
assert_evals_to!(
indoc!(
@ -350,7 +351,7 @@ mod gen_tags {
when x is
These a b -> a + b
That v -> 8
That v -> v
This v -> v
"#
),
@ -616,10 +617,10 @@ mod gen_tags {
x : [ Pair Int ]
x = Pair 2
0x3
x
"#
),
3,
2,
i64
);
}
@ -637,11 +638,11 @@ mod gen_tags {
x = Just (Just 41)
main =
5
x
"#
),
5,
i64
(0, (0, 41)),
(i64, (i64, i64))
);
}
#[test]
@ -654,11 +655,11 @@ mod gen_tags {
v : Unit
v = Unit
1
v
"#
),
1,
i64
(),
()
);
}
@ -667,8 +668,6 @@ mod gen_tags {
assert_evals_to!(
indoc!(
r#"
Maybe a : [ Nothing, Just a ]
x = { a : { b : 0x5 } }
y = x.a

View file

@ -1,3 +1,5 @@
use libloading::Library;
use roc_build::link::module_to_dylib;
use roc_collections::all::{MutMap, MutSet};
fn promote_expr_to_module(src: &str) -> String {
@ -19,12 +21,7 @@ pub fn helper<'a>(
stdlib: roc_builtins::std::StdLib,
leak: bool,
context: &'a inkwell::context::Context,
) -> (
&'static str,
Vec<roc_problem::can::Problem>,
inkwell::execution_engine::ExecutionEngine<'a>,
) {
use inkwell::OptimizationLevel;
) -> (&'static str, Vec<roc_problem::can::Problem>, Library) {
use roc_gen::llvm::build::{build_proc, build_proc_header, Scope};
use std::path::{Path, PathBuf};
@ -53,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,
@ -79,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);
@ -127,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");
@ -166,14 +152,14 @@ pub fn helper<'a>(
let (module_pass, function_pass) =
roc_gen::llvm::build::construct_optimization_passes(module, opt_level);
let execution_engine = module
.create_jit_execution_engine(OptimizationLevel::None)
.expect("Error creating JIT execution engine for test");
let (dibuilder, compile_unit) = roc_gen::llvm::build::Env::new_debug_info(module);
// Compile and add all the Procs before adding main
let env = roc_gen::llvm::build::Env {
arena: &arena,
builder: &builder,
dibuilder: &dibuilder,
compile_unit: &compile_unit,
context,
interns,
module,
@ -183,7 +169,7 @@ pub fn helper<'a>(
exposed_to_host: MutSet::default(),
};
let mut layout_ids = roc_gen::layout_id::LayoutIds::default();
let mut layout_ids = roc_mono::layout::LayoutIds::default();
let mut headers = Vec::with_capacity(procedures.len());
// Add all the Proc headers to the module.
@ -213,6 +199,9 @@ pub fn helper<'a>(
build_proc(&env, &mut layout_ids, scope.clone(), proc, fn_val);
// call finalize() before any code generation/verification
env.dibuilder.finalize();
if fn_val.verify(true) {
function_pass.run_on(&fn_val);
} else {
@ -246,6 +235,8 @@ pub fn helper<'a>(
&main_fn_layout,
);
env.dibuilder.finalize();
// Uncomment this to see the module's un-optimized LLVM instruction output:
// env.module.print_to_stderr();
@ -265,7 +256,10 @@ pub fn helper<'a>(
// Uncomment this to see the module's optimized LLVM instruction output:
// env.module.print_to_stderr();
(main_fn_name, errors, execution_engine.clone())
let lib = module_to_dylib(&env.module, &target, opt_level)
.expect("Error loading compiled dylib for test");
(main_fn_name, delayed_errors, lib)
}
// TODO this is almost all code duplication with assert_llvm_evals_to
@ -284,7 +278,7 @@ macro_rules! assert_opt_evals_to {
let stdlib = roc_builtins::unique::uniq_stdlib();
let (main_fn_name, errors, execution_engine) =
let (main_fn_name, errors, lib) =
$crate::helpers::eval::helper(&arena, $src, stdlib, $leak, &context);
let transform = |success| {
@ -292,7 +286,7 @@ macro_rules! assert_opt_evals_to {
let given = $transform(success);
assert_eq!(&given, &expected);
};
run_jit_function!(execution_engine, main_fn_name, $ty, transform, errors)
run_jit_function!(lib, main_fn_name, $ty, transform, errors)
};
($src:expr, $expected:expr, $ty:ty, $transform:expr) => {
@ -312,7 +306,7 @@ macro_rules! assert_llvm_evals_to {
let context = Context::create();
let stdlib = roc_builtins::std::standard_stdlib();
let (main_fn_name, errors, execution_engine) =
let (main_fn_name, errors, lib) =
$crate::helpers::eval::helper(&arena, $src, stdlib, $leak, &context);
let transform = |success| {
@ -320,7 +314,7 @@ macro_rules! assert_llvm_evals_to {
let given = $transform(success);
assert_eq!(&given, &expected);
};
run_jit_function!(execution_engine, main_fn_name, $ty, transform, errors)
run_jit_function!(lib, main_fn_name, $ty, transform, errors)
};
($src:expr, $expected:expr, $ty:ty, $transform:expr) => {
@ -352,3 +346,19 @@ macro_rules! assert_evals_to {
}
};
}
#[macro_export]
macro_rules! assert_non_opt_evals_to {
($src:expr, $expected:expr, $ty:ty) => {{
assert_llvm_evals_to!($src, $expected, $ty, (|val| val));
}};
($src:expr, $expected:expr, $ty:ty, $transform:expr) => {
// Same as above, except with an additional transformation argument.
{
assert_llvm_evals_to!($src, $expected, $ty, $transform, true);
}
};
($src:expr, $expected:expr, $ty:ty, $transform:expr, $leak:expr) => {{
assert_llvm_evals_to!($src, $expected, $ty, $transform, $leak);
}};
}

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