Merge branch 'trunk' of github.com:rtfeldman/roc into no-unnecessary-benches

This commit is contained in:
Anton-4 2021-08-03 10:12:51 +02:00
commit a1971ead6e
171 changed files with 3848 additions and 4569 deletions

4
Cargo.lock generated
View file

@ -1,5 +1,7 @@
# This file is automatically @generated by Cargo. # This file is automatically @generated by Cargo.
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3
[[package]] [[package]]
name = "ab_glyph" name = "ab_glyph"
version = "0.2.11" version = "0.2.11"
@ -3138,6 +3140,7 @@ dependencies = [
"roc_collections", "roc_collections",
"roc_load", "roc_load",
"roc_module", "roc_module",
"roc_parse",
"roc_region", "roc_region",
"roc_types", "roc_types",
] ]
@ -3356,6 +3359,7 @@ dependencies = [
"roc_types", "roc_types",
"roc_unify", "roc_unify",
"ven_ena", "ven_ena",
"ven_graph",
"ven_pretty", "ven_pretty",
] ]

View file

@ -32,7 +32,7 @@ These are the policies for upholding our community's standards of conduct. If yo
In the Roc community we strive to go the extra step to look out for each other. Don't just aim to be technically unimpeachable, try to be your best self. In particular, avoid flirting with offensive or sensitive issues, particularly if they're off-topic; this all too often leads to unnecessary fights, hurt feelings, and damaged trust; worse, it can drive people away from the community entirely. In the Roc community we strive to go the extra step to look out for each other. Don't just aim to be technically unimpeachable, try to be your best self. In particular, avoid flirting with offensive or sensitive issues, particularly if they're off-topic; this all too often leads to unnecessary fights, hurt feelings, and damaged trust; worse, it can drive people away from the community entirely.
And if someone takes issue with something you said or did, resist the urge to be defensive. Just stop doing what it was they complained about and apologize. Even if you feel you were misinterpreted or unfairly accused, chances are good there was something you could've communicated better — remember that it's your responsibility to make your fellow Roc progammers comfortable. Everyone wants to get along and we are all here first and foremost because we want to talk about cool technology. You will find that people will be eager to assume good intent and forgive as long as you earn their trust. And if someone takes issue with something you said or did, resist the urge to be defensive. Just stop doing what it was they complained about and apologize. Even if you feel you were misinterpreted or unfairly accused, chances are good there was something you could've communicated better — remember that it's your responsibility to make your fellow Roc programmers comfortable. Everyone wants to get along and we are all here first and foremost because we want to talk about cool technology. You will find that people will be eager to assume good intent and forgive as long as you earn their trust.
The enforcement policies listed above apply to all official Roc venues; including official IRC channels (#rust, #rust-internals, #rust-tools, #rust-libs, #rustc, #rust-beginners, #rust-docs, #rust-community, #rust-lang, and #cargo); GitHub repositories under rust-lang, rust-lang-nursery, and rust-lang-deprecated; and all forums under rust-lang.org (users.rust-lang.org, internals.rust-lang.org). For other projects adopting the Roc Code of Conduct, please contact the maintainers of those projects for enforcement. If you wish to use this code of conduct for your own project, consider explicitly mentioning your moderation policy or making a copy with your own moderation policy so as to avoid confusion. The enforcement policies listed above apply to all official Roc venues; including official IRC channels (#rust, #rust-internals, #rust-tools, #rust-libs, #rustc, #rust-beginners, #rust-docs, #rust-community, #rust-lang, and #cargo); GitHub repositories under rust-lang, rust-lang-nursery, and rust-lang-deprecated; and all forums under rust-lang.org (users.rust-lang.org, internals.rust-lang.org). For other projects adopting the Roc Code of Conduct, please contact the maintainers of those projects for enforcement. If you wish to use this code of conduct for your own project, consider explicitly mentioning your moderation policy or making a copy with your own moderation policy so as to avoid confusion.

View file

@ -1,4 +1,4 @@
FROM rust:1.53-slim-buster FROM rust:1.54-slim-buster
WORKDIR /earthbuild WORKDIR /earthbuild
prep-debian: prep-debian:
@ -101,7 +101,7 @@ check-rustfmt:
RUN cargo fmt --all -- --check RUN cargo fmt --all -- --check
check-typos: check-typos:
RUN cargo install typos-cli RUN cargo install --version 1.0.11 typos-cli
COPY --dir .github ci cli compiler docs editor examples nightly_benches packages roc_std www *.md LEGAL_DETAILS shell.nix ./ COPY --dir .github ci cli compiler docs editor examples nightly_benches packages roc_std www *.md LEGAL_DETAILS shell.nix ./
RUN typos RUN typos

View file

@ -36,7 +36,7 @@ fn check_cmd_output(
) { ) {
let out = run_cmd( let out = run_cmd(
file.with_file_name(executable_filename).to_str().unwrap(), file.with_file_name(executable_filename).to_str().unwrap(),
stdin_str, &[stdin_str],
&[], &[],
); );
@ -60,7 +60,7 @@ fn bench_cmd<T: Measurement>(
b.iter(|| { b.iter(|| {
run_cmd( run_cmd(
black_box(file.with_file_name(executable_filename).to_str().unwrap()), black_box(file.with_file_name(executable_filename).to_str().unwrap()),
black_box(stdin_str), black_box(&[stdin_str]),
&[], &[],
) )
}) })
@ -68,7 +68,7 @@ fn bench_cmd<T: Measurement>(
} else { } else {
run_cmd( run_cmd(
black_box(file.with_file_name(executable_filename).to_str().unwrap()), black_box(file.with_file_name(executable_filename).to_str().unwrap()),
black_box(stdin_str), black_box(&[stdin_str]),
&[], &[],
); );
} }

View file

@ -65,7 +65,7 @@ pub fn run_roc(args: &[&str]) -> Out {
} }
#[allow(dead_code)] #[allow(dead_code)]
pub fn run_cmd(cmd_name: &str, stdin_str: &str, args: &[&str]) -> Out { pub fn run_cmd(cmd_name: &str, stdin_vals: &[&str], args: &[&str]) -> Out {
let mut cmd = Command::new(cmd_name); let mut cmd = Command::new(cmd_name);
for arg in args { for arg in args {
@ -81,10 +81,13 @@ pub fn run_cmd(cmd_name: &str, stdin_str: &str, args: &[&str]) -> Out {
{ {
let stdin = child.stdin.as_mut().expect("Failed to open stdin"); let stdin = child.stdin.as_mut().expect("Failed to open stdin");
for stdin_str in stdin_vals {
stdin stdin
.write_all(stdin_str.as_bytes()) .write_all(stdin_str.as_bytes())
.expect("Failed to write to stdin"); .expect("Failed to write to stdin");
} }
}
let output = child let output = child
.wait_with_output() .wait_with_output()
@ -98,7 +101,7 @@ pub fn run_cmd(cmd_name: &str, stdin_str: &str, args: &[&str]) -> Out {
} }
#[allow(dead_code)] #[allow(dead_code)]
pub fn run_with_valgrind(stdin_str: &str, args: &[&str]) -> (Out, String) { pub fn run_with_valgrind(stdin_vals: &[&str], args: &[&str]) -> (Out, String) {
//TODO: figure out if there is a better way to get the valgrind executable. //TODO: figure out if there is a better way to get the valgrind executable.
let mut cmd = Command::new("valgrind"); let mut cmd = Command::new("valgrind");
let named_tempfile = let named_tempfile =
@ -142,10 +145,13 @@ pub fn run_with_valgrind(stdin_str: &str, args: &[&str]) -> (Out, String) {
{ {
let stdin = child.stdin.as_mut().expect("Failed to open stdin"); let stdin = child.stdin.as_mut().expect("Failed to open stdin");
for stdin_str in stdin_vals {
stdin stdin
.write_all(stdin_str.as_bytes()) .write_all(stdin_str.as_bytes())
.expect("Failed to write to stdin"); .expect("Failed to write to stdin");
} }
}
let output = child let output = child
.wait_with_output() .wait_with_output()
@ -228,7 +234,7 @@ pub fn extract_valgrind_errors(xml: &str) -> Result<Vec<ValgrindError>, serde_xm
} }
#[allow(dead_code)] #[allow(dead_code)]
pub fn example_dir(dir_name: &str) -> PathBuf { pub fn root_dir() -> PathBuf {
let mut path = env::current_exe().ok().unwrap(); let mut path = env::current_exe().ok().unwrap();
// Get rid of the filename in target/debug/deps/cli_run-99c65e4e9a1fbd06 // Get rid of the filename in target/debug/deps/cli_run-99c65e4e9a1fbd06
@ -243,6 +249,13 @@ pub fn example_dir(dir_name: &str) -> PathBuf {
path.pop(); path.pop();
path.pop(); path.pop();
path
}
#[allow(dead_code)]
pub fn examples_dir(dir_name: &str) -> PathBuf {
let mut path = root_dir();
// Descend into examples/{dir_name} // Descend into examples/{dir_name}
path.push("examples"); path.push("examples");
path.push(dir_name); path.push(dir_name);
@ -252,7 +265,7 @@ pub fn example_dir(dir_name: &str) -> PathBuf {
#[allow(dead_code)] #[allow(dead_code)]
pub fn example_file(dir_name: &str, file_name: &str) -> PathBuf { pub fn example_file(dir_name: &str, file_name: &str) -> PathBuf {
let mut path = example_dir(dir_name); let mut path = examples_dir(dir_name);
path.push(file_name); path.push(file_name);
@ -261,19 +274,7 @@ pub fn example_file(dir_name: &str, file_name: &str) -> PathBuf {
#[allow(dead_code)] #[allow(dead_code)]
pub fn fixtures_dir(dir_name: &str) -> PathBuf { pub fn fixtures_dir(dir_name: &str) -> PathBuf {
let mut path = env::current_exe().ok().unwrap(); let mut path = root_dir();
// 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} // Descend into cli/tests/fixtures/{dir_name}
path.push("cli"); path.push("cli");

View file

@ -65,7 +65,7 @@ pub fn build_file<'a>(
}; };
let loaded = roc_load::file::load_and_monomorphize( let loaded = roc_load::file::load_and_monomorphize(
&arena, arena,
roc_file_path.clone(), roc_file_path.clone(),
stdlib, stdlib,
src_dir.as_path(), src_dir.as_path(),
@ -128,11 +128,11 @@ pub fn build_file<'a>(
let cwd = roc_file_path.parent().unwrap(); let cwd = roc_file_path.parent().unwrap();
let binary_path = cwd.join(&*loaded.output_path); // TODO should join ".exe" on Windows let binary_path = cwd.join(&*loaded.output_path); // TODO should join ".exe" on Windows
let code_gen_timing = program::gen_from_mono_module( let code_gen_timing = program::gen_from_mono_module(
&arena, arena,
loaded, loaded,
&roc_file_path, &roc_file_path,
Triple::host(), Triple::host(),
&app_o_file, app_o_file,
opt_level, opt_level,
emit_debug_info, emit_debug_info,
); );

View file

@ -151,8 +151,7 @@ pub fn build(target: &Triple, matches: &ArgMatches, config: BuildConfig) -> io::
LinkType::Executable LinkType::Executable
}; };
let path = Path::new(filename).canonicalize().unwrap(); let path = Path::new(filename);
let src_dir = path.parent().unwrap().canonicalize().unwrap();
// Spawn the root task // Spawn the root task
let path = path.canonicalize().unwrap_or_else(|err| { let path = path.canonicalize().unwrap_or_else(|err| {
@ -173,6 +172,7 @@ pub fn build(target: &Triple, matches: &ArgMatches, config: BuildConfig) -> io::
} }
}); });
let src_dir = path.parent().unwrap().canonicalize().unwrap();
let res_binary_path = build_file( let res_binary_path = build_file(
&arena, &arena,
target, target,

View file

@ -1,17 +1,15 @@
use bumpalo::collections::Vec; use bumpalo::collections::Vec;
use bumpalo::Bump; use bumpalo::Bump;
use libloading::Library; use libloading::Library;
use roc_collections::all::MutMap;
use roc_gen_llvm::{run_jit_function, run_jit_function_dynamic_type}; use roc_gen_llvm::{run_jit_function, run_jit_function_dynamic_type};
use roc_module::ident::{Lowercase, TagName}; use roc_module::ident::TagName;
use roc_module::operator::CalledVia; use roc_module::operator::CalledVia;
use roc_module::symbol::{Interns, ModuleId, Symbol}; use roc_module::symbol::{Interns, ModuleId, Symbol};
use roc_mono::ir::ProcLayout; use roc_mono::ir::ProcLayout;
use roc_mono::layout::{union_sorted_tags_help, Builtin, Layout, UnionLayout, UnionVariant}; use roc_mono::layout::{union_sorted_tags_help, Builtin, Layout, UnionLayout, UnionVariant};
use roc_parse::ast::{AssignedField, Expr, StrLiteral}; use roc_parse::ast::{AssignedField, Expr, StrLiteral};
use roc_region::all::{Located, Region}; use roc_region::all::{Located, Region};
use roc_types::subs::{Content, FlatType, Subs, Variable}; use roc_types::subs::{Content, FlatType, RecordFields, Subs, Variable};
use roc_types::types::RecordField;
struct Env<'a, 'env> { struct Env<'a, 'env> {
arena: &'a Bump, arena: &'a Bump,
@ -155,9 +153,12 @@ fn jit_to_ast_help<'a>(
Content::Structure(FlatType::Record(fields, _)) => { Content::Structure(FlatType::Record(fields, _)) => {
Ok(struct_to_ast(env, ptr, field_layouts, fields)) Ok(struct_to_ast(env, ptr, field_layouts, fields))
} }
Content::Structure(FlatType::EmptyRecord) => { Content::Structure(FlatType::EmptyRecord) => Ok(struct_to_ast(
Ok(struct_to_ast(env, ptr, field_layouts, &MutMap::default())) env,
} ptr,
field_layouts,
&RecordFields::with_capacity(0),
)),
Content::Structure(FlatType::TagUnion(tags, _)) => { Content::Structure(FlatType::TagUnion(tags, _)) => {
debug_assert_eq!(tags.len(), 1); debug_assert_eq!(tags.len(), 1);
@ -172,7 +173,7 @@ fn jit_to_ast_help<'a>(
)) ))
} }
Content::Structure(FlatType::FunctionOrTagUnion(tag_name, _, _)) => Ok( Content::Structure(FlatType::FunctionOrTagUnion(tag_name, _, _)) => Ok(
single_tag_union_to_ast(env, ptr, field_layouts, tag_name.clone(), &[]), single_tag_union_to_ast(env, ptr, field_layouts, *tag_name.clone(), &[]),
), ),
Content::Structure(FlatType::Func(_, _, _)) => { Content::Structure(FlatType::Func(_, _, _)) => {
// a function with a struct as the closure environment // a function with a struct as the closure environment
@ -245,6 +246,11 @@ fn jit_to_ast_help<'a>(
Builtin::Int16 => { Builtin::Int16 => {
*(ptr.add(offset as usize) as *const i16) as i64 *(ptr.add(offset as usize) as *const i16) as i64
} }
Builtin::Int64 => {
// used by non-recursive tag unions at the
// moment, remove if that is no longer the case
*(ptr.add(offset as usize) as *const i64) as i64
}
_ => unreachable!("invalid tag id layout"), _ => unreachable!("invalid tag id layout"),
}; };
@ -319,9 +325,9 @@ fn jit_to_ast_help<'a>(
todo!("print recursive tag unions in the REPL") todo!("print recursive tag unions in the REPL")
} }
Content::Alias(_, _, actual) => { Content::Alias(_, _, actual) => {
let content = env.subs.get_without_compacting(*actual).content; let content = env.subs.get_content_without_compacting(*actual);
jit_to_ast_help(env, lib, main_fn_name, layout, &content) jit_to_ast_help(env, lib, main_fn_name, layout, content)
} }
other => unreachable!("Weird content for Union layout: {:?}", other), other => unreachable!("Weird content for Union layout: {:?}", other),
} }
@ -429,10 +435,10 @@ fn ptr_to_ast<'a>(
single_tag_union_to_ast(env, ptr, field_layouts, tag_name.clone(), payload_vars) single_tag_union_to_ast(env, ptr, field_layouts, tag_name.clone(), payload_vars)
} }
Content::Structure(FlatType::FunctionOrTagUnion(tag_name, _, _)) => { Content::Structure(FlatType::FunctionOrTagUnion(tag_name, _, _)) => {
single_tag_union_to_ast(env, ptr, field_layouts, tag_name.clone(), &[]) single_tag_union_to_ast(env, ptr, field_layouts, *tag_name.clone(), &[])
} }
Content::Structure(FlatType::EmptyRecord) => { Content::Structure(FlatType::EmptyRecord) => {
struct_to_ast(env, ptr, &[], &MutMap::default()) struct_to_ast(env, ptr, &[], &RecordFields::with_capacity(0))
} }
other => { other => {
unreachable!( unreachable!(
@ -463,7 +469,7 @@ fn list_to_ast<'a>(
let elem_var = *vars.first().unwrap(); let elem_var = *vars.first().unwrap();
env.subs.get_without_compacting(elem_var).content env.subs.get_content_without_compacting(elem_var)
} }
other => { other => {
unreachable!( unreachable!(
@ -474,14 +480,14 @@ fn list_to_ast<'a>(
}; };
let arena = env.arena; let arena = env.arena;
let mut output = Vec::with_capacity_in(len, &arena); let mut output = Vec::with_capacity_in(len, arena);
let elem_size = elem_layout.stack_size(env.ptr_bytes) as usize; let elem_size = elem_layout.stack_size(env.ptr_bytes) as usize;
for index in 0..len { for index in 0..len {
let offset_bytes = index * elem_size; let offset_bytes = index * elem_size;
let elem_ptr = unsafe { ptr.add(offset_bytes) }; let elem_ptr = unsafe { ptr.add(offset_bytes) };
let loc_expr = &*arena.alloc(Located { let loc_expr = &*arena.alloc(Located {
value: ptr_to_ast(env, elem_ptr, elem_layout, &elem_content), value: ptr_to_ast(env, elem_ptr, elem_layout, elem_content),
region: Region::zero(), region: Region::zero(),
}); });
@ -528,14 +534,14 @@ where
{ {
let arena = env.arena; let arena = env.arena;
let subs = env.subs; let subs = env.subs;
let mut output = Vec::with_capacity_in(sequence.len(), &arena); let mut output = Vec::with_capacity_in(sequence.len(), arena);
// We'll advance this as we iterate through the fields // We'll advance this as we iterate through the fields
let mut field_ptr = ptr as *const u8; let mut field_ptr = ptr as *const u8;
for (var, layout) in sequence { for (var, layout) in sequence {
let content = subs.get_without_compacting(var).content; let content = subs.get_content_without_compacting(var);
let expr = ptr_to_ast(env, field_ptr, layout, &content); let expr = ptr_to_ast(env, field_ptr, layout, content);
let loc_expr = Located::at_zero(expr); let loc_expr = Located::at_zero(expr);
output.push(&*arena.alloc(loc_expr)); output.push(&*arena.alloc(loc_expr));
@ -551,31 +557,20 @@ fn struct_to_ast<'a>(
env: &Env<'a, '_>, env: &Env<'a, '_>,
ptr: *const u8, ptr: *const u8,
field_layouts: &'a [Layout<'a>], field_layouts: &'a [Layout<'a>],
fields: &MutMap<Lowercase, RecordField<Variable>>, sorted_fields: &RecordFields,
) -> Expr<'a> { ) -> Expr<'a> {
let arena = env.arena; let arena = env.arena;
let subs = env.subs; let subs = env.subs;
let mut output = Vec::with_capacity_in(field_layouts.len(), &arena); let mut output = Vec::with_capacity_in(field_layouts.len(), arena);
// The fields, sorted alphabetically
let mut sorted_fields = {
let mut vec = fields
.iter()
.collect::<std::vec::Vec<(&Lowercase, &RecordField<Variable>)>>();
vec.sort_by(|(label1, _), (label2, _)| label1.cmp(label2));
vec
};
if sorted_fields.len() == 1 { if sorted_fields.len() == 1 {
// this is a 1-field wrapper record around another record or 1-tag tag union // this is a 1-field wrapper record around another record or 1-tag tag union
let (label, field) = sorted_fields.pop().unwrap(); let (label, field) = sorted_fields.into_iter().next().unwrap();
let inner_content = env.subs.get_without_compacting(field.into_inner()).content; let inner_content = env.subs.get_content_without_compacting(field.into_inner());
let loc_expr = &*arena.alloc(Located { let loc_expr = &*arena.alloc(Located {
value: ptr_to_ast(env, ptr, &Layout::Struct(field_layouts), &inner_content), value: ptr_to_ast(env, ptr, &Layout::Struct(field_layouts), inner_content),
region: Region::zero(), region: Region::zero(),
}); });
@ -601,9 +596,9 @@ fn struct_to_ast<'a>(
let mut field_ptr = ptr; let mut field_ptr = ptr;
for ((label, field), field_layout) in sorted_fields.iter().zip(field_layouts.iter()) { for ((label, field), field_layout) in sorted_fields.iter().zip(field_layouts.iter()) {
let content = subs.get_without_compacting(*field.as_inner()).content; let content = subs.get_content_without_compacting(*field.as_inner());
let loc_expr = &*arena.alloc(Located { let loc_expr = &*arena.alloc(Located {
value: ptr_to_ast(env, field_ptr, field_layout, &content), value: ptr_to_ast(env, field_ptr, field_layout, content),
region: Region::zero(), region: Region::zero(),
}); });
@ -654,9 +649,9 @@ fn bool_to_ast<'a>(env: &Env<'a, '_>, value: bool, content: &Content) -> Expr<'a
// and/or records (e.g. { a: { b: { c: True } } }), // and/or records (e.g. { a: { b: { c: True } } }),
// so we need to do this recursively on the field type. // so we need to do this recursively on the field type.
let field_var = *field.as_inner(); let field_var = *field.as_inner();
let field_content = env.subs.get_without_compacting(field_var).content; let field_content = env.subs.get_content_without_compacting(field_var);
let loc_expr = Located { let loc_expr = Located {
value: bool_to_ast(env, value, &field_content), value: bool_to_ast(env, value, field_content),
region: Region::zero(), region: Region::zero(),
}; };
@ -696,10 +691,10 @@ fn bool_to_ast<'a>(env: &Env<'a, '_>, value: bool, content: &Content) -> Expr<'a
debug_assert_eq!(payload_vars.len(), 1); debug_assert_eq!(payload_vars.len(), 1);
let var = *payload_vars.iter().next().unwrap(); let var = *payload_vars.iter().next().unwrap();
let content = env.subs.get_without_compacting(var).content; let content = env.subs.get_content_without_compacting(var);
let loc_payload = &*arena.alloc(Located { let loc_payload = &*arena.alloc(Located {
value: bool_to_ast(env, value, &content), value: bool_to_ast(env, value, content),
region: Region::zero(), region: Region::zero(),
}); });
@ -734,9 +729,9 @@ fn bool_to_ast<'a>(env: &Env<'a, '_>, value: bool, content: &Content) -> Expr<'a
} }
} }
Alias(_, _, var) => { Alias(_, _, var) => {
let content = env.subs.get_without_compacting(*var).content; let content = env.subs.get_content_without_compacting(*var);
bool_to_ast(env, value, &content) bool_to_ast(env, value, content)
} }
other => { other => {
unreachable!("Unexpected FlatType {:?} in bool_to_ast", other); unreachable!("Unexpected FlatType {:?} in bool_to_ast", other);
@ -766,9 +761,9 @@ fn byte_to_ast<'a>(env: &Env<'a, '_>, value: u8, content: &Content) -> Expr<'a>
// and/or records (e.g. { a: { b: { c: True } } }), // and/or records (e.g. { a: { b: { c: True } } }),
// so we need to do this recursively on the field type. // so we need to do this recursively on the field type.
let field_var = *field.as_inner(); let field_var = *field.as_inner();
let field_content = env.subs.get_without_compacting(field_var).content; let field_content = env.subs.get_content_without_compacting(field_var);
let loc_expr = Located { let loc_expr = Located {
value: byte_to_ast(env, value, &field_content), value: byte_to_ast(env, value, field_content),
region: Region::zero(), region: Region::zero(),
}; };
@ -808,10 +803,10 @@ fn byte_to_ast<'a>(env: &Env<'a, '_>, value: u8, content: &Content) -> Expr<'a>
debug_assert_eq!(payload_vars.len(), 1); debug_assert_eq!(payload_vars.len(), 1);
let var = *payload_vars.iter().next().unwrap(); let var = *payload_vars.iter().next().unwrap();
let content = env.subs.get_without_compacting(var).content; let content = env.subs.get_content_without_compacting(var);
let loc_payload = &*arena.alloc(Located { let loc_payload = &*arena.alloc(Located {
value: byte_to_ast(env, value, &content), value: byte_to_ast(env, value, content),
region: Region::zero(), region: Region::zero(),
}); });
@ -845,9 +840,9 @@ fn byte_to_ast<'a>(env: &Env<'a, '_>, value: u8, content: &Content) -> Expr<'a>
} }
} }
Alias(_, _, var) => { Alias(_, _, var) => {
let content = env.subs.get_without_compacting(*var).content; let content = env.subs.get_content_without_compacting(*var);
byte_to_ast(env, value, &content) byte_to_ast(env, value, content)
} }
other => { other => {
unreachable!("Unexpected FlatType {:?} in bool_to_ast", other); unreachable!("Unexpected FlatType {:?} in bool_to_ast", other);
@ -882,9 +877,9 @@ fn num_to_ast<'a>(env: &Env<'a, '_>, num_expr: Expr<'a>, content: &Content) -> E
// and/or records (e.g. { a: { b: { c: 5 } } }), // and/or records (e.g. { a: { b: { c: 5 } } }),
// so we need to do this recursively on the field type. // so we need to do this recursively on the field type.
let field_var = *field.as_inner(); let field_var = *field.as_inner();
let field_content = env.subs.get_without_compacting(field_var).content; let field_content = env.subs.get_content_without_compacting(field_var);
let loc_expr = Located { let loc_expr = Located {
value: num_to_ast(env, num_expr, &field_content), value: num_to_ast(env, num_expr, field_content),
region: Region::zero(), region: Region::zero(),
}; };
@ -932,10 +927,10 @@ fn num_to_ast<'a>(env: &Env<'a, '_>, num_expr: Expr<'a>, content: &Content) -> E
debug_assert_eq!(payload_vars.len(), 1); debug_assert_eq!(payload_vars.len(), 1);
let var = *payload_vars.iter().next().unwrap(); let var = *payload_vars.iter().next().unwrap();
let content = env.subs.get_without_compacting(var).content; let content = env.subs.get_content_without_compacting(var);
let loc_payload = &*arena.alloc(Located { let loc_payload = &*arena.alloc(Located {
value: num_to_ast(env, num_expr, &content), value: num_to_ast(env, num_expr, content),
region: Region::zero(), region: Region::zero(),
}); });
@ -950,9 +945,9 @@ fn num_to_ast<'a>(env: &Env<'a, '_>, num_expr: Expr<'a>, content: &Content) -> E
} }
} }
Alias(_, _, var) => { Alias(_, _, var) => {
let content = env.subs.get_without_compacting(*var).content; let content = env.subs.get_content_without_compacting(*var);
num_to_ast(env, num_expr, &content) num_to_ast(env, num_expr, content)
} }
other => { other => {
panic!("Unexpected FlatType {:?} in num_to_ast", other); panic!("Unexpected FlatType {:?} in num_to_ast", other);

View file

@ -154,8 +154,8 @@ pub fn gen_and_eval<'a>(
// pretty-print the expr type string for later. // pretty-print the expr type string for later.
name_all_type_vars(main_fn_var, &mut subs); name_all_type_vars(main_fn_var, &mut subs);
let content = subs.get(main_fn_var).content; let content = subs.get_content_without_compacting(main_fn_var);
let expr_type_str = content_to_string(content.clone(), &subs, home, &interns); let expr_type_str = content_to_string(content, &subs, home, &interns);
let (_, main_fn_layout) = match procedures.keys().find(|(s, _)| *s == main_fn_symbol) { let (_, main_fn_layout) = match procedures.keys().find(|(s, _)| *s == main_fn_symbol) {
Some(layout) => *layout, Some(layout) => *layout,
@ -219,7 +219,7 @@ pub fn gen_and_eval<'a>(
); );
} }
let lib = module_to_dylib(&env.module, &target, opt_level) let lib = module_to_dylib(env.module, &target, opt_level)
.expect("Error loading compiled dylib for test"); .expect("Error loading compiled dylib for test");
let res_answer = unsafe { let res_answer = unsafe {
eval::jit_to_ast( eval::jit_to_ast(
@ -227,7 +227,7 @@ pub fn gen_and_eval<'a>(
lib, lib,
main_fn_name, main_fn_name,
main_fn_layout, main_fn_layout,
&content, content,
&env.interns, &env.interns,
home, home,
&subs, &subs,

View file

@ -1,4 +1,4 @@
// #[macro_use] #[macro_use]
extern crate pretty_assertions; extern crate pretty_assertions;
extern crate bumpalo; extern crate bumpalo;
@ -10,12 +10,15 @@ extern crate roc_module;
#[cfg(test)] #[cfg(test)]
mod cli_run { mod cli_run {
use cli_utils::helpers::{ use cli_utils::helpers::{
example_file, extract_valgrind_errors, fixture_file, run_cmd, run_roc, run_with_valgrind, example_file, examples_dir, extract_valgrind_errors, fixture_file, run_cmd, run_roc,
ValgrindError, ValgrindErrorXWhat, run_with_valgrind, ValgrindError, ValgrindErrorXWhat,
}; };
use serial_test::serial; use serial_test::serial;
use std::path::Path; use std::path::Path;
#[cfg(not(debug_assertions))]
use roc_collections::all::MutMap;
#[cfg(not(target_os = "macos"))] #[cfg(not(target_os = "macos"))]
const ALLOW_VALGRIND: bool = true; const ALLOW_VALGRIND: bool = true;
@ -25,26 +28,18 @@ mod cli_run {
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
const ALLOW_VALGRIND: bool = false; const ALLOW_VALGRIND: bool = false;
fn check_output( #[derive(Debug, PartialEq, Eq)]
file: &Path, struct Example<'a> {
executable_filename: &str, filename: &'a str,
flags: &[&str], executable_filename: &'a str,
expected_ending: &str, stdin: &'a [&'a str],
expected_ending: &'a str,
use_valgrind: bool, use_valgrind: bool,
) {
check_output_with_stdin(
file,
"",
executable_filename,
flags,
expected_ending,
use_valgrind,
)
} }
fn check_output_with_stdin( fn check_output_with_stdin(
file: &Path, file: &Path,
stdin_str: &str, stdin: &[&str],
executable_filename: &str, executable_filename: &str,
flags: &[&str], flags: &[&str],
expected_ending: &str, expected_ending: &str,
@ -59,7 +54,7 @@ mod cli_run {
let out = if use_valgrind && ALLOW_VALGRIND { let out = if use_valgrind && ALLOW_VALGRIND {
let (valgrind_out, raw_xml) = run_with_valgrind( let (valgrind_out, raw_xml) = run_with_valgrind(
stdin_str, stdin,
&[file.with_file_name(executable_filename).to_str().unwrap()], &[file.with_file_name(executable_filename).to_str().unwrap()],
); );
@ -101,7 +96,7 @@ mod cli_run {
} else { } else {
run_cmd( run_cmd(
file.with_file_name(executable_filename).to_str().unwrap(), file.with_file_name(executable_filename).to_str().unwrap(),
stdin_str, stdin,
&[], &[],
) )
}; };
@ -114,182 +109,344 @@ mod cli_run {
assert!(out.status.success()); assert!(out.status.success());
} }
/// This macro does two things.
///
/// First, it generates and runs a separate test for each of the given
/// Example expressions. Each of these should test a particular .roc file
/// in the examples/ directory.
///
/// Second, it generates an extra test which (non-recursively) traverses the
/// examples/ directory and verifies that each of the .roc files in there
/// has had a corresponding test generated in the previous step. This test
/// will fail if we ever add a new .roc file to examples/ and forget to
/// add a test for it here!
macro_rules! examples {
($($test_name:ident:$name:expr => $example:expr,)+) => {
$(
#[test] #[test]
#[serial(hello_world)] fn $test_name() {
fn run_hello_world() { let dir_name = $name;
check_output( let example = $example;
&example_file("hello-world", "Hello.roc"), let file_name = example_file(dir_name, example.filename);
"hello-world",
&[],
"Hello, World!\n",
true,
);
}
#[test] // Check with and without optimizations
#[serial(hello_world)] check_output_with_stdin(
fn run_hello_world_optimized() { &file_name,
check_output( example.stdin,
&example_file("hello-world", "Hello.roc"), example.executable_filename,
"hello-world",
&[], &[],
"Hello, World!\n", example.expected_ending,
true, example.use_valgrind,
); );
}
#[test] check_output_with_stdin(
#[serial(quicksort)] &file_name,
fn run_quicksort_not_optimized() { example.stdin,
check_output( example.executable_filename,
&example_file("quicksort", "Quicksort.roc"),
"quicksort",
&[],
"[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)]
fn run_quicksort_optimized() {
check_output(
&example_file("quicksort", "Quicksort.roc"),
"quicksort",
&["--optimize"], &["--optimize"],
"[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n", example.expected_ending,
true, example.use_valgrind,
); );
} }
)*
#[test] #[test]
#[serial(quicksort)] #[cfg(not(debug_assertions))]
fn run_quicksort_optimized_valgrind() { fn all_examples_have_tests() {
check_output( let mut all_examples: MutMap<&str, Example<'_>> = MutMap::default();
&example_file("quicksort", "Quicksort.roc"),
"quicksort", $(
all_examples.insert($name, $example);
)*
check_for_tests("../examples", &mut all_examples);
}
}
}
// examples! macro format:
//
// "name-of-subdirectory-inside-examples-dir" => [
// test_name_1: Example {
// ...
// },
// test_name_2: Example {
// ...
// },
// ]
examples! {
hello_world:"hello-world" => Example {
filename: "Hello.roc",
executable_filename: "hello-world",
stdin: &[],
expected_ending:"Hello, World!\n",
use_valgrind: true,
},
hello_zig:"hello-zig" => Example {
filename: "Hello.roc",
executable_filename: "hello-world",
stdin: &[],
expected_ending:"Hello, World!\n",
use_valgrind: true,
},
hello_rust:"hello-rust" => Example {
filename: "Hello.roc",
executable_filename: "hello-world",
stdin: &[],
expected_ending:"Hello, World!\n",
use_valgrind: true,
},
quicksort:"quicksort" => Example {
filename: "Quicksort.roc",
executable_filename: "quicksort",
stdin: &[],
expected_ending: "[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n",
use_valgrind: true,
},
// shared_quicksort:"shared-quicksort" => Example {
// filename: "Quicksort.roc",
// executable_filename: "quicksort",
// stdin: &[],
// expected_ending: "[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n",
// use_valgrind: true,
// },
effect:"effect" => Example {
filename: "Main.roc",
executable_filename: "effect-example",
stdin: &["hi there!"],
expected_ending: "hi there!\n",
use_valgrind: true,
},
// tea:"tea" => Example {
// filename: "Main.roc",
// executable_filename: "tea-example",
// stdin: &[],
// expected_ending: "",
// use_valgrind: true,
// },
// cli:"cli" => Example {
// filename: "Echo.roc",
// executable_filename: "echo",
// stdin: &["Giovanni\n", "Giorgio\n"],
// expected_ending: "Giovanni Giorgio!\n",
// use_valgrind: true,
// },
// custom_malloc:"custom-malloc" => Example {
// filename: "Main.roc",
// executable_filename: "custom-malloc-example",
// stdin: &[],
// expected_ending: "ms!\nThe list was small!\n",
// use_valgrind: true,
// },
// task:"task" => Example {
// filename: "Main.roc",
// executable_filename: "task-example",
// stdin: &[],
// expected_ending: "successfully wrote to file\n",
// use_valgrind: true,
// },
}
macro_rules! benchmarks {
($($test_name:ident => $benchmark:expr,)+) => {
$(
#[test]
#[cfg_attr(not(debug_assertions), serial(benchmark))]
fn $test_name() {
let benchmark = $benchmark;
let file_name = examples_dir("benchmarks").join(benchmark.filename);
// TODO fix QuicksortApp and RBTreeCk and then remove this!
match benchmark.filename {
"QuicksortApp.roc" | "RBTreeCk.roc" => {
eprintln!("WARNING: skipping testing benchmark {} because the test is broken right now!", benchmark.filename);
return;
}
_ => {}
}
// Check with and without optimizations
check_output_with_stdin(
&file_name,
benchmark.stdin,
benchmark.executable_filename,
&[],
benchmark.expected_ending,
benchmark.use_valgrind,
);
check_output_with_stdin(
&file_name,
benchmark.stdin,
benchmark.executable_filename,
&["--optimize"], &["--optimize"],
"[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n", benchmark.expected_ending,
true, benchmark.use_valgrind,
); );
} }
)*
#[test] #[test]
#[serial(nqueens)] #[cfg(not(debug_assertions))]
fn run_nqueens_not_optimized() { fn all_benchmarks_have_tests() {
check_output_with_stdin( let mut all_benchmarks: MutMap<&str, Example<'_>> = MutMap::default();
&example_file("benchmarks", "NQueens.roc"),
"6", $(
"nqueens", let benchmark = $benchmark;
&[],
"4\n", all_benchmarks.insert(benchmark.filename, benchmark);
true, )*
);
check_for_benchmarks("../examples/benchmarks", &mut all_benchmarks);
}
}
} }
#[test] benchmarks! {
#[serial(cfold)] nqueens => Example {
fn run_cfold_not_optimized() { filename: "NQueens.roc",
check_output_with_stdin( executable_filename: "nqueens",
&example_file("benchmarks", "CFold.roc"), stdin: &["6"],
"3", expected_ending: "4\n",
"cfold", use_valgrind: true,
&[], },
"11 & 11\n", cfold => Example {
true, filename: "CFold.roc",
); executable_filename: "cfold",
stdin: &["3"],
expected_ending: "11 & 11\n",
use_valgrind: true,
},
deriv => Example {
filename: "Deriv.roc",
executable_filename: "deriv",
stdin: &["2"],
expected_ending: "1 count: 6\n2 count: 22\n",
use_valgrind: true,
},
rbtree_ck => Example {
filename: "RBTreeCk.roc",
executable_filename: "rbtree-ck",
stdin: &[],
expected_ending: "Node Black 0 {} Empty Empty\n",
use_valgrind: true,
},
rbtree_insert => Example {
filename: "RBTreeInsert.roc",
executable_filename: "rbtree-insert",
stdin: &[],
expected_ending: "Node Black 0 {} Empty Empty\n",
use_valgrind: true,
},
rbtree_del => Example {
filename: "RBTreeDel.roc",
executable_filename: "rbtree-del",
stdin: &["420"],
expected_ending: "30\n",
use_valgrind: true,
},
astar => Example {
filename: "TestAStar.roc",
executable_filename: "test-astar",
stdin: &[],
expected_ending: "True\n",
use_valgrind: false,
},
base64 => Example {
filename: "TestBase64.roc",
executable_filename: "test-base64",
stdin: &[],
expected_ending: "encoded: SGVsbG8gV29ybGQ=\ndecoded: Hello World\n",
use_valgrind: true,
},
closure => Example {
filename: "Closure.roc",
executable_filename: "closure",
stdin: &[],
expected_ending: "",
use_valgrind: true,
},
quicksort_app => Example {
filename: "QuicksortApp.roc",
executable_filename: "quicksortapp",
stdin: &[],
expected_ending: "todo put the correct quicksort answer here",
use_valgrind: true,
},
} }
#[test] #[cfg(not(debug_assertions))]
#[serial(deriv)] fn check_for_tests(examples_dir: &str, all_examples: &mut MutMap<&str, Example<'_>>) {
fn run_deriv_not_optimized() { let entries = std::fs::read_dir(examples_dir).unwrap_or_else(|err| {
check_output_with_stdin( panic!(
&example_file("benchmarks", "Deriv.roc"), "Error trying to read {} as an examples directory: {}",
"2", examples_dir, err
"deriv",
&[],
"1 count: 6\n2 count: 22\n",
true,
); );
});
for entry in entries {
let entry = entry.unwrap();
if entry.file_type().unwrap().is_dir() {
let example_dir_name = entry.file_name().into_string().unwrap();
// We test benchmarks separately
if example_dir_name != "benchmarks" {
all_examples.remove(example_dir_name.as_str()).unwrap_or_else(|| {
panic!("The example directory {}/{} does not have any corresponding tests in cli_run. Please add one, so if it ever stops working, we'll know about it right away!", examples_dir, example_dir_name);
});
}
}
} }
#[test] assert_eq!(all_examples, &mut MutMap::default());
#[serial(deriv)]
fn run_rbtree_insert_not_optimized() {
check_output(
&example_file("benchmarks", "RBTreeInsert.roc"),
"rbtree-insert",
&[],
"Node Black 0 {} Empty Empty\n",
true,
);
} }
#[test] #[cfg(not(debug_assertions))]
#[serial(deriv)] fn check_for_benchmarks(benchmarks_dir: &str, all_benchmarks: &mut MutMap<&str, Example<'_>>) {
fn run_rbtree_delete_not_optimized() { use std::ffi::OsStr;
check_output_with_stdin( use std::fs::File;
&example_file("benchmarks", "RBTreeDel.roc"), use std::io::Read;
"420",
"rbtree-del", let entries = std::fs::read_dir(benchmarks_dir).unwrap_or_else(|err| {
&[], panic!(
"30\n", "Error trying to read {} as a benchmark directory: {}",
true, benchmarks_dir, err
); );
});
for entry in entries {
let entry = entry.unwrap();
let path = entry.path();
if let Some("roc") = path.extension().and_then(OsStr::to_str) {
let benchmark_file_name = entry.file_name().into_string().unwrap();
// Verify that this is an app module by reading the first 3
// bytes of the file.
let buf: &mut [u8] = &mut [0, 0, 0];
let mut file = File::open(path).unwrap();
file.read_exact(buf).unwrap();
// Only app modules in this directory are considered benchmarks.
if "app".as_bytes() == buf {
all_benchmarks.remove(benchmark_file_name.as_str()).unwrap_or_else(|| {
panic!("The benchmark {}/{} does not have any corresponding tests in cli_run. Please add one, so if it ever stops working, we'll know about it right away!", benchmarks_dir, benchmark_file_name);
});
}
}
} }
#[test] assert_eq!(all_benchmarks, &mut MutMap::default());
#[serial(astar)]
fn run_astar_optimized_1() {
check_output(
&example_file("benchmarks", "TestAStar.roc"),
"test-astar",
&[],
"True\n",
false,
);
} }
#[test]
#[serial(base64)]
fn base64() {
check_output(
&example_file("benchmarks", "TestBase64.roc"),
"test-base64",
&[],
"encoded: SGVsbG8gV29ybGQ=\ndecoded: Hello World\n",
true,
);
}
#[test]
#[serial(closure)]
fn closure() {
check_output(
&example_file("benchmarks", "Closure.roc"),
"closure",
&[],
"",
true,
);
}
// #[test]
// #[serial(effect)]
// fn run_effect_unoptimized() {
// check_output(
// &example_file("effect", "Main.roc"),
// &[],
// "I am Dep2.str2\n",
// true,
// );
// }
#[test] #[test]
#[serial(multi_dep_str)] #[serial(multi_dep_str)]
fn run_multi_dep_str_unoptimized() { fn run_multi_dep_str_unoptimized() {
check_output( check_output_with_stdin(
&fixture_file("multi-dep-str", "Main.roc"), &fixture_file("multi-dep-str", "Main.roc"),
&[],
"multi-dep-str", "multi-dep-str",
&[], &[],
"I am Dep2.str2\n", "I am Dep2.str2\n",
@ -300,8 +457,9 @@ mod cli_run {
#[test] #[test]
#[serial(multi_dep_str)] #[serial(multi_dep_str)]
fn run_multi_dep_str_optimized() { fn run_multi_dep_str_optimized() {
check_output( check_output_with_stdin(
&fixture_file("multi-dep-str", "Main.roc"), &fixture_file("multi-dep-str", "Main.roc"),
&[],
"multi-dep-str", "multi-dep-str",
&["--optimize"], &["--optimize"],
"I am Dep2.str2\n", "I am Dep2.str2\n",
@ -312,8 +470,9 @@ mod cli_run {
#[test] #[test]
#[serial(multi_dep_thunk)] #[serial(multi_dep_thunk)]
fn run_multi_dep_thunk_unoptimized() { fn run_multi_dep_thunk_unoptimized() {
check_output( check_output_with_stdin(
&fixture_file("multi-dep-thunk", "Main.roc"), &fixture_file("multi-dep-thunk", "Main.roc"),
&[],
"multi-dep-thunk", "multi-dep-thunk",
&[], &[],
"I am Dep2.value2\n", "I am Dep2.value2\n",
@ -324,25 +483,13 @@ mod cli_run {
#[test] #[test]
#[serial(multi_dep_thunk)] #[serial(multi_dep_thunk)]
fn run_multi_dep_thunk_optimized() { fn run_multi_dep_thunk_optimized() {
check_output( check_output_with_stdin(
&fixture_file("multi-dep-thunk", "Main.roc"), &fixture_file("multi-dep-thunk", "Main.roc"),
&[],
"multi-dep-thunk", "multi-dep-thunk",
&["--optimize"], &["--optimize"],
"I am Dep2.value2\n", "I am Dep2.value2\n",
true, true,
); );
} }
#[test]
#[serial(effect)]
fn run_effect() {
check_output_with_stdin(
&example_file("effect", "Main.roc"),
"hello world how are you",
"effect-example",
&[],
"hello world how are you\n",
true,
);
}
} }

View file

@ -56,7 +56,7 @@ fn find_zig_str_path() -> PathBuf {
} }
#[cfg(not(target_os = "macos"))] #[cfg(not(target_os = "macos"))]
fn build_zig_host( pub fn build_zig_host(
env_path: &str, env_path: &str,
env_home: &str, env_home: &str,
emit_bin: &str, emit_bin: &str,
@ -86,7 +86,7 @@ fn build_zig_host(
} }
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
fn build_zig_host( pub fn build_zig_host(
env_path: &str, env_path: &str,
env_home: &str, env_home: &str,
emit_bin: &str, emit_bin: &str,
@ -140,7 +140,7 @@ fn build_zig_host(
.args(&[ .args(&[
"build-obj", "build-obj",
zig_host_src, zig_host_src,
&emit_bin, emit_bin,
"--pkg-begin", "--pkg-begin",
"str", "str",
zig_str_path, zig_str_path,
@ -333,7 +333,7 @@ fn link_linux(
output_path, output_path,
), ),
LinkType::Dylib => { LinkType::Dylib => {
// TODO: do we acually need the version number on this? // TODO: do we actually need the version number on this?
// Do we even need the "-soname" argument? // 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 // See https://software.intel.com/content/www/us/en/develop/articles/create-a-unix-including-linux-shared-library.html
@ -432,7 +432,7 @@ fn link_macos(
// This path only exists on macOS Big Sur, and it causes ld errors // This path only exists on macOS Big Sur, and it causes ld errors
// on Catalina if it's specified with -L, so we replace it with a // on Catalina if it's specified with -L, so we replace it with a
// redundant -lSystem if the directory isn't there. // redundant -lSystem if the directory isn't there.
let big_sur_path = "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/lib"; let big_sur_path = "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib";
let big_sur_fix = if Path::new(big_sur_path).exists() { let big_sur_fix = if Path::new(big_sur_path).exists() {
format!("-L{}", big_sur_path) format!("-L{}", big_sur_path)
} else { } else {
@ -496,7 +496,7 @@ pub fn module_to_dylib(
app_o_file.set_file_name("app.o"); app_o_file.set_file_name("app.o");
// Emit the .o file using position-indepedent code (PIC) - needed for dylibs // Emit the .o file using position-independent code (PIC) - needed for dylibs
let reloc = RelocMode::PIC; let reloc = RelocMode::PIC;
let model = CodeModel::Default; let model = CodeModel::Default;
let target_machine = let target_machine =

View file

@ -116,9 +116,10 @@ pub fn gen_from_mono_module(
} }
if name.starts_with("roc_builtins.dict") if name.starts_with("roc_builtins.dict")
|| name.starts_with("dict.RocDict")
|| name.starts_with("roc_builtins.list") || name.starts_with("roc_builtins.list")
|| name.starts_with("roc_builtins.dec")
|| name.starts_with("list.RocList") || name.starts_with("list.RocList")
|| name.starts_with("dict.RocDict")
{ {
function.add_attribute(AttributeLoc::Function, enum_attr); function.add_attribute(AttributeLoc::Function, enum_attr);
} }
@ -131,7 +132,7 @@ pub fn gen_from_mono_module(
// Compile and add all the Procs before adding main // Compile and add all the Procs before adding main
let ptr_bytes = target.pointer_width().unwrap().bytes() as u32; let ptr_bytes = target.pointer_width().unwrap().bytes() as u32;
let env = roc_gen_llvm::llvm::build::Env { let env = roc_gen_llvm::llvm::build::Env {
arena: &arena, arena,
builder: &builder, builder: &builder,
dibuilder: &dibuilder, dibuilder: &dibuilder,
compile_unit: &compile_unit, compile_unit: &compile_unit,
@ -242,7 +243,7 @@ pub fn gen_from_mono_module(
target::target_machine(&target, convert_opt_level(opt_level), reloc, model).unwrap(); target::target_machine(&target, convert_opt_level(opt_level), reloc, model).unwrap();
target_machine target_machine
.write_to_file(&env.module, FileType::Object, &app_o_file) .write_to_file(env.module, FileType::Object, app_o_file)
.expect("Writing .o file failed"); .expect("Writing .o file failed");
} }

View file

@ -27,7 +27,6 @@ pub fn build(b: *Builder) void {
llvm_obj.strip = true; llvm_obj.strip = true;
llvm_obj.emit_llvm_ir = true; llvm_obj.emit_llvm_ir = true;
llvm_obj.emit_bin = false; llvm_obj.emit_bin = false;
llvm_obj.bundle_compiler_rt = true;
const ir = b.step("ir", "Build LLVM ir"); const ir = b.step("ir", "Build LLVM ir");
ir.dependOn(&llvm_obj.step); ir.dependOn(&llvm_obj.step);

View file

@ -1,16 +1,19 @@
const std = @import("std"); const std = @import("std");
const str = @import("str.zig"); const str = @import("str.zig");
const utils = @import("utils.zig");
const math = std.math; const math = std.math;
const always_inline = std.builtin.CallOptions.Modifier.always_inline;
const RocStr = str.RocStr; const RocStr = str.RocStr;
const WithOverflow = utils.WithOverflow;
pub const RocDec = struct { pub const RocDec = extern struct {
num: i128, num: i128,
pub const decimal_places: u5 = 18; pub const decimal_places: u5 = 18;
pub const whole_number_places: u5 = 21; pub const whole_number_places: u5 = 21;
const max_digits: u6 = 39; const max_digits: u6 = 39;
const leading_zeros: [17]u8 = "00000000000000000".*; const max_str_length: u6 = max_digits + 2; // + 2 here to account for the sign & decimal dot
pub const min: RocDec = .{ .num = math.minInt(i128) }; pub const min: RocDec = .{ .num = math.minInt(i128) };
pub const max: RocDec = .{ .num = math.maxInt(i128) }; pub const max: RocDec = .{ .num = math.maxInt(i128) };
@ -22,6 +25,23 @@ pub const RocDec = struct {
return .{ .num = num * one_point_zero_i128 }; return .{ .num = num * one_point_zero_i128 };
} }
// TODO: There's got to be a better way to do this other than converting to Str
pub fn fromF64(num: f64) ?RocDec {
var digit_bytes: [19]u8 = undefined; // 19 = max f64 digits + '.' + '-'
var fbs = std.io.fixedBufferStream(digit_bytes[0..]);
std.fmt.formatFloatDecimal(num, .{}, fbs.writer()) catch
return null;
var dec = RocDec.fromStr(RocStr.init(&digit_bytes, fbs.pos));
if (dec) |d| {
return d;
} else {
return null;
}
}
pub fn fromStr(roc_str: RocStr) ?RocDec { pub fn fromStr(roc_str: RocStr) ?RocDec {
if (roc_str.isEmpty()) { if (roc_str.isEmpty()) {
return null; return null;
@ -57,7 +77,7 @@ pub const RocDec = struct {
var after_str_len = (length - 1) - pi; var after_str_len = (length - 1) - pi;
if (after_str_len > decimal_places) { if (after_str_len > decimal_places) {
std.debug.panic("TODO runtime exception for too many decimal places!", .{}); @panic("TODO runtime exception for too many decimal places!");
} }
var diff_decimal_places = decimal_places - after_str_len; var diff_decimal_places = decimal_places - after_str_len;
@ -74,37 +94,38 @@ pub const RocDec = struct {
var result: i128 = undefined; var result: i128 = undefined;
var overflowed = @mulWithOverflow(i128, before, one_point_zero_i128, &result); var overflowed = @mulWithOverflow(i128, before, one_point_zero_i128, &result);
if (overflowed) { if (overflowed) {
std.debug.panic("TODO runtime exception for overflow!", .{}); @panic("TODO runtime exception for overflow!");
} }
before_val_i128 = result; before_val_i128 = result;
} }
var dec: ?RocDec = null; const dec: RocDec = blk: {
if (before_val_i128) |before| { if (before_val_i128) |before| {
if (after_val_i128) |after| { if (after_val_i128) |after| {
var result: i128 = undefined; var result: i128 = undefined;
var overflowed = @addWithOverflow(i128, before, after, &result); var overflowed = @addWithOverflow(i128, before, after, &result);
if (overflowed) { if (overflowed) {
std.debug.panic("TODO runtime exception for overflow!", .{}); @panic("TODO runtime exception for overflow!");
} }
dec = .{ .num = result }; break :blk .{ .num = result };
} else { } else {
dec = .{ .num = before }; break :blk .{ .num = before };
} }
} else if (after_val_i128) |after| { } else if (after_val_i128) |after| {
dec = .{ .num = after }; break :blk .{ .num = after };
} else {
return null;
} }
};
if (dec) |d| {
if (is_negative) { if (is_negative) {
dec = d.negate(); return dec.negate();
} } else {
}
return dec; return dec;
} }
}
fn isDigit(c: u8) bool { inline fn isDigit(c: u8) bool {
return (c -% 48) <= 9; return (c -% 48) <= 9;
} }
@ -114,80 +135,84 @@ pub const RocDec = struct {
return RocStr.init("0.0", 3); return RocStr.init("0.0", 3);
} }
// Check if this Dec is negative, and if so convert to positive const num = self.num;
// We will handle adding the '-' later const is_negative = num < 0;
const is_negative = self.num < 0;
const num = if (is_negative) std.math.negate(self.num) catch {
std.debug.panic("TODO runtime exception failing to negate", .{});
} else self.num;
// Format the backing i128 into an array of digits (u8s) // Format the backing i128 into an array of digit (ascii) characters (u8s)
var digit_bytes: [max_digits + 1]u8 = undefined; var digit_bytes_storage: [max_digits + 1]u8 = undefined;
var num_digits = std.fmt.formatIntBuf(digit_bytes[0..], num, 10, false, .{}); var num_digits = std.fmt.formatIntBuf(digit_bytes_storage[0..], num, 10, false, .{});
var digit_bytes: [*]u8 = digit_bytes_storage[0..];
// space where we assemble all the characters that make up the final string
var str_bytes: [max_str_length]u8 = undefined;
var position: usize = 0;
// if negative, the first character is a negating minus
if (is_negative) {
str_bytes[position] = '-';
position += 1;
// but also, we have one fewer digit than we have characters
num_digits -= 1;
// and we drop the minus to make later arithmetic correct
digit_bytes += 1;
}
// Get the slice for before the decimal point // Get the slice for before the decimal point
var before_digits_slice: []const u8 = undefined;
var before_digits_offset: usize = 0; var before_digits_offset: usize = 0;
var before_digits_adjust: u6 = 0;
if (num_digits > decimal_places) { if (num_digits > decimal_places) {
// we have more digits than fit after the decimal point,
// so we must have digits before the decimal point
before_digits_offset = num_digits - decimal_places; before_digits_offset = num_digits - decimal_places;
before_digits_slice = digit_bytes[0..before_digits_offset];
for (digit_bytes[0..before_digits_offset]) |c| {
str_bytes[position] = c;
position += 1;
}
} else { } else {
before_digits_adjust = @intCast(u6, math.absInt(@intCast(i7, num_digits) - decimal_places) catch { // otherwise there are no actual digits before the decimal point
std.debug.panic("TODO runtime exception for overflow when getting abs", .{}); // but we format it with a '0'
}); str_bytes[position] = '0';
before_digits_slice = "0"; position += 1;
} }
// Figure out how many trailing zeros there are // we've done everything before the decimal point, so now we can put the decimal point in
// I tried to use https://ziglang.org/documentation/0.8.0/#ctz and it mostly worked, str_bytes[position] = '.';
// but was giving seemingly incorrect values for certain numbers. So instead we use position += 1;
// a while loop and figure it out that way.
//
// const trailing_zeros = @ctz(u6, num);
//
var trailing_zeros: u6 = 0;
var index = decimal_places - 1 - before_digits_adjust;
var is_consecutive_zero = true;
while (index != 0) {
var digit = digit_bytes[before_digits_offset + index];
if (digit == '0' and is_consecutive_zero) {
trailing_zeros += 1;
} else {
is_consecutive_zero = false;
}
index -= 1;
}
const trailing_zeros: u6 = count_trailing_zeros_base10(num);
if (trailing_zeros == decimal_places) {
// add just a single zero if all decimal digits are zero
str_bytes[position] = '0';
position += 1;
} else {
// Figure out if we need to prepend any zeros to the after decimal point // Figure out if we need to prepend any zeros to the after decimal point
// For example, for the number 0.000123 we need to prepend 3 zeros after the decimal point // For example, for the number 0.000123 we need to prepend 3 zeros after the decimal point
// This will only be needed for numbers less 0.01, otherwise after_digits_slice will handle this
const after_zeros_num = if (num_digits < decimal_places) decimal_places - num_digits else 0; const after_zeros_num = if (num_digits < decimal_places) decimal_places - num_digits else 0;
const after_zeros_slice: []const u8 = leading_zeros[0..after_zeros_num];
// Get the slice for after the decimal point var i: usize = 0;
var after_digits_slice: []const u8 = undefined; while (i < after_zeros_num) : (i += 1) {
if ((num_digits - before_digits_offset) == trailing_zeros) { str_bytes[position] = '0';
after_digits_slice = "0"; position += 1;
} else {
after_digits_slice = digit_bytes[before_digits_offset .. num_digits - trailing_zeros];
} }
// Get the slice for the sign // otherwise append the decimal digits except the trailing zeros
const sign_slice: []const u8 = if (is_negative) "-" else leading_zeros[0..0]; for (digit_bytes[before_digits_offset .. num_digits - trailing_zeros]) |c| {
str_bytes[position] = c;
position += 1;
}
}
// Hardcode adding a `1` for the '.' character return RocStr.init(&str_bytes, position);
const str_len: usize = sign_slice.len + before_digits_slice.len + 1 + after_zeros_slice.len + after_digits_slice.len; }
// Join the slices together pub fn eq(self: RocDec, other: RocDec) bool {
// We do `max_digits + 2` here because we need to account for a possible sign ('-') and the dot ('.'). return self.num == other.num;
// Ideally, we'd use str_len here }
var str_bytes: [max_digits + 2]u8 = undefined;
_ = std.fmt.bufPrint(str_bytes[0..str_len], "{s}{s}.{s}{s}", .{ sign_slice, before_digits_slice, after_zeros_slice, after_digits_slice }) catch {
std.debug.panic("TODO runtime exception failing to print slices", .{});
};
return RocStr.init(&str_bytes, str_len); pub fn neq(self: RocDec, other: RocDec) bool {
return self.num != other.num;
} }
pub fn negate(self: RocDec) ?RocDec { pub fn negate(self: RocDec) ?RocDec {
@ -195,29 +220,41 @@ pub const RocDec = struct {
return if (negated) |n| .{ .num = n } else null; return if (negated) |n| .{ .num = n } else null;
} }
pub fn add(self: RocDec, other: RocDec) RocDec { pub fn addWithOverflow(self: RocDec, other: RocDec) WithOverflow(RocDec) {
var answer: i128 = undefined; var answer: i128 = undefined;
const overflowed = @addWithOverflow(i128, self.num, other.num, &answer); const overflowed = @addWithOverflow(i128, self.num, other.num, &answer);
if (!overflowed) { return .{ .value = RocDec{ .num = answer }, .has_overflowed = overflowed };
return RocDec{ .num = answer }; }
pub fn add(self: RocDec, other: RocDec) RocDec {
const answer = RocDec.addWithOverflow(self, other);
if (answer.has_overflowed) {
@panic("TODO runtime exception for overflow!");
} else { } else {
std.debug.panic("TODO runtime exception for overflow!", .{}); return answer.value;
} }
} }
pub fn sub(self: RocDec, other: RocDec) RocDec { pub fn subWithOverflow(self: RocDec, other: RocDec) WithOverflow(RocDec) {
var answer: i128 = undefined; var answer: i128 = undefined;
const overflowed = @subWithOverflow(i128, self.num, other.num, &answer); const overflowed = @subWithOverflow(i128, self.num, other.num, &answer);
if (!overflowed) { return .{ .value = RocDec{ .num = answer }, .has_overflowed = overflowed };
return RocDec{ .num = answer }; }
pub fn sub(self: RocDec, other: RocDec) RocDec {
const answer = RocDec.subWithOverflow(self, other);
if (answer.has_overflowed) {
@panic("TODO runtime exception for overflow!");
} else { } else {
std.debug.panic("TODO runtime exception for overflow!", .{}); return answer.value;
} }
} }
pub fn mul(self: RocDec, other: RocDec) RocDec { pub fn mulWithOverflow(self: RocDec, other: RocDec) WithOverflow(RocDec) {
const self_i128 = self.num; const self_i128 = self.num;
const other_i128 = other.num; const other_i128 = other.num;
// const answer = 0; //self_i256 * other_i256; // const answer = 0; //self_i256 * other_i256;
@ -226,30 +263,40 @@ pub const RocDec = struct {
const self_u128 = @intCast(u128, math.absInt(self_i128) catch { const self_u128 = @intCast(u128, math.absInt(self_i128) catch {
if (other_i128 == 0) { if (other_i128 == 0) {
return .{ .num = 0 }; return .{ .value = RocDec{ .num = 0 }, .has_overflowed = false };
} else if (other_i128 == RocDec.one_point_zero.num) { } else if (other_i128 == RocDec.one_point_zero.num) {
return self; return .{ .value = self, .has_overflowed = false };
} else { } else {
std.debug.panic("TODO runtime exception for overflow!", .{}); return .{ .value = undefined, .has_overflowed = true };
} }
}); });
const other_u128 = @intCast(u128, math.absInt(other_i128) catch { const other_u128 = @intCast(u128, math.absInt(other_i128) catch {
if (self_i128 == 0) { if (self_i128 == 0) {
return .{ .num = 0 }; return .{ .value = RocDec{ .num = 0 }, .has_overflowed = false };
} else if (self_i128 == RocDec.one_point_zero.num) { } else if (self_i128 == RocDec.one_point_zero.num) {
return other; return .{ .value = other, .has_overflowed = false };
} else { } else {
std.debug.panic("TODO runtime exception for overflow!", .{}); return .{ .value = undefined, .has_overflowed = true };
} }
}); });
const unsigned_answer: i128 = mul_and_decimalize(self_u128, other_u128); const unsigned_answer: i128 = mul_and_decimalize(self_u128, other_u128);
if (is_answer_negative) { if (is_answer_negative) {
return .{ .num = -unsigned_answer }; return .{ .value = RocDec{ .num = -unsigned_answer }, .has_overflowed = false };
} else { } else {
return .{ .num = unsigned_answer }; return .{ .value = RocDec{ .num = unsigned_answer }, .has_overflowed = false };
}
}
pub fn mul(self: RocDec, other: RocDec) RocDec {
const answer = RocDec.mulWithOverflow(self, other);
if (answer.has_overflowed) {
@panic("TODO runtime exception for overflow!");
} else {
return answer.value;
} }
} }
@ -264,7 +311,9 @@ pub const RocDec = struct {
// (n / 0) is an error // (n / 0) is an error
if (denominator_i128 == 0) { if (denominator_i128 == 0) {
std.debug.panic("TODO runtime exception for divide by 0!", .{}); // The compiler frontend does the `denominator == 0` check for us,
// therefore this case is unreachable from roc user code
unreachable;
} }
// If they're both negative, or if neither is negative, the final answer // If they're both negative, or if neither is negative, the final answer
@ -292,7 +341,7 @@ pub const RocDec = struct {
if (denominator_i128 == one_point_zero_i128) { if (denominator_i128 == one_point_zero_i128) {
return self; return self;
} else { } else {
std.debug.panic("TODO runtime exception for overflow when dividing!", .{}); @panic("TODO runtime exception for overflow when dividing!");
} }
}; };
const numerator_u128 = @intCast(u128, numerator_abs_i128); const numerator_u128 = @intCast(u128, numerator_abs_i128);
@ -305,7 +354,7 @@ pub const RocDec = struct {
if (numerator_i128 == one_point_zero_i128) { if (numerator_i128 == one_point_zero_i128) {
return other; return other;
} else { } else {
std.debug.panic("TODO runtime exception for overflow when dividing!", .{}); @panic("TODO runtime exception for overflow when dividing!");
} }
}; };
const denominator_u128 = @intCast(u128, denominator_abs_i128); const denominator_u128 = @intCast(u128, denominator_abs_i128);
@ -317,13 +366,35 @@ pub const RocDec = struct {
if (answer.hi == 0 and answer.lo <= math.maxInt(i128)) { if (answer.hi == 0 and answer.lo <= math.maxInt(i128)) {
unsigned_answer = @intCast(i128, answer.lo); unsigned_answer = @intCast(i128, answer.lo);
} else { } else {
std.debug.panic("TODO runtime exception for overflow when dividing!", .{}); @panic("TODO runtime exception for overflow when dividing!");
} }
return RocDec{ .num = if (is_answer_negative) -unsigned_answer else unsigned_answer }; return RocDec{ .num = if (is_answer_negative) -unsigned_answer else unsigned_answer };
} }
}; };
// A number has `k` trailling zeros if `10^k` divides into it cleanly
inline fn count_trailing_zeros_base10(input: i128) u6 {
if (input == 0) {
// this should not happen in practice
return 0;
}
var count: u6 = 0;
var k: i128 = 1;
while (true) {
if (@mod(input, std.math.pow(i128, 10, k)) == 0) {
count += 1;
k += 1;
} else {
break;
}
}
return count;
}
const U256 = struct { const U256 = struct {
hi: u128, hi: u128,
lo: u128, lo: u128,
@ -437,7 +508,7 @@ fn mul_and_decimalize(a: u128, b: u128) i128 {
overflowed = overflowed or @addWithOverflow(u128, d, c_carry4, &d); overflowed = overflowed or @addWithOverflow(u128, d, c_carry4, &d);
if (overflowed) { if (overflowed) {
std.debug.panic("TODO runtime exception for overflow!", .{}); @panic("TODO runtime exception for overflow!");
} }
// Final 512bit value is d, c, b, a // Final 512bit value is d, c, b, a
@ -652,6 +723,11 @@ test "fromU64" {
try expectEqual(RocDec{ .num = 25000000000000000000 }, dec); try expectEqual(RocDec{ .num = 25000000000000000000 }, dec);
} }
test "fromF64" {
var dec = RocDec.fromF64(25.5);
try expectEqual(RocDec{ .num = 25500000000000000000 }, dec.?);
}
test "fromStr: empty" { test "fromStr: empty" {
var roc_str = RocStr.init("", 0); var roc_str = RocStr.init("", 0);
var dec = RocDec.fromStr(roc_str); var dec = RocDec.fromStr(roc_str);
@ -867,6 +943,26 @@ test "toStr: 12345678912345678912.111111111111111111 (max number of digits)" {
try expectEqualSlices(u8, res_slice, res_roc_str.?.asSlice()); try expectEqualSlices(u8, res_slice, res_roc_str.?.asSlice());
} }
test "toStr: std.math.maxInt" {
var dec: RocDec = .{ .num = std.math.maxInt(i128) };
var res_roc_str = dec.toStr();
errdefer res_roc_str.?.deinit();
defer res_roc_str.?.deinit();
const res_slice: []const u8 = "170141183460469231731.687303715884105727"[0..];
try expectEqualSlices(u8, res_slice, res_roc_str.?.asSlice());
}
test "toStr: std.math.minInt" {
var dec: RocDec = .{ .num = std.math.minInt(i128) };
var res_roc_str = dec.toStr();
errdefer res_roc_str.?.deinit();
defer res_roc_str.?.deinit();
const res_slice: []const u8 = "-170141183460469231731.687303715884105728"[0..];
try expectEqualSlices(u8, res_slice, res_roc_str.?.asSlice());
}
test "toStr: 0" { test "toStr: 0" {
var dec: RocDec = .{ .num = 0 }; var dec: RocDec = .{ .num = 0 };
var res_roc_str = dec.toStr(); var res_roc_str = dec.toStr();
@ -953,3 +1049,37 @@ test "div: 10 / 3" {
try expectEqual(res, numer.div(denom)); try expectEqual(res, numer.div(denom));
} }
// exports
pub fn fromF64C(arg: f64) callconv(.C) i128 {
return if (@call(.{ .modifier = always_inline }, RocDec.fromF64, .{arg})) |dec| dec.num else @panic("TODO runtime exception failing convert f64 to RocDec");
}
pub fn eqC(arg1: RocDec, arg2: RocDec) callconv(.C) bool {
return @call(.{ .modifier = always_inline }, RocDec.eq, .{ arg1, arg2 });
}
pub fn neqC(arg1: RocDec, arg2: RocDec) callconv(.C) bool {
return @call(.{ .modifier = always_inline }, RocDec.neq, .{ arg1, arg2 });
}
pub fn negateC(arg: RocDec) callconv(.C) i128 {
return if (@call(.{ .modifier = always_inline }, RocDec.negate, .{arg})) |dec| dec.num else @panic("TODO overflow for negating RocDec");
}
pub fn addC(arg1: RocDec, arg2: RocDec) callconv(.C) WithOverflow(RocDec) {
return @call(.{ .modifier = always_inline }, RocDec.addWithOverflow, .{ arg1, arg2 });
}
pub fn subC(arg1: RocDec, arg2: RocDec) callconv(.C) WithOverflow(RocDec) {
return @call(.{ .modifier = always_inline }, RocDec.subWithOverflow, .{ arg1, arg2 });
}
pub fn mulC(arg1: RocDec, arg2: RocDec) callconv(.C) WithOverflow(RocDec) {
return @call(.{ .modifier = always_inline }, RocDec.mulWithOverflow, .{ arg1, arg2 });
}
pub fn divC(arg1: RocDec, arg2: RocDec) callconv(.C) i128 {
return @call(.{ .modifier = always_inline }, RocDec.div, .{ arg1, arg2 }).num;
}

View file

@ -3,8 +3,6 @@ const utils = @import("utils.zig");
const RocResult = utils.RocResult; const RocResult = utils.RocResult;
const mem = std.mem; const mem = std.mem;
const TAG_WIDTH = 8;
const EqFn = fn (?[*]u8, ?[*]u8) callconv(.C) bool; const EqFn = fn (?[*]u8, ?[*]u8) callconv(.C) bool;
const CompareFn = fn (?[*]u8, ?[*]u8, ?[*]u8) callconv(.C) u8; const CompareFn = fn (?[*]u8, ?[*]u8, ?[*]u8) callconv(.C) u8;
const Opaque = ?[*]u8; const Opaque = ?[*]u8;

View file

@ -5,6 +5,17 @@ const testing = std.testing;
// Dec Module // Dec Module
const dec = @import("dec.zig"); const dec = @import("dec.zig");
comptime {
exportDecFn(dec.fromF64C, "from_f64");
exportDecFn(dec.eqC, "eq");
exportDecFn(dec.neqC, "neq");
exportDecFn(dec.negateC, "negate");
exportDecFn(dec.addC, "add_with_overflow");
exportDecFn(dec.subC, "sub_with_overflow");
exportDecFn(dec.mulC, "mul_with_overflow");
exportDecFn(dec.divC, "div");
}
// List Module // List Module
const list = @import("list.zig"); const list = @import("list.zig");
@ -102,13 +113,71 @@ fn exportStrFn(comptime func: anytype, comptime func_name: []const u8) void {
fn exportDictFn(comptime func: anytype, comptime func_name: []const u8) void { fn exportDictFn(comptime func: anytype, comptime func_name: []const u8) void {
exportBuiltinFn(func, "dict." ++ func_name); exportBuiltinFn(func, "dict." ++ func_name);
} }
fn exportListFn(comptime func: anytype, comptime func_name: []const u8) void { fn exportListFn(comptime func: anytype, comptime func_name: []const u8) void {
exportBuiltinFn(func, "list." ++ func_name); exportBuiltinFn(func, "list." ++ func_name);
} }
fn exportDecFn(comptime func: anytype, comptime func_name: []const u8) void {
exportBuiltinFn(func, "dec." ++ func_name);
}
// Custom panic function, as builtin Zig version errors during LLVM verification
pub fn panic(message: []const u8, stacktrace: ?*std.builtin.StackTrace) noreturn {
std.debug.print("{s}: {?}", .{ message, stacktrace });
unreachable;
}
// Run all tests in imported modules // Run all tests in imported modules
// https://github.com/ziglang/zig/blob/master/lib/std/std.zig#L94 // https://github.com/ziglang/zig/blob/master/lib/std/std.zig#L94
test "" { test "" {
testing.refAllDecls(@This()); testing.refAllDecls(@This());
} }
// For unclear reasons, sometimes this function is not linked in on some machines.
// Therefore we provide it as LLVM bitcode and mark it as externally linked during our LLVM codegen
//
// Taken from
// https://github.com/ziglang/zig/blob/85755c51d529e7d9b406c6bdf69ce0a0f33f3353/lib/std/special/compiler_rt/muloti4.zig
//
// Thank you Zig Contributors!
export fn __muloti4(a: i128, b: i128, overflow: *c_int) callconv(.C) i128 {
// @setRuntimeSafety(builtin.is_test);
const min = @bitCast(i128, @as(u128, 1 << (128 - 1)));
const max = ~min;
overflow.* = 0;
const r = a *% b;
if (a == min) {
if (b != 0 and b != 1) {
overflow.* = 1;
}
return r;
}
if (b == min) {
if (a != 0 and a != 1) {
overflow.* = 1;
}
return r;
}
const sa = a >> (128 - 1);
const abs_a = (a ^ sa) -% sa;
const sb = b >> (128 - 1);
const abs_b = (b ^ sb) -% sb;
if (abs_a < 2 or abs_b < 2) {
return r;
}
if (sa == sb) {
if (abs_a > @divTrunc(max, abs_b)) {
overflow.* = 1;
}
} else {
if (abs_a > @divTrunc(min, -abs_b)) {
overflow.* = 1;
}
}
return r;
}

View file

@ -1,6 +1,10 @@
const std = @import("std"); const std = @import("std");
const always_inline = std.builtin.CallOptions.Modifier.always_inline; const always_inline = std.builtin.CallOptions.Modifier.always_inline;
pub fn WithOverflow(comptime T: type) type {
return extern struct { value: T, has_overflowed: bool };
}
// If allocation fails, this must cxa_throw - it must not return a null pointer! // If allocation fails, this must cxa_throw - it must not return a null pointer!
extern fn roc_alloc(size: usize, alignment: u32) callconv(.C) ?*c_void; extern fn roc_alloc(size: usize, alignment: u32) callconv(.C) ?*c_void;

View file

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

View file

@ -66,3 +66,12 @@ pub const LIST_SORT_WITH: &str = "roc_builtins.list.sort_with";
pub const LIST_CONCAT: &str = "roc_builtins.list.concat"; pub const LIST_CONCAT: &str = "roc_builtins.list.concat";
pub const LIST_SET: &str = "roc_builtins.list.set"; pub const LIST_SET: &str = "roc_builtins.list.set";
pub const LIST_SET_IN_PLACE: &str = "roc_builtins.list.set_in_place"; pub const LIST_SET_IN_PLACE: &str = "roc_builtins.list.set_in_place";
pub const DEC_FROM_F64: &str = "roc_builtins.dec.from_f64";
pub const DEC_EQ: &str = "roc_builtins.dec.eq";
pub const DEC_NEQ: &str = "roc_builtins.dec.neq";
pub const DEC_NEGATE: &str = "roc_builtins.dec.negate";
pub const DEC_ADD_WITH_OVERFLOW: &str = "roc_builtins.dec.add_with_overflow";
pub const DEC_SUB_WITH_OVERFLOW: &str = "roc_builtins.dec.sub_with_overflow";
pub const DEC_MUL_WITH_OVERFLOW: &str = "roc_builtins.dec.mul_with_overflow";
pub const DEC_DIV: &str = "roc_builtins.dec.div";

View file

@ -455,8 +455,7 @@ fn num_add_wrap(symbol: Symbol, var_store: &mut VarStore) -> Def {
num_binop(symbol, var_store, LowLevel::NumAddWrap) num_binop(symbol, var_store, LowLevel::NumAddWrap)
} }
/// Num.addChecked : Num a, Num a -> Result (Num a) [ Overflow ]* fn num_overflow_checked(symbol: Symbol, var_store: &mut VarStore, lowlevel: LowLevel) -> Def {
fn num_add_checked(symbol: Symbol, var_store: &mut VarStore) -> Def {
let bool_var = var_store.fresh(); let bool_var = var_store.fresh();
let num_var_1 = var_store.fresh(); let num_var_1 = var_store.fresh();
let num_var_2 = var_store.fresh(); let num_var_2 = var_store.fresh();
@ -464,7 +463,7 @@ fn num_add_checked(symbol: Symbol, var_store: &mut VarStore) -> Def {
let ret_var = var_store.fresh(); let ret_var = var_store.fresh();
let record_var = var_store.fresh(); let record_var = var_store.fresh();
// let arg_3 = RunLowLevel NumAddChecked arg_1 arg_2 // let arg_3 = RunLowLevel NumXXXChecked arg_1 arg_2
// //
// if arg_3.b then // if arg_3.b then
// # overflow // # overflow
@ -517,11 +516,11 @@ fn num_add_checked(symbol: Symbol, var_store: &mut VarStore) -> Def {
), ),
}; };
// arg_3 = RunLowLevel NumAddChecked arg_1 arg_2 // arg_3 = RunLowLevel NumXXXChecked arg_1 arg_2
let def = crate::def::Def { let def = crate::def::Def {
loc_pattern: no_region(Pattern::Identifier(Symbol::ARG_3)), loc_pattern: no_region(Pattern::Identifier(Symbol::ARG_3)),
loc_expr: no_region(RunLowLevel { loc_expr: no_region(RunLowLevel {
op: LowLevel::NumAddChecked, op: lowlevel,
args: vec![ args: vec![
(num_var_1, Var(Symbol::ARG_1)), (num_var_1, Var(Symbol::ARG_1)),
(num_var_2, Var(Symbol::ARG_2)), (num_var_2, Var(Symbol::ARG_2)),
@ -544,6 +543,11 @@ fn num_add_checked(symbol: Symbol, var_store: &mut VarStore) -> Def {
) )
} }
/// Num.addChecked : Num a, Num a -> Result (Num a) [ Overflow ]*
fn num_add_checked(symbol: Symbol, var_store: &mut VarStore) -> Def {
num_overflow_checked(symbol, var_store, LowLevel::NumAddChecked)
}
/// Num.sub : Num a, Num a -> Num a /// Num.sub : Num a, Num a -> Num a
fn num_sub(symbol: Symbol, var_store: &mut VarStore) -> Def { fn num_sub(symbol: Symbol, var_store: &mut VarStore) -> Def {
num_binop(symbol, var_store, LowLevel::NumSub) num_binop(symbol, var_store, LowLevel::NumSub)
@ -556,91 +560,7 @@ fn num_sub_wrap(symbol: Symbol, var_store: &mut VarStore) -> Def {
/// Num.subChecked : Num a, Num a -> Result (Num a) [ Overflow ]* /// Num.subChecked : Num a, Num a -> Result (Num a) [ Overflow ]*
fn num_sub_checked(symbol: Symbol, var_store: &mut VarStore) -> Def { fn num_sub_checked(symbol: Symbol, var_store: &mut VarStore) -> Def {
let bool_var = var_store.fresh(); num_overflow_checked(symbol, var_store, LowLevel::NumSubChecked)
let num_var_1 = var_store.fresh();
let num_var_2 = var_store.fresh();
let num_var_3 = var_store.fresh();
let ret_var = var_store.fresh();
let record_var = var_store.fresh();
// let arg_3 = RunLowLevel NumSubChecked arg_1 arg_2
//
// if arg_3.b then
// # overflow
// Err Overflow
// else
// # all is well
// Ok arg_3.a
let cont = If {
branch_var: ret_var,
cond_var: bool_var,
branches: vec![(
// if-condition
no_region(
// arg_3.b
Access {
record_var,
ext_var: var_store.fresh(),
field: "b".into(),
field_var: var_store.fresh(),
loc_expr: Box::new(no_region(Var(Symbol::ARG_3))),
},
),
// overflow!
no_region(tag(
"Err",
vec![tag("Overflow", Vec::new(), var_store)],
var_store,
)),
)],
final_else: Box::new(
// all is well
no_region(
// Ok arg_3.a
tag(
"Ok",
vec![
// arg_3.a
Access {
record_var,
ext_var: var_store.fresh(),
field: "a".into(),
field_var: num_var_3,
loc_expr: Box::new(no_region(Var(Symbol::ARG_3))),
},
],
var_store,
),
),
),
};
// arg_3 = RunLowLevel NumSubChecked arg_1 arg_2
let def = crate::def::Def {
loc_pattern: no_region(Pattern::Identifier(Symbol::ARG_3)),
loc_expr: no_region(RunLowLevel {
op: LowLevel::NumSubChecked,
args: vec![
(num_var_1, Var(Symbol::ARG_1)),
(num_var_2, Var(Symbol::ARG_2)),
],
ret_var: record_var,
}),
expr_var: record_var,
pattern_vars: SendMap::default(),
annotation: None,
};
let body = LetNonRec(Box::new(def), Box::new(no_region(cont)), ret_var);
defn(
symbol,
vec![(num_var_1, Symbol::ARG_1), (num_var_2, Symbol::ARG_2)],
var_store,
body,
ret_var,
)
} }
/// Num.mul : Num a, Num a -> Num a /// Num.mul : Num a, Num a -> Num a
@ -655,91 +575,7 @@ fn num_mul_wrap(symbol: Symbol, var_store: &mut VarStore) -> Def {
/// Num.mulChecked : Num a, Num a -> Result (Num a) [ Overflow ]* /// Num.mulChecked : Num a, Num a -> Result (Num a) [ Overflow ]*
fn num_mul_checked(symbol: Symbol, var_store: &mut VarStore) -> Def { fn num_mul_checked(symbol: Symbol, var_store: &mut VarStore) -> Def {
let bool_var = var_store.fresh(); num_overflow_checked(symbol, var_store, LowLevel::NumMulChecked)
let num_var_1 = var_store.fresh();
let num_var_2 = var_store.fresh();
let num_var_3 = var_store.fresh();
let ret_var = var_store.fresh();
let record_var = var_store.fresh();
// let arg_3 = RunLowLevel NumMulChecked arg_1 arg_2
//
// if arg_3.b then
// # overflow
// Err Overflow
// else
// # all is well
// Ok arg_3.a
let cont = If {
branch_var: ret_var,
cond_var: bool_var,
branches: vec![(
// if-condition
no_region(
// arg_3.b
Access {
record_var,
ext_var: var_store.fresh(),
field: "b".into(),
field_var: var_store.fresh(),
loc_expr: Box::new(no_region(Var(Symbol::ARG_3))),
},
),
// overflow!
no_region(tag(
"Err",
vec![tag("Overflow", Vec::new(), var_store)],
var_store,
)),
)],
final_else: Box::new(
// all is well
no_region(
// Ok arg_3.a
tag(
"Ok",
vec![
// arg_3.a
Access {
record_var,
ext_var: var_store.fresh(),
field: "a".into(),
field_var: num_var_3,
loc_expr: Box::new(no_region(Var(Symbol::ARG_3))),
},
],
var_store,
),
),
),
};
// arg_3 = RunLowLevel NumMulChecked arg_1 arg_2
let def = crate::def::Def {
loc_pattern: no_region(Pattern::Identifier(Symbol::ARG_3)),
loc_expr: no_region(RunLowLevel {
op: LowLevel::NumMulChecked,
args: vec![
(num_var_1, Var(Symbol::ARG_1)),
(num_var_2, Var(Symbol::ARG_2)),
],
ret_var: record_var,
}),
expr_var: record_var,
pattern_vars: SendMap::default(),
annotation: None,
};
let body = LetNonRec(Box::new(def), Box::new(no_region(cont)), ret_var);
defn(
symbol,
vec![(num_var_1, Symbol::ARG_1), (num_var_2, Symbol::ARG_2)],
var_store,
body,
ret_var,
)
} }
/// Num.isGt : Num a, Num a -> Bool /// Num.isGt : Num a, Num a -> Bool

View file

@ -64,7 +64,7 @@ impl Constraint {
fn subtract(declared: &Declared, detail: &VariableDetail, accum: &mut VariableDetail) { fn subtract(declared: &Declared, detail: &VariableDetail, accum: &mut VariableDetail) {
for var in &detail.type_variables { for var in &detail.type_variables {
if !(declared.rigid_vars.contains(&var) || declared.flex_vars.contains(&var)) { if !(declared.rigid_vars.contains(var) || declared.flex_vars.contains(var)) {
accum.type_variables.insert(*var); accum.type_variables.insert(*var);
} }
} }
@ -82,11 +82,11 @@ fn subtract(declared: &Declared, detail: &VariableDetail, accum: &mut VariableDe
// recursion vars should be always rigid // recursion vars should be always rigid
for var in &detail.recursion_variables { for var in &detail.recursion_variables {
if declared.flex_vars.contains(&var) { if declared.flex_vars.contains(var) {
panic!("recursion variable {:?} is declared as flex", var); panic!("recursion variable {:?} is declared as flex", var);
} }
if !declared.rigid_vars.contains(&var) { if !declared.rigid_vars.contains(var) {
accum.recursion_variables.insert(*var); accum.recursion_variables.insert(*var);
} }
} }

View file

@ -380,7 +380,7 @@ pub fn sort_can_defs(
// //
// In the above example, `f` cannot reference `a`, and in the closure // In the above example, `f` cannot reference `a`, and in the closure
// a call to `f` cannot cycle back to `a`. // a call to `f` cannot cycle back to `a`.
let mut loc_succ = local_successors(&references, &env.closures); let mut loc_succ = local_successors(references, &env.closures);
// if the current symbol is a closure, peek into its body // if the current symbol is a closure, peek into its body
if let Some(References { lookups, .. }) = env.closures.get(symbol) { if let Some(References { lookups, .. }) = env.closures.get(symbol) {
@ -430,7 +430,7 @@ pub fn sort_can_defs(
// //
// In the above example, `f` cannot reference `a`, and in the closure // In the above example, `f` cannot reference `a`, and in the closure
// a call to `f` cannot cycle back to `a`. // a call to `f` cannot cycle back to `a`.
let mut loc_succ = local_successors(&references, &env.closures); let mut loc_succ = local_successors(references, &env.closures);
// if the current symbol is a closure, peek into its body // if the current symbol is a closure, peek into its body
if let Some(References { lookups, .. }) = env.closures.get(symbol) { if let Some(References { lookups, .. }) = env.closures.get(symbol) {
@ -454,7 +454,7 @@ pub fn sort_can_defs(
let direct_successors = |symbol: &Symbol| -> ImSet<Symbol> { let direct_successors = |symbol: &Symbol| -> ImSet<Symbol> {
match refs_by_symbol.get(symbol) { match refs_by_symbol.get(symbol) {
Some((_, references)) => { Some((_, references)) => {
let mut loc_succ = local_successors(&references, &env.closures); let mut loc_succ = local_successors(references, &env.closures);
// NOTE: if the symbol is a closure we DONT look into its body // NOTE: if the symbol is a closure we DONT look into its body
@ -540,7 +540,7 @@ pub fn sort_can_defs(
), ),
Some((region, _)) => { Some((region, _)) => {
let expr_region = let expr_region =
can_defs_by_symbol.get(&symbol).unwrap().loc_expr.region; can_defs_by_symbol.get(symbol).unwrap().loc_expr.region;
let entry = CycleEntry { let entry = CycleEntry {
symbol: *symbol, symbol: *symbol,
@ -662,11 +662,11 @@ fn group_to_declaration(
// for a definition, so every definition is only inserted (thus typechecked and emitted) once // for a definition, so every definition is only inserted (thus typechecked and emitted) once
let mut seen_pattern_regions: ImSet<Region> = ImSet::default(); let mut seen_pattern_regions: ImSet<Region> = ImSet::default();
for cycle in strongly_connected_components(&group, filtered_successors) { for cycle in strongly_connected_components(group, filtered_successors) {
if cycle.len() == 1 { if cycle.len() == 1 {
let symbol = &cycle[0]; let symbol = &cycle[0];
if let Some(can_def) = can_defs_by_symbol.get(&symbol) { if let Some(can_def) = can_defs_by_symbol.get(symbol) {
let mut new_def = can_def.clone(); let mut new_def = can_def.clone();
// Determine recursivity of closures that are not tail-recursive // Determine recursivity of closures that are not tail-recursive
@ -678,7 +678,7 @@ fn group_to_declaration(
*recursive = closure_recursivity(*symbol, closures); *recursive = closure_recursivity(*symbol, closures);
} }
let is_recursive = successors(&symbol).contains(&symbol); let is_recursive = successors(symbol).contains(symbol);
if !seen_pattern_regions.contains(&new_def.loc_pattern.region) { if !seen_pattern_regions.contains(&new_def.loc_pattern.region) {
if is_recursive { if is_recursive {
@ -854,7 +854,7 @@ fn canonicalize_pending_def<'a>(
}; };
for (_, (symbol, _)) in scope.idents() { for (_, (symbol, _)) in scope.idents() {
if !vars_by_symbol.contains_key(&symbol) { if !vars_by_symbol.contains_key(symbol) {
continue; continue;
} }
@ -999,7 +999,7 @@ fn canonicalize_pending_def<'a>(
// //
// Only defs of the form (foo = ...) can be closure declarations or self tail calls. // Only defs of the form (foo = ...) can be closure declarations or self tail calls.
if let ( if let (
&ast::Pattern::Identifier(ref _name), &ast::Pattern::Identifier(_name),
&Pattern::Identifier(ref defined_symbol), &Pattern::Identifier(ref defined_symbol),
&Closure { &Closure {
function_type, function_type,
@ -1021,7 +1021,7 @@ fn canonicalize_pending_def<'a>(
// Since everywhere in the code it'll be referred to by its defined name, // Since everywhere in the code it'll be referred to by its defined name,
// remove its generated name from the closure map. (We'll re-insert it later.) // remove its generated name from the closure map. (We'll re-insert it later.)
let references = env.closures.remove(&symbol).unwrap_or_else(|| { let references = env.closures.remove(symbol).unwrap_or_else(|| {
panic!( panic!(
"Tried to remove symbol {:?} from procedures, but it was not found: {:?}", "Tried to remove symbol {:?} from procedures, but it was not found: {:?}",
symbol, env.closures symbol, env.closures
@ -1065,7 +1065,7 @@ fn canonicalize_pending_def<'a>(
// Store the referenced locals in the refs_by_symbol map, so we can later figure out // Store the referenced locals in the refs_by_symbol map, so we can later figure out
// which defined names reference each other. // which defined names reference each other.
for (_, (symbol, region)) in scope.idents() { for (_, (symbol, region)) in scope.idents() {
if !vars_by_symbol.contains_key(&symbol) { if !vars_by_symbol.contains_key(symbol) {
continue; continue;
} }
@ -1110,10 +1110,8 @@ fn canonicalize_pending_def<'a>(
// identifier (e.g. `f = \x -> ...`), then this symbol can be tail-called. // identifier (e.g. `f = \x -> ...`), then this symbol can be tail-called.
let outer_identifier = env.tailcallable_symbol; let outer_identifier = env.tailcallable_symbol;
if let ( if let (&ast::Pattern::Identifier(_name), &Pattern::Identifier(ref defined_symbol)) =
&ast::Pattern::Identifier(ref _name), (&loc_pattern.value, &loc_can_pattern.value)
&Pattern::Identifier(ref defined_symbol),
) = (&loc_pattern.value, &loc_can_pattern.value)
{ {
env.tailcallable_symbol = Some(*defined_symbol); env.tailcallable_symbol = Some(*defined_symbol);
@ -1144,7 +1142,7 @@ fn canonicalize_pending_def<'a>(
// //
// Only defs of the form (foo = ...) can be closure declarations or self tail calls. // Only defs of the form (foo = ...) can be closure declarations or self tail calls.
if let ( if let (
&ast::Pattern::Identifier(ref _name), &ast::Pattern::Identifier(_name),
&Pattern::Identifier(ref defined_symbol), &Pattern::Identifier(ref defined_symbol),
&Closure { &Closure {
function_type, function_type,
@ -1166,7 +1164,7 @@ fn canonicalize_pending_def<'a>(
// Since everywhere in the code it'll be referred to by its defined name, // Since everywhere in the code it'll be referred to by its defined name,
// remove its generated name from the closure map. (We'll re-insert it later.) // remove its generated name from the closure map. (We'll re-insert it later.)
let references = env.closures.remove(&symbol).unwrap_or_else(|| { let references = env.closures.remove(symbol).unwrap_or_else(|| {
panic!( panic!(
"Tried to remove symbol {:?} from procedures, but it was not found: {:?}", "Tried to remove symbol {:?} from procedures, but it was not found: {:?}",
symbol, env.closures symbol, env.closures
@ -1555,7 +1553,7 @@ fn correct_mutual_recursive_type_alias<'a>(
let mut loc_succ = alias.typ.symbols(); let mut loc_succ = alias.typ.symbols();
// remove anything that is not defined in the current block // remove anything that is not defined in the current block
loc_succ.retain(|key| symbols_introduced.contains(key)); loc_succ.retain(|key| symbols_introduced.contains(key));
loc_succ.remove(&symbol); loc_succ.remove(symbol);
loc_succ loc_succ
} }

View file

@ -12,7 +12,7 @@ pub struct Env<'a> {
/// are assumed to be relative to this path. /// are assumed to be relative to this path.
pub home: ModuleId, pub home: ModuleId,
pub dep_idents: MutMap<ModuleId, IdentIds>, pub dep_idents: &'a MutMap<ModuleId, IdentIds>,
pub module_ids: &'a ModuleIds, pub module_ids: &'a ModuleIds,
@ -40,7 +40,7 @@ pub struct Env<'a> {
impl<'a> Env<'a> { impl<'a> Env<'a> {
pub fn new( pub fn new(
home: ModuleId, home: ModuleId,
dep_idents: MutMap<ModuleId, IdentIds>, dep_idents: &'a MutMap<ModuleId, IdentIds>,
module_ids: &'a ModuleIds, module_ids: &'a ModuleIds,
exposed_ident_ids: IdentIds, exposed_ident_ids: IdentIds,
) -> Env<'a> { ) -> Env<'a> {

View file

@ -980,7 +980,7 @@ where
visited.insert(defined_symbol); visited.insert(defined_symbol);
for local in refs.lookups.iter() { for local in refs.lookups.iter() {
if !visited.contains(&local) { if !visited.contains(local) {
let other_refs: References = let other_refs: References =
references_from_local(*local, visited, refs_by_def, closures); references_from_local(*local, visited, refs_by_def, closures);
@ -991,7 +991,7 @@ where
} }
for call in refs.calls.iter() { for call in refs.calls.iter() {
if !visited.contains(&call) { if !visited.contains(call) {
let other_refs = references_from_call(*call, visited, refs_by_def, closures); let other_refs = references_from_call(*call, visited, refs_by_def, closures);
answer = answer.union(other_refs); answer = answer.union(other_refs);
@ -1022,7 +1022,7 @@ where
visited.insert(call_symbol); visited.insert(call_symbol);
for closed_over_local in references.lookups.iter() { for closed_over_local in references.lookups.iter() {
if !visited.contains(&closed_over_local) { if !visited.contains(closed_over_local) {
let other_refs = let other_refs =
references_from_local(*closed_over_local, visited, refs_by_def, closures); references_from_local(*closed_over_local, visited, refs_by_def, closures);
@ -1033,7 +1033,7 @@ where
} }
for call in references.calls.iter() { for call in references.calls.iter() {
if !visited.contains(&call) { if !visited.contains(call) {
let other_refs = references_from_call(*call, visited, refs_by_def, closures); let other_refs = references_from_call(*call, visited, refs_by_def, closures);
answer = answer.union(other_refs); answer = answer.union(other_refs);

View file

@ -47,7 +47,7 @@ pub fn canonicalize_module_defs<'a, F>(
home: ModuleId, home: ModuleId,
module_ids: &ModuleIds, module_ids: &ModuleIds,
exposed_ident_ids: IdentIds, exposed_ident_ids: IdentIds,
dep_idents: MutMap<ModuleId, IdentIds>, dep_idents: &'a MutMap<ModuleId, IdentIds>,
aliases: MutMap<Symbol, Alias>, aliases: MutMap<Symbol, Alias>,
exposed_imports: MutMap<Ident, (Symbol, Region)>, exposed_imports: MutMap<Ident, (Symbol, Region)>,
exposed_symbols: &MutSet<Symbol>, exposed_symbols: &MutSet<Symbol>,
@ -139,7 +139,7 @@ where
} }
} }
let (defs, _scope, output, symbols_introduced) = canonicalize_defs( let (defs, scope, output, symbols_introduced) = canonicalize_defs(
&mut env, &mut env,
Output::default(), Output::default(),
var_store, var_store,

View file

@ -276,7 +276,7 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located<Expr<'a>>) -> &'a
}) })
} }
When(loc_cond_expr, branches) => { When(loc_cond_expr, branches) => {
let loc_desugared_cond = &*arena.alloc(desugar_expr(arena, &loc_cond_expr)); let loc_desugared_cond = &*arena.alloc(desugar_expr(arena, loc_cond_expr));
let mut desugared_branches = Vec::with_capacity_in(branches.len(), arena); let mut desugared_branches = Vec::with_capacity_in(branches.len(), arena);
for branch in branches.iter() { for branch in branches.iter() {
@ -346,7 +346,7 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located<Expr<'a>>) -> &'a
} }
If(if_thens, final_else_branch) => { If(if_thens, final_else_branch) => {
// If does not get desugared into `when` so we can give more targeted error messages during type checking. // If does not get desugared into `when` so we can give more targeted error messages during type checking.
let desugared_final_else = &*arena.alloc(desugar_expr(arena, &final_else_branch)); let desugared_final_else = &*arena.alloc(desugar_expr(arena, final_else_branch));
let mut desugared_if_thens = Vec::with_capacity_in(if_thens.len(), arena); let mut desugared_if_thens = Vec::with_capacity_in(if_thens.len(), arena);
@ -363,8 +363,8 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located<Expr<'a>>) -> &'a
}) })
} }
Expect(condition, continuation) => { Expect(condition, continuation) => {
let desugared_condition = &*arena.alloc(desugar_expr(arena, &condition)); let desugared_condition = &*arena.alloc(desugar_expr(arena, condition));
let desugared_continuation = &*arena.alloc(desugar_expr(arena, &continuation)); let desugared_continuation = &*arena.alloc(desugar_expr(arena, continuation));
arena.alloc(Located { arena.alloc(Located {
value: Expect(desugared_condition, desugared_continuation), value: Expect(desugared_condition, desugared_continuation),
region: loc_expr.region, region: loc_expr.region,

View file

@ -185,7 +185,7 @@ pub fn canonicalize_pattern<'a>(
} }
} }
FloatLiteral(ref string) => match pattern_type { FloatLiteral(string) => match pattern_type {
WhenBranch => match finish_parsing_float(string) { WhenBranch => match finish_parsing_float(string) {
Err(_error) => { Err(_error) => {
let problem = MalformedPatternProblem::MalformedFloat; let problem = MalformedPatternProblem::MalformedFloat;

View file

@ -124,7 +124,7 @@ impl Scope {
// If this IdentId was already added previously // If this IdentId was already added previously
// when the value was exposed in the module header, // when the value was exposed in the module header,
// use that existing IdentId. Otherwise, create a fresh one. // use that existing IdentId. Otherwise, create a fresh one.
let ident_id = match exposed_ident_ids.get_id(&ident.as_inline_str()) { let ident_id = match exposed_ident_ids.get_id(ident.as_inline_str()) {
Some(ident_id) => *ident_id, Some(ident_id) => *ident_id,
None => all_ident_ids.add(ident.clone().into()), None => all_ident_ids.add(ident.clone().into()),
}; };

View file

@ -34,7 +34,7 @@ pub struct CanExprOut {
#[allow(dead_code)] #[allow(dead_code)]
pub fn can_expr_with(arena: &Bump, home: ModuleId, expr_str: &str) -> CanExprOut { pub fn can_expr_with(arena: &Bump, home: ModuleId, expr_str: &str) -> CanExprOut {
let loc_expr = roc_parse::test_helpers::parse_loc_with(&arena, expr_str).unwrap_or_else(|e| { let loc_expr = roc_parse::test_helpers::parse_loc_with(arena, expr_str).unwrap_or_else(|e| {
panic!( panic!(
"can_expr_with() got a parse error when attempting to canonicalize:\n\n{:?} {:?}", "can_expr_with() got a parse error when attempting to canonicalize:\n\n{:?} {:?}",
expr_str, e expr_str, e
@ -56,7 +56,7 @@ pub fn can_expr_with(arena: &Bump, home: ModuleId, expr_str: &str) -> CanExprOut
let mut scope = Scope::new(home, &mut var_store); let mut scope = Scope::new(home, &mut var_store);
let dep_idents = IdentIds::exposed_builtins(0); let dep_idents = IdentIds::exposed_builtins(0);
let mut env = Env::new(home, dep_idents, &module_ids, IdentIds::default()); let mut env = Env::new(home, &dep_idents, &module_ids, IdentIds::default());
let (loc_expr, output) = canonicalize_expr( let (loc_expr, output) = canonicalize_expr(
&mut env, &mut env,
&mut var_store, &mut var_store,

View file

@ -145,7 +145,7 @@ mod test_can {
let region = Region::zero(); let region = Region::zero();
assert_can( assert_can(
&string.clone(), string.clone(),
RuntimeError(RuntimeError::InvalidFloat( RuntimeError(RuntimeError::InvalidFloat(
FloatErrorKind::Error, FloatErrorKind::Error,
region, region,
@ -658,7 +658,7 @@ mod test_can {
recursive: recursion, recursive: recursion,
.. ..
}) => recursion.clone(), }) => recursion.clone(),
Some(other @ _) => { Some(other) => {
panic!("assignment at {} is not a closure, but a {:?}", i, other) panic!("assignment at {} is not a closure, but a {:?}", i, other)
} }
None => { None => {
@ -680,7 +680,7 @@ mod test_can {
recursive: recursion, recursive: recursion,
.. ..
} => recursion.clone(), } => recursion.clone(),
other @ _ => { other => {
panic!("assignment at {} is not a closure, but a {:?}", i, other) panic!("assignment at {} is not a closure, but a {:?}", i, other)
} }
} }

View file

@ -79,7 +79,7 @@ where
let mut buf = String::new_in(arena); let mut buf = String::new_in(arena);
if let Some(first) = strings.next() { if let Some(first) = strings.next() {
buf.push_str(&first); buf.push_str(first);
for string in strings { for string in strings {
buf.reserve(join_str.len() + string.len()); buf.reserve(join_str.len() + string.len());
@ -133,7 +133,7 @@ where
let mut answer = MutMap::default(); let mut answer = MutMap::default();
for (key, right_value) in map2 { for (key, right_value) in map2 {
match std::collections::HashMap::get(map1, &key) { match std::collections::HashMap::get(map1, key) {
None => (), None => (),
Some(left_value) => { Some(left_value) => {
answer.insert(key.clone(), (left_value.clone(), right_value.clone())); answer.insert(key.clone(), (left_value.clone(), right_value.clone()));

View file

@ -1446,7 +1446,7 @@ fn instantiate_rigids(
let mut rigid_substitution: ImMap<Variable, Type> = ImMap::default(); let mut rigid_substitution: ImMap<Variable, Type> = ImMap::default();
for (name, var) in introduced_vars.var_by_name.iter() { for (name, var) in introduced_vars.var_by_name.iter() {
if let Some(existing_rigid) = ftv.get(&name) { if let Some(existing_rigid) = ftv.get(name) {
rigid_substitution.insert(*var, Type::Variable(*existing_rigid)); rigid_substitution.insert(*var, Type::Variable(*existing_rigid));
} else { } else {
// It's possible to use this rigid in nested defs // It's possible to use this rigid in nested defs

View file

@ -119,7 +119,7 @@ fn headers_from_annotation_help(
} }
/// This accepts PatternState (rather than returning it) so that the caller can /// This accepts PatternState (rather than returning it) so that the caller can
/// intiialize the Vecs in PatternState using with_capacity /// initialize the Vecs in PatternState using with_capacity
/// based on its knowledge of their lengths. /// based on its knowledge of their lengths.
pub fn constrain_pattern( pub fn constrain_pattern(
env: &Env, env: &Env,
@ -206,7 +206,7 @@ pub fn constrain_pattern(
let pat_type = Type::Variable(*var); let pat_type = Type::Variable(*var);
let expected = PExpected::NoExpectation(pat_type.clone()); let expected = PExpected::NoExpectation(pat_type.clone());
if !state.headers.contains_key(&symbol) { if !state.headers.contains_key(symbol) {
state state
.headers .headers
.insert(*symbol, Located::at(region, pat_type.clone())); .insert(*symbol, Located::at(region, pat_type.clone()));

View file

@ -295,7 +295,7 @@ impl<'a> Formattable<'a> for Expr<'a> {
items, items,
final_comments, final_comments,
} => { } => {
fmt_list(buf, &items, final_comments, indent); fmt_list(buf, items, final_comments, indent);
} }
BinOps(lefts, right) => fmt_bin_ops(buf, lefts, right, false, parens, indent), BinOps(lefts, right) => fmt_bin_ops(buf, lefts, right, false, parens, indent),
UnaryOp(sub_expr, unary_op) => { UnaryOp(sub_expr, unary_op) => {
@ -1027,7 +1027,7 @@ fn format_field_multiline<'a, T>(
format_field_multiline(buf, sub_field, indent, separator_prefix); format_field_multiline(buf, sub_field, indent, separator_prefix);
} }
AssignedField::SpaceAfter(sub_field, spaces) => { AssignedField::SpaceAfter(sub_field, spaces) => {
// We have somethig like that: // We have something like that:
// ``` // ```
// field # comment // field # comment
// , otherfield // , otherfield

View file

@ -74,7 +74,7 @@ where
/// build_proc creates a procedure and outputs it to the wrapped object writer. /// build_proc creates a procedure and outputs it to the wrapped object writer.
fn build_proc(&mut self, proc: Proc<'a>) -> Result<(&'a [u8], &[Relocation]), String> { fn build_proc(&mut self, proc: Proc<'a>) -> Result<(&'a [u8], &[Relocation]), String> {
self.reset(); self.reset();
self.load_args(&proc.args)?; self.load_args(proc.args)?;
// let start = std::time::Instant::now(); // let start = std::time::Instant::now();
self.scan_ast(&proc.body); self.scan_ast(&proc.body);
self.create_free_map(); self.create_free_map();

View file

@ -31,7 +31,7 @@ mod dev_num {
assert_evals_to!("-0.0", 0.0, f64); assert_evals_to!("-0.0", 0.0, f64);
assert_evals_to!("1.0", 1.0, f64); assert_evals_to!("1.0", 1.0, f64);
assert_evals_to!("-1.0", -1.0, f64); assert_evals_to!("-1.0", -1.0, f64);
assert_evals_to!("3.1415926535897932", 3.1415926535897932, f64); assert_evals_to!("3.1415926535897932", 3.141_592_653_589_793, f64);
assert_evals_to!(&format!("{:0.1}", f64::MIN), f64::MIN, f64); assert_evals_to!(&format!("{:0.1}", f64::MIN), f64::MIN, f64);
assert_evals_to!(&format!("{:0.1}", f64::MAX), f64::MAX, f64); assert_evals_to!(&format!("{:0.1}", f64::MAX), f64::MAX, f64);
} }

View file

@ -49,7 +49,7 @@ pub fn helper<'a>(
let loaded = roc_load::file::load_and_monomorphize_from_str( let loaded = roc_load::file::load_and_monomorphize_from_str(
arena, arena,
filename, filename,
&module_src, module_src,
&stdlib, &stdlib,
src_dir, src_dir,
exposed_types, exposed_types,

View file

@ -78,7 +78,7 @@ pub fn build_has_tag_id<'a, 'ctx, 'env>(
match env.module.get_function(fn_name) { match env.module.get_function(fn_name) {
Some(function_value) => function_value, Some(function_value) => function_value,
None => build_has_tag_id_help(env, union_layout, &fn_name), None => build_has_tag_id_help(env, union_layout, fn_name),
} }
} }
@ -97,9 +97,9 @@ fn build_has_tag_id_help<'a, 'ctx, 'env>(
let function_value = crate::llvm::refcounting::build_header_help( let function_value = crate::llvm::refcounting::build_header_help(
env, env,
&fn_name, fn_name,
output_type.into(), output_type.into(),
&argument_types, argument_types,
); );
// called from zig, must use C calling convention // called from zig, must use C calling convention
@ -204,7 +204,7 @@ pub fn build_transform_caller<'a, 'ctx, 'env>(
function, function,
closure_data_layout, closure_data_layout,
argument_layouts, argument_layouts,
&fn_name, fn_name,
), ),
} }
} }
@ -225,7 +225,7 @@ fn build_transform_caller_help<'a, 'ctx, 'env>(
let function_value = crate::llvm::refcounting::build_header_help( let function_value = crate::llvm::refcounting::build_header_help(
env, env,
&fn_name, fn_name,
env.context.void_type().into(), env.context.void_type().into(),
&(bumpalo::vec![ in env.arena; BasicTypeEnum::PointerType(arg_type); argument_layouts.len() + 2 ]), &(bumpalo::vec![ in env.arena; BasicTypeEnum::PointerType(arg_type); argument_layouts.len() + 2 ]),
); );
@ -394,7 +394,7 @@ fn build_rc_wrapper<'a, 'ctx, 'env>(
let symbol = Symbol::GENERIC_RC_REF; let symbol = Symbol::GENERIC_RC_REF;
let fn_name = layout_ids let fn_name = layout_ids
.get(symbol, &layout) .get(symbol, layout)
.to_symbol_string(symbol, &env.interns); .to_symbol_string(symbol, &env.interns);
let fn_name = match rc_operation { let fn_name = match rc_operation {
@ -489,7 +489,7 @@ pub fn build_eq_wrapper<'a, 'ctx, 'env>(
let symbol = Symbol::GENERIC_EQ_REF; let symbol = Symbol::GENERIC_EQ_REF;
let fn_name = layout_ids let fn_name = layout_ids
.get(symbol, &layout) .get(symbol, layout)
.to_symbol_string(symbol, &env.interns); .to_symbol_string(symbol, &env.interns);
let function_value = match env.module.get_function(fn_name.as_str()) { let function_value = match env.module.get_function(fn_name.as_str()) {
@ -576,7 +576,7 @@ pub fn build_compare_wrapper<'a, 'ctx, 'env>(
let function_value = crate::llvm::refcounting::build_header_help( let function_value = crate::llvm::refcounting::build_header_help(
env, env,
&fn_name, fn_name,
env.context.i8_type().into(), env.context.i8_type().into(),
&[arg_type.into(), arg_type.into(), arg_type.into()], &[arg_type.into(), arg_type.into(), arg_type.into()],
); );

View file

@ -1,6 +1,6 @@
use std::path::Path; use std::path::Path;
use crate::llvm::bitcode::call_bitcode_fn; use crate::llvm::bitcode::{call_bitcode_fn, call_void_bitcode_fn};
use crate::llvm::build_dict::{ use crate::llvm::build_dict::{
dict_contains, dict_difference, dict_empty, dict_get, dict_insert, dict_intersection, dict_contains, dict_difference, dict_empty, dict_get, dict_insert, dict_intersection,
dict_keys, dict_len, dict_remove, dict_union, dict_values, dict_walk, set_from_list, dict_keys, dict_len, dict_remove, dict_union, dict_values, dict_walk, set_from_list,
@ -347,7 +347,7 @@ pub fn module_from_builtins<'ctx>(ctx: &'ctx Context, module_name: &str) -> Modu
// we compile the builtins into LLVM bitcode // we compile the builtins into LLVM bitcode
let bitcode_bytes: &[u8] = include_bytes!("../../../builtins/bitcode/builtins.bc"); let bitcode_bytes: &[u8] = include_bytes!("../../../builtins/bitcode/builtins.bc");
let memory_buffer = MemoryBuffer::create_from_memory_range(&bitcode_bytes, module_name); let memory_buffer = MemoryBuffer::create_from_memory_range(bitcode_bytes, module_name);
let module = Module::parse_bitcode_from_buffer(&memory_buffer, ctx) let module = Module::parse_bitcode_from_buffer(&memory_buffer, ctx)
.unwrap_or_else(|err| panic!("Unable to import builtins bitcode. LLVM error: {:?}", err)); .unwrap_or_else(|err| panic!("Unable to import builtins bitcode. LLVM error: {:?}", err));
@ -362,13 +362,16 @@ fn add_intrinsics<'ctx>(ctx: &'ctx Context, module: &Module<'ctx>) {
// List of all supported LLVM intrinsics: // List of all supported LLVM intrinsics:
// //
// https://releases.llvm.org/10.0.0/docs/LangRef.html#standard-c-library-intrinsics // https://releases.llvm.org/10.0.0/docs/LangRef.html#standard-c-library-intrinsics
let i1_type = ctx.bool_type();
let f64_type = ctx.f64_type(); let f64_type = ctx.f64_type();
let i128_type = ctx.i128_type(); let i1_type = ctx.bool_type();
let i64_type = ctx.i64_type();
let i32_type = ctx.i32_type();
let i16_type = ctx.i16_type();
let i8_type = ctx.i8_type(); let i8_type = ctx.i8_type();
let i16_type = ctx.i16_type();
let i32_type = ctx.i32_type();
let i64_type = ctx.i64_type();
if let Some(func) = module.get_function("__muloti4") {
func.set_linkage(Linkage::WeakAny);
}
add_intrinsic( add_intrinsic(
module, module,
@ -418,8 +421,6 @@ fn add_intrinsics<'ctx>(ctx: &'ctx Context, module: &Module<'ctx>) {
f64_type.fn_type(&[f64_type.into()], false), f64_type.fn_type(&[f64_type.into()], false),
); );
// add with overflow
add_intrinsic(module, LLVM_SADD_WITH_OVERFLOW_I8, { add_intrinsic(module, LLVM_SADD_WITH_OVERFLOW_I8, {
let fields = [i8_type.into(), i1_type.into()]; let fields = [i8_type.into(), i1_type.into()];
ctx.struct_type(&fields, false) ctx.struct_type(&fields, false)
@ -444,14 +445,6 @@ fn add_intrinsics<'ctx>(ctx: &'ctx Context, module: &Module<'ctx>) {
.fn_type(&[i64_type.into(), i64_type.into()], false) .fn_type(&[i64_type.into(), i64_type.into()], false)
}); });
add_intrinsic(module, LLVM_SADD_WITH_OVERFLOW_I128, {
let fields = [i128_type.into(), i1_type.into()];
ctx.struct_type(&fields, false)
.fn_type(&[i128_type.into(), i128_type.into()], false)
});
// sub with overflow
add_intrinsic(module, LLVM_SSUB_WITH_OVERFLOW_I8, { add_intrinsic(module, LLVM_SSUB_WITH_OVERFLOW_I8, {
let fields = [i8_type.into(), i1_type.into()]; let fields = [i8_type.into(), i1_type.into()];
ctx.struct_type(&fields, false) ctx.struct_type(&fields, false)
@ -475,12 +468,6 @@ fn add_intrinsics<'ctx>(ctx: &'ctx Context, module: &Module<'ctx>) {
ctx.struct_type(&fields, false) ctx.struct_type(&fields, false)
.fn_type(&[i64_type.into(), i64_type.into()], false) .fn_type(&[i64_type.into(), i64_type.into()], false)
}); });
add_intrinsic(module, LLVM_SSUB_WITH_OVERFLOW_I128, {
let fields = [i128_type.into(), i1_type.into()];
ctx.struct_type(&fields, false)
.fn_type(&[i128_type.into(), i128_type.into()], false)
});
} }
static LLVM_MEMSET_I64: &str = "llvm.memset.p0i8.i64"; static LLVM_MEMSET_I64: &str = "llvm.memset.p0i8.i64";
@ -640,10 +627,15 @@ pub fn float_with_precision<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
value: f64, value: f64,
precision: &Builtin, precision: &Builtin,
) -> FloatValue<'ctx> { ) -> BasicValueEnum<'ctx> {
match precision { match precision {
Builtin::Float64 => env.context.f64_type().const_float(value), Builtin::Decimal => call_bitcode_fn(
Builtin::Float32 => env.context.f32_type().const_float(value), env,
&[env.context.f64_type().const_float(value).into()],
bitcode::DEC_FROM_F64,
),
Builtin::Float64 => env.context.f64_type().const_float(value).into(),
Builtin::Float32 => env.context.f32_type().const_float(value).into(),
_ => panic!("Invalid layout for float literal = {:?}", precision), _ => panic!("Invalid layout for float literal = {:?}", precision),
} }
} }
@ -662,7 +654,7 @@ pub fn build_exp_literal<'a, 'ctx, 'env>(
}, },
Float(float) => match layout { Float(float) => match layout {
Layout::Builtin(builtin) => float_with_precision(env, *float, builtin).into(), Layout::Builtin(builtin) => float_with_precision(env, *float, builtin),
_ => panic!("Invalid layout for float literal = {:?}", layout), _ => panic!("Invalid layout for float literal = {:?}", layout),
}, },
@ -984,7 +976,7 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
// The layout of the struct expects them to be dropped! // The layout of the struct expects them to be dropped!
let (field_expr, field_layout) = load_symbol_and_layout(scope, symbol); let (field_expr, field_layout) = load_symbol_and_layout(scope, symbol);
if !field_layout.is_dropped_because_empty() { if !field_layout.is_dropped_because_empty() {
field_types.push(basic_type_from_layout(env, &field_layout)); field_types.push(basic_type_from_layout(env, field_layout));
field_vals.push(field_expr); field_vals.push(field_expr);
} }
@ -1195,7 +1187,7 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
) )
} }
UnionLayout::NonNullableUnwrapped(field_layouts) => { UnionLayout::NonNullableUnwrapped(field_layouts) => {
let struct_layout = Layout::Struct(&field_layouts); let struct_layout = Layout::Struct(field_layouts);
let struct_type = basic_type_from_layout(env, &struct_layout); let struct_type = basic_type_from_layout(env, &struct_layout);
@ -1268,7 +1260,7 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
// cast the argument bytes into the desired shape for this tag // cast the argument bytes into the desired shape for this tag
let (argument, _structure_layout) = load_symbol_and_layout(scope, structure); let (argument, _structure_layout) = load_symbol_and_layout(scope, structure);
get_tag_id(env, parent, &union_layout, argument).into() get_tag_id(env, parent, union_layout, argument).into()
} }
} }
} }
@ -1467,7 +1459,7 @@ pub fn build_tag<'a, 'ctx, 'env>(
union_layout, union_layout,
tag_id, tag_id,
arguments, arguments,
&tag_field_layouts, tag_field_layouts,
tags, tags,
reuse_allocation, reuse_allocation,
parent, parent,
@ -1499,7 +1491,7 @@ pub fn build_tag<'a, 'ctx, 'env>(
union_layout, union_layout,
tag_id, tag_id,
arguments, arguments,
&tag_field_layouts, tag_field_layouts,
tags, tags,
reuse_allocation, reuse_allocation,
parent, parent,
@ -2277,7 +2269,7 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
scope, scope,
parent, parent,
layout, layout,
&expr, expr,
); );
// Make a new scope which includes the binding we just encountered. // Make a new scope which includes the binding we just encountered.
@ -2397,7 +2389,7 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
let exception_object = scope.get(&exception_id.into_inner()).unwrap().1; let exception_object = scope.get(&exception_id.into_inner()).unwrap().1;
env.builder.build_resume(exception_object); env.builder.build_resume(exception_object);
env.context.i64_type().const_zero().into() env.ptr_int().const_zero().into()
} }
Switch { Switch {
@ -2407,7 +2399,7 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
cond_layout, cond_layout,
cond_symbol, cond_symbol,
} => { } => {
let ret_type = basic_type_from_layout(env, &ret_layout); let ret_type = basic_type_from_layout(env, ret_layout);
let switch_args = SwitchArgsIr { let switch_args = SwitchArgsIr {
cond_layout: *cond_layout, cond_layout: *cond_layout,
@ -2485,7 +2477,7 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
); );
// remove this join point again // remove this join point again
scope.join_points.remove(&id); scope.join_points.remove(id);
cont_block.move_after(phi_block).unwrap(); cont_block.move_after(phi_block).unwrap();
@ -2835,7 +2827,7 @@ fn build_switch_ir<'a, 'ctx, 'env>(
.into_int_value() .into_int_value()
} }
Layout::Union(variant) => { Layout::Union(variant) => {
cond_layout = Layout::Builtin(Builtin::Int64); cond_layout = variant.tag_id_layout();
get_tag_id(env, parent, &variant, cond_value) get_tag_id(env, parent, &variant, cond_value)
} }
@ -3129,7 +3121,7 @@ where
let call_result = { let call_result = {
let call = builder.build_invoke( let call = builder.build_invoke(
function, function,
&arguments, arguments,
then_block, then_block,
catch_block, catch_block,
"call_roc_function", "call_roc_function",
@ -3299,7 +3291,7 @@ fn make_exception_catching_wrapper<'a, 'ctx, 'env>(
// Add main to the module. // Add main to the module.
let wrapper_function = add_func( let wrapper_function = add_func(
env.module, env.module,
&wrapper_function_name, wrapper_function_name,
wrapper_function_type, wrapper_function_type,
Linkage::External, Linkage::External,
C_CALL_CONV, C_CALL_CONV,
@ -3422,7 +3414,7 @@ fn build_procedures_help<'a, 'ctx, 'env>(
// Add all the Proc headers to the module. // Add all the Proc headers to the module.
// We have to do this in a separate pass first, // We have to do this in a separate pass first,
// because their bodies may reference each other. // because their bodies may reference each other.
let headers = build_proc_headers(env, &mod_solutions, procedures, &mut scope); let headers = build_proc_headers(env, mod_solutions, procedures, &mut scope);
let (_, function_pass) = construct_optimization_passes(env.module, opt_level); let (_, function_pass) = construct_optimization_passes(env.module, opt_level);
@ -3436,7 +3428,7 @@ fn build_procedures_help<'a, 'ctx, 'env>(
current_scope.retain_top_level_thunks_for_module(home); current_scope.retain_top_level_thunks_for_module(home);
build_proc( build_proc(
&env, env,
mod_solutions, mod_solutions,
&mut layout_ids, &mut layout_ids,
func_spec_solutions, func_spec_solutions,
@ -3449,7 +3441,7 @@ fn build_procedures_help<'a, 'ctx, 'env>(
env.dibuilder.finalize(); env.dibuilder.finalize();
if fn_val.verify(true) { if fn_val.verify(true) {
function_pass.run_on(&fn_val); function_pass.run_on(fn_val);
} else { } else {
let mode = "NON-OPTIMIZED"; let mode = "NON-OPTIMIZED";
@ -3519,7 +3511,7 @@ fn build_proc_header<'a, 'ctx, 'env>(
let mut arg_basic_types = Vec::with_capacity_in(args.len(), arena); let mut arg_basic_types = Vec::with_capacity_in(args.len(), arena);
for (layout, _) in args.iter() { for (layout, _) in args.iter() {
let arg_type = basic_type_from_layout(env, &layout); let arg_type = basic_type_from_layout(env, layout);
arg_basic_types.push(arg_type); arg_basic_types.push(arg_type);
} }
@ -5257,6 +5249,39 @@ fn build_foreign_symbol<'a, 'ctx, 'env>(
} }
} }
fn throw_on_overflow<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
parent: FunctionValue<'ctx>,
result: StructValue<'ctx>, // of the form { value: T, has_overflowed: bool }
message: &str,
) -> BasicValueEnum<'ctx> {
let bd = env.builder;
let context = env.context;
let has_overflowed = bd.build_extract_value(result, 1, "has_overflowed").unwrap();
let condition = bd.build_int_compare(
IntPredicate::EQ,
has_overflowed.into_int_value(),
context.bool_type().const_zero(),
"has_not_overflowed",
);
let then_block = context.append_basic_block(parent, "then_block");
let throw_block = context.append_basic_block(parent, "throw_block");
bd.build_conditional_branch(condition, then_block, throw_block);
bd.position_at_end(throw_block);
throw_exception(env, message);
bd.position_at_end(then_block);
bd.build_extract_value(result, 0, "operation_result")
.unwrap()
}
fn build_int_binop<'a, 'ctx, 'env>( fn build_int_binop<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
parent: FunctionValue<'ctx>, parent: FunctionValue<'ctx>,
@ -5273,8 +5298,6 @@ fn build_int_binop<'a, 'ctx, 'env>(
match op { match op {
NumAdd => { NumAdd => {
let context = env.context;
let intrinsic = match lhs_layout { let intrinsic = match lhs_layout {
Layout::Builtin(Builtin::Int8) => LLVM_SADD_WITH_OVERFLOW_I8, Layout::Builtin(Builtin::Int8) => LLVM_SADD_WITH_OVERFLOW_I8,
Layout::Builtin(Builtin::Int16) => LLVM_SADD_WITH_OVERFLOW_I16, Layout::Builtin(Builtin::Int16) => LLVM_SADD_WITH_OVERFLOW_I16,
@ -5293,34 +5316,11 @@ fn build_int_binop<'a, 'ctx, 'env>(
.call_intrinsic(intrinsic, &[lhs.into(), rhs.into()]) .call_intrinsic(intrinsic, &[lhs.into(), rhs.into()])
.into_struct_value(); .into_struct_value();
let add_result = bd.build_extract_value(result, 0, "add_result").unwrap(); throw_on_overflow(env, parent, result, "integer addition overflowed!")
let has_overflowed = bd.build_extract_value(result, 1, "has_overflowed").unwrap();
let condition = bd.build_int_compare(
IntPredicate::EQ,
has_overflowed.into_int_value(),
context.bool_type().const_zero(),
"has_not_overflowed",
);
let then_block = context.append_basic_block(parent, "then_block");
let throw_block = context.append_basic_block(parent, "throw_block");
bd.build_conditional_branch(condition, then_block, throw_block);
bd.position_at_end(throw_block);
throw_exception(env, "integer addition overflowed!");
bd.position_at_end(then_block);
add_result
} }
NumAddWrap => bd.build_int_add(lhs, rhs, "add_int_wrap").into(), NumAddWrap => bd.build_int_add(lhs, rhs, "add_int_wrap").into(),
NumAddChecked => env.call_intrinsic(LLVM_SADD_WITH_OVERFLOW_I64, &[lhs.into(), rhs.into()]), NumAddChecked => env.call_intrinsic(LLVM_SADD_WITH_OVERFLOW_I64, &[lhs.into(), rhs.into()]),
NumSub => { NumSub => {
let context = env.context;
let intrinsic = match lhs_layout { let intrinsic = match lhs_layout {
Layout::Builtin(Builtin::Int8) => LLVM_SSUB_WITH_OVERFLOW_I8, Layout::Builtin(Builtin::Int8) => LLVM_SSUB_WITH_OVERFLOW_I8,
Layout::Builtin(Builtin::Int16) => LLVM_SSUB_WITH_OVERFLOW_I16, Layout::Builtin(Builtin::Int16) => LLVM_SSUB_WITH_OVERFLOW_I16,
@ -5339,59 +5339,16 @@ fn build_int_binop<'a, 'ctx, 'env>(
.call_intrinsic(intrinsic, &[lhs.into(), rhs.into()]) .call_intrinsic(intrinsic, &[lhs.into(), rhs.into()])
.into_struct_value(); .into_struct_value();
let sub_result = bd.build_extract_value(result, 0, "sub_result").unwrap(); throw_on_overflow(env, parent, result, "integer subtraction overflowed!")
let has_overflowed = bd.build_extract_value(result, 1, "has_overflowed").unwrap();
let condition = bd.build_int_compare(
IntPredicate::EQ,
has_overflowed.into_int_value(),
context.bool_type().const_zero(),
"has_not_overflowed",
);
let then_block = context.append_basic_block(parent, "then_block");
let throw_block = context.append_basic_block(parent, "throw_block");
bd.build_conditional_branch(condition, then_block, throw_block);
bd.position_at_end(throw_block);
throw_exception(env, "integer subtraction overflowed!");
bd.position_at_end(then_block);
sub_result
} }
NumSubWrap => bd.build_int_sub(lhs, rhs, "sub_int").into(), NumSubWrap => bd.build_int_sub(lhs, rhs, "sub_int").into(),
NumSubChecked => env.call_intrinsic(LLVM_SSUB_WITH_OVERFLOW_I64, &[lhs.into(), rhs.into()]), NumSubChecked => env.call_intrinsic(LLVM_SSUB_WITH_OVERFLOW_I64, &[lhs.into(), rhs.into()]),
NumMul => { NumMul => {
let context = env.context;
let result = env let result = env
.call_intrinsic(LLVM_SMUL_WITH_OVERFLOW_I64, &[lhs.into(), rhs.into()]) .call_intrinsic(LLVM_SMUL_WITH_OVERFLOW_I64, &[lhs.into(), rhs.into()])
.into_struct_value(); .into_struct_value();
let mul_result = bd.build_extract_value(result, 0, "mul_result").unwrap(); throw_on_overflow(env, parent, result, "integer multiplication overflowed!")
let has_overflowed = bd.build_extract_value(result, 1, "has_overflowed").unwrap();
let condition = bd.build_int_compare(
IntPredicate::EQ,
has_overflowed.into_int_value(),
context.bool_type().const_zero(),
"has_not_overflowed",
);
let then_block = context.append_basic_block(parent, "then_block");
let throw_block = context.append_basic_block(parent, "throw_block");
bd.build_conditional_branch(condition, then_block, throw_block);
bd.position_at_end(throw_block);
throw_exception(env, "integer multiplication overflowed!");
bd.position_at_end(then_block);
mul_result
} }
NumMulWrap => bd.build_int_mul(lhs, rhs, "mul_int").into(), NumMulWrap => bd.build_int_mul(lhs, rhs, "mul_int").into(),
NumMulChecked => env.call_intrinsic(LLVM_SMUL_WITH_OVERFLOW_I64, &[lhs.into(), rhs.into()]), NumMulChecked => env.call_intrinsic(LLVM_SMUL_WITH_OVERFLOW_I64, &[lhs.into(), rhs.into()]),
@ -5469,7 +5426,7 @@ fn build_int_binop<'a, 'ctx, 'env>(
} }
} }
NumDivUnchecked => bd.build_int_signed_div(lhs, rhs, "div_int").into(), NumDivUnchecked => bd.build_int_signed_div(lhs, rhs, "div_int").into(),
NumPowInt => call_bitcode_fn(env, &[lhs.into(), rhs.into()], &bitcode::NUM_POW_INT), NumPowInt => call_bitcode_fn(env, &[lhs.into(), rhs.into()], bitcode::NUM_POW_INT),
NumBitwiseAnd => bd.build_and(lhs, rhs, "int_bitwise_and").into(), NumBitwiseAnd => bd.build_and(lhs, rhs, "int_bitwise_and").into(),
NumBitwiseXor => bd.build_xor(lhs, rhs, "int_bitwise_xor").into(), NumBitwiseXor => bd.build_xor(lhs, rhs, "int_bitwise_xor").into(),
NumBitwiseOr => bd.build_or(lhs, rhs, "int_bitwise_or").into(), NumBitwiseOr => bd.build_or(lhs, rhs, "int_bitwise_or").into(),
@ -5530,6 +5487,9 @@ pub fn build_num_binop<'a, 'ctx, 'env>(
rhs_layout, rhs_layout,
op, op,
), ),
Decimal => {
build_dec_binop(env, parent, lhs_arg, lhs_layout, rhs_arg, rhs_layout, op)
}
_ => { _ => {
unreachable!("Compiler bug: tried to run numeric operation {:?} on invalid builtin layout: ({:?})", op, lhs_layout); unreachable!("Compiler bug: tried to run numeric operation {:?} on invalid builtin layout: ({:?})", op, lhs_layout);
} }
@ -5563,7 +5523,7 @@ fn build_float_binop<'a, 'ctx, 'env>(
let result = bd.build_float_add(lhs, rhs, "add_float"); let result = bd.build_float_add(lhs, rhs, "add_float");
let is_finite = let is_finite =
call_bitcode_fn(env, &[result.into()], &bitcode::NUM_IS_FINITE).into_int_value(); call_bitcode_fn(env, &[result.into()], bitcode::NUM_IS_FINITE).into_int_value();
let then_block = context.append_basic_block(parent, "then_block"); let then_block = context.append_basic_block(parent, "then_block");
let throw_block = context.append_basic_block(parent, "throw_block"); let throw_block = context.append_basic_block(parent, "throw_block");
@ -5584,7 +5544,7 @@ fn build_float_binop<'a, 'ctx, 'env>(
let result = bd.build_float_add(lhs, rhs, "add_float"); let result = bd.build_float_add(lhs, rhs, "add_float");
let is_finite = let is_finite =
call_bitcode_fn(env, &[result.into()], &bitcode::NUM_IS_FINITE).into_int_value(); call_bitcode_fn(env, &[result.into()], bitcode::NUM_IS_FINITE).into_int_value();
let is_infinite = bd.build_not(is_finite, "negate"); let is_infinite = bd.build_not(is_finite, "negate");
let struct_type = context.struct_type( let struct_type = context.struct_type(
@ -5612,7 +5572,7 @@ fn build_float_binop<'a, 'ctx, 'env>(
let result = bd.build_float_sub(lhs, rhs, "sub_float"); let result = bd.build_float_sub(lhs, rhs, "sub_float");
let is_finite = let is_finite =
call_bitcode_fn(env, &[result.into()], &bitcode::NUM_IS_FINITE).into_int_value(); call_bitcode_fn(env, &[result.into()], bitcode::NUM_IS_FINITE).into_int_value();
let then_block = context.append_basic_block(parent, "then_block"); let then_block = context.append_basic_block(parent, "then_block");
let throw_block = context.append_basic_block(parent, "throw_block"); let throw_block = context.append_basic_block(parent, "throw_block");
@ -5633,7 +5593,7 @@ fn build_float_binop<'a, 'ctx, 'env>(
let result = bd.build_float_sub(lhs, rhs, "sub_float"); let result = bd.build_float_sub(lhs, rhs, "sub_float");
let is_finite = let is_finite =
call_bitcode_fn(env, &[result.into()], &bitcode::NUM_IS_FINITE).into_int_value(); call_bitcode_fn(env, &[result.into()], bitcode::NUM_IS_FINITE).into_int_value();
let is_infinite = bd.build_not(is_finite, "negate"); let is_infinite = bd.build_not(is_finite, "negate");
let struct_type = context.struct_type( let struct_type = context.struct_type(
@ -5661,7 +5621,7 @@ fn build_float_binop<'a, 'ctx, 'env>(
let result = bd.build_float_mul(lhs, rhs, "mul_float"); let result = bd.build_float_mul(lhs, rhs, "mul_float");
let is_finite = let is_finite =
call_bitcode_fn(env, &[result.into()], &bitcode::NUM_IS_FINITE).into_int_value(); call_bitcode_fn(env, &[result.into()], bitcode::NUM_IS_FINITE).into_int_value();
let then_block = context.append_basic_block(parent, "then_block"); let then_block = context.append_basic_block(parent, "then_block");
let throw_block = context.append_basic_block(parent, "throw_block"); let throw_block = context.append_basic_block(parent, "throw_block");
@ -5682,7 +5642,7 @@ fn build_float_binop<'a, 'ctx, 'env>(
let result = bd.build_float_mul(lhs, rhs, "mul_float"); let result = bd.build_float_mul(lhs, rhs, "mul_float");
let is_finite = let is_finite =
call_bitcode_fn(env, &[result.into()], &bitcode::NUM_IS_FINITE).into_int_value(); call_bitcode_fn(env, &[result.into()], bitcode::NUM_IS_FINITE).into_int_value();
let is_infinite = bd.build_not(is_finite, "negate"); let is_infinite = bd.build_not(is_finite, "negate");
let struct_type = context.struct_type( let struct_type = context.struct_type(
@ -5716,6 +5676,75 @@ fn build_float_binop<'a, 'ctx, 'env>(
} }
} }
fn build_dec_binop<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
parent: FunctionValue<'ctx>,
lhs: BasicValueEnum<'ctx>,
_lhs_layout: &Layout<'a>,
rhs: BasicValueEnum<'ctx>,
_rhs_layout: &Layout<'a>,
op: LowLevel,
) -> BasicValueEnum<'ctx> {
use roc_module::low_level::LowLevel::*;
match op {
NumAddChecked => call_bitcode_fn(env, &[lhs, rhs], bitcode::DEC_ADD_WITH_OVERFLOW),
NumSubChecked => call_bitcode_fn(env, &[lhs, rhs], bitcode::DEC_SUB_WITH_OVERFLOW),
NumMulChecked => call_bitcode_fn(env, &[lhs, rhs], bitcode::DEC_MUL_WITH_OVERFLOW),
NumAdd => build_dec_binop_throw_on_overflow(
env,
parent,
bitcode::DEC_ADD_WITH_OVERFLOW,
lhs,
rhs,
"decimal addition overflowed",
),
NumSub => build_dec_binop_throw_on_overflow(
env,
parent,
bitcode::DEC_SUB_WITH_OVERFLOW,
lhs,
rhs,
"decimal subtraction overflowed",
),
NumMul => build_dec_binop_throw_on_overflow(
env,
parent,
bitcode::DEC_MUL_WITH_OVERFLOW,
lhs,
rhs,
"decimal multiplication overflowed",
),
NumDivUnchecked => call_bitcode_fn(env, &[lhs, rhs], bitcode::DEC_DIV),
_ => {
unreachable!("Unrecognized int binary operation: {:?}", op);
}
}
}
fn build_dec_binop_throw_on_overflow<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
parent: FunctionValue<'ctx>,
operation: &str,
lhs: BasicValueEnum<'ctx>,
rhs: BasicValueEnum<'ctx>,
message: &str,
) -> BasicValueEnum<'ctx> {
let overflow_type = crate::llvm::convert::zig_with_overflow_roc_dec(env);
let result_ptr = env.builder.build_alloca(overflow_type, "result_ptr");
call_void_bitcode_fn(env, &[result_ptr.into(), lhs, rhs], operation);
let result = env
.builder
.build_load(result_ptr, "load_overflow")
.into_struct_value();
let value = throw_on_overflow(env, parent, result, message).into_struct_value();
env.builder.build_extract_value(value, 0, "num").unwrap()
}
fn int_type_signed_min(int_type: IntType) -> IntValue { fn int_type_signed_min(int_type: IntType) -> IntValue {
let width = int_type.get_bit_width(); let width = int_type.get_bit_width();
@ -5909,10 +5938,10 @@ fn build_float_unary_op<'a, 'ctx, 'env>(
env.context.i64_type(), env.context.i64_type(),
"num_floor", "num_floor",
), ),
NumIsFinite => call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_IS_FINITE), NumIsFinite => call_bitcode_fn(env, &[arg.into()], bitcode::NUM_IS_FINITE),
NumAtan => call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_ATAN), NumAtan => call_bitcode_fn(env, &[arg.into()], bitcode::NUM_ATAN),
NumAcos => call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_ACOS), NumAcos => call_bitcode_fn(env, &[arg.into()], bitcode::NUM_ACOS),
NumAsin => call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_ASIN), NumAsin => call_bitcode_fn(env, &[arg.into()], bitcode::NUM_ASIN),
_ => { _ => {
unreachable!("Unrecognized int unary operation: {:?}", op); unreachable!("Unrecognized int unary operation: {:?}", op);
} }
@ -6078,7 +6107,7 @@ fn cxa_allocate_exception<'a, 'ctx, 'env>(
let context = env.context; let context = env.context;
let u8_ptr = context.i8_type().ptr_type(AddressSpace::Generic); let u8_ptr = context.i8_type().ptr_type(AddressSpace::Generic);
let function = match module.get_function(&name) { let function = match module.get_function(name) {
Some(gvalue) => gvalue, Some(gvalue) => gvalue,
None => { None => {
// void *__cxa_allocate_exception(size_t thrown_size); // void *__cxa_allocate_exception(size_t thrown_size);
@ -6112,7 +6141,7 @@ fn cxa_throw_exception<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>, info: BasicVal
let u8_ptr = context.i8_type().ptr_type(AddressSpace::Generic); let u8_ptr = context.i8_type().ptr_type(AddressSpace::Generic);
let function = match module.get_function(&name) { let function = match module.get_function(name) {
Some(value) => value, Some(value) => value,
None => { None => {
// void __cxa_throw (void *thrown_exception, std::type_info *tinfo, void (*dest) (void *) ); // void __cxa_throw (void *thrown_exception, std::type_info *tinfo, void (*dest) (void *) );
@ -6178,7 +6207,7 @@ fn get_gxx_personality_v0<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> Function
let module = env.module; let module = env.module;
let context = env.context; let context = env.context;
match module.get_function(&name) { match module.get_function(name) {
Some(gvalue) => gvalue, Some(gvalue) => gvalue,
None => { None => {
let personality_func = add_func( let personality_func = add_func(
@ -6200,7 +6229,7 @@ fn cxa_end_catch(env: &Env<'_, '_, '_>) {
let module = env.module; let module = env.module;
let context = env.context; let context = env.context;
let function = match module.get_function(&name) { let function = match module.get_function(name) {
Some(gvalue) => gvalue, Some(gvalue) => gvalue,
None => { None => {
let cxa_end_catch = add_func( let cxa_end_catch = add_func(
@ -6228,7 +6257,7 @@ fn cxa_begin_catch<'a, 'ctx, 'env>(
let module = env.module; let module = env.module;
let context = env.context; let context = env.context;
let function = match module.get_function(&name) { let function = match module.get_function(name) {
Some(gvalue) => gvalue, Some(gvalue) => gvalue,
None => { None => {
let u8_ptr = context.i8_type().ptr_type(AddressSpace::Generic); let u8_ptr = context.i8_type().ptr_type(AddressSpace::Generic);

View file

@ -64,7 +64,7 @@ pub fn dict_len<'a, 'ctx, 'env>(
.build_alloca(dict_as_zig_dict.get_type(), "dict_ptr"); .build_alloca(dict_as_zig_dict.get_type(), "dict_ptr");
env.builder.build_store(dict_ptr, dict_as_zig_dict); env.builder.build_store(dict_ptr, dict_as_zig_dict);
call_bitcode_fn(env, &[dict_ptr.into()], &bitcode::DICT_LEN) call_bitcode_fn(env, &[dict_ptr.into()], bitcode::DICT_LEN)
} }
Layout::Builtin(Builtin::EmptyDict) => ctx.i64_type().const_zero().into(), Layout::Builtin(Builtin::EmptyDict) => ctx.i64_type().const_zero().into(),
_ => unreachable!("Invalid layout given to Dict.len : {:?}", dict_layout), _ => unreachable!("Invalid layout given to Dict.len : {:?}", dict_layout),
@ -78,7 +78,7 @@ pub fn dict_empty<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> BasicValueEnum<'
// we must give a pointer for the bitcode function to write the result into // we must give a pointer for the bitcode function to write the result into
let result_alloc = env.builder.build_alloca(roc_dict_type, "dict_empty"); let result_alloc = env.builder.build_alloca(roc_dict_type, "dict_empty");
call_void_bitcode_fn(env, &[result_alloc.into()], &bitcode::DICT_EMPTY); call_void_bitcode_fn(env, &[result_alloc.into()], bitcode::DICT_EMPTY);
env.builder.build_load(result_alloc, "load_result") env.builder.build_load(result_alloc, "load_result")
} }
@ -140,7 +140,7 @@ pub fn dict_insert<'a, 'ctx, 'env>(
dec_value_fn.as_global_value().as_pointer_value().into(), dec_value_fn.as_global_value().as_pointer_value().into(),
result_ptr.into(), result_ptr.into(),
], ],
&bitcode::DICT_INSERT, bitcode::DICT_INSERT,
); );
env.builder.build_load(result_ptr, "load_result") env.builder.build_load(result_ptr, "load_result")
@ -199,7 +199,7 @@ pub fn dict_remove<'a, 'ctx, 'env>(
dec_value_fn.as_global_value().as_pointer_value().into(), dec_value_fn.as_global_value().as_pointer_value().into(),
result_ptr.into(), result_ptr.into(),
], ],
&bitcode::DICT_REMOVE, bitcode::DICT_REMOVE,
); );
env.builder.build_load(result_ptr, "load_result") env.builder.build_load(result_ptr, "load_result")
@ -250,7 +250,7 @@ pub fn dict_contains<'a, 'ctx, 'env>(
hash_fn.as_global_value().as_pointer_value().into(), hash_fn.as_global_value().as_pointer_value().into(),
eq_fn.as_global_value().as_pointer_value().into(), eq_fn.as_global_value().as_pointer_value().into(),
], ],
&bitcode::DICT_CONTAINS, bitcode::DICT_CONTAINS,
) )
} }
@ -303,7 +303,7 @@ pub fn dict_get<'a, 'ctx, 'env>(
eq_fn.as_global_value().as_pointer_value().into(), eq_fn.as_global_value().as_pointer_value().into(),
inc_value_fn.as_global_value().as_pointer_value().into(), inc_value_fn.as_global_value().as_pointer_value().into(),
], ],
&bitcode::DICT_GET, bitcode::DICT_GET,
) )
.into_struct_value(); .into_struct_value();
@ -415,7 +415,7 @@ pub fn dict_elements_rc<'a, 'ctx, 'env>(
key_fn.as_global_value().as_pointer_value().into(), key_fn.as_global_value().as_pointer_value().into(),
value_fn.as_global_value().as_pointer_value().into(), value_fn.as_global_value().as_pointer_value().into(),
], ],
&bitcode::DICT_ELEMENTS_RC, bitcode::DICT_ELEMENTS_RC,
); );
} }
@ -460,7 +460,7 @@ pub fn dict_keys<'a, 'ctx, 'env>(
inc_key_fn.as_global_value().as_pointer_value().into(), inc_key_fn.as_global_value().as_pointer_value().into(),
list_ptr.into(), list_ptr.into(),
], ],
&bitcode::DICT_KEYS, bitcode::DICT_KEYS,
); );
let list_ptr = env let list_ptr = env
@ -527,7 +527,7 @@ pub fn dict_union<'a, 'ctx, 'env>(
inc_value_fn.as_global_value().as_pointer_value().into(), inc_value_fn.as_global_value().as_pointer_value().into(),
output_ptr.into(), output_ptr.into(),
], ],
&bitcode::DICT_UNION, bitcode::DICT_UNION,
); );
env.builder.build_load(output_ptr, "load_output_ptr") env.builder.build_load(output_ptr, "load_output_ptr")
@ -549,7 +549,7 @@ pub fn dict_difference<'a, 'ctx, 'env>(
dict2, dict2,
key_layout, key_layout,
value_layout, value_layout,
&bitcode::DICT_DIFFERENCE, bitcode::DICT_DIFFERENCE,
) )
} }
@ -569,7 +569,7 @@ pub fn dict_intersection<'a, 'ctx, 'env>(
dict2, dict2,
key_layout, key_layout,
value_layout, value_layout,
&bitcode::DICT_INTERSECTION, bitcode::DICT_INTERSECTION,
) )
} }
@ -674,7 +674,7 @@ pub fn dict_walk<'a, 'ctx, 'env>(
layout_width(env, accum_layout), layout_width(env, accum_layout),
env.builder.build_bitcast(output_ptr, u8_ptr, "to_opaque"), env.builder.build_bitcast(output_ptr, u8_ptr, "to_opaque"),
], ],
&bitcode::DICT_WALK, bitcode::DICT_WALK,
); );
env.builder.build_load(output_ptr, "load_output_ptr") env.builder.build_load(output_ptr, "load_output_ptr")
@ -721,7 +721,7 @@ pub fn dict_values<'a, 'ctx, 'env>(
inc_value_fn.as_global_value().as_pointer_value().into(), inc_value_fn.as_global_value().as_pointer_value().into(),
list_ptr.into(), list_ptr.into(),
], ],
&bitcode::DICT_VALUES, bitcode::DICT_VALUES,
); );
let list_ptr = env let list_ptr = env
@ -784,7 +784,7 @@ pub fn set_from_list<'a, 'ctx, 'env>(
dec_key_fn.as_global_value().as_pointer_value().into(), dec_key_fn.as_global_value().as_pointer_value().into(),
result_alloca.into(), result_alloca.into(),
], ],
&bitcode::SET_FROM_LIST, bitcode::SET_FROM_LIST,
); );
env.builder.build_load(result_alloca, "load_result") env.builder.build_load(result_alloca, "load_result")
@ -800,7 +800,7 @@ fn build_hash_wrapper<'a, 'ctx, 'env>(
let symbol = Symbol::GENERIC_HASH_REF; let symbol = Symbol::GENERIC_HASH_REF;
let fn_name = layout_ids let fn_name = layout_ids
.get(symbol, &layout) .get(symbol, layout)
.to_symbol_string(symbol, &env.interns); .to_symbol_string(symbol, &env.interns);
let function_value = match env.module.get_function(fn_name.as_str()) { let function_value = match env.module.get_function(fn_name.as_str()) {
@ -867,8 +867,7 @@ fn dict_symbol_to_zig_dict<'a, 'ctx, 'env>(
let zig_dict_type = env.module.get_struct_type("dict.RocDict").unwrap(); let zig_dict_type = env.module.get_struct_type("dict.RocDict").unwrap();
complex_bitcast(&env.builder, dict, zig_dict_type.into(), "dict_to_zig_dict") complex_bitcast(env.builder, dict, zig_dict_type.into(), "dict_to_zig_dict").into_struct_value()
.into_struct_value()
} }
fn zig_dict_type<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> inkwell::types::StructType<'ctx> { fn zig_dict_type<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> inkwell::types::StructType<'ctx> {

View file

@ -128,8 +128,9 @@ fn hash_builtin<'a, 'ctx, 'env>(
| Builtin::Float32 | Builtin::Float32
| Builtin::Float128 | Builtin::Float128
| Builtin::Float16 | Builtin::Float16
| Builtin::Decimal
| Builtin::Usize => { | Builtin::Usize => {
let hash_bytes = store_and_use_as_u8_ptr(env, val, &layout); let hash_bytes = store_and_use_as_u8_ptr(env, val, layout);
hash_bitcode_fn(env, seed, hash_bytes, layout.stack_size(ptr_bytes)) hash_bitcode_fn(env, seed, hash_bytes, layout.stack_size(ptr_bytes))
} }
Builtin::Str => { Builtin::Str => {
@ -137,7 +138,7 @@ fn hash_builtin<'a, 'ctx, 'env>(
call_bitcode_fn( call_bitcode_fn(
env, env,
&[seed.into(), build_str::str_to_i128(env, val).into()], &[seed.into(), build_str::str_to_i128(env, val).into()],
&bitcode::DICT_HASH_STR, bitcode::DICT_HASH_STR,
) )
.into_int_value() .into_int_value()
} }
@ -326,7 +327,7 @@ fn build_hash_tag<'a, 'ctx, 'env>(
let symbol = Symbol::GENERIC_HASH; let symbol = Symbol::GENERIC_HASH;
let fn_name = layout_ids let fn_name = layout_ids
.get(symbol, &layout) .get(symbol, layout)
.to_symbol_string(symbol, &env.interns); .to_symbol_string(symbol, &env.interns);
let function = match env.module.get_function(fn_name.as_str()) { let function = match env.module.get_function(fn_name.as_str()) {
@ -334,7 +335,7 @@ fn build_hash_tag<'a, 'ctx, 'env>(
None => { None => {
let seed_type = env.context.i64_type(); let seed_type = env.context.i64_type();
let arg_type = basic_type_from_layout(env, &layout); let arg_type = basic_type_from_layout(env, layout);
let function_value = crate::llvm::refcounting::build_header_help( let function_value = crate::llvm::refcounting::build_header_help(
env, env,
@ -658,7 +659,7 @@ fn build_hash_list<'a, 'ctx, 'env>(
let symbol = Symbol::GENERIC_HASH; let symbol = Symbol::GENERIC_HASH;
let fn_name = layout_ids let fn_name = layout_ids
.get(symbol, &layout) .get(symbol, layout)
.to_symbol_string(symbol, &env.interns); .to_symbol_string(symbol, &env.interns);
let function = match env.module.get_function(fn_name.as_str()) { let function = match env.module.get_function(fn_name.as_str()) {
@ -666,7 +667,7 @@ fn build_hash_list<'a, 'ctx, 'env>(
None => { None => {
let seed_type = env.context.i64_type(); let seed_type = env.context.i64_type();
let arg_type = basic_type_from_layout(env, &layout); let arg_type = basic_type_from_layout(env, layout);
let function_value = crate::llvm::refcounting::build_header_help( let function_value = crate::llvm::refcounting::build_header_help(
env, env,
@ -869,7 +870,7 @@ fn store_and_use_as_u8_ptr<'a, 'ctx, 'env>(
value: BasicValueEnum<'ctx>, value: BasicValueEnum<'ctx>,
layout: &Layout<'a>, layout: &Layout<'a>,
) -> PointerValue<'ctx> { ) -> PointerValue<'ctx> {
let basic_type = basic_type_from_layout(env, &layout); let basic_type = basic_type_from_layout(env, layout);
let alloc = env.builder.build_alloca(basic_type, "store"); let alloc = env.builder.build_alloca(basic_type, "store");
env.builder.build_store(alloc, value); env.builder.build_store(alloc, value);
@ -894,7 +895,7 @@ fn hash_bitcode_fn<'a, 'ctx, 'env>(
call_bitcode_fn( call_bitcode_fn(
env, env,
&[seed.into(), buffer.into(), num_bytes.into()], &[seed.into(), buffer.into(), num_bytes.into()],
&bitcode::DICT_HASH, bitcode::DICT_HASH,
) )
.into_int_value() .into_int_value()
} }

View file

@ -94,7 +94,7 @@ pub fn list_single<'a, 'ctx, 'env>(
pass_element_as_opaque(env, element), pass_element_as_opaque(env, element),
layout_width(env, element_layout), layout_width(env, element_layout),
], ],
&bitcode::LIST_SINGLE, bitcode::LIST_SINGLE,
) )
} }
@ -129,7 +129,6 @@ pub fn list_prepend<'a, 'ctx, 'env>(
elem_layout: &Layout<'a>, elem_layout: &Layout<'a>,
) -> BasicValueEnum<'ctx> { ) -> BasicValueEnum<'ctx> {
let builder = env.builder; let builder = env.builder;
let ctx = env.context;
// Load the usize length from the wrapper. // Load the usize length from the wrapper.
let len = list_len(builder, original_wrapper); let len = list_len(builder, original_wrapper);
@ -139,7 +138,7 @@ pub fn list_prepend<'a, 'ctx, 'env>(
// The output list length, which is the old list length + 1 // The output list length, which is the old list length + 1
let new_list_len = env.builder.build_int_add( let new_list_len = env.builder.build_int_add(
ctx.i64_type().const_int(1_u64, false), env.ptr_int().const_int(1_u64, false),
len, len,
"new_list_length", "new_list_length",
); );
@ -152,7 +151,7 @@ pub fn list_prepend<'a, 'ctx, 'env>(
let index_1_ptr = unsafe { let index_1_ptr = unsafe {
builder.build_in_bounds_gep( builder.build_in_bounds_gep(
clone_ptr, clone_ptr,
&[ctx.i64_type().const_int(1_u64, false)], &[env.ptr_int().const_int(1_u64, false)],
"load_index", "load_index",
) )
}; };
@ -207,7 +206,7 @@ pub fn list_join<'a, 'ctx, 'env>(
env.alignment_intvalue(element_layout), env.alignment_intvalue(element_layout),
layout_width(env, element_layout), layout_width(env, element_layout),
], ],
&bitcode::LIST_JOIN, bitcode::LIST_JOIN,
) )
} }
_ => { _ => {
@ -240,7 +239,7 @@ pub fn list_reverse<'a, 'ctx, 'env>(
env.alignment_intvalue(&element_layout), env.alignment_intvalue(&element_layout),
layout_width(env, &element_layout), layout_width(env, &element_layout),
], ],
&bitcode::LIST_REVERSE, bitcode::LIST_REVERSE,
) )
} }
@ -292,11 +291,11 @@ pub fn list_append<'a, 'ctx, 'env>(
env, env,
&[ &[
pass_list_as_i128(env, original_wrapper.into()), pass_list_as_i128(env, original_wrapper.into()),
env.alignment_intvalue(&element_layout), env.alignment_intvalue(element_layout),
pass_element_as_opaque(env, element), pass_element_as_opaque(env, element),
layout_width(env, element_layout), layout_width(env, element_layout),
], ],
&bitcode::LIST_APPEND, bitcode::LIST_APPEND,
) )
} }
@ -312,12 +311,12 @@ pub fn list_swap<'a, 'ctx, 'env>(
env, env,
&[ &[
pass_list_as_i128(env, original_wrapper.into()), pass_list_as_i128(env, original_wrapper.into()),
env.alignment_intvalue(&element_layout), env.alignment_intvalue(element_layout),
layout_width(env, &element_layout), layout_width(env, element_layout),
index_1.into(), index_1.into(),
index_2.into(), index_2.into(),
], ],
&bitcode::LIST_SWAP, bitcode::LIST_SWAP,
) )
} }
@ -329,17 +328,17 @@ pub fn list_drop<'a, 'ctx, 'env>(
count: IntValue<'ctx>, count: IntValue<'ctx>,
element_layout: &Layout<'a>, element_layout: &Layout<'a>,
) -> BasicValueEnum<'ctx> { ) -> BasicValueEnum<'ctx> {
let dec_element_fn = build_dec_wrapper(env, layout_ids, &element_layout); let dec_element_fn = build_dec_wrapper(env, layout_ids, element_layout);
call_bitcode_fn_returns_list( call_bitcode_fn_returns_list(
env, env,
&[ &[
pass_list_as_i128(env, original_wrapper.into()), pass_list_as_i128(env, original_wrapper.into()),
env.alignment_intvalue(&element_layout), env.alignment_intvalue(element_layout),
layout_width(env, &element_layout), layout_width(env, element_layout),
count.into(), count.into(),
dec_element_fn.as_global_value().as_pointer_value().into(), dec_element_fn.as_global_value().as_pointer_value().into(),
], ],
&bitcode::LIST_DROP, bitcode::LIST_DROP,
) )
} }
@ -378,7 +377,7 @@ pub fn list_set<'a, 'ctx, 'env>(
&[ &[
bytes.into(), bytes.into(),
length.into(), length.into(),
env.alignment_intvalue(&element_layout), env.alignment_intvalue(element_layout),
index.into(), index.into(),
pass_element_as_opaque(env, element), pass_element_as_opaque(env, element),
layout_width(env, element_layout), layout_width(env, element_layout),
@ -457,7 +456,7 @@ pub fn list_walk_generic<'a, 'ctx, 'env>(
roc_function_call.inc_n_data.into(), roc_function_call.inc_n_data.into(),
roc_function_call.data_is_owned.into(), roc_function_call.data_is_owned.into(),
pass_as_opaque(env, default_ptr), pass_as_opaque(env, default_ptr),
env.alignment_intvalue(&element_layout), env.alignment_intvalue(element_layout),
layout_width(env, element_layout), layout_width(env, element_layout),
layout_width(env, default_layout), layout_width(env, default_layout),
pass_as_opaque(env, result_ptr), pass_as_opaque(env, result_ptr),
@ -488,7 +487,7 @@ pub fn list_walk_generic<'a, 'ctx, 'env>(
roc_function_call.inc_n_data.into(), roc_function_call.inc_n_data.into(),
roc_function_call.data_is_owned.into(), roc_function_call.data_is_owned.into(),
pass_as_opaque(env, default_ptr), pass_as_opaque(env, default_ptr),
env.alignment_intvalue(&element_layout), env.alignment_intvalue(element_layout),
layout_width(env, element_layout), layout_width(env, element_layout),
layout_width(env, function_call_return_layout), layout_width(env, function_call_return_layout),
layout_width(env, default_layout), layout_width(env, default_layout),
@ -564,7 +563,7 @@ pub fn list_range<'a, 'ctx, 'env>(
pass_as_opaque(env, low_ptr), pass_as_opaque(env, low_ptr),
pass_as_opaque(env, high_ptr), pass_as_opaque(env, high_ptr),
], ],
&bitcode::LIST_RANGE, bitcode::LIST_RANGE,
) )
} }
@ -612,12 +611,12 @@ pub fn list_keep_if<'a, 'ctx, 'env>(
pass_as_opaque(env, roc_function_call.data), pass_as_opaque(env, roc_function_call.data),
roc_function_call.inc_n_data.into(), roc_function_call.inc_n_data.into(),
roc_function_call.data_is_owned.into(), roc_function_call.data_is_owned.into(),
env.alignment_intvalue(&element_layout), env.alignment_intvalue(element_layout),
layout_width(env, element_layout), layout_width(env, element_layout),
inc_element_fn.as_global_value().as_pointer_value().into(), inc_element_fn.as_global_value().as_pointer_value().into(),
dec_element_fn.as_global_value().as_pointer_value().into(), dec_element_fn.as_global_value().as_pointer_value().into(),
], ],
&bitcode::LIST_KEEP_IF, bitcode::LIST_KEEP_IF,
) )
} }
@ -654,7 +653,7 @@ pub fn list_keep_oks<'a, 'ctx, 'env>(
pass_as_opaque(env, roc_function_call.data), pass_as_opaque(env, roc_function_call.data),
roc_function_call.inc_n_data.into(), roc_function_call.inc_n_data.into(),
roc_function_call.data_is_owned.into(), roc_function_call.data_is_owned.into(),
env.alignment_intvalue(&before_layout), env.alignment_intvalue(before_layout),
layout_width(env, before_layout), layout_width(env, before_layout),
layout_width(env, result_layout), layout_width(env, result_layout),
layout_width(env, after_layout), layout_width(env, after_layout),
@ -698,7 +697,7 @@ pub fn list_keep_errs<'a, 'ctx, 'env>(
pass_as_opaque(env, roc_function_call.data), pass_as_opaque(env, roc_function_call.data),
roc_function_call.inc_n_data.into(), roc_function_call.inc_n_data.into(),
roc_function_call.data_is_owned.into(), roc_function_call.data_is_owned.into(),
env.alignment_intvalue(&before_layout), env.alignment_intvalue(before_layout),
layout_width(env, before_layout), layout_width(env, before_layout),
layout_width(env, result_layout), layout_width(env, result_layout),
layout_width(env, after_layout), layout_width(env, after_layout),
@ -725,7 +724,7 @@ pub fn list_sort_with<'a, 'ctx, 'env>(
pass_as_opaque(env, roc_function_call.data), pass_as_opaque(env, roc_function_call.data),
roc_function_call.inc_n_data.into(), roc_function_call.inc_n_data.into(),
roc_function_call.data_is_owned.into(), roc_function_call.data_is_owned.into(),
env.alignment_intvalue(&element_layout), env.alignment_intvalue(element_layout),
layout_width(env, element_layout), layout_width(env, element_layout),
], ],
bitcode::LIST_SORT_WITH, bitcode::LIST_SORT_WITH,
@ -748,7 +747,7 @@ pub fn list_map_with_index<'a, 'ctx, 'env>(
pass_as_opaque(env, roc_function_call.data), pass_as_opaque(env, roc_function_call.data),
roc_function_call.inc_n_data.into(), roc_function_call.inc_n_data.into(),
roc_function_call.data_is_owned.into(), roc_function_call.data_is_owned.into(),
env.alignment_intvalue(&element_layout), env.alignment_intvalue(element_layout),
layout_width(env, element_layout), layout_width(env, element_layout),
layout_width(env, return_layout), layout_width(env, return_layout),
], ],
@ -772,7 +771,7 @@ pub fn list_map<'a, 'ctx, 'env>(
pass_as_opaque(env, roc_function_call.data), pass_as_opaque(env, roc_function_call.data),
roc_function_call.inc_n_data.into(), roc_function_call.inc_n_data.into(),
roc_function_call.data_is_owned.into(), roc_function_call.data_is_owned.into(),
env.alignment_intvalue(&element_layout), env.alignment_intvalue(element_layout),
layout_width(env, element_layout), layout_width(env, element_layout),
layout_width(env, return_layout), layout_width(env, return_layout),
], ],
@ -874,7 +873,7 @@ pub fn list_concat<'a, 'ctx, 'env>(
env.alignment_intvalue(elem_layout), env.alignment_intvalue(elem_layout),
layout_width(env, elem_layout), layout_width(env, elem_layout),
], ],
&bitcode::LIST_CONCAT, bitcode::LIST_CONCAT,
), ),
_ => { _ => {
unreachable!("Invalid List layout for List.concat {:?}", list_layout); unreachable!("Invalid List layout for List.concat {:?}", list_layout);

View file

@ -27,7 +27,7 @@ pub fn str_split<'a, 'ctx, 'env>(
let segment_count = call_bitcode_fn( let segment_count = call_bitcode_fn(
env, env,
&[str_i128.into(), delim_i128.into()], &[str_i128.into(), delim_i128.into()],
&bitcode::STR_COUNT_SEGMENTS, bitcode::STR_COUNT_SEGMENTS,
) )
.into_int_value(); .into_int_value();
@ -47,7 +47,7 @@ pub fn str_split<'a, 'ctx, 'env>(
call_void_bitcode_fn( call_void_bitcode_fn(
env, env,
&[ret_list_ptr_zig_rocstr, str_i128.into(), delim_i128.into()], &[ret_list_ptr_zig_rocstr, str_i128.into(), delim_i128.into()],
&bitcode::STR_STR_SPLIT_IN_PLACE, bitcode::STR_STR_SPLIT_IN_PLACE,
); );
store_list(env, ret_list_ptr, segment_count) store_list(env, ret_list_ptr, segment_count)
@ -62,7 +62,7 @@ fn str_symbol_to_i128<'a, 'ctx, 'env>(
let i128_type = env.context.i128_type().into(); let i128_type = env.context.i128_type().into();
complex_bitcast(&env.builder, string, i128_type, "str_to_i128").into_int_value() complex_bitcast(env.builder, string, i128_type, "str_to_i128").into_int_value()
} }
pub fn str_to_i128<'a, 'ctx, 'env>( pub fn str_to_i128<'a, 'ctx, 'env>(
@ -119,7 +119,7 @@ pub fn str_concat<'a, 'ctx, 'env>(
call_bitcode_fn( call_bitcode_fn(
env, env,
&[str1_i128.into(), str2_i128.into()], &[str1_i128.into(), str2_i128.into()],
&bitcode::STR_CONCAT, bitcode::STR_CONCAT,
) )
} }
@ -138,7 +138,7 @@ pub fn str_join_with<'a, 'ctx, 'env>(
call_bitcode_fn( call_bitcode_fn(
env, env,
&[list_i128.into(), str_i128.into()], &[list_i128.into(), str_i128.into()],
&bitcode::STR_JOIN_WITH, bitcode::STR_JOIN_WITH,
) )
} }
@ -151,7 +151,7 @@ pub fn str_number_of_bytes<'a, 'ctx, 'env>(
// the builtin will always return an u64 // the builtin will always return an u64
let length = let length =
call_bitcode_fn(env, &[str_i128.into()], &bitcode::STR_NUMBER_OF_BYTES).into_int_value(); call_bitcode_fn(env, &[str_i128.into()], bitcode::STR_NUMBER_OF_BYTES).into_int_value();
// cast to the appropriate usize of the current build // cast to the appropriate usize of the current build
env.builder env.builder
@ -171,7 +171,7 @@ pub fn str_starts_with<'a, 'ctx, 'env>(
call_bitcode_fn( call_bitcode_fn(
env, env,
&[str_i128.into(), prefix_i128.into()], &[str_i128.into(), prefix_i128.into()],
&bitcode::STR_STARTS_WITH, bitcode::STR_STARTS_WITH,
) )
} }
@ -188,7 +188,7 @@ pub fn str_starts_with_code_point<'a, 'ctx, 'env>(
call_bitcode_fn( call_bitcode_fn(
env, env,
&[str_i128.into(), prefix], &[str_i128.into(), prefix],
&bitcode::STR_STARTS_WITH_CODE_POINT, bitcode::STR_STARTS_WITH_CODE_POINT,
) )
} }
@ -205,7 +205,7 @@ pub fn str_ends_with<'a, 'ctx, 'env>(
call_bitcode_fn( call_bitcode_fn(
env, env,
&[str_i128.into(), prefix_i128.into()], &[str_i128.into(), prefix_i128.into()],
&bitcode::STR_ENDS_WITH, bitcode::STR_ENDS_WITH,
) )
} }
@ -220,7 +220,7 @@ pub fn str_count_graphemes<'a, 'ctx, 'env>(
call_bitcode_fn( call_bitcode_fn(
env, env,
&[str_i128.into()], &[str_i128.into()],
&bitcode::STR_COUNT_GRAPEHEME_CLUSTERS, bitcode::STR_COUNT_GRAPEHEME_CLUSTERS,
) )
} }
@ -232,7 +232,7 @@ pub fn str_from_int<'a, 'ctx, 'env>(
) -> BasicValueEnum<'ctx> { ) -> BasicValueEnum<'ctx> {
let int = load_symbol(scope, &int_symbol); let int = load_symbol(scope, &int_symbol);
call_bitcode_fn(env, &[int], &bitcode::STR_FROM_INT) call_bitcode_fn(env, &[int], bitcode::STR_FROM_INT)
} }
/// Str.toBytes : Str -> List U8 /// Str.toBytes : Str -> List U8
@ -247,7 +247,7 @@ pub fn str_to_bytes<'a, 'ctx, 'env>(
"to_bytes", "to_bytes",
); );
call_bitcode_fn_returns_list(env, &[string], &bitcode::STR_TO_BYTES) call_bitcode_fn_returns_list(env, &[string], bitcode::STR_TO_BYTES)
} }
/// Str.fromUtf8 : List U8 -> { a : Bool, b : Str, c : Nat, d : I8 } /// Str.fromUtf8 : List U8 -> { a : Bool, b : Str, c : Nat, d : I8 }
@ -273,7 +273,7 @@ pub fn str_from_utf8<'a, 'ctx, 'env>(
), ),
result_ptr.into(), result_ptr.into(),
], ],
&bitcode::STR_FROM_UTF8, bitcode::STR_FROM_UTF8,
); );
let record_type = env.context.struct_type( let record_type = env.context.struct_type(
@ -306,7 +306,7 @@ pub fn str_from_float<'a, 'ctx, 'env>(
) -> BasicValueEnum<'ctx> { ) -> BasicValueEnum<'ctx> {
let float = load_symbol(scope, &int_symbol); let float = load_symbol(scope, &int_symbol);
call_bitcode_fn(env, &[float], &bitcode::STR_FROM_FLOAT) call_bitcode_fn(env, &[float], bitcode::STR_FROM_FLOAT)
} }
/// Str.equal : Str, Str -> Bool /// Str.equal : Str, Str -> Bool
@ -321,7 +321,7 @@ pub fn str_equal<'a, 'ctx, 'env>(
call_bitcode_fn( call_bitcode_fn(
env, env,
&[str1_i128.into(), str2_i128.into()], &[str1_i128.into(), str2_i128.into()],
&bitcode::STR_EQUAL, bitcode::STR_EQUAL,
) )
} }

View file

@ -1,5 +1,7 @@
use crate::llvm::build::{cast_block_of_memory_to_tag, get_tag_id, FAST_CALL_CONV}; use crate::llvm::bitcode::call_bitcode_fn;
use crate::llvm::build::{tag_pointer_clear_tag_id, Env}; use crate::llvm::build::{
cast_block_of_memory_to_tag, get_tag_id, tag_pointer_clear_tag_id, Env, FAST_CALL_CONV,
};
use crate::llvm::build_list::{list_len, load_list_ptr}; use crate::llvm::build_list::{list_len, load_list_ptr};
use crate::llvm::build_str::str_equal; use crate::llvm::build_str::str_equal;
use crate::llvm::convert::basic_type_from_layout; use crate::llvm::convert::basic_type_from_layout;
@ -9,6 +11,7 @@ use inkwell::values::{
BasicValue, BasicValueEnum, FunctionValue, IntValue, PointerValue, StructValue, BasicValue, BasicValueEnum, FunctionValue, IntValue, PointerValue, StructValue,
}; };
use inkwell::{AddressSpace, FloatPredicate, IntPredicate}; use inkwell::{AddressSpace, FloatPredicate, IntPredicate};
use roc_builtins::bitcode;
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use roc_mono::layout::{Builtin, Layout, LayoutIds, UnionLayout}; use roc_mono::layout::{Builtin, Layout, LayoutIds, UnionLayout};
@ -96,6 +99,7 @@ fn build_eq_builtin<'a, 'ctx, 'env>(
Builtin::Usize => int_cmp(IntPredicate::EQ, "eq_usize"), Builtin::Usize => int_cmp(IntPredicate::EQ, "eq_usize"),
Builtin::Decimal => call_bitcode_fn(env, &[lhs_val, rhs_val], bitcode::DEC_EQ),
Builtin::Float128 => float_cmp(FloatPredicate::OEQ, "eq_f128"), Builtin::Float128 => float_cmp(FloatPredicate::OEQ, "eq_f128"),
Builtin::Float64 => float_cmp(FloatPredicate::OEQ, "eq_f64"), Builtin::Float64 => float_cmp(FloatPredicate::OEQ, "eq_f64"),
Builtin::Float32 => float_cmp(FloatPredicate::OEQ, "eq_f32"), Builtin::Float32 => float_cmp(FloatPredicate::OEQ, "eq_f32"),
@ -241,6 +245,7 @@ fn build_neq_builtin<'a, 'ctx, 'env>(
Builtin::Usize => int_cmp(IntPredicate::NE, "neq_usize"), Builtin::Usize => int_cmp(IntPredicate::NE, "neq_usize"),
Builtin::Decimal => call_bitcode_fn(env, &[lhs_val, rhs_val], bitcode::DEC_NEQ),
Builtin::Float128 => float_cmp(FloatPredicate::ONE, "neq_f128"), Builtin::Float128 => float_cmp(FloatPredicate::ONE, "neq_f128"),
Builtin::Float64 => float_cmp(FloatPredicate::ONE, "neq_f64"), Builtin::Float64 => float_cmp(FloatPredicate::ONE, "neq_f64"),
Builtin::Float32 => float_cmp(FloatPredicate::ONE, "neq_f32"), Builtin::Float32 => float_cmp(FloatPredicate::ONE, "neq_f32"),
@ -356,13 +361,13 @@ fn build_list_eq<'a, 'ctx, 'env>(
let symbol = Symbol::LIST_EQ; let symbol = Symbol::LIST_EQ;
let fn_name = layout_ids let fn_name = layout_ids
.get(symbol, &element_layout) .get(symbol, element_layout)
.to_symbol_string(symbol, &env.interns); .to_symbol_string(symbol, &env.interns);
let function = match env.module.get_function(fn_name.as_str()) { let function = match env.module.get_function(fn_name.as_str()) {
Some(function_value) => function_value, Some(function_value) => function_value,
None => { None => {
let arg_type = basic_type_from_layout(env, &list_layout); let arg_type = basic_type_from_layout(env, list_layout);
let function_value = crate::llvm::refcounting::build_header_help( let function_value = crate::llvm::refcounting::build_header_help(
env, env,
@ -423,7 +428,7 @@ fn build_list_eq_help<'a, 'ctx, 'env>(
/* current_scope */ lexical_block.as_debug_info_scope(), /* current_scope */ lexical_block.as_debug_info_scope(),
/* inlined_at */ None, /* inlined_at */ None,
); );
builder.set_current_debug_location(&ctx, loc); builder.set_current_debug_location(ctx, loc);
} }
// Add args to scope // Add args to scope
@ -631,7 +636,7 @@ fn build_struct_eq_help<'a, 'ctx, 'env>(
/* current_scope */ lexical_block.as_debug_info_scope(), /* current_scope */ lexical_block.as_debug_info_scope(),
/* inlined_at */ None, /* inlined_at */ None,
); );
builder.set_current_debug_location(&ctx, loc); builder.set_current_debug_location(ctx, loc);
} }
// Add args to scope // Add args to scope
@ -747,13 +752,13 @@ fn build_tag_eq<'a, 'ctx, 'env>(
let symbol = Symbol::GENERIC_EQ; let symbol = Symbol::GENERIC_EQ;
let fn_name = layout_ids let fn_name = layout_ids
.get(symbol, &tag_layout) .get(symbol, tag_layout)
.to_symbol_string(symbol, &env.interns); .to_symbol_string(symbol, &env.interns);
let function = match env.module.get_function(fn_name.as_str()) { let function = match env.module.get_function(fn_name.as_str()) {
Some(function_value) => function_value, Some(function_value) => function_value,
None => { None => {
let arg_type = basic_type_from_layout(env, &tag_layout); let arg_type = basic_type_from_layout(env, tag_layout);
let function_value = crate::llvm::refcounting::build_header_help( let function_value = crate::llvm::refcounting::build_header_help(
env, env,
@ -812,7 +817,7 @@ fn build_tag_eq_help<'a, 'ctx, 'env>(
/* current_scope */ lexical_block.as_debug_info_scope(), /* current_scope */ lexical_block.as_debug_info_scope(),
/* inlined_at */ None, /* inlined_at */ None,
); );
builder.set_current_debug_location(&ctx, loc); builder.set_current_debug_location(ctx, loc);
} }
// Add args to scope // Add args to scope

View file

@ -58,8 +58,7 @@ pub fn basic_type_from_layout<'a, 'ctx, 'env>(
} }
} }
NullableUnwrapped { other_fields, .. } => { NullableUnwrapped { other_fields, .. } => {
let block = let block = block_of_memory_slices(env.context, &[other_fields], env.ptr_bytes);
block_of_memory_slices(env.context, &[&other_fields], env.ptr_bytes);
block.ptr_type(AddressSpace::Generic).into() block.ptr_type(AddressSpace::Generic).into()
} }
NonNullableUnwrapped(fields) => { NonNullableUnwrapped(fields) => {
@ -97,6 +96,7 @@ pub fn basic_type_from_builtin<'a, 'ctx, 'env>(
Int8 => context.i8_type().as_basic_type_enum(), Int8 => context.i8_type().as_basic_type_enum(),
Int1 => context.bool_type().as_basic_type_enum(), Int1 => context.bool_type().as_basic_type_enum(),
Usize => ptr_int(context, ptr_bytes).as_basic_type_enum(), Usize => ptr_int(context, ptr_bytes).as_basic_type_enum(),
Decimal => context.i128_type().as_basic_type_enum(),
Float128 => context.f128_type().as_basic_type_enum(), Float128 => context.f128_type().as_basic_type_enum(),
Float64 => context.f64_type().as_basic_type_enum(), Float64 => context.f64_type().as_basic_type_enum(),
Float32 => context.f32_type().as_basic_type_enum(), Float32 => context.f32_type().as_basic_type_enum(),
@ -221,3 +221,11 @@ pub fn zig_has_tag_id_type<'a, 'ctx, 'env>(
) -> StructType<'ctx> { ) -> StructType<'ctx> {
env.module.get_struct_type("list.HasTagId").unwrap() env.module.get_struct_type("list.HasTagId").unwrap()
} }
pub fn zig_with_overflow_roc_dec<'a, 'ctx, 'env>(
env: &crate::llvm::build::Env<'a, 'ctx, 'env>,
) -> StructType<'ctx> {
env.module
.get_struct_type("utils.WithOverflow(dec.RocDec)")
.unwrap()
}

View file

@ -720,14 +720,14 @@ fn modify_refcount_list<'a, 'ctx, 'env>(
&env.interns, &env.interns,
"increment_list", "increment_list",
"decrement_list", "decrement_list",
&layout, layout,
mode, mode,
); );
let function = match env.module.get_function(fn_name.as_str()) { let function = match env.module.get_function(fn_name.as_str()) {
Some(function_value) => function_value, Some(function_value) => function_value,
None => { None => {
let basic_type = basic_type_from_layout(env, &layout); let basic_type = basic_type_from_layout(env, layout);
let function_value = build_header(env, basic_type, mode, &fn_name); let function_value = build_header(env, basic_type, mode, &fn_name);
modify_refcount_list_help( modify_refcount_list_help(
@ -857,14 +857,14 @@ fn modify_refcount_str<'a, 'ctx, 'env>(
&env.interns, &env.interns,
"increment_str", "increment_str",
"decrement_str", "decrement_str",
&layout, layout,
mode, mode,
); );
let function = match env.module.get_function(fn_name.as_str()) { let function = match env.module.get_function(fn_name.as_str()) {
Some(function_value) => function_value, Some(function_value) => function_value,
None => { None => {
let basic_type = basic_type_from_layout(env, &layout); let basic_type = basic_type_from_layout(env, layout);
let function_value = build_header(env, basic_type, mode, &fn_name); let function_value = build_header(env, basic_type, mode, &fn_name);
modify_refcount_str_help(env, mode, layout, function_value); modify_refcount_str_help(env, mode, layout, function_value);
@ -956,14 +956,14 @@ fn modify_refcount_dict<'a, 'ctx, 'env>(
&env.interns, &env.interns,
"increment_dict", "increment_dict",
"decrement_dict", "decrement_dict",
&layout, layout,
mode, mode,
); );
let function = match env.module.get_function(fn_name.as_str()) { let function = match env.module.get_function(fn_name.as_str()) {
Some(function_value) => function_value, Some(function_value) => function_value,
None => { None => {
let basic_type = basic_type_from_layout(env, &layout); let basic_type = basic_type_from_layout(env, layout);
let function_value = build_header(env, basic_type, mode, &fn_name); let function_value = build_header(env, basic_type, mode, &fn_name);
modify_refcount_dict_help( modify_refcount_dict_help(
@ -1118,7 +1118,7 @@ pub fn build_header_help<'a, 'ctx, 'env>(
FAST_CALL_CONV, // Because it's an internal-only function, it should use the fast calling convention. FAST_CALL_CONV, // Because it's an internal-only function, it should use the fast calling convention.
); );
let subprogram = env.new_subprogram(&fn_name); let subprogram = env.new_subprogram(fn_name);
fn_val.set_subprogram(subprogram); fn_val.set_subprogram(subprogram);
env.dibuilder.finalize(); env.dibuilder.finalize();

View file

@ -148,7 +148,7 @@ fn generate_entry_doc<'a>(
match def { match def {
Def::SpaceBefore(sub_def, comments_or_new_lines) => { Def::SpaceBefore(sub_def, comments_or_new_lines) => {
// Comments before a definition are attached to the current defition // Comments before a definition are attached to the current definition
for detached_doc in detached_docs_from_comments_and_new_lines(comments_or_new_lines) { for detached_doc in detached_docs_from_comments_and_new_lines(comments_or_new_lines) {
acc.push(DetachedDoc(detached_doc)); acc.push(DetachedDoc(detached_doc));

View file

@ -225,7 +225,7 @@ impl<'a> Dependencies<'a> {
if let Some(to_notify) = self.notifies.get(&key) { if let Some(to_notify) = self.notifies.get(&key) {
for notify_key in to_notify { for notify_key in to_notify {
let mut is_empty = false; let mut is_empty = false;
if let Some(waiting_for_pairs) = self.waiting_for.get_mut(&notify_key) { if let Some(waiting_for_pairs) = self.waiting_for.get_mut(notify_key) {
waiting_for_pairs.remove(&key); waiting_for_pairs.remove(&key);
is_empty = waiting_for_pairs.is_empty(); is_empty = waiting_for_pairs.is_empty();
} }
@ -469,7 +469,7 @@ fn start_phase<'a>(
for dep_id in deps_by_name.values() { for dep_id in deps_by_name.values() {
// We already verified that these are all present, // We already verified that these are all present,
// so unwrapping should always succeed here. // so unwrapping should always succeed here.
let idents = ident_ids_by_module.get(&dep_id).unwrap(); let idents = ident_ids_by_module.get(dep_id).unwrap();
dep_idents.insert(*dep_id, idents.clone()); dep_idents.insert(*dep_id, idents.clone());
} }
@ -524,6 +524,7 @@ fn start_phase<'a>(
var_store, var_store,
imported_modules, imported_modules,
declarations, declarations,
dep_idents,
.. ..
} = constrained; } = constrained;
@ -535,7 +536,8 @@ fn start_phase<'a>(
var_store, var_store,
imported_modules, imported_modules,
&mut state.exposed_types, &mut state.exposed_types,
&state.stdlib, state.stdlib,
dep_idents,
declarations, declarations,
) )
} }
@ -621,6 +623,7 @@ pub struct LoadedModule {
pub type_problems: MutMap<ModuleId, Vec<solve::TypeError>>, pub type_problems: MutMap<ModuleId, Vec<solve::TypeError>>,
pub declarations_by_id: MutMap<ModuleId, Vec<Declaration>>, pub declarations_by_id: MutMap<ModuleId, Vec<Declaration>>,
pub exposed_to_host: MutMap<Symbol, Variable>, pub exposed_to_host: MutMap<Symbol, Variable>,
pub dep_idents: MutMap<ModuleId, IdentIds>,
pub exposed_aliases: MutMap<Symbol, Alias>, pub exposed_aliases: MutMap<Symbol, Alias>,
pub exposed_values: Vec<Symbol>, pub exposed_values: Vec<Symbol>,
pub header_sources: MutMap<ModuleId, (PathBuf, Box<str>)>, pub header_sources: MutMap<ModuleId, (PathBuf, Box<str>)>,
@ -676,6 +679,7 @@ struct ConstrainedModule {
constraint: Constraint, constraint: Constraint,
ident_ids: IdentIds, ident_ids: IdentIds,
var_store: VarStore, var_store: VarStore,
dep_idents: MutMap<ModuleId, IdentIds>,
module_timing: ModuleTiming, module_timing: ModuleTiming,
} }
@ -759,6 +763,7 @@ enum Msg<'a> {
solved_module: SolvedModule, solved_module: SolvedModule,
solved_subs: Solved<Subs>, solved_subs: Solved<Subs>,
decls: Vec<Declaration>, decls: Vec<Declaration>,
dep_idents: MutMap<ModuleId, IdentIds>,
module_timing: ModuleTiming, module_timing: ModuleTiming,
unused_imports: MutMap<ModuleId, Region>, unused_imports: MutMap<ModuleId, Region>,
}, },
@ -767,6 +772,7 @@ enum Msg<'a> {
exposed_vars_by_symbol: MutMap<Symbol, Variable>, exposed_vars_by_symbol: MutMap<Symbol, Variable>,
exposed_aliases_by_symbol: MutMap<Symbol, Alias>, exposed_aliases_by_symbol: MutMap<Symbol, Alias>,
exposed_values: Vec<Symbol>, exposed_values: Vec<Symbol>,
dep_idents: MutMap<ModuleId, IdentIds>,
documentation: MutMap<ModuleId, ModuleDocumentation>, documentation: MutMap<ModuleId, ModuleDocumentation>,
}, },
FoundSpecializations { FoundSpecializations {
@ -985,6 +991,7 @@ enum BuildTask<'a> {
constraint: Constraint, constraint: Constraint,
var_store: VarStore, var_store: VarStore,
declarations: Vec<Declaration>, declarations: Vec<Declaration>,
dep_idents: MutMap<ModuleId, IdentIds>,
unused_imports: MutMap<ModuleId, Region>, unused_imports: MutMap<ModuleId, Region>,
}, },
BuildPendingSpecializations { BuildPendingSpecializations {
@ -1516,6 +1523,7 @@ where
exposed_vars_by_symbol, exposed_vars_by_symbol,
exposed_aliases_by_symbol, exposed_aliases_by_symbol,
exposed_values, exposed_values,
dep_idents,
documentation, documentation,
} => { } => {
// We're done! There should be no more messages pending. // We're done! There should be no more messages pending.
@ -1534,6 +1542,7 @@ where
exposed_values, exposed_values,
exposed_aliases_by_symbol, exposed_aliases_by_symbol,
exposed_vars_by_symbol, exposed_vars_by_symbol,
dep_idents,
documentation, documentation,
))); )));
} }
@ -1635,7 +1644,7 @@ fn start_tasks<'a>(
) -> Result<(), LoadingProblem<'a>> { ) -> Result<(), LoadingProblem<'a>> {
for (module_id, phase) in work { for (module_id, phase) in work {
for task in start_phase(module_id, phase, arena, state) { for task in start_phase(module_id, phase, arena, state) {
enqueue_task(&injector, worker_listeners, task)? enqueue_task(injector, worker_listeners, task)?
} }
} }
@ -1759,11 +1768,11 @@ fn update<'a>(
state.module_cache.headers.insert(header.module_id, header); state.module_cache.headers.insert(header.module_id, header);
start_tasks(arena, &mut state, work, &injector, worker_listeners)?; start_tasks(arena, &mut state, work, injector, worker_listeners)?;
let work = state.dependencies.notify(home, Phase::LoadHeader); let work = state.dependencies.notify(home, Phase::LoadHeader);
start_tasks(arena, &mut state, work, &injector, worker_listeners)?; start_tasks(arena, &mut state, work, injector, worker_listeners)?;
Ok(state) Ok(state)
} }
@ -1796,7 +1805,7 @@ fn update<'a>(
let work = state.dependencies.notify(module_id, Phase::Parse); let work = state.dependencies.notify(module_id, Phase::Parse);
start_tasks(arena, &mut state, work, &injector, worker_listeners)?; start_tasks(arena, &mut state, work, injector, worker_listeners)?;
Ok(state) Ok(state)
} }
@ -1831,7 +1840,7 @@ fn update<'a>(
.dependencies .dependencies
.notify(module_id, Phase::CanonicalizeAndConstrain); .notify(module_id, Phase::CanonicalizeAndConstrain);
start_tasks(arena, &mut state, work, &injector, worker_listeners)?; start_tasks(arena, &mut state, work, injector, worker_listeners)?;
Ok(state) Ok(state)
} }
@ -1882,7 +1891,7 @@ fn update<'a>(
.notify(module_id, Phase::CanonicalizeAndConstrain), .notify(module_id, Phase::CanonicalizeAndConstrain),
); );
start_tasks(arena, &mut state, work, &injector, worker_listeners)?; start_tasks(arena, &mut state, work, injector, worker_listeners)?;
Ok(state) Ok(state)
} }
@ -1892,6 +1901,7 @@ fn update<'a>(
solved_module, solved_module,
solved_subs, solved_subs,
decls, decls,
dep_idents,
mut module_timing, mut module_timing,
mut unused_imports, mut unused_imports,
} => { } => {
@ -1949,6 +1959,7 @@ fn update<'a>(
exposed_vars_by_symbol: solved_module.exposed_vars_by_symbol, exposed_vars_by_symbol: solved_module.exposed_vars_by_symbol,
exposed_values: solved_module.exposed_symbols, exposed_values: solved_module.exposed_symbols,
exposed_aliases_by_symbol: solved_module.aliases, exposed_aliases_by_symbol: solved_module.aliases,
dep_idents,
documentation, documentation,
}) })
.map_err(|_| LoadingProblem::MsgChannelDied)?; .map_err(|_| LoadingProblem::MsgChannelDied)?;
@ -1986,7 +1997,7 @@ fn update<'a>(
state.constrained_ident_ids.insert(module_id, ident_ids); state.constrained_ident_ids.insert(module_id, ident_ids);
} }
start_tasks(arena, &mut state, work, &injector, worker_listeners)?; start_tasks(arena, &mut state, work, injector, worker_listeners)?;
} }
Ok(state) Ok(state)
@ -2041,7 +2052,7 @@ fn update<'a>(
.dependencies .dependencies
.notify(module_id, Phase::FindSpecializations); .notify(module_id, Phase::FindSpecializations);
start_tasks(arena, &mut state, work, &injector, worker_listeners)?; start_tasks(arena, &mut state, work, injector, worker_listeners)?;
Ok(state) Ok(state)
} }
@ -2143,7 +2154,7 @@ fn update<'a>(
existing.extend(requested); existing.extend(requested);
} }
start_tasks(arena, &mut state, work, &injector, worker_listeners)?; start_tasks(arena, &mut state, work, injector, worker_listeners)?;
} }
Ok(state) Ok(state)
@ -2283,6 +2294,7 @@ fn finish(
exposed_values: Vec<Symbol>, exposed_values: Vec<Symbol>,
exposed_aliases_by_symbol: MutMap<Symbol, Alias>, exposed_aliases_by_symbol: MutMap<Symbol, Alias>,
exposed_vars_by_symbol: MutMap<Symbol, Variable>, exposed_vars_by_symbol: MutMap<Symbol, Variable>,
dep_idents: MutMap<ModuleId, IdentIds>,
documentation: MutMap<ModuleId, ModuleDocumentation>, documentation: MutMap<ModuleId, ModuleDocumentation>,
) -> LoadedModule { ) -> LoadedModule {
let module_ids = Arc::try_unwrap(state.arc_modules) let module_ids = Arc::try_unwrap(state.arc_modules)
@ -2316,6 +2328,7 @@ fn finish(
can_problems: state.module_cache.can_problems, can_problems: state.module_cache.can_problems,
type_problems: state.module_cache.type_problems, type_problems: state.module_cache.type_problems,
declarations_by_id: state.declarations_by_id, declarations_by_id: state.declarations_by_id,
dep_idents,
exposed_aliases: exposed_aliases_by_symbol, exposed_aliases: exposed_aliases_by_symbol,
exposed_values, exposed_values,
exposed_to_host: exposed_vars_by_symbol.into_iter().collect(), exposed_to_host: exposed_vars_by_symbol.into_iter().collect(),
@ -2348,7 +2361,7 @@ fn load_pkg_config<'a>(
let parse_start = SystemTime::now(); let parse_start = SystemTime::now();
let bytes = arena.alloc(bytes_vec); let bytes = arena.alloc(bytes_vec);
let parse_state = parser::State::new(bytes); let parse_state = parser::State::new(bytes);
let parsed = roc_parse::module::parse_header(&arena, parse_state); let parsed = roc_parse::module::parse_header(arena, parse_state);
let parse_header_duration = parse_start.elapsed().unwrap(); let parse_header_duration = parse_start.elapsed().unwrap();
// Insert the first entries for this module's timings // Insert the first entries for this module's timings
@ -2518,7 +2531,7 @@ fn parse_header<'a>(
) -> Result<(ModuleId, Msg<'a>), LoadingProblem<'a>> { ) -> Result<(ModuleId, Msg<'a>), LoadingProblem<'a>> {
let parse_start = SystemTime::now(); let parse_start = SystemTime::now();
let parse_state = parser::State::new(src_bytes); let parse_state = parser::State::new(src_bytes);
let parsed = roc_parse::module::parse_header(&arena, parse_state); let parsed = roc_parse::module::parse_header(arena, parse_state);
let parse_header_duration = parse_start.elapsed().unwrap(); let parse_header_duration = parse_start.elapsed().unwrap();
// Insert the first entries for this module's timings // Insert the first entries for this module's timings
@ -2667,7 +2680,7 @@ fn parse_header<'a>(
} }
Ok((ast::Module::Platform { header }, _parse_state)) => Ok(fabricate_effects_module( Ok((ast::Module::Platform { header }, _parse_state)) => Ok(fabricate_effects_module(
arena, arena,
&"", "",
module_ids, module_ids,
ident_ids_by_module, ident_ids_by_module,
header, header,
@ -2825,7 +2838,7 @@ fn send_header<'a>(
let name = match opt_shorthand { let name = match opt_shorthand {
Some(shorthand) => { Some(shorthand) => {
PQModuleName::Qualified(&shorthand, declared_name.as_inline_str().clone()) PQModuleName::Qualified(shorthand, declared_name.as_inline_str().clone())
} }
None => PQModuleName::Unqualified(declared_name.as_inline_str().clone()), None => PQModuleName::Unqualified(declared_name.as_inline_str().clone()),
}; };
@ -2901,13 +2914,13 @@ fn send_header<'a>(
} }
if cfg!(debug_assertions) { if cfg!(debug_assertions) {
home.register_debug_idents(&ident_ids); home.register_debug_idents(ident_ids);
} }
ident_ids.clone() ident_ids.clone()
}; };
let mut parse_entries: Vec<_> = (&packages).iter().map(|x| &x.value).collect(); let mut parse_entries: Vec<_> = packages.iter().map(|x| &x.value).collect();
let mut package_entries = MutMap::default(); let mut package_entries = MutMap::default();
while let Some(parse_entry) = parse_entries.pop() { while let Some(parse_entry) = parse_entries.pop() {
@ -3053,7 +3066,7 @@ fn send_header_two<'a>(
let mut module_ids = (*module_ids).lock(); let mut module_ids = (*module_ids).lock();
let mut ident_ids_by_module = (*ident_ids_by_module).lock(); let mut ident_ids_by_module = (*ident_ids_by_module).lock();
let name = PQModuleName::Qualified(&shorthand, declared_name); let name = PQModuleName::Qualified(shorthand, declared_name);
home = module_ids.get_or_insert(&name); home = module_ids.get_or_insert(&name);
// Ensure this module has an entry in the exposed_ident_ids map. // Ensure this module has an entry in the exposed_ident_ids map.
@ -3138,13 +3151,13 @@ fn send_header_two<'a>(
} }
if cfg!(debug_assertions) { if cfg!(debug_assertions) {
home.register_debug_idents(&ident_ids); home.register_debug_idents(ident_ids);
} }
ident_ids.clone() ident_ids.clone()
}; };
let mut parse_entries: Vec<_> = (&packages).iter().map(|x| &x.value).collect(); let mut parse_entries: Vec<_> = packages.iter().map(|x| &x.value).collect();
let mut package_entries = MutMap::default(); let mut package_entries = MutMap::default();
while let Some(parse_entry) = parse_entries.pop() { while let Some(parse_entry) = parse_entries.pop() {
@ -3234,6 +3247,7 @@ impl<'a> BuildTask<'a> {
imported_modules: MutMap<ModuleId, Region>, imported_modules: MutMap<ModuleId, Region>,
exposed_types: &mut SubsByModule, exposed_types: &mut SubsByModule,
stdlib: &StdLib, stdlib: &StdLib,
dep_idents: MutMap<ModuleId, IdentIds>,
declarations: Vec<Declaration>, declarations: Vec<Declaration>,
) -> Self { ) -> Self {
let home = module.module_id; let home = module.module_id;
@ -3261,6 +3275,7 @@ impl<'a> BuildTask<'a> {
constraint, constraint,
var_store, var_store,
declarations, declarations,
dep_idents,
module_timing, module_timing,
unused_imports, unused_imports,
} }
@ -3276,6 +3291,7 @@ fn run_solve<'a>(
constraint: Constraint, constraint: Constraint,
mut var_store: VarStore, mut var_store: VarStore,
decls: Vec<Declaration>, decls: Vec<Declaration>,
dep_idents: MutMap<ModuleId, IdentIds>,
unused_imports: MutMap<ModuleId, Region>, unused_imports: MutMap<ModuleId, Region>,
) -> Msg<'a> { ) -> Msg<'a> {
// We have more constraining work to do now, so we'll add it to our timings. // We have more constraining work to do now, so we'll add it to our timings.
@ -3330,6 +3346,7 @@ fn run_solve<'a>(
solved_subs, solved_subs,
ident_ids, ident_ids,
decls, decls,
dep_idents,
solved_module, solved_module,
module_timing, module_timing,
unused_imports, unused_imports,
@ -3390,7 +3407,7 @@ fn fabricate_effects_module<'a>(
let module_id: ModuleId; let module_id: ModuleId;
let effect_entries = unpack_exposes_entries(arena, &effects.entries); let effect_entries = unpack_exposes_entries(arena, effects.entries);
let name = effects.effect_type_name; let name = effects.effect_type_name;
let declared_name: ModuleName = name.into(); let declared_name: ModuleName = name.into();
@ -3464,7 +3481,7 @@ fn fabricate_effects_module<'a>(
} }
if cfg!(debug_assertions) { if cfg!(debug_assertions) {
module_id.register_debug_idents(&ident_ids); module_id.register_debug_idents(ident_ids);
} }
ident_ids.clone() ident_ids.clone()
@ -3478,7 +3495,8 @@ fn fabricate_effects_module<'a>(
let module_ids = { (*module_ids).lock().clone() }.into_module_ids(); let module_ids = { (*module_ids).lock().clone() }.into_module_ids();
let mut scope = roc_can::scope::Scope::new(module_id, &mut var_store); let mut scope = roc_can::scope::Scope::new(module_id, &mut var_store);
let mut can_env = roc_can::env::Env::new(module_id, dep_idents, &module_ids, exposed_ident_ids); let mut can_env =
roc_can::env::Env::new(module_id, &dep_idents, &module_ids, exposed_ident_ids);
let effect_symbol = scope let effect_symbol = scope
.introduce( .introduce(
@ -3611,6 +3629,7 @@ fn fabricate_effects_module<'a>(
var_store, var_store,
constraint, constraint,
ident_ids: module_output.ident_ids, ident_ids: module_output.ident_ids,
dep_idents,
module_timing, module_timing,
}; };
@ -3685,12 +3704,12 @@ where
let mut var_store = VarStore::default(); let mut var_store = VarStore::default();
let canonicalized = canonicalize_module_defs( let canonicalized = canonicalize_module_defs(
&arena, arena,
parsed_defs, parsed_defs,
module_id, module_id,
module_ids, module_ids,
exposed_ident_ids, exposed_ident_ids,
dep_idents, &dep_idents,
aliases, aliases,
exposed_imports, exposed_imports,
&exposed_symbols, &exposed_symbols,
@ -3712,7 +3731,7 @@ where
module_output.scope, module_output.scope,
name.as_str().into(), name.as_str().into(),
&module_output.ident_ids, &module_output.ident_ids,
&parsed_defs, parsed_defs,
)), )),
}; };
@ -3738,6 +3757,7 @@ where
var_store, var_store,
constraint, constraint,
ident_ids: module_output.ident_ids, ident_ids: module_output.ident_ids,
dep_idents,
module_timing, module_timing,
}; };
@ -3761,7 +3781,7 @@ fn parse<'a>(arena: &'a Bump, header: ModuleHeader<'a>) -> Result<Msg<'a>, Loadi
let parse_start = SystemTime::now(); let parse_start = SystemTime::now();
let source = header.parse_state.bytes; let source = header.parse_state.bytes;
let parse_state = header.parse_state; let parse_state = header.parse_state;
let parsed_defs = match module_defs().parse(&arena, parse_state) { let parsed_defs = match module_defs().parse(arena, parse_state) {
Ok((_, success, _state)) => success, Ok((_, success, _state)) => success,
Err((_, fail, _)) => { Err((_, fail, _)) => {
return Err(LoadingProblem::ParsingFailed(fail.into_parse_problem( return Err(LoadingProblem::ParsingFailed(fail.into_parse_problem(
@ -4210,6 +4230,7 @@ where
var_store, var_store,
ident_ids, ident_ids,
declarations, declarations,
dep_idents,
unused_imports, unused_imports,
} => Ok(run_solve( } => Ok(run_solve(
module, module,
@ -4219,6 +4240,7 @@ where
constraint, constraint,
var_store, var_store,
declarations, declarations,
dep_idents,
unused_imports, unused_imports,
)), )),
BuildPendingSpecializations { BuildPendingSpecializations {

View file

@ -184,12 +184,11 @@ mod test_load {
expected_types: &mut HashMap<&str, &str>, expected_types: &mut HashMap<&str, &str>,
) { ) {
for (symbol, expr_var) in &def.pattern_vars { for (symbol, expr_var) in &def.pattern_vars {
let content = subs.get(*expr_var).content;
name_all_type_vars(*expr_var, subs); name_all_type_vars(*expr_var, subs);
let actual_str = content_to_string(content, subs, home, &interns); let content = subs.get_content_without_compacting(*expr_var);
let fully_qualified = symbol.fully_qualified(&interns, home).to_string(); let actual_str = content_to_string(content, subs, home, interns);
let fully_qualified = symbol.fully_qualified(interns, home).to_string();
let expected_type = expected_types let expected_type = expected_types
.remove(fully_qualified.as_str()) .remove(fully_qualified.as_str())
.unwrap_or_else(|| { .unwrap_or_else(|| {

View file

@ -317,7 +317,7 @@ impl fmt::Debug for ModuleId {
} }
} }
/// In relese builds, all we have access to is the number, so only display that. /// In release builds, all we have access to is the number, so only display that.
#[cfg(not(debug_assertions))] #[cfg(not(debug_assertions))]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(f) self.0.fmt(f)
@ -368,7 +368,7 @@ impl<'a> PackageModuleIds<'a> {
self.by_name.insert(module_name.clone(), module_id); self.by_name.insert(module_name.clone(), module_id);
if cfg!(debug_assertions) { if cfg!(debug_assertions) {
Self::insert_debug_name(module_id, &module_name); Self::insert_debug_name(module_id, module_name);
} }
module_id module_id
@ -449,7 +449,7 @@ impl ModuleIds {
self.by_name.insert(module_name.clone(), module_id); self.by_name.insert(module_name.clone(), module_id);
if cfg!(debug_assertions) { if cfg!(debug_assertions) {
Self::insert_debug_name(module_id, &module_name); Self::insert_debug_name(module_id, module_name);
} }
module_id module_id
@ -594,13 +594,16 @@ macro_rules! define_builtins {
$ident_name.into(), $ident_name.into(),
)+ )+
]; ];
let mut by_ident = MutMap::default(); let mut by_ident = MutMap::with_capacity_and_hasher(by_id.len(), default_hasher());
$( $(
debug_assert!(!by_ident.contains_key($ident_name.clone().into()), "Error setting up Builtins: when inserting {} …: {:?} into module {} …: {:?} - the Ident name {:?} is already present in the map. Check the map for duplicate ident names within the {:?} module!", $ident_id, $ident_name, $module_id, $module_name, $ident_name, $module_name);
debug_assert!(by_ident.len() == $ident_id, "Error setting up Builtins: when inserting {} …: {:?} into module {} …: {:?} - this entry was assigned an ID of {}, but based on insertion order, it should have had an ID of {} instead! To fix this, change it from {} …: {:?} to {} …: {:?} instead.", $ident_id, $ident_name, $module_id, $module_name, $ident_id, by_ident.len(), $ident_id, $ident_name, by_ident.len(), $ident_name); debug_assert!(by_ident.len() == $ident_id, "Error setting up Builtins: when inserting {} …: {:?} into module {} …: {:?} - this entry was assigned an ID of {}, but based on insertion order, it should have had an ID of {} instead! To fix this, change it from {} …: {:?} to {} …: {:?} instead.", $ident_id, $ident_name, $module_id, $module_name, $ident_id, by_ident.len(), $ident_id, $ident_name, by_ident.len(), $ident_name);
by_ident.insert($ident_name.into(), IdentId($ident_id)); let exists = by_ident.insert($ident_name.into(), IdentId($ident_id));
if let Some(_) = exists {
debug_assert!(false, "Error setting up Builtins: when inserting {} …: {:?} into module {} …: {:?} - the Ident name {:?} is already present in the map. Check the map for duplicate ident names within the {:?} module!", $ident_id, $ident_name, $module_id, $module_name, $ident_name, $module_name);
}
)+ )+
IdentIds { IdentIds {
@ -771,6 +774,9 @@ define_builtins! {
// a caller (wrapper) for comparison // a caller (wrapper) for comparison
21 GENERIC_COMPARE_REF: "#generic_compare_ref" 21 GENERIC_COMPARE_REF: "#generic_compare_ref"
// used to initialize parameters in borrow.rs
22 EMPTY_PARAM: "#empty_param"
} }
1 NUM: "Num" => { 1 NUM: "Num" => {
0 NUM_NUM: "Num" imported // the Num.Num type alias 0 NUM_NUM: "Num" imported // the Num.Num type alias
@ -873,6 +879,9 @@ define_builtins! {
97 NUM_INT_CAST: "intCast" 97 NUM_INT_CAST: "intCast"
98 NUM_MAX_I128: "maxI128" 98 NUM_MAX_I128: "maxI128"
99 NUM_IS_MULTIPLE_OF: "isMultipleOf" 99 NUM_IS_MULTIPLE_OF: "isMultipleOf"
100 NUM_AT_DECIMAL: "@Decimal"
101 NUM_DECIMAL: "Decimal" imported
102 NUM_DEC: "Dec" imported // the Num.Dectype alias
} }
2 BOOL: "Bool" => { 2 BOOL: "Bool" => {

View file

@ -19,6 +19,7 @@ morphic_lib = { path = "../../vendor/morphic_lib" }
bumpalo = { version = "3.6.1", features = ["collections"] } bumpalo = { version = "3.6.1", features = ["collections"] }
hashbrown = { version = "0.11.2", features = [ "bumpalo" ] } hashbrown = { version = "0.11.2", features = [ "bumpalo" ] }
ven_ena = { path = "../../vendor/ena" } ven_ena = { path = "../../vendor/ena" }
ven_graph = { path = "../../vendor/pathfinding" }
linked-hash-map = "0.5.4" linked-hash-map = "0.5.4"
[dev-dependencies] [dev-dependencies]

View file

@ -842,7 +842,19 @@ fn lowlevel_spec(
builder.add_bag_insert(block, bag, to_insert)?; builder.add_bag_insert(block, bag, to_insert)?;
Ok(list) let new_cell = builder.add_new_heap_cell(block)?;
builder.add_make_tuple(block, &[new_cell, bag])
}
ListSwap => {
let list = env.symbols[&arguments[0]];
let bag = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?;
let cell = builder.add_get_tuple_field(block, list, LIST_CELL_INDEX)?;
let _unit = builder.add_update(block, update_mode_var, cell)?;
let new_cell = builder.add_new_heap_cell(block)?;
builder.add_make_tuple(block, &[new_cell, bag])
} }
ListAppend => { ListAppend => {
let list = env.symbols[&arguments[0]]; let list = env.symbols[&arguments[0]];
@ -853,9 +865,11 @@ fn lowlevel_spec(
let _unit = builder.add_update(block, update_mode_var, cell)?; let _unit = builder.add_update(block, update_mode_var, cell)?;
// TODO new heap cell
builder.add_bag_insert(block, bag, to_insert)?; builder.add_bag_insert(block, bag, to_insert)?;
Ok(list) let new_cell = builder.add_new_heap_cell(block)?;
builder.add_make_tuple(block, &[new_cell, bag])
} }
DictEmpty => { DictEmpty => {
match layout { match layout {
@ -887,7 +901,6 @@ fn lowlevel_spec(
let cell = builder.add_get_tuple_field(block, dict, DICT_CELL_INDEX)?; let cell = builder.add_get_tuple_field(block, dict, DICT_CELL_INDEX)?;
let _unit = builder.add_touch(block, cell)?; let _unit = builder.add_touch(block, cell)?;
builder.add_bag_get(block, bag) builder.add_bag_get(block, bag)
} }
DictInsert => { DictInsert => {
@ -904,7 +917,8 @@ fn lowlevel_spec(
builder.add_bag_insert(block, bag, key_value)?; builder.add_bag_insert(block, bag, key_value)?;
Ok(dict) let new_cell = builder.add_new_heap_cell(block)?;
builder.add_make_tuple(block, &[new_cell, bag])
} }
_other => { _other => {
// println!("missing {:?}", _other); // println!("missing {:?}", _other);
@ -1094,7 +1108,7 @@ fn expr_spec<'a>(
let index = (*index) as u32; let index = (*index) as u32;
let tag_value_id = env.symbols[structure]; let tag_value_id = env.symbols[structure];
let type_name_bytes = recursive_tag_union_name_bytes(&union_layout).as_bytes(); let type_name_bytes = recursive_tag_union_name_bytes(union_layout).as_bytes();
let type_name = TypeName(&type_name_bytes); let type_name = TypeName(&type_name_bytes);
let union_id = builder.add_unwrap_named(block, MOD_APP, type_name, tag_value_id)?; let union_id = builder.add_unwrap_named(block, MOD_APP, type_name, tag_value_id)?;
@ -1114,7 +1128,7 @@ fn expr_spec<'a>(
let tag_value_id = env.symbols[structure]; let tag_value_id = env.symbols[structure];
let type_name_bytes = recursive_tag_union_name_bytes(&union_layout).as_bytes(); let type_name_bytes = recursive_tag_union_name_bytes(union_layout).as_bytes();
let type_name = TypeName(&type_name_bytes); let type_name = TypeName(&type_name_bytes);
let variant_id = let variant_id =
@ -1220,7 +1234,7 @@ fn layout_spec_help(
| UnionLayout::NullableUnwrapped { .. } | UnionLayout::NullableUnwrapped { .. }
| UnionLayout::NullableWrapped { .. } | UnionLayout::NullableWrapped { .. }
| UnionLayout::NonNullableUnwrapped(_) => { | UnionLayout::NonNullableUnwrapped(_) => {
let type_name_bytes = recursive_tag_union_name_bytes(&union_layout).as_bytes(); let type_name_bytes = recursive_tag_union_name_bytes(union_layout).as_bytes();
let type_name = TypeName(&type_name_bytes); let type_name = TypeName(&type_name_bytes);
Ok(builder.add_named_type(MOD_APP, type_name)) Ok(builder.add_named_type(MOD_APP, type_name))
@ -1261,7 +1275,7 @@ fn builtin_spec(
match builtin { match builtin {
Int128 | Int64 | Int32 | Int16 | Int8 | Int1 | Usize => builder.add_tuple_type(&[]), Int128 | Int64 | Int32 | Int16 | Int8 | Int1 | Usize => builder.add_tuple_type(&[]),
Float128 | Float64 | Float32 | Float16 => builder.add_tuple_type(&[]), Decimal | Float128 | Float64 | Float32 | Float16 => builder.add_tuple_type(&[]),
Str | EmptyStr => str_type(builder), Str | EmptyStr => str_type(builder),
Dict(key_layout, value_layout) => { Dict(key_layout, value_layout) => {
let value_type = layout_spec_help(builder, value_layout, when_recursive)?; let value_type = layout_spec_help(builder, value_layout, when_recursive)?;

View file

@ -2,7 +2,7 @@ use crate::ir::{Expr, JoinPointId, Param, Proc, ProcLayout, Stmt};
use crate::layout::Layout; use crate::layout::Layout;
use bumpalo::collections::Vec; use bumpalo::collections::Vec;
use bumpalo::Bump; use bumpalo::Bump;
use roc_collections::all::{MutMap, MutSet}; use roc_collections::all::{default_hasher, MutMap, MutSet};
use roc_module::low_level::LowLevel; use roc_module::low_level::LowLevel;
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
@ -20,8 +20,16 @@ pub fn infer_borrow<'a>(
arena: &'a Bump, arena: &'a Bump,
procs: &MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>, procs: &MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>,
) -> ParamMap<'a> { ) -> ParamMap<'a> {
let mut param_map = ParamMap { // intern the layouts
items: MutMap::default(),
let mut param_map = {
let (declaration_to_index, total_number_of_params) = DeclarationToIndex::new(arena, procs);
ParamMap {
declaration_to_index,
join_points: MutMap::default(),
declarations: bumpalo::vec![in arena; Param::EMPTY; total_number_of_params],
}
}; };
for (key, proc) in procs { for (key, proc) in procs {
@ -33,10 +41,94 @@ pub fn infer_borrow<'a>(
param_set: MutSet::default(), param_set: MutSet::default(),
owned: MutMap::default(), owned: MutMap::default(),
modified: false, modified: false,
param_map,
arena, arena,
}; };
// next we first partition the functions into strongly connected components, then do a
// topological sort on these components, finally run the fix-point borrow analysis on each
// component (in top-sorted order, from primitives (std-lib) to main)
let successor_map = &make_successor_mapping(arena, procs);
let successors = move |key: &Symbol| successor_map[key].iter().copied();
let mut symbols = Vec::with_capacity_in(procs.len(), arena);
symbols.extend(procs.keys().map(|x| x.0));
let sccs = ven_graph::strongly_connected_components(&symbols, successors);
let mut symbol_to_component = MutMap::default();
for (i, symbols) in sccs.iter().enumerate() {
for symbol in symbols {
symbol_to_component.insert(*symbol, i);
}
}
let mut component_to_successors = Vec::with_capacity_in(sccs.len(), arena);
for (i, symbols) in sccs.iter().enumerate() {
// guess: every function has ~1 successor
let mut succs = Vec::with_capacity_in(symbols.len(), arena);
for symbol in symbols {
for s in successors(symbol) {
let c = symbol_to_component[&s];
// don't insert self to prevent cycles
if c != i {
succs.push(c);
}
}
}
succs.sort_unstable();
succs.dedup();
component_to_successors.push(succs);
}
let mut components = Vec::with_capacity_in(component_to_successors.len(), arena);
components.extend(0..component_to_successors.len());
let mut groups = Vec::new_in(arena);
let component_to_successors = &component_to_successors;
match ven_graph::topological_sort_into_groups(&components, |c: &usize| {
component_to_successors[*c].iter().copied()
}) {
Ok(component_groups) => {
let mut component_to_group = bumpalo::vec![in arena; usize::MAX; components.len()];
// for each component, store which group it is in
for (group_index, component_group) in component_groups.iter().enumerate() {
for component in component_group {
component_to_group[*component] = group_index;
}
}
// prepare groups
groups.reserve(component_groups.len());
for _ in 0..component_groups.len() {
groups.push(Vec::new_in(arena));
}
for (key, proc) in procs {
let symbol = key.0;
let offset = param_map.get_param_offset(key.0, key.1);
// the component this symbol is a part of
let component = symbol_to_component[&symbol];
// now find the group that this component belongs to
let group = component_to_group[component];
groups[group].push((proc, offset));
}
}
Err((_groups, _remainder)) => {
unreachable!("because we find strongly-connected components first");
}
}
for group in groups.into_iter().rev() {
// This is a fixed-point analysis // This is a fixed-point analysis
// //
// all functions initiall own all their parameters // all functions initiall own all their parameters
@ -45,12 +137,8 @@ pub fn infer_borrow<'a>(
// //
// when the signatures no longer change, the analysis stops and returns the signatures // when the signatures no longer change, the analysis stops and returns the signatures
loop { loop {
// sort the symbols (roughly) in definition order. for (proc, param_offset) in group.iter() {
// TODO in the future I think we need to do this properly, and group env.collect_proc(&mut param_map, proc, *param_offset);
// mutually recursive functions (or just make all their arguments owned)
for (key, proc) in procs {
env.collect_proc(proc, key.1);
} }
if !env.modified { if !env.modified {
@ -61,54 +149,117 @@ pub fn infer_borrow<'a>(
env.modified = false; env.modified = false;
} }
} }
}
env.param_map param_map
} }
#[derive(Debug, PartialEq, Eq, Hash, Clone)] #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
pub enum Key<'a> { pub struct ParamOffset(usize);
Declaration(Symbol, ProcLayout<'a>),
JoinPoint(JoinPointId), impl From<ParamOffset> for usize {
fn from(id: ParamOffset) -> Self {
id.0 as usize
}
} }
#[derive(Debug, Clone, Default)] #[derive(Debug)]
struct DeclarationToIndex<'a> {
elements: Vec<'a, ((Symbol, ProcLayout<'a>), ParamOffset)>,
}
impl<'a> DeclarationToIndex<'a> {
fn new(arena: &'a Bump, procs: &MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>) -> (Self, usize) {
let mut declaration_to_index = Vec::with_capacity_in(procs.len(), arena);
let mut i = 0;
for key in procs.keys().copied() {
declaration_to_index.push((key, ParamOffset(i)));
i += key.1.arguments.len();
}
declaration_to_index.sort_unstable_by_key(|t| t.0 .0);
(
DeclarationToIndex {
elements: declaration_to_index,
},
i,
)
}
fn get_param_offset(
&self,
needle_symbol: Symbol,
needle_layout: ProcLayout<'a>,
) -> ParamOffset {
if let Ok(middle_index) = self
.elements
.binary_search_by_key(&needle_symbol, |t| t.0 .0)
{
// first, iterate backward until we hit a different symbol
let backward = self.elements[..middle_index].iter().rev();
for ((symbol, proc_layout), param_offset) in backward {
if *symbol != needle_symbol {
break;
} else if *proc_layout == needle_layout {
return *param_offset;
}
}
// if not found, iterate forward until we find our combo
let forward = self.elements[middle_index..].iter();
for ((symbol, proc_layout), param_offset) in forward {
if *symbol != needle_symbol {
break;
} else if *proc_layout == needle_layout {
return *param_offset;
}
}
}
unreachable!("symbol/layout combo must be in DeclarationToIndex")
}
}
#[derive(Debug)]
pub struct ParamMap<'a> { pub struct ParamMap<'a> {
items: MutMap<Key<'a>, &'a [Param<'a>]>, /// Map a (Symbol, ProcLayout) pair to the starting index in the `declarations` array
} declaration_to_index: DeclarationToIndex<'a>,
/// the parameters of all functions in a single flat array.
impl<'a> IntoIterator for ParamMap<'a> { ///
type Item = (Key<'a>, &'a [Param<'a>]); /// - the map above gives the index of the first parameter for the function
type IntoIter = <std::collections::HashMap<Key<'a>, &'a [Param<'a>]> as IntoIterator>::IntoIter; /// - the length of the ProcLayout's argument field gives the total number of parameters
///
fn into_iter(self) -> Self::IntoIter { /// These can be read by taking a slice into this array, and can also be updated in-place
self.items.into_iter() declarations: Vec<'a, Param<'a>>,
} join_points: MutMap<JoinPointId, &'a [Param<'a>]>,
}
impl<'a> IntoIterator for &'a ParamMap<'a> {
type Item = (&'a Key<'a>, &'a &'a [Param<'a>]);
type IntoIter =
<&'a std::collections::HashMap<Key<'a>, &'a [Param<'a>]> as IntoIterator>::IntoIter;
fn into_iter(self) -> Self::IntoIter {
self.items.iter()
}
} }
impl<'a> ParamMap<'a> { impl<'a> ParamMap<'a> {
pub fn get_symbol(&self, symbol: Symbol, layout: ProcLayout<'a>) -> Option<&'a [Param<'a>]> { pub fn get_param_offset(&self, symbol: Symbol, layout: ProcLayout<'a>) -> ParamOffset {
let key = Key::Declaration(symbol, layout); self.declaration_to_index.get_param_offset(symbol, layout)
self.items.get(&key).copied()
} }
pub fn get_join_point(&self, id: JoinPointId) -> &'a [Param<'a>] {
let key = Key::JoinPoint(id);
match self.items.get(&key) { pub fn get_symbol(&self, symbol: Symbol, layout: ProcLayout<'a>) -> Option<&[Param<'a>]> {
// let index: usize = self.declaration_to_index[&(symbol, layout)].into();
let index: usize = self.get_param_offset(symbol, layout).into();
self.declarations.get(index..index + layout.arguments.len())
}
pub fn get_join_point(&self, id: JoinPointId) -> &'a [Param<'a>] {
match self.join_points.get(&id) {
Some(slice) => slice, Some(slice) => slice,
None => unreachable!("join point not in param map: {:?}", id), None => unreachable!("join point not in param map: {:?}", id),
} }
} }
pub fn iter_symbols(&'a self) -> impl Iterator<Item = &'a Symbol> {
self.declaration_to_index.elements.iter().map(|t| &t.0 .0)
}
} }
impl<'a> ParamMap<'a> { impl<'a> ParamMap<'a> {
@ -156,11 +307,16 @@ impl<'a> ParamMap<'a> {
self.visit_proc_always_owned(arena, proc, key); self.visit_proc_always_owned(arena, proc, key);
return; return;
} }
let already_in_there = self.items.insert(
Key::Declaration(proc.name, key.1), let index: usize = self.get_param_offset(key.0, key.1).into();
Self::init_borrow_args(arena, proc.args),
); for (i, param) in Self::init_borrow_args(arena, proc.args)
debug_assert!(already_in_there.is_none()); .iter()
.copied()
.enumerate()
{
self.declarations[index + i] = param;
}
self.visit_stmt(arena, proc.name, &proc.body); self.visit_stmt(arena, proc.name, &proc.body);
} }
@ -171,11 +327,15 @@ impl<'a> ParamMap<'a> {
proc: &Proc<'a>, proc: &Proc<'a>,
key: (Symbol, ProcLayout<'a>), key: (Symbol, ProcLayout<'a>),
) { ) {
let already_in_there = self.items.insert( let index: usize = self.get_param_offset(key.0, key.1).into();
Key::Declaration(proc.name, key.1),
Self::init_borrow_args_always_owned(arena, proc.args), for (i, param) in Self::init_borrow_args_always_owned(arena, proc.args)
); .iter()
debug_assert!(already_in_there.is_none()); .copied()
.enumerate()
{
self.declarations[index + i] = param;
}
self.visit_stmt(arena, proc.name, &proc.body); self.visit_stmt(arena, proc.name, &proc.body);
} }
@ -193,14 +353,8 @@ impl<'a> ParamMap<'a> {
remainder: v, remainder: v,
body: b, body: b,
} => { } => {
let already_in_there = self self.join_points
.items .insert(*j, Self::init_borrow_params(arena, xs));
.insert(Key::JoinPoint(*j), Self::init_borrow_params(arena, xs));
debug_assert!(
already_in_there.is_none(),
"join point {:?} is already defined!",
j
);
stack.push(v); stack.push(v);
stack.push(b); stack.push(b);
@ -237,7 +391,6 @@ struct BorrowInfState<'a> {
param_set: MutSet<Symbol>, param_set: MutSet<Symbol>,
owned: MutMap<Symbol, MutSet<Symbol>>, owned: MutMap<Symbol, MutSet<Symbol>>,
modified: bool, modified: bool,
param_map: ParamMap<'a>,
arena: &'a Bump, arena: &'a Bump,
} }
@ -245,14 +398,30 @@ impl<'a> BorrowInfState<'a> {
pub fn own_var(&mut self, x: Symbol) { pub fn own_var(&mut self, x: Symbol) {
let current = self.owned.get_mut(&self.current_proc).unwrap(); let current = self.owned.get_mut(&self.current_proc).unwrap();
if current.contains(&x) { if current.insert(x) {
// do nothing // entered if key was not yet present. If so, the set is modified,
} else { // hence we set this flag
current.insert(x);
self.modified = true; self.modified = true;
} }
} }
/// if the extracted value is owned, then the surrounding structure must be too
fn if_is_owned_then_own(&mut self, extracted: Symbol, structure: Symbol) {
match self.owned.get_mut(&self.current_proc) {
None => unreachable!(
"the current procedure symbol {:?} is not in the owned map",
self.current_proc
),
Some(set) => {
if set.contains(&extracted) && set.insert(structure) {
// entered if key was not yet present. If so, the set is modified,
// hence we set this flag
self.modified = true;
}
}
}
}
fn is_owned(&self, x: Symbol) -> bool { fn is_owned(&self, x: Symbol) -> bool {
match self.owned.get(&self.current_proc) { match self.owned.get(&self.current_proc) {
None => unreachable!( None => unreachable!(
@ -263,28 +432,50 @@ impl<'a> BorrowInfState<'a> {
} }
} }
fn update_param_map(&mut self, k: Key<'a>) { fn update_param_map_help(&mut self, ps: &[Param<'a>]) -> &'a [Param<'a>] {
let arena = self.arena; let mut new_ps = Vec::with_capacity_in(ps.len(), self.arena);
if let Some(ps) = self.param_map.items.get(&k) { new_ps.extend(ps.iter().map(|p| {
let ps = Vec::from_iter_in(
ps.iter().map(|p| {
if !p.borrow { if !p.borrow {
p.clone() *p
} else if self.is_owned(p.symbol) { } else if self.is_owned(p.symbol) {
self.modified = true; self.modified = true;
let mut p = p.clone(); let mut p = *p;
p.borrow = false; p.borrow = false;
p p
} else { } else {
p.clone() *p
} }
}), }));
arena,
);
self.param_map.items.insert(k, ps.into_bump_slice()); new_ps.into_bump_slice()
} }
fn update_param_map_declaration(
&mut self,
param_map: &mut ParamMap<'a>,
start: ParamOffset,
length: usize,
) {
let index: usize = start.into();
let ps = &mut param_map.declarations[index..][..length];
for p in ps.iter_mut() {
if !p.borrow {
// do nothing
} else if self.is_owned(p.symbol) {
self.modified = true;
p.borrow = false;
} else {
// do nothing
}
}
}
fn update_param_map_join_point(&mut self, param_map: &mut ParamMap<'a>, id: JoinPointId) {
let ps = param_map.join_points[&id];
let new_ps = self.update_param_map_help(ps);
param_map.join_points.insert(id, new_ps);
} }
/// This looks at an application `f x1 x2 x3` /// This looks at an application `f x1 x2 x3`
@ -345,13 +536,13 @@ impl<'a> BorrowInfState<'a> {
} }
} }
/// This looks at the assignement /// This looks at the assignment
/// ///
/// let z = e in ... /// let z = e in ...
/// ///
/// and determines whether z and which of the symbols used in e /// and determines whether z and which of the symbols used in e
/// must be taken as owned parameters /// must be taken as owned parameters
fn collect_call(&mut self, z: Symbol, e: &crate::ir::Call<'a>) { fn collect_call(&mut self, param_map: &mut ParamMap<'a>, z: Symbol, e: &crate::ir::Call<'a>) {
use crate::ir::CallType::*; use crate::ir::CallType::*;
let crate::ir::Call { let crate::ir::Call {
@ -369,8 +560,7 @@ impl<'a> BorrowInfState<'a> {
let top_level = ProcLayout::new(self.arena, arg_layouts, *ret_layout); let top_level = ProcLayout::new(self.arena, arg_layouts, *ret_layout);
// get the borrow signature of the applied function // get the borrow signature of the applied function
let ps = self let ps = param_map
.param_map
.get_symbol(*name, top_level) .get_symbol(*name, top_level)
.expect("function is defined"); .expect("function is defined");
@ -386,6 +576,7 @@ impl<'a> BorrowInfState<'a> {
ps.len(), ps.len(),
arguments.len() arguments.len()
); );
self.own_args_using_params(arguments, ps); self.own_args_using_params(arguments, ps);
} }
@ -416,7 +607,7 @@ impl<'a> BorrowInfState<'a> {
match op { match op {
ListMap | ListKeepIf | ListKeepOks | ListKeepErrs => { ListMap | ListKeepIf | ListKeepOks | ListKeepErrs => {
match self.param_map.get_symbol(arguments[1], closure_layout) { match param_map.get_symbol(arguments[1], closure_layout) {
Some(function_ps) => { Some(function_ps) => {
// own the list if the function wants to own the element // own the list if the function wants to own the element
if !function_ps[0].borrow { if !function_ps[0].borrow {
@ -432,7 +623,7 @@ impl<'a> BorrowInfState<'a> {
} }
} }
ListMapWithIndex => { ListMapWithIndex => {
match self.param_map.get_symbol(arguments[1], closure_layout) { match param_map.get_symbol(arguments[1], closure_layout) {
Some(function_ps) => { Some(function_ps) => {
// own the list if the function wants to own the element // own the list if the function wants to own the element
if !function_ps[1].borrow { if !function_ps[1].borrow {
@ -447,7 +638,7 @@ impl<'a> BorrowInfState<'a> {
None => unreachable!(), None => unreachable!(),
} }
} }
ListMap2 => match self.param_map.get_symbol(arguments[2], closure_layout) { ListMap2 => match param_map.get_symbol(arguments[2], closure_layout) {
Some(function_ps) => { Some(function_ps) => {
// own the lists if the function wants to own the element // own the lists if the function wants to own the element
if !function_ps[0].borrow { if !function_ps[0].borrow {
@ -465,7 +656,7 @@ impl<'a> BorrowInfState<'a> {
} }
None => unreachable!(), None => unreachable!(),
}, },
ListMap3 => match self.param_map.get_symbol(arguments[3], closure_layout) { ListMap3 => match param_map.get_symbol(arguments[3], closure_layout) {
Some(function_ps) => { Some(function_ps) => {
// own the lists if the function wants to own the element // own the lists if the function wants to own the element
if !function_ps[0].borrow { if !function_ps[0].borrow {
@ -486,7 +677,7 @@ impl<'a> BorrowInfState<'a> {
None => unreachable!(), None => unreachable!(),
}, },
ListSortWith => { ListSortWith => {
match self.param_map.get_symbol(arguments[1], closure_layout) { match param_map.get_symbol(arguments[1], closure_layout) {
Some(function_ps) => { Some(function_ps) => {
// always own the input list // always own the input list
self.own_var(arguments[0]); self.own_var(arguments[0]);
@ -500,7 +691,7 @@ impl<'a> BorrowInfState<'a> {
} }
} }
ListWalk | ListWalkUntil | ListWalkBackwards | DictWalk => { ListWalk | ListWalkUntil | ListWalkBackwards | DictWalk => {
match self.param_map.get_symbol(arguments[2], closure_layout) { match param_map.get_symbol(arguments[2], closure_layout) {
Some(function_ps) => { Some(function_ps) => {
// own the data structure if the function wants to own the element // own the data structure if the function wants to own the element
if !function_ps[0].borrow { if !function_ps[0].borrow {
@ -542,7 +733,7 @@ impl<'a> BorrowInfState<'a> {
} }
} }
fn collect_expr(&mut self, z: Symbol, e: &Expr<'a>) { fn collect_expr(&mut self, param_map: &mut ParamMap<'a>, z: Symbol, e: &Expr<'a>) {
use Expr::*; use Expr::*;
match e { match e {
@ -570,50 +761,44 @@ impl<'a> BorrowInfState<'a> {
self.own_var(z); self.own_var(z);
} }
Call(call) => self.collect_call(z, call), Call(call) => self.collect_call(param_map, z, call),
Literal(_) | RuntimeErrorFunction(_) => {} Literal(_) | RuntimeErrorFunction(_) => {}
StructAtIndex { structure: x, .. } => { StructAtIndex { structure: x, .. } => {
// if the structure (record/tag/array) is owned, the extracted value is // if the structure (record/tag/array) is owned, the extracted value is
if self.is_owned(*x) { self.if_is_owned_then_own(*x, z);
self.own_var(z);
}
// if the extracted value is owned, the structure must be too // if the extracted value is owned, the structure must be too
if self.is_owned(z) { self.if_is_owned_then_own(z, *x);
self.own_var(*x);
}
} }
UnionAtIndex { structure: x, .. } => { UnionAtIndex { structure: x, .. } => {
// if the structure (record/tag/array) is owned, the extracted value is // if the structure (record/tag/array) is owned, the extracted value is
if self.is_owned(*x) { self.if_is_owned_then_own(*x, z);
self.own_var(z);
}
// if the extracted value is owned, the structure must be too // if the extracted value is owned, the structure must be too
if self.is_owned(z) { self.if_is_owned_then_own(z, *x);
self.own_var(*x);
}
} }
GetTagId { structure: x, .. } => { GetTagId { structure: x, .. } => {
// if the structure (record/tag/array) is owned, the extracted value is // if the structure (record/tag/array) is owned, the extracted value is
if self.is_owned(*x) { self.if_is_owned_then_own(*x, z);
self.own_var(z);
}
// if the extracted value is owned, the structure must be too // if the extracted value is owned, the structure must be too
if self.is_owned(z) { self.if_is_owned_then_own(z, *x);
self.own_var(*x);
}
} }
} }
} }
#[allow(clippy::many_single_char_names)] #[allow(clippy::many_single_char_names)]
fn preserve_tail_call(&mut self, x: Symbol, v: &Expr<'a>, b: &Stmt<'a>) { fn preserve_tail_call(
&mut self,
param_map: &mut ParamMap<'a>,
x: Symbol,
v: &Expr<'a>,
b: &Stmt<'a>,
) {
if let ( if let (
Expr::Call(crate::ir::Call { Expr::Call(crate::ir::Call {
call_type: call_type:
@ -634,7 +819,7 @@ impl<'a> BorrowInfState<'a> {
if self.current_proc == *g && x == *z { if self.current_proc == *g && x == *z {
// anonymous functions (for which the ps may not be known) // anonymous functions (for which the ps may not be known)
// can never be tail-recursive, so this is fine // can never be tail-recursive, so this is fine
if let Some(ps) = self.param_map.get_symbol(*g, top_level) { if let Some(ps) = param_map.get_symbol(*g, top_level) {
self.own_params_using_args(ys, ps) self.own_params_using_args(ys, ps)
} }
} }
@ -653,7 +838,7 @@ impl<'a> BorrowInfState<'a> {
} }
} }
fn collect_stmt(&mut self, stmt: &Stmt<'a>) { fn collect_stmt(&mut self, param_map: &mut ParamMap<'a>, stmt: &Stmt<'a>) {
use Stmt::*; use Stmt::*;
match stmt { match stmt {
@ -665,17 +850,35 @@ impl<'a> BorrowInfState<'a> {
} => { } => {
let old = self.param_set.clone(); let old = self.param_set.clone();
self.update_param_set(ys); self.update_param_set(ys);
self.collect_stmt(v); self.collect_stmt(param_map, v);
self.param_set = old; self.param_set = old;
self.update_param_map(Key::JoinPoint(*j)); self.update_param_map_join_point(param_map, *j);
self.collect_stmt(b); self.collect_stmt(param_map, b);
} }
Let(x, v, _, b) => { Let(x, v, _, mut b) => {
self.collect_stmt(b); let mut stack = Vec::new_in(self.arena);
self.collect_expr(*x, v);
self.preserve_tail_call(*x, v, b); stack.push((*x, v));
while let Stmt::Let(symbol, expr, _, tail) = b {
b = tail;
stack.push((*symbol, expr));
}
self.collect_stmt(param_map, b);
let mut it = stack.into_iter().rev();
// collect the final expr, and see if we need to preserve a tail call
let (x, v) = it.next().unwrap();
self.collect_expr(param_map, x, v);
self.preserve_tail_call(param_map, x, v, b);
for (x, v) in it {
self.collect_expr(param_map, x, v);
}
} }
Invoke { Invoke {
@ -686,17 +889,17 @@ impl<'a> BorrowInfState<'a> {
fail, fail,
exception_id: _, exception_id: _,
} => { } => {
self.collect_stmt(pass); self.collect_stmt(param_map, pass);
self.collect_stmt(fail); self.collect_stmt(param_map, fail);
self.collect_call(*symbol, call); self.collect_call(param_map, *symbol, call);
// TODO how to preserve the tail call of an invoke? // TODO how to preserve the tail call of an invoke?
// self.preserve_tail_call(*x, v, b); // self.preserve_tail_call(*x, v, b);
} }
Jump(j, ys) => { Jump(j, ys) => {
let ps = self.param_map.get_join_point(*j); let ps = param_map.get_join_point(*j);
// for making sure the join point can reuse // for making sure the join point can reuse
self.own_args_using_params(ys, ps); self.own_args_using_params(ys, ps);
@ -710,9 +913,9 @@ impl<'a> BorrowInfState<'a> {
.. ..
} => { } => {
for (_, _, b) in branches.iter() { for (_, _, b) in branches.iter() {
self.collect_stmt(b); self.collect_stmt(param_map, b);
} }
self.collect_stmt(default_branch.1); self.collect_stmt(param_map, default_branch.1);
} }
Refcounting(_, _) => unreachable!("these have not been introduced yet"), Refcounting(_, _) => unreachable!("these have not been introduced yet"),
@ -722,7 +925,12 @@ impl<'a> BorrowInfState<'a> {
} }
} }
fn collect_proc(&mut self, proc: &Proc<'a>, layout: ProcLayout<'a>) { fn collect_proc(
&mut self,
param_map: &mut ParamMap<'a>,
proc: &Proc<'a>,
param_offset: ParamOffset,
) {
let old = self.param_set.clone(); let old = self.param_set.clone();
let ys = Vec::from_iter_in(proc.args.iter().map(|t| t.1), self.arena).into_bump_slice(); let ys = Vec::from_iter_in(proc.args.iter().map(|t| t.1), self.arena).into_bump_slice();
@ -732,8 +940,8 @@ impl<'a> BorrowInfState<'a> {
// ensure that current_proc is in the owned map // ensure that current_proc is in the owned map
self.owned.entry(proc.name).or_default(); self.owned.entry(proc.name).or_default();
self.collect_stmt(&proc.body); self.collect_stmt(param_map, &proc.body);
self.update_param_map(Key::Declaration(proc.name, layout)); self.update_param_map_declaration(param_map, param_offset, proc.args.len());
self.param_set = old; self.param_set = old;
} }
@ -827,3 +1035,87 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
ExpectTrue => arena.alloc_slice_copy(&[irrelevant]), ExpectTrue => arena.alloc_slice_copy(&[irrelevant]),
} }
} }
fn make_successor_mapping<'a>(
arena: &'a Bump,
procs: &MutMap<(Symbol, ProcLayout<'_>), Proc<'a>>,
) -> MutMap<Symbol, Vec<'a, Symbol>> {
let mut result = MutMap::with_capacity_and_hasher(procs.len(), default_hasher());
for (key, proc) in procs {
let mut call_info = CallInfo {
keys: Vec::new_in(arena),
};
call_info_stmt(arena, &proc.body, &mut call_info);
let mut keys = call_info.keys;
keys.sort_unstable();
keys.dedup();
result.insert(key.0, keys);
}
result
}
struct CallInfo<'a> {
keys: Vec<'a, Symbol>,
}
fn call_info_call<'a>(call: &crate::ir::Call<'a>, info: &mut CallInfo<'a>) {
use crate::ir::CallType::*;
match call.call_type {
ByName { name, .. } => {
info.keys.push(name);
}
Foreign { .. } => {}
LowLevel { .. } => {}
HigherOrderLowLevel { .. } => {}
}
}
fn call_info_stmt<'a>(arena: &'a Bump, stmt: &Stmt<'a>, info: &mut CallInfo<'a>) {
use Stmt::*;
let mut stack = bumpalo::vec![ in arena; stmt ];
while let Some(stmt) = stack.pop() {
match stmt {
Join {
remainder: v,
body: b,
..
} => {
stack.push(v);
stack.push(b);
}
Let(_, expr, _, cont) => {
if let Expr::Call(call) = expr {
call_info_call(call, info);
}
stack.push(cont);
}
Invoke {
call, pass, fail, ..
} => {
call_info_call(call, info);
stack.push(pass);
stack.push(fail);
}
Switch {
branches,
default_branch,
..
} => {
stack.extend(branches.iter().map(|b| &b.2));
stack.push(default_branch.1);
}
Refcounting(_, _) => unreachable!("these have not been introduced yet"),
Ret(_) | Resume(_) | Jump(_, _) | RuntimeError(_) => {
// these are terminal, do nothing
}
}
}
}

View file

@ -924,7 +924,7 @@ fn pick_path<'a>(branches: &'a [Branch]) -> &'a Vec<PathInstruction> {
for (path, pattern) in &branch.patterns { for (path, pattern) in &branch.patterns {
// NOTE we no longer check for the guard here // NOTE we no longer check for the guard here
// if !branch.guard.is_none() || needs_tests(&pattern) { // if !branch.guard.is_none() || needs_tests(&pattern) {
if needs_tests(&pattern) { if needs_tests(pattern) {
all_paths.push(path); all_paths.push(path);
} else { } else {
// do nothing // do nothing
@ -996,7 +996,7 @@ where
let mut min_paths = vec![first_path]; let mut min_paths = vec![first_path];
for path in all_paths { for path in all_paths {
let weight = small_defaults(branches, &path); let weight = small_defaults(branches, path);
use std::cmp::Ordering; use std::cmp::Ordering;
match weight.cmp(&min_weight) { match weight.cmp(&min_weight) {
@ -1217,15 +1217,9 @@ fn test_to_equality<'a>(
cond_layout: &Layout<'a>, cond_layout: &Layout<'a>,
path: &[PathInstruction], path: &[PathInstruction],
test: Test<'a>, test: Test<'a>,
) -> ( ) -> (StoresVec<'a>, Symbol, Symbol, Option<ConstructorKnown<'a>>) {
StoresVec<'a>,
Symbol,
Symbol,
Layout<'a>,
Option<ConstructorKnown<'a>>,
) {
let (rhs_symbol, mut stores, test_layout) = let (rhs_symbol, mut stores, test_layout) =
path_to_expr_help(env, cond_symbol, &path, *cond_layout); path_to_expr_help(env, cond_symbol, path, *cond_layout);
match test { match test {
Test::IsCtor { tag_id, union, .. } => { Test::IsCtor { tag_id, union, .. } => {
@ -1255,7 +1249,6 @@ fn test_to_equality<'a>(
stores, stores,
lhs_symbol, lhs_symbol,
rhs_symbol, rhs_symbol,
Layout::Builtin(Builtin::Int64),
Some(ConstructorKnown::OnlyPass { Some(ConstructorKnown::OnlyPass {
scrutinee: path_symbol, scrutinee: path_symbol,
layout: *cond_layout, layout: *cond_layout,
@ -1273,13 +1266,7 @@ fn test_to_equality<'a>(
let lhs_symbol = env.unique_symbol(); let lhs_symbol = env.unique_symbol();
stores.push((lhs_symbol, Layout::Builtin(Builtin::Int64), lhs)); stores.push((lhs_symbol, Layout::Builtin(Builtin::Int64), lhs));
( (stores, lhs_symbol, rhs_symbol, None)
stores,
lhs_symbol,
rhs_symbol,
Layout::Builtin(Builtin::Int64),
None,
)
} }
Test::IsFloat(test_int) => { Test::IsFloat(test_int) => {
@ -1289,13 +1276,7 @@ fn test_to_equality<'a>(
let lhs_symbol = env.unique_symbol(); let lhs_symbol = env.unique_symbol();
stores.push((lhs_symbol, Layout::Builtin(Builtin::Float64), lhs)); stores.push((lhs_symbol, Layout::Builtin(Builtin::Float64), lhs));
( (stores, lhs_symbol, rhs_symbol, None)
stores,
lhs_symbol,
rhs_symbol,
Layout::Builtin(Builtin::Float64),
None,
)
} }
Test::IsByte { Test::IsByte {
@ -1305,13 +1286,7 @@ fn test_to_equality<'a>(
let lhs_symbol = env.unique_symbol(); let lhs_symbol = env.unique_symbol();
stores.push((lhs_symbol, Layout::Builtin(Builtin::Int8), lhs)); stores.push((lhs_symbol, Layout::Builtin(Builtin::Int8), lhs));
( (stores, lhs_symbol, rhs_symbol, None)
stores,
lhs_symbol,
rhs_symbol,
Layout::Builtin(Builtin::Int8),
None,
)
} }
Test::IsBit(test_bit) => { Test::IsBit(test_bit) => {
@ -1319,13 +1294,7 @@ fn test_to_equality<'a>(
let lhs_symbol = env.unique_symbol(); let lhs_symbol = env.unique_symbol();
stores.push((lhs_symbol, Layout::Builtin(Builtin::Int1), lhs)); stores.push((lhs_symbol, Layout::Builtin(Builtin::Int1), lhs));
( (stores, lhs_symbol, rhs_symbol, None)
stores,
lhs_symbol,
rhs_symbol,
Layout::Builtin(Builtin::Int1),
None,
)
} }
Test::IsStr(test_str) => { Test::IsStr(test_str) => {
@ -1334,13 +1303,7 @@ fn test_to_equality<'a>(
stores.push((lhs_symbol, Layout::Builtin(Builtin::Str), lhs)); stores.push((lhs_symbol, Layout::Builtin(Builtin::Str), lhs));
( (stores, lhs_symbol, rhs_symbol, None)
stores,
lhs_symbol,
rhs_symbol,
Layout::Builtin(Builtin::Str),
None,
)
} }
} }
} }
@ -1349,7 +1312,6 @@ type Tests<'a> = std::vec::Vec<(
bumpalo::collections::Vec<'a, (Symbol, Layout<'a>, Expr<'a>)>, bumpalo::collections::Vec<'a, (Symbol, Layout<'a>, Expr<'a>)>,
Symbol, Symbol,
Symbol, Symbol,
Layout<'a>,
Option<ConstructorKnown<'a>>, Option<ConstructorKnown<'a>>,
)>; )>;
@ -1363,13 +1325,7 @@ fn stores_and_condition<'a>(
// Assumption: there is at most 1 guard, and it is the outer layer. // Assumption: there is at most 1 guard, and it is the outer layer.
for (path, test) in test_chain { for (path, test) in test_chain {
tests.push(test_to_equality( tests.push(test_to_equality(env, cond_symbol, cond_layout, &path, test))
env,
cond_symbol,
&cond_layout,
&path,
test,
))
} }
tests tests
@ -1495,7 +1451,7 @@ fn compile_tests<'a>(
fail: &'a Stmt<'a>, fail: &'a Stmt<'a>,
mut cond: Stmt<'a>, mut cond: Stmt<'a>,
) -> Stmt<'a> { ) -> Stmt<'a> {
for (new_stores, lhs, rhs, _layout, opt_constructor_info) in tests.into_iter() { for (new_stores, lhs, rhs, opt_constructor_info) in tests.into_iter() {
match opt_constructor_info { match opt_constructor_info {
None => { None => {
cond = compile_test(env, ret_layout, new_stores, lhs, rhs, fail, cond); cond = compile_test(env, ret_layout, new_stores, lhs, rhs, fail, cond);
@ -1578,7 +1534,7 @@ fn decide_to_branching<'a>(
match decider { match decider {
Leaf(Jump(label)) => { Leaf(Jump(label)) => {
let index = jumps let index = jumps
.binary_search_by_key(&label, |ref r| r.0) .binary_search_by_key(&label, |r| r.0)
.expect("jump not in list of jumps"); .expect("jump not in list of jumps");
Stmt::Jump(jumps[index].1, &[]) Stmt::Jump(jumps[index].1, &[])
@ -1684,7 +1640,7 @@ fn decide_to_branching<'a>(
if number_of_tests == 1 { if number_of_tests == 1 {
// if there is just one test, compile to a simple if-then-else // if there is just one test, compile to a simple if-then-else
let (new_stores, lhs, rhs, _layout, _cinfo) = tests.into_iter().next().unwrap(); let (new_stores, lhs, rhs, _cinfo) = tests.into_iter().next().unwrap();
compile_test_help( compile_test_help(
env, env,

View file

@ -50,7 +50,7 @@ use roc_module::symbol::{IdentIds, ModuleId, Symbol};
// Let's work through the `Cons x xx` example // Let's work through the `Cons x xx` example
// //
// First we need to know the constructor of `xs` in the particular block. This information would // First we need to know the constructor of `xs` in the particular block. This information would
// normally be lost when we compile pattern matches, but we keep it in the `BrachInfo` field of // normally be lost when we compile pattern matches, but we keep it in the `BranchInfo` field of
// switch branches. here we also store the symbol that was switched on, and the layout of that // switch branches. here we also store the symbol that was switched on, and the layout of that
// symbol. // symbol.
// //
@ -187,7 +187,7 @@ impl<'a, 'i> Env<'a, 'i> {
pub fn unique_symbol(&mut self) -> Symbol { pub fn unique_symbol(&mut self) -> Symbol {
let ident_id = self.ident_ids.gen_unique(); let ident_id = self.ident_ids.gen_unique();
self.home.register_debug_idents(&self.ident_ids); self.home.register_debug_idents(self.ident_ids);
Symbol::new(self.home, ident_id) Symbol::new(self.home, ident_id)
} }
@ -195,7 +195,7 @@ impl<'a, 'i> Env<'a, 'i> {
fn manual_unique_symbol(home: ModuleId, ident_ids: &mut IdentIds) -> Symbol { fn manual_unique_symbol(home: ModuleId, ident_ids: &mut IdentIds) -> Symbol {
let ident_id = ident_ids.gen_unique(); let ident_id = ident_ids.gen_unique();
home.register_debug_idents(&ident_ids); home.register_debug_idents(ident_ids);
Symbol::new(home, ident_id) Symbol::new(home, ident_id)
} }

View file

@ -247,8 +247,7 @@ impl<'a> Context<'a> {
pub fn new(arena: &'a Bump, param_map: &'a ParamMap<'a>) -> Self { pub fn new(arena: &'a Bump, param_map: &'a ParamMap<'a>) -> Self {
let mut vars = MutMap::default(); let mut vars = MutMap::default();
for (key, _) in param_map.into_iter() { for symbol in param_map.iter_symbols() {
if let crate::borrow::Key::Declaration(symbol, _) = key {
vars.insert( vars.insert(
*symbol, *symbol,
VarInfo { VarInfo {
@ -259,7 +258,6 @@ impl<'a> Context<'a> {
}, },
); );
} }
}
Self { Self {
arena, arena,
@ -742,7 +740,7 @@ impl<'a> Context<'a> {
) -> (&'a Stmt<'a>, LiveVarSet) { ) -> (&'a Stmt<'a>, LiveVarSet) {
use Expr::*; use Expr::*;
let mut live_vars = update_live_vars(&v, &b_live_vars); let mut live_vars = update_live_vars(&v, b_live_vars);
live_vars.remove(&z); live_vars.remove(&z);
let new_b = match v { let new_b = match v {
@ -752,7 +750,7 @@ impl<'a> Context<'a> {
| Array { elems: ys, .. } => self.add_inc_before_consume_all( | Array { elems: ys, .. } => self.add_inc_before_consume_all(
ys, ys,
self.arena.alloc(Stmt::Let(z, v, l, b)), self.arena.alloc(Stmt::Let(z, v, l, b)),
&b_live_vars, b_live_vars,
), ),
Call(crate::ir::Call { Call(crate::ir::Call {
@ -1261,28 +1259,25 @@ fn update_jp_live_vars(j: JoinPointId, ys: &[Param], v: &Stmt<'_>, m: &mut JPLiv
m.insert(j, j_live_vars); m.insert(j, j_live_vars);
} }
/// used to process the main function in the repl pub fn visit_procs<'a>(
pub fn visit_declaration<'a>(
arena: &'a Bump, arena: &'a Bump,
param_map: &'a ParamMap<'a>, param_map: &'a ParamMap<'a>,
stmt: &'a Stmt<'a>, procs: &mut MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>,
) -> &'a Stmt<'a> {
let ctx = Context::new(arena, param_map);
let params = &[] as &[_];
let ctx = ctx.update_var_info_with_params(params);
let (b, b_live_vars) = ctx.visit_stmt(stmt);
ctx.add_dec_for_dead_params(params, b, &b_live_vars)
}
pub fn visit_proc<'a>(
arena: &'a Bump,
param_map: &'a ParamMap<'a>,
proc: &mut Proc<'a>,
layout: ProcLayout<'a>,
) { ) {
let ctx = Context::new(arena, param_map); let ctx = Context::new(arena, param_map);
for (key, proc) in procs.iter_mut() {
visit_proc(arena, param_map, &ctx, proc, key.1);
}
}
fn visit_proc<'a>(
arena: &'a Bump,
param_map: &'a ParamMap<'a>,
ctx: &Context<'a>,
proc: &mut Proc<'a>,
layout: ProcLayout<'a>,
) {
let params = match param_map.get_symbol(proc.name, layout) { let params = match param_map.get_symbol(proc.name, layout) {
Some(slice) => slice, Some(slice) => slice,
None => Vec::from_iter_in( None => Vec::from_iter_in(

View file

@ -222,9 +222,7 @@ impl<'a> Proc<'a> {
) { ) {
let borrow_params = arena.alloc(crate::borrow::infer_borrow(arena, procs)); let borrow_params = arena.alloc(crate::borrow::infer_borrow(arena, procs));
for (key, proc) in procs.iter_mut() { crate::inc_dec::visit_procs(arena, borrow_params, procs);
crate::inc_dec::visit_proc(arena, borrow_params, proc, key.1);
}
} }
pub fn insert_reset_reuse_operations<'i>( pub fn insert_reset_reuse_operations<'i>(
@ -430,9 +428,7 @@ impl<'a> Procs<'a> {
let borrow_params = arena.alloc(crate::borrow::infer_borrow(arena, &result)); let borrow_params = arena.alloc(crate::borrow::infer_borrow(arena, &result));
for (key, proc) in result.iter_mut() { crate::inc_dec::visit_procs(arena, borrow_params, &mut result);
crate::inc_dec::visit_proc(arena, borrow_params, proc, key.1);
}
result result
} }
@ -473,9 +469,7 @@ impl<'a> Procs<'a> {
let borrow_params = arena.alloc(crate::borrow::infer_borrow(arena, &result)); let borrow_params = arena.alloc(crate::borrow::infer_borrow(arena, &result));
for (key, proc) in result.iter_mut() { crate::inc_dec::visit_procs(arena, borrow_params, &mut result);
crate::inc_dec::visit_proc(arena, borrow_params, proc, key.1);
}
(result, borrow_params) (result, borrow_params)
} }
@ -815,7 +809,7 @@ impl<'a, 'i> Env<'a, 'i> {
pub fn unique_symbol(&mut self) -> Symbol { pub fn unique_symbol(&mut self) -> Symbol {
let ident_id = self.ident_ids.gen_unique(); let ident_id = self.ident_ids.gen_unique();
self.home.register_debug_idents(&self.ident_ids); self.home.register_debug_idents(self.ident_ids);
Symbol::new(self.home, ident_id) Symbol::new(self.home, ident_id)
} }
@ -848,13 +842,21 @@ impl<'a, 'i> Env<'a, 'i> {
#[derive(Clone, Debug, PartialEq, Copy, Eq, Hash)] #[derive(Clone, Debug, PartialEq, Copy, Eq, Hash)]
pub struct JoinPointId(pub Symbol); pub struct JoinPointId(pub Symbol);
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Copy, Debug, PartialEq)]
pub struct Param<'a> { pub struct Param<'a> {
pub symbol: Symbol, pub symbol: Symbol,
pub borrow: bool, pub borrow: bool,
pub layout: Layout<'a>, pub layout: Layout<'a>,
} }
impl<'a> Param<'a> {
pub const EMPTY: Self = Param {
symbol: Symbol::EMPTY_PARAM,
borrow: false,
layout: Layout::Struct(&[]),
};
}
pub fn cond<'a>( pub fn cond<'a>(
env: &mut Env<'a, '_>, env: &mut Env<'a, '_>,
cond_symbol: Symbol, cond_symbol: Symbol,
@ -1698,7 +1700,7 @@ fn pattern_to_when<'a>(
UnsupportedPattern(region) => { UnsupportedPattern(region) => {
// create the runtime error here, instead of delegating to When. // create the runtime error here, instead of delegating to When.
// UnsupportedPattern should then never occcur in When // UnsupportedPattern should then never occur in When
let error = roc_problem::can::RuntimeError::UnsupportedPattern(*region); let error = roc_problem::can::RuntimeError::UnsupportedPattern(*region);
(env.unique_symbol(), Located::at_zero(RuntimeError(error))) (env.unique_symbol(), Located::at_zero(RuntimeError(error)))
} }
@ -2106,8 +2108,6 @@ fn specialize_external<'a>(
let expr = Expr::UnionAtIndex { let expr = Expr::UnionAtIndex {
tag_id, tag_id,
structure: Symbol::ARG_CLOSURE, structure: Symbol::ARG_CLOSURE,
// union at index still expects the index to be +1; it thinks
// the tag id is stored
index: index as u64, index: index as u64,
union_layout, union_layout,
}; };
@ -2468,7 +2468,7 @@ fn specialize_solved_type<'a>(
// for debugging only // for debugging only
let attempted_layout = layout_cache let attempted_layout = layout_cache
.from_var(&env.arena, fn_var, env.subs) .from_var(env.arena, fn_var, env.subs)
.unwrap_or_else(|err| panic!("TODO handle invalid function {:?}", err)); .unwrap_or_else(|err| panic!("TODO handle invalid function {:?}", err));
let raw = match attempted_layout { let raw = match attempted_layout {
@ -2509,7 +2509,7 @@ fn specialize_solved_type<'a>(
debug_assert_eq!( debug_assert_eq!(
attempted_layout, attempted_layout,
layout_cache layout_cache
.from_var(&env.arena, fn_var, env.subs) .from_var(env.arena, fn_var, env.subs)
.unwrap_or_else(|err| panic!("TODO handle invalid function {:?}", err)) .unwrap_or_else(|err| panic!("TODO handle invalid function {:?}", err))
); );
@ -2733,10 +2733,10 @@ pub fn with_hole<'a>(
Layout::Builtin(float_precision_to_builtin(precision)), Layout::Builtin(float_precision_to_builtin(precision)),
hole, hole,
), ),
IntOrFloat::DecimalFloatType(precision) => Stmt::Let( IntOrFloat::DecimalFloatType => Stmt::Let(
assigned, assigned,
Expr::Literal(Literal::Float(num as f64)), Expr::Literal(Literal::Float(num as f64)),
Layout::Builtin(float_precision_to_builtin(precision)), Layout::Builtin(Builtin::Decimal),
hole, hole,
), ),
_ => unreachable!("unexpected float precision for integer"), _ => unreachable!("unexpected float precision for integer"),
@ -2769,10 +2769,10 @@ pub fn with_hole<'a>(
Layout::Builtin(float_precision_to_builtin(precision)), Layout::Builtin(float_precision_to_builtin(precision)),
hole, hole,
), ),
IntOrFloat::DecimalFloatType(precision) => Stmt::Let( IntOrFloat::DecimalFloatType => Stmt::Let(
assigned, assigned,
Expr::Literal(Literal::Float(num as f64)), Expr::Literal(Literal::Float(num as f64)),
Layout::Builtin(float_precision_to_builtin(precision)), Layout::Builtin(Builtin::Decimal),
hole, hole,
), ),
}, },
@ -3010,7 +3010,7 @@ pub fn with_hole<'a>(
let arena = env.arena; let arena = env.arena;
debug_assert!(!matches!( debug_assert!(!matches!(
env.subs.get_without_compacting(variant_var).content, env.subs.get_content_without_compacting(variant_var),
Content::Structure(FlatType::Func(_, _, _)) Content::Structure(FlatType::Func(_, _, _))
)); ));
convert_tag_union( convert_tag_union(
@ -3035,12 +3035,15 @@ pub fn with_hole<'a>(
} => { } => {
let arena = env.arena; let arena = env.arena;
let desc = env.subs.get_without_compacting(variant_var); let content = env.subs.get_content_without_compacting(variant_var);
if let Content::Structure(FlatType::Func(arg_vars, _, ret_var)) = content {
let ret_var = *ret_var;
let arg_vars = arg_vars.clone();
if let Content::Structure(FlatType::Func(arg_vars, _, ret_var)) = desc.content {
tag_union_to_function( tag_union_to_function(
env, env,
arg_vars, &arg_vars,
ret_var, ret_var,
tag_name, tag_name,
closure_name, closure_name,
@ -3855,7 +3858,7 @@ pub fn with_hole<'a>(
let mut arg_symbols = Vec::with_capacity_in(args.len(), env.arena); let mut arg_symbols = Vec::with_capacity_in(args.len(), env.arena);
for (_, arg_expr) in args.iter() { for (_, arg_expr) in args.iter() {
arg_symbols.push(possible_reuse_symbol(env, procs, &arg_expr)); arg_symbols.push(possible_reuse_symbol(env, procs, arg_expr));
} }
let arg_symbols = arg_symbols.into_bump_slice(); let arg_symbols = arg_symbols.into_bump_slice();
@ -3885,7 +3888,7 @@ pub fn with_hole<'a>(
let mut arg_symbols = Vec::with_capacity_in(args.len(), env.arena); let mut arg_symbols = Vec::with_capacity_in(args.len(), env.arena);
for (_, arg_expr) in args.iter() { for (_, arg_expr) in args.iter() {
arg_symbols.push(possible_reuse_symbol(env, procs, &arg_expr)); arg_symbols.push(possible_reuse_symbol(env, procs, arg_expr));
} }
let arg_symbols = arg_symbols.into_bump_slice(); let arg_symbols = arg_symbols.into_bump_slice();
@ -4343,7 +4346,7 @@ fn convert_tag_union<'a>(
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
fn tag_union_to_function<'a>( fn tag_union_to_function<'a>(
env: &mut Env<'a, '_>, env: &mut Env<'a, '_>,
argument_variables: std::vec::Vec<Variable>, argument_variables: &[Variable],
return_variable: Variable, return_variable: Variable,
tag_name: TagName, tag_name: TagName,
proc_symbol: Symbol, proc_symbol: Symbol,
@ -4364,8 +4367,8 @@ fn tag_union_to_function<'a>(
let loc_expr = Located::at_zero(roc_can::expr::Expr::Var(arg_symbol)); let loc_expr = Located::at_zero(roc_can::expr::Expr::Var(arg_symbol));
loc_pattern_args.push((arg_var, loc_pattern)); loc_pattern_args.push((*arg_var, loc_pattern));
loc_expr_args.push((arg_var, loc_expr)); loc_expr_args.push((*arg_var, loc_expr));
} }
let loc_body = Located::at_zero(roc_can::expr::Expr::Tag { let loc_body = Located::at_zero(roc_can::expr::Expr::Tag {
@ -5540,7 +5543,7 @@ fn store_pattern_help<'a>(
layout_cache, layout_cache,
outer_symbol, outer_symbol,
&layout, &layout,
&arguments, arguments,
stmt, stmt,
); );
} }
@ -5557,7 +5560,7 @@ fn store_pattern_help<'a>(
layout_cache, layout_cache,
outer_symbol, outer_symbol,
*layout, *layout,
&arguments, arguments,
*tag_id, *tag_id,
stmt, stmt,
); );
@ -6362,18 +6365,19 @@ fn call_by_name_help<'a>(
let top_level_layout = ProcLayout::new(env.arena, argument_layouts, *ret_layout); let top_level_layout = ProcLayout::new(env.arena, argument_layouts, *ret_layout);
// the arguments given to the function, stored in symbols // the arguments given to the function, stored in symbols
let field_symbols = Vec::from_iter_in( let mut field_symbols = Vec::with_capacity_in(loc_args.len(), arena);
field_symbols.extend(
loc_args loc_args
.iter() .iter()
.map(|(_, arg_expr)| possible_reuse_symbol(env, procs, &arg_expr.value)), .map(|(_, arg_expr)| possible_reuse_symbol(env, procs, &arg_expr.value)),
arena, );
)
.into_bump_slice(); let field_symbols = field_symbols.into_bump_slice();
// the variables of the given arguments // the variables of the given arguments
let mut pattern_vars = Vec::with_capacity_in(loc_args.len(), arena); let mut pattern_vars = Vec::with_capacity_in(loc_args.len(), arena);
for (var, _) in &loc_args { for (var, _) in &loc_args {
match layout_cache.from_var(&env.arena, *var, &env.subs) { match layout_cache.from_var(env.arena, *var, env.subs) {
Ok(_) => { Ok(_) => {
pattern_vars.push(*var); pattern_vars.push(*var);
} }
@ -6870,7 +6874,7 @@ fn from_can_pattern_help<'a>(
IntOrFloat::SignedIntType(_) => Ok(Pattern::IntLiteral(*num as i128)), IntOrFloat::SignedIntType(_) => Ok(Pattern::IntLiteral(*num as i128)),
IntOrFloat::UnsignedIntType(_) => Ok(Pattern::IntLiteral(*num as i128)), IntOrFloat::UnsignedIntType(_) => Ok(Pattern::IntLiteral(*num as i128)),
IntOrFloat::BinaryFloatType(_) => Ok(Pattern::FloatLiteral(*num as u64)), IntOrFloat::BinaryFloatType(_) => Ok(Pattern::FloatLiteral(*num as u64)),
IntOrFloat::DecimalFloatType(_) => Ok(Pattern::FloatLiteral(*num as u64)), IntOrFloat::DecimalFloatType => Ok(Pattern::FloatLiteral(*num as u64)),
} }
} }
@ -7394,8 +7398,8 @@ fn from_can_pattern_help<'a>(
// TODO these don't match up in the uniqueness inference; when we remove // TODO these don't match up in the uniqueness inference; when we remove
// that, reinstate this assert! // that, reinstate this assert!
// //
// dbg!(&env.subs.get_without_compacting(*field_var).content); // dbg!(&env.subs.get_content_without_compacting(*field_var));
// dbg!(&env.subs.get_without_compacting(destruct.value.var).content); // dbg!(&env.subs.get_content_without_compacting(destruct.var).content);
// debug_assert_eq!( // debug_assert_eq!(
// env.subs.get_root_key_without_compacting(*field_var), // env.subs.get_root_key_without_compacting(*field_var),
// env.subs.get_root_key_without_compacting(destruct.value.var) // env.subs.get_root_key_without_compacting(destruct.value.var)
@ -7460,7 +7464,7 @@ pub enum IntOrFloat {
SignedIntType(IntPrecision), SignedIntType(IntPrecision),
UnsignedIntType(IntPrecision), UnsignedIntType(IntPrecision),
BinaryFloatType(FloatPrecision), BinaryFloatType(FloatPrecision),
DecimalFloatType(FloatPrecision), DecimalFloatType,
} }
fn float_precision_to_builtin(precision: FloatPrecision) -> Builtin<'static> { fn float_precision_to_builtin(precision: FloatPrecision) -> Builtin<'static> {
@ -7489,7 +7493,7 @@ pub fn num_argument_to_int_or_float(
var: Variable, var: Variable,
known_to_be_float: bool, known_to_be_float: bool,
) -> IntOrFloat { ) -> IntOrFloat {
match subs.get_without_compacting(var).content { match subs.get_content_without_compacting(var){
Content::FlexVar(_) | Content::RigidVar(_) if known_to_be_float => IntOrFloat::BinaryFloatType(FloatPrecision::F64), Content::FlexVar(_) | Content::RigidVar(_) if known_to_be_float => IntOrFloat::BinaryFloatType(FloatPrecision::F64),
Content::FlexVar(_) | Content::RigidVar(_) => IntOrFloat::SignedIntType(IntPrecision::I64), // We default (Num *) to I64 Content::FlexVar(_) | Content::RigidVar(_) => IntOrFloat::SignedIntType(IntPrecision::I64), // We default (Num *) to I64
@ -7563,6 +7567,10 @@ pub fn num_argument_to_int_or_float(
| Content::Alias(Symbol::NUM_AT_BINARY64, _, _) => { | Content::Alias(Symbol::NUM_AT_BINARY64, _, _) => {
IntOrFloat::BinaryFloatType(FloatPrecision::F64) IntOrFloat::BinaryFloatType(FloatPrecision::F64)
} }
Content::Alias(Symbol::NUM_DECIMAL, _, _)
| Content::Alias(Symbol::NUM_AT_DECIMAL, _, _) => {
IntOrFloat::DecimalFloatType
}
Content::Alias(Symbol::NUM_F32, _, _) Content::Alias(Symbol::NUM_F32, _, _)
| Content::Alias(Symbol::NUM_BINARY32, _, _) | Content::Alias(Symbol::NUM_BINARY32, _, _)
| Content::Alias(Symbol::NUM_AT_BINARY32, _, _) => { | Content::Alias(Symbol::NUM_AT_BINARY32, _, _) => {

View file

@ -170,7 +170,16 @@ impl<'a> UnionLayout<'a> {
pub fn tag_id_builtin(&self) -> Builtin<'a> { pub fn tag_id_builtin(&self) -> Builtin<'a> {
match self { match self {
UnionLayout::NonRecursive(tags) | UnionLayout::Recursive(tags) => { UnionLayout::NonRecursive(_tags) => {
// let union_size = tags.len();
// Self::tag_id_builtin_help(union_size)
// The quicksort-benchmarks version of Quicksort.roc segfaults when
// this number is not I64. There must be some dependence on that fact
// somewhere in the code, I have not found where that is yet...
Builtin::Int64
}
UnionLayout::Recursive(tags) => {
let union_size = tags.len(); let union_size = tags.len();
Self::tag_id_builtin_help(union_size) Self::tag_id_builtin_help(union_size)
@ -460,6 +469,7 @@ pub enum Builtin<'a> {
Int8, Int8,
Int1, Int1,
Usize, Usize,
Decimal,
Float128, Float128,
Float64, Float64,
Float32, Float32,
@ -510,8 +520,8 @@ impl<'a> Layout<'a> {
match content { match content {
FlexVar(_) | RigidVar(_) => Err(LayoutProblem::UnresolvedTypeVar(var)), FlexVar(_) | RigidVar(_) => Err(LayoutProblem::UnresolvedTypeVar(var)),
RecursionVar { structure, .. } => { RecursionVar { structure, .. } => {
let structure_content = env.subs.get_without_compacting(structure).content; let structure_content = env.subs.get_content_without_compacting(structure);
Self::new_help(env, structure, structure_content) Self::new_help(env, structure, structure_content.clone())
} }
Structure(flat_type) => layout_from_flat_type(env, flat_type), Structure(flat_type) => layout_from_flat_type(env, flat_type),
@ -581,8 +591,8 @@ impl<'a> Layout<'a> {
if env.is_seen(var) { if env.is_seen(var) {
Ok(Layout::RecursivePointer) Ok(Layout::RecursivePointer)
} else { } else {
let content = env.subs.get_without_compacting(var).content; let content = env.subs.get_content_without_compacting(var);
Self::new_help(env, var, content) Self::new_help(env, var, content.clone())
} }
} }
@ -655,7 +665,7 @@ impl<'a> Layout<'a> {
.max() .max()
.unwrap_or_default() .unwrap_or_default()
// the size of the tag_id // the size of the tag_id
+ pointer_size + variant.tag_id_builtin().stack_size(pointer_size)
} }
Recursive(_) Recursive(_)
@ -952,6 +962,7 @@ impl<'a> Builtin<'a> {
const I8_SIZE: u32 = std::mem::size_of::<i8>() as u32; const I8_SIZE: u32 = std::mem::size_of::<i8>() as u32;
const I1_SIZE: u32 = std::mem::size_of::<bool>() as u32; const I1_SIZE: u32 = std::mem::size_of::<bool>() as u32;
const USIZE_SIZE: u32 = std::mem::size_of::<usize>() as u32; const USIZE_SIZE: u32 = std::mem::size_of::<usize>() as u32;
const DECIMAL_SIZE: u32 = std::mem::size_of::<i128>() as u32;
const F128_SIZE: u32 = 16; const F128_SIZE: u32 = 16;
const F64_SIZE: u32 = std::mem::size_of::<f64>() as u32; const F64_SIZE: u32 = std::mem::size_of::<f64>() as u32;
const F32_SIZE: u32 = std::mem::size_of::<f32>() as u32; const F32_SIZE: u32 = std::mem::size_of::<f32>() as u32;
@ -981,6 +992,7 @@ impl<'a> Builtin<'a> {
Int8 => Builtin::I8_SIZE, Int8 => Builtin::I8_SIZE,
Int1 => Builtin::I1_SIZE, Int1 => Builtin::I1_SIZE,
Usize => Builtin::USIZE_SIZE, Usize => Builtin::USIZE_SIZE,
Decimal => Builtin::DECIMAL_SIZE,
Float128 => Builtin::F128_SIZE, Float128 => Builtin::F128_SIZE,
Float64 => Builtin::F64_SIZE, Float64 => Builtin::F64_SIZE,
Float32 => Builtin::F32_SIZE, Float32 => Builtin::F32_SIZE,
@ -1007,6 +1019,7 @@ impl<'a> Builtin<'a> {
Int8 => align_of::<i8>() as u32, Int8 => align_of::<i8>() as u32,
Int1 => align_of::<bool>() as u32, Int1 => align_of::<bool>() as u32,
Usize => align_of::<usize>() as u32, Usize => align_of::<usize>() as u32,
Decimal => align_of::<i128>() as u32,
Float128 => align_of::<i128>() as u32, Float128 => align_of::<i128>() as u32,
Float64 => align_of::<f64>() as u32, Float64 => align_of::<f64>() as u32,
Float32 => align_of::<f32>() as u32, Float32 => align_of::<f32>() as u32,
@ -1022,8 +1035,8 @@ impl<'a> Builtin<'a> {
use Builtin::*; use Builtin::*;
match self { match self {
Int128 | Int64 | Int32 | Int16 | Int8 | Int1 | Usize | Float128 | Float64 | Float32 Int128 | Int64 | Int32 | Int16 | Int8 | Int1 | Usize | Decimal | Float128 | Float64
| Float16 | EmptyStr | EmptyDict | EmptyList | EmptySet => true, | Float32 | Float16 | EmptyStr | EmptyDict | EmptyList | EmptySet => true,
Str | Dict(_, _) | Set(_) | List(_) => false, Str | Dict(_, _) | Set(_) | List(_) => false,
} }
} }
@ -1033,8 +1046,8 @@ impl<'a> Builtin<'a> {
use Builtin::*; use Builtin::*;
match self { match self {
Int128 | Int64 | Int32 | Int16 | Int8 | Int1 | Usize | Float128 | Float64 | Float32 Int128 | Int64 | Int32 | Int16 | Int8 | Int1 | Usize | Decimal | Float128 | Float64
| Float16 | EmptyStr | EmptyDict | EmptyList | EmptySet => false, | Float32 | Float16 | EmptyStr | EmptyDict | EmptyList | EmptySet => false,
List(_) => true, List(_) => true,
Str | Dict(_, _) | Set(_) => true, Str | Dict(_, _) | Set(_) => true,
@ -1057,6 +1070,7 @@ impl<'a> Builtin<'a> {
Int8 => alloc.text("Int8"), Int8 => alloc.text("Int8"),
Int1 => alloc.text("Int1"), Int1 => alloc.text("Int1"),
Usize => alloc.text("Usize"), Usize => alloc.text("Usize"),
Decimal => alloc.text("Decimal"),
Float128 => alloc.text("Float128"), Float128 => alloc.text("Float128"),
Float64 => alloc.text("Float64"), Float64 => alloc.text("Float64"),
Float32 => alloc.text("Float32"), Float32 => alloc.text("Float32"),
@ -1144,6 +1158,10 @@ fn layout_from_flat_type<'a>(
} }
// Floats // Floats
Symbol::NUM_DEC => {
debug_assert_eq!(args.len(), 0);
Ok(Layout::Builtin(Builtin::Decimal))
}
Symbol::NUM_F64 => { Symbol::NUM_F64 => {
debug_assert_eq!(args.len(), 0); debug_assert_eq!(args.len(), 0);
Ok(Layout::Builtin(Builtin::Float64)) Ok(Layout::Builtin(Builtin::Float64))
@ -1158,7 +1176,7 @@ fn layout_from_flat_type<'a>(
debug_assert_eq!(args.len(), 1); debug_assert_eq!(args.len(), 1);
let var = args.first().unwrap(); let var = args.first().unwrap();
let content = subs.get_without_compacting(*var).content; let content = subs.get_content_without_compacting(*var);
layout_from_num_content(content) layout_from_num_content(content)
} }
@ -1197,25 +1215,8 @@ fn layout_from_flat_type<'a>(
Err(_) => unreachable!("this would have been a type error"), Err(_) => unreachable!("this would have been a type error"),
} }
let sorted_fields = sort_record_fields_help(env, fields_map); // discard optional fields
let mut layouts = sort_stored_record_fields(env, fields_map);
// Determine the layouts of the fields, maintaining sort order
let mut layouts = Vec::with_capacity_in(sorted_fields.len(), arena);
for (_, _, res_layout) in sorted_fields {
match res_layout {
Ok(layout) => {
// Drop any zero-sized fields like {}.
if !layout.is_dropped_because_empty() {
layouts.push(layout);
}
}
Err(_) => {
// optional field, ignore
continue;
}
}
}
if layouts.len() == 1 { if layouts.len() == 1 {
// If the record has only one field that isn't zero-sized, // If the record has only one field that isn't zero-sized,
@ -1234,7 +1235,7 @@ fn layout_from_flat_type<'a>(
debug_assert!(ext_var_is_empty_tag_union(subs, ext_var)); debug_assert!(ext_var_is_empty_tag_union(subs, ext_var));
let mut tags = MutMap::default(); let mut tags = MutMap::default();
tags.insert(tag_name, vec![]); tags.insert(*tag_name, vec![]);
Ok(layout_from_tag_union(arena, tags, subs)) Ok(layout_from_tag_union(arena, tags, subs))
} }
@ -1316,7 +1317,7 @@ fn layout_from_flat_type<'a>(
} }
} else if tag_layouts.len() == 1 { } else if tag_layouts.len() == 1 {
// drop the tag id // drop the tag id
UnionLayout::NonNullableUnwrapped(&tag_layouts.pop().unwrap()) UnionLayout::NonNullableUnwrapped(tag_layouts.pop().unwrap())
} else { } else {
UnionLayout::Recursive(tag_layouts.into_bump_slice()) UnionLayout::Recursive(tag_layouts.into_bump_slice())
}; };
@ -1394,6 +1395,43 @@ fn sort_record_fields_help<'a>(
sorted_fields sorted_fields
} }
// drops optional fields
fn sort_stored_record_fields<'a>(
env: &mut Env<'a, '_>,
fields_map: MutMap<Lowercase, RecordField<Variable>>,
) -> Vec<'a, Layout<'a>> {
// Sort the fields by label
let mut sorted_fields = Vec::with_capacity_in(fields_map.len(), env.arena);
for (label, field) in fields_map {
let var = match field {
RecordField::Demanded(v) => v,
RecordField::Required(v) => v,
RecordField::Optional(_) => {
continue;
}
};
let layout = Layout::from_var(env, var).expect("invalid layout from var");
sorted_fields.push((label, layout));
}
sorted_fields.sort_by(|(label1, layout1), (label2, layout2)| {
let ptr_bytes = 8;
let size1 = layout1.alignment_bytes(ptr_bytes);
let size2 = layout2.alignment_bytes(ptr_bytes);
size2.cmp(&size1).then(label1.cmp(label2))
});
let mut result = Vec::with_capacity_in(sorted_fields.len(), env.arena);
result.extend(sorted_fields.into_iter().map(|t| t.1));
result
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)] #[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum UnionVariant<'a> { pub enum UnionVariant<'a> {
Never, Never,
@ -1519,8 +1557,8 @@ pub fn union_sorted_tags<'a>(
subs: &Subs, subs: &Subs,
) -> Result<UnionVariant<'a>, LayoutProblem> { ) -> Result<UnionVariant<'a>, LayoutProblem> {
let var = let var =
if let Content::RecursionVar { structure, .. } = subs.get_without_compacting(var).content { if let Content::RecursionVar { structure, .. } = subs.get_content_without_compacting(var) {
structure *structure
} else { } else {
var var
}; };
@ -1539,9 +1577,9 @@ pub fn union_sorted_tags<'a>(
} }
fn get_recursion_var(subs: &Subs, var: Variable) -> Option<Variable> { fn get_recursion_var(subs: &Subs, var: Variable) -> Option<Variable> {
match subs.get_without_compacting(var).content { match subs.get_content_without_compacting(var) {
Content::Structure(FlatType::RecursiveTagUnion(rec_var, _, _)) => Some(rec_var), Content::Structure(FlatType::RecursiveTagUnion(rec_var, _, _)) => Some(*rec_var),
Content::Alias(_, _, actual) => get_recursion_var(subs, actual), Content::Alias(_, _, actual) => get_recursion_var(subs, *actual),
_ => None, _ => None,
} }
} }
@ -1868,7 +1906,7 @@ fn ext_var_is_empty_tag_union(_: &Subs, _: Variable) -> bool {
unreachable!(); unreachable!();
} }
fn layout_from_num_content<'a>(content: Content) -> Result<Layout<'a>, LayoutProblem> { fn layout_from_num_content<'a>(content: &Content) -> Result<Layout<'a>, LayoutProblem> {
use roc_types::subs::Content::*; use roc_types::subs::Content::*;
use roc_types::subs::FlatType::*; use roc_types::subs::FlatType::*;
@ -1881,7 +1919,7 @@ fn layout_from_num_content<'a>(content: Content) -> Result<Layout<'a>, LayoutPro
// (e.g. for (5 + 5) assume both 5s are 64-bit integers.) // (e.g. for (5 + 5) assume both 5s are 64-bit integers.)
Ok(Layout::Builtin(DEFAULT_NUM_BUILTIN)) Ok(Layout::Builtin(DEFAULT_NUM_BUILTIN))
} }
Structure(Apply(symbol, args)) => match symbol { Structure(Apply(symbol, args)) => match *symbol {
// Ints // Ints
Symbol::NUM_NAT => Ok(Layout::Builtin(Builtin::Usize)), Symbol::NUM_NAT => Ok(Layout::Builtin(Builtin::Usize)),
@ -1900,13 +1938,14 @@ fn layout_from_num_content<'a>(content: Content) -> Result<Layout<'a>, LayoutPro
// Floats // Floats
Symbol::NUM_FLOATINGPOINT => Ok(Layout::Builtin(Builtin::Float64)), Symbol::NUM_FLOATINGPOINT => Ok(Layout::Builtin(Builtin::Float64)),
Symbol::NUM_DEC => Ok(Layout::Builtin(Builtin::Decimal)),
Symbol::NUM_F64 => Ok(Layout::Builtin(Builtin::Float64)), Symbol::NUM_F64 => Ok(Layout::Builtin(Builtin::Float64)),
Symbol::NUM_F32 => Ok(Layout::Builtin(Builtin::Float32)), Symbol::NUM_F32 => Ok(Layout::Builtin(Builtin::Float32)),
_ => { _ => {
panic!( panic!(
"Invalid Num.Num type application: {:?}", "Invalid Num.Num type application: Apply({:?}, {:?})",
Apply(symbol, args) symbol, args
); );
} }
}, },
@ -1921,19 +1960,19 @@ fn layout_from_num_content<'a>(content: Content) -> Result<Layout<'a>, LayoutPro
} }
fn unwrap_num_tag<'a>(subs: &Subs, var: Variable) -> Result<Layout<'a>, LayoutProblem> { fn unwrap_num_tag<'a>(subs: &Subs, var: Variable) -> Result<Layout<'a>, LayoutProblem> {
match subs.get_without_compacting(var).content { match subs.get_content_without_compacting(var) {
Content::Alias(Symbol::NUM_INTEGER, args, _) => { Content::Alias(Symbol::NUM_INTEGER, args, _) => {
debug_assert!(args.len() == 1); debug_assert!(args.len() == 1);
let (_, precision_var) = args[0]; let (_, precision_var) = args[0];
let precision = subs.get_without_compacting(precision_var).content; let precision = subs.get_content_without_compacting(precision_var);
match precision { match precision {
Content::Alias(symbol, args, _) => { Content::Alias(symbol, args, _) => {
debug_assert!(args.is_empty()); debug_assert!(args.is_empty());
let builtin = match symbol { let builtin = match *symbol {
Symbol::NUM_SIGNED128 => Builtin::Int128, Symbol::NUM_SIGNED128 => Builtin::Int128,
Symbol::NUM_SIGNED64 => Builtin::Int64, Symbol::NUM_SIGNED64 => Builtin::Int64,
Symbol::NUM_SIGNED32 => Builtin::Int32, Symbol::NUM_SIGNED32 => Builtin::Int32,
@ -1962,7 +2001,7 @@ fn unwrap_num_tag<'a>(subs: &Subs, var: Variable) -> Result<Layout<'a>, LayoutPr
let (_, precision_var) = args[0]; let (_, precision_var) = args[0];
let precision = subs.get_without_compacting(precision_var).content; let precision = subs.get_content_without_compacting(precision_var);
match precision { match precision {
Content::Alias(Symbol::NUM_BINARY32, args, _) => { Content::Alias(Symbol::NUM_BINARY32, args, _) => {
@ -1975,6 +2014,11 @@ fn unwrap_num_tag<'a>(subs: &Subs, var: Variable) -> Result<Layout<'a>, LayoutPr
Ok(Layout::Builtin(Builtin::Float64)) Ok(Layout::Builtin(Builtin::Float64))
} }
Content::Alias(Symbol::NUM_DECIMAL, args, _) => {
debug_assert!(args.is_empty());
Ok(Layout::Builtin(Builtin::Decimal))
}
Content::FlexVar(_) | Content::RigidVar(_) => { Content::FlexVar(_) | Content::RigidVar(_) => {
// default to f64 // default to f64
Ok(Layout::Builtin(Builtin::Float64)) Ok(Layout::Builtin(Builtin::Float64))
@ -1997,15 +2041,15 @@ fn dict_layout_from_key_value<'a>(
key_var: Variable, key_var: Variable,
value_var: Variable, value_var: Variable,
) -> Result<Layout<'a>, LayoutProblem> { ) -> Result<Layout<'a>, LayoutProblem> {
match env.subs.get_without_compacting(key_var).content { match env.subs.get_content_without_compacting(key_var) {
Content::FlexVar(_) | Content::RigidVar(_) => { Content::FlexVar(_) | Content::RigidVar(_) => {
// If this was still a (Dict * *) then it must have been an empty dict // If this was still a (Dict * *) then it must have been an empty dict
Ok(Layout::Builtin(Builtin::EmptyDict)) Ok(Layout::Builtin(Builtin::EmptyDict))
} }
key_content => { key_content => {
let value_content = env.subs.get_without_compacting(value_var).content; let value_content = env.subs.get_content_without_compacting(value_var);
let key_layout = Layout::new_help(env, key_var, key_content)?; let key_layout = Layout::new_help(env, key_var, key_content.clone())?;
let value_layout = Layout::new_help(env, value_var, value_content)?; let value_layout = Layout::new_help(env, value_var, value_content.clone())?;
// This is a normal list. // This is a normal list.
Ok(Layout::Builtin(Builtin::Dict( Ok(Layout::Builtin(Builtin::Dict(
@ -2020,7 +2064,7 @@ pub fn list_layout_from_elem<'a>(
env: &mut Env<'a, '_>, env: &mut Env<'a, '_>,
elem_var: Variable, elem_var: Variable,
) -> Result<Layout<'a>, LayoutProblem> { ) -> Result<Layout<'a>, LayoutProblem> {
match env.subs.get_without_compacting(elem_var).content { match env.subs.get_content_without_compacting(elem_var) {
Content::FlexVar(_) | Content::RigidVar(_) => { Content::FlexVar(_) | Content::RigidVar(_) => {
// If this was still a (List *) then it must have been an empty list // If this was still a (List *) then it must have been an empty list
Ok(Layout::Builtin(Builtin::EmptyList)) Ok(Layout::Builtin(Builtin::EmptyList))
@ -2070,7 +2114,7 @@ impl<'a> LayoutIds<'a> {
}); });
// Get the id associated with this layout, or default to next_id. // 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); 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; // 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. // store the ID we're going to return and increment next_id.
@ -2101,7 +2145,7 @@ impl<'a> LayoutIds<'a> {
// Get the id associated with this layout, or default to next_id. // Get the id associated with this layout, or default to next_id.
let answer = ids let answer = ids
.toplevels_by_id .toplevels_by_id
.get(&layout) .get(layout)
.copied() .copied()
.unwrap_or(ids.next_id); .unwrap_or(ids.next_id);

View file

@ -58,7 +58,7 @@ impl<'a, 'i> Env<'a, 'i> {
fn unique_symbol(&mut self) -> Symbol { fn unique_symbol(&mut self) -> Symbol {
let ident_id = self.ident_ids.gen_unique(); let ident_id = self.ident_ids.gen_unique();
self.home.register_debug_idents(&self.ident_ids); self.home.register_debug_idents(self.ident_ids);
Symbol::new(self.home, ident_id) Symbol::new(self.home, ident_id)
} }
@ -247,8 +247,6 @@ fn insert_reset<'a>(
let reset_expr = Expr::Reset(x); let reset_expr = Expr::Reset(x);
// const I64: Layout<'static> = Layout::Builtin(crate::layout::Builtin::Int64);
let layout = Layout::Union(union_layout); let layout = Layout::Union(union_layout);
stmt = env.arena.alloc(Stmt::Let(w, reset_expr, layout, stmt)); stmt = env.arena.alloc(Stmt::Let(w, reset_expr, layout, stmt));

View file

@ -35,9 +35,9 @@ pub fn make_tail_recursive<'a>(
stmt: Stmt<'a>, stmt: Stmt<'a>,
args: &'a [(Layout<'a>, Symbol)], args: &'a [(Layout<'a>, Symbol)],
) -> Stmt<'a> { ) -> Stmt<'a> {
let alloced = arena.alloc(stmt); let allocated = arena.alloc(stmt);
match insert_jumps(arena, alloced, id, needle) { match insert_jumps(arena, allocated, id, needle) {
None => alloced.clone(), None => allocated.clone(),
Some(new) => { Some(new) => {
// jumps were inserted, we must now add a join point // jumps were inserted, we must now add a join point

View file

@ -1,6 +1,6 @@
use crate::ast::{AssignedField, CommentOrNewline, Def, Expr, Pattern, Spaceable, TypeAnnotation}; use crate::ast::{AssignedField, CommentOrNewline, Def, Expr, Pattern, Spaceable, TypeAnnotation};
use crate::blankspace::{space0_after_e, space0_around_ee, space0_before_e, space0_e}; use crate::blankspace::{space0_after_e, space0_around_ee, space0_before_e, space0_e};
use crate::ident::{lowercase_ident, parse_ident_help, Ident}; use crate::ident::{lowercase_ident, parse_ident, Ident};
use crate::keyword; use crate::keyword;
use crate::parser::{ use crate::parser::{
self, backtrackable, optional, sep_by1, sep_by1_e, specialize, specialize_ref, then, self, backtrackable, optional, sep_by1, sep_by1_e, specialize, specialize_ref, then,
@ -252,7 +252,7 @@ fn underscore_expression<'a>() -> impl Parser<'a, Expr<'a>, EExpr<'a>> {
match output { match output {
Some(name) => Ok((MadeProgress, Expr::Underscore(name), final_state)), Some(name) => Ok((MadeProgress, Expr::Underscore(name), final_state)),
None => Ok((MadeProgress, Expr::Underscore(&""), final_state)), None => Ok((MadeProgress, Expr::Underscore(""), final_state)),
} }
} }
} }
@ -2097,7 +2097,7 @@ fn if_expr_help<'a>(
/// 5. A reserved keyword (e.g. `if ` or `case `), meaning we should do something else. /// 5. A reserved keyword (e.g. `if ` or `case `), meaning we should do something else.
fn assign_or_destructure_identifier<'a>() -> impl Parser<'a, Ident<'a>, EExpr<'a>> { fn assign_or_destructure_identifier<'a>() -> impl Parser<'a, Ident<'a>, EExpr<'a>> {
crate::ident::parse_ident_help crate::ident::parse_ident
} }
#[allow(dead_code)] #[allow(dead_code)]
@ -2238,7 +2238,7 @@ fn record_field_help<'a>(
fn record_updateable_identifier<'a>() -> impl Parser<'a, Expr<'a>, ERecord<'a>> { fn record_updateable_identifier<'a>() -> impl Parser<'a, Expr<'a>, ERecord<'a>> {
specialize( specialize(
|_, r, c| ERecord::Updateable(r, c), |_, r, c| ERecord::Updateable(r, c),
map_with_arena!(parse_ident_help, ident_to_expr), map_with_arena!(parse_ident, ident_to_expr),
) )
} }

View file

@ -138,13 +138,10 @@ macro_rules! advance_state {
}; };
} }
pub fn parse_ident_help<'a>( pub fn parse_ident<'a>(arena: &'a Bump, state: State<'a>) -> ParseResult<'a, Ident<'a>, EExpr<'a>> {
arena: &'a Bump,
state: State<'a>,
) -> ParseResult<'a, Ident<'a>, EExpr<'a>> {
let initial = state; let initial = state;
match parse_ident_help_help(arena, state) { match parse_ident_help(arena, state) {
Ok((progress, ident, state)) => { Ok((progress, ident, state)) => {
if let Ident::Access { module_name, parts } = ident { if let Ident::Access { module_name, parts } = ident {
if module_name.is_empty() { if module_name.is_empty() {
@ -526,7 +523,7 @@ fn chomp_access_chain<'a>(buffer: &'a [u8], parts: &mut Vec<'a, &'a str>) -> Res
} }
} }
fn parse_ident_help_help<'a>( fn parse_ident_help<'a>(
arena: &'a Bump, arena: &'a Bump,
mut state: State<'a>, mut state: State<'a>,
) -> ParseResult<'a, Ident<'a>, BadIdent> { ) -> ParseResult<'a, Ident<'a>, BadIdent> {

View file

@ -15,7 +15,7 @@ pub fn positive_number_literal<'a>() -> impl Parser<'a, NumLiteral<'a>, Number>
move |_arena, state: State<'a>| { move |_arena, state: State<'a>| {
match state.bytes.get(0) { match state.bytes.get(0) {
Some(first_byte) if (*first_byte as char).is_ascii_digit() => { Some(first_byte) if (*first_byte as char).is_ascii_digit() => {
parse_number_base(false, &state.bytes, state) parse_number_base(false, state.bytes, state)
} }
_ => { _ => {
// this is not a number at all // this is not a number at all
@ -33,7 +33,7 @@ pub fn number_literal<'a>() -> impl Parser<'a, NumLiteral<'a>, Number> {
parse_number_base(true, &state.bytes[1..], state) parse_number_base(true, &state.bytes[1..], state)
} }
Some(first_byte) if (*first_byte as char).is_ascii_digit() => { Some(first_byte) if (*first_byte as char).is_ascii_digit() => {
parse_number_base(false, &state.bytes, state) parse_number_base(false, state.bytes, state)
} }
_ => { _ => {
// this is not a number at all // this is not a number at all

View file

@ -1,6 +1,6 @@
use crate::ast::Pattern; use crate::ast::Pattern;
use crate::blankspace::{space0_around_ee, space0_before_e, space0_e}; use crate::blankspace::{space0_around_ee, space0_before_e, space0_e};
use crate::ident::{lowercase_ident, parse_ident_help, Ident}; use crate::ident::{lowercase_ident, parse_ident, Ident};
use crate::parser::Progress::{self, *}; use crate::parser::Progress::{self, *};
use crate::parser::{ use crate::parser::{
backtrackable, optional, specialize, specialize_ref, word1, EPattern, PInParens, PRecord, backtrackable, optional, specialize, specialize_ref, word1, EPattern, PInParens, PRecord,
@ -172,8 +172,7 @@ fn loc_ident_pattern_help<'a>(
let original_state = state; let original_state = state;
let (_, loc_ident, state) = let (_, loc_ident, state) =
specialize(|_, r, c| EPattern::Start(r, c), loc!(parse_ident_help)) specialize(|_, r, c| EPattern::Start(r, c), loc!(parse_ident)).parse(arena, state)?;
.parse(arena, state)?;
match loc_ident.value { match loc_ident.value {
Ident::GlobalTag(tag) => { Ident::GlobalTag(tag) => {
@ -259,7 +258,7 @@ fn loc_ident_pattern_help<'a>(
Located { Located {
region: loc_ident.region, region: loc_ident.region,
value: Pattern::Malformed( value: Pattern::Malformed(
String::from_str_in(&malformed_str, &arena).into_bump_str(), String::from_str_in(&malformed_str, arena).into_bump_str(),
), ),
}, },
state, state,
@ -299,7 +298,7 @@ fn underscore_pattern_help<'a>() -> impl Parser<'a, Pattern<'a>, EPattern<'a>> {
match output { match output {
Some(name) => Ok((MadeProgress, Pattern::Underscore(name), final_state)), Some(name) => Ok((MadeProgress, Pattern::Underscore(name), final_state)),
None => Ok((MadeProgress, Pattern::Underscore(&""), final_state)), None => Ok((MadeProgress, Pattern::Underscore(""), final_state)),
} }
} }
} }

View file

@ -303,12 +303,12 @@ fn applied_type<'a>(min_indent: u16) -> impl Parser<'a, TypeAnnotation<'a>, Type
), ),
|(ctor, args): (TypeAnnotation<'a>, Vec<'a, Located<TypeAnnotation<'a>>>)| { |(ctor, args): (TypeAnnotation<'a>, Vec<'a, Located<TypeAnnotation<'a>>>)| {
match &ctor { match &ctor {
TypeAnnotation::Apply(ref module_name, ref name, _) => { TypeAnnotation::Apply(module_name, name, _) => {
if args.is_empty() { if args.is_empty() {
// ctor is already an Apply with no args, so return it directly. // ctor is already an Apply with no args, so return it directly.
ctor ctor
} else { } else {
TypeAnnotation::Apply(*module_name, *name, args.into_bump_slice()) TypeAnnotation::Apply(module_name, name, args.into_bump_slice())
} }
} }
TypeAnnotation::Malformed(_) => ctor, TypeAnnotation::Malformed(_) => ctor,
@ -371,7 +371,7 @@ fn expression<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>
.parse(arena, state)?; .parse(arena, state)?;
// prepare arguments // prepare arguments
let mut arguments = Vec::with_capacity_in(rest.len() + 1, &arena); let mut arguments = Vec::with_capacity_in(rest.len() + 1, arena);
arguments.push(first); arguments.push(first);
arguments.extend(rest); arguments.extend(rest);
let output = arena.alloc(arguments); let output = arena.alloc(arguments);

View file

@ -1609,7 +1609,7 @@ mod test_parse {
#[test] #[test]
fn single_underscore_closure() { fn single_underscore_closure() {
let arena = Bump::new(); let arena = Bump::new();
let pattern = Located::new(0, 0, 1, 2, Pattern::Underscore(&"")); let pattern = Located::new(0, 0, 1, 2, Pattern::Underscore(""));
let patterns = &[pattern]; let patterns = &[pattern];
let expected = Closure(patterns, arena.alloc(Located::new(0, 0, 6, 8, Num("42")))); let expected = Closure(patterns, arena.alloc(Located::new(0, 0, 6, 8, Num("42"))));
let actual = parse_expr_with(&arena, "\\_ -> 42"); let actual = parse_expr_with(&arena, "\\_ -> 42");
@ -1629,7 +1629,7 @@ mod test_parse {
0, 0,
1, 1,
11, 11,
Pattern::MalformedIdent(&"the_answer", roc_parse::ident::BadIdent::Underscore(0, 5)), Pattern::MalformedIdent("the_answer", roc_parse::ident::BadIdent::Underscore(0, 5)),
); );
let patterns = &[pattern]; let patterns = &[pattern];
let expr = Located::new(0, 0, 15, 17, Expr::Num("42")); let expr = Located::new(0, 0, 15, 17, Expr::Num("42"));
@ -1671,8 +1671,8 @@ mod test_parse {
#[test] #[test]
fn closure_with_underscores() { fn closure_with_underscores() {
let arena = Bump::new(); let arena = Bump::new();
let underscore1 = Located::new(0, 0, 1, 2, Pattern::Underscore(&"")); let underscore1 = Located::new(0, 0, 1, 2, Pattern::Underscore(""));
let underscore2 = Located::new(0, 0, 4, 9, Pattern::Underscore(&"name")); let underscore2 = Located::new(0, 0, 4, 9, Pattern::Underscore("name"));
let patterns = bumpalo::vec![in &arena; underscore1, underscore2]; let patterns = bumpalo::vec![in &arena; underscore1, underscore2];
let expected = Closure( let expected = Closure(
arena.alloc(patterns), arena.alloc(patterns),
@ -1906,7 +1906,7 @@ mod test_parse {
fn underscore_backpassing() { fn underscore_backpassing() {
let arena = Bump::new(); let arena = Bump::new();
let newlines = bumpalo::vec![in &arena; Newline, Newline]; let newlines = bumpalo::vec![in &arena; Newline, Newline];
let underscore = Located::new(1, 1, 0, 1, Pattern::Underscore(&"")); let underscore = Located::new(1, 1, 0, 1, Pattern::Underscore(""));
let identifier_y = Located::new(1, 1, 7, 8, Identifier("y")); let identifier_y = Located::new(1, 1, 7, 8, Identifier("y"));
let num_4 = Num("4"); let num_4 = Num("4");
@ -3531,7 +3531,6 @@ mod test_parse {
match parsed { match parsed {
Ok((_, _, _state)) => { Ok((_, _, _state)) => {
// dbg!(_state); // dbg!(_state);
return;
} }
Err((_, _fail, _state)) => { Err((_, _fail, _state)) => {
// dbg!(_fail, _state); // dbg!(_fail, _state);
@ -3707,7 +3706,7 @@ mod test_parse {
guard: None, guard: None,
}); });
let newlines = &[Newline]; let newlines = &[Newline];
let pattern2 = Pattern::SpaceBefore(arena.alloc(Pattern::Underscore(&"")), newlines); let pattern2 = Pattern::SpaceBefore(arena.alloc(Pattern::Underscore("")), newlines);
let loc_pattern2 = Located::new(2, 2, 4, 5, pattern2); let loc_pattern2 = Located::new(2, 2, 4, 5, pattern2);
let expr2 = Num("4"); let expr2 = Num("4");
let loc_expr2 = Located::new(2, 2, 9, 10, expr2); let loc_expr2 = Located::new(2, 2, 9, 10, expr2);
@ -3752,7 +3751,7 @@ mod test_parse {
guard: None, guard: None,
}); });
let newlines = &[Newline]; let newlines = &[Newline];
let pattern2 = Pattern::SpaceBefore(arena.alloc(Pattern::Underscore(&"")), newlines); let pattern2 = Pattern::SpaceBefore(arena.alloc(Pattern::Underscore("")), newlines);
let loc_pattern2 = Located::new(2, 2, 4, 5, pattern2); let loc_pattern2 = Located::new(2, 2, 4, 5, pattern2);
let expr2 = Num("4"); let expr2 = Num("4");
let loc_expr2 = Located::new(2, 2, 9, 10, expr2); let loc_expr2 = Located::new(2, 2, 9, 10, expr2);

View file

@ -159,17 +159,17 @@ fn to_syntax_report<'a>(
title: "PARSE PROBLEM".to_string(), title: "PARSE PROBLEM".to_string(),
} }
} }
Type(typ) => to_type_report(alloc, filename, &typ, 0, 0), Type(typ) => to_type_report(alloc, filename, typ, 0, 0),
Pattern(pat) => to_pattern_report(alloc, filename, &pat, 0, 0), Pattern(pat) => to_pattern_report(alloc, filename, pat, 0, 0),
Expr(expr) => to_expr_report( Expr(expr) => to_expr_report(
alloc, alloc,
filename, filename,
Context::InDef(start_row, start_col), Context::InDef(start_row, start_col),
&expr, expr,
0, 0,
0, 0,
), ),
Header(header) => to_header_report(alloc, filename, &header, 0, 0), Header(header) => to_header_report(alloc, filename, header, 0, 0),
_ => todo!("unhandled parse error: {:?}", parse_problem), _ => todo!("unhandled parse error: {:?}", parse_problem),
} }
} }
@ -205,19 +205,17 @@ fn to_expr_report<'a>(
use roc_parse::parser::EExpr; use roc_parse::parser::EExpr;
match parse_problem { match parse_problem {
EExpr::If(if_, row, col) => to_if_report(alloc, filename, context, &if_, *row, *col), EExpr::If(if_, row, col) => to_if_report(alloc, filename, context, if_, *row, *col),
EExpr::When(when, row, col) => to_when_report(alloc, filename, context, &when, *row, *col), EExpr::When(when, row, col) => to_when_report(alloc, filename, context, when, *row, *col),
EExpr::Lambda(lambda, row, col) => { EExpr::Lambda(lambda, row, col) => {
to_lambda_report(alloc, filename, context, &lambda, *row, *col) to_lambda_report(alloc, filename, context, lambda, *row, *col)
}
EExpr::List(list, row, col) => to_list_report(alloc, filename, context, &list, *row, *col),
EExpr::Str(string, row, col) => {
to_str_report(alloc, filename, context, &string, *row, *col)
} }
EExpr::List(list, row, col) => to_list_report(alloc, filename, context, list, *row, *col),
EExpr::Str(string, row, col) => to_str_report(alloc, filename, context, string, *row, *col),
EExpr::InParens(expr, row, col) => { EExpr::InParens(expr, row, col) => {
to_expr_in_parens_report(alloc, filename, context, &expr, *row, *col) to_expr_in_parens_report(alloc, filename, context, expr, *row, *col)
} }
EExpr::Type(tipe, row, col) => to_type_report(alloc, filename, &tipe, *row, *col), EExpr::Type(tipe, row, col) => to_type_report(alloc, filename, tipe, *row, *col),
EExpr::ElmStyleFunction(region, row, col) => { EExpr::ElmStyleFunction(region, row, col) => {
let surroundings = Region::from_rows_cols(start_row, start_col, *row, *col); let surroundings = Region::from_rows_cols(start_row, start_col, *row, *col);
let region = *region; let region = *region;
@ -529,7 +527,7 @@ fn to_expr_report<'a>(
} }
} }
EExpr::Space(error, row, col) => to_space_report(alloc, filename, &error, *row, *col), EExpr::Space(error, row, col) => to_space_report(alloc, filename, error, *row, *col),
_ => todo!("unhandled parse error: {:?}", parse_problem), _ => todo!("unhandled parse error: {:?}", parse_problem),
} }
@ -1557,10 +1555,10 @@ fn to_pattern_report<'a>(
} }
} }
EPattern::Record(record, row, col) => { EPattern::Record(record, row, col) => {
to_precord_report(alloc, filename, &record, *row, *col) to_precord_report(alloc, filename, record, *row, *col)
} }
EPattern::PInParens(inparens, row, col) => { EPattern::PInParens(inparens, row, col) => {
to_pattern_in_parens_report(alloc, filename, &inparens, *row, *col) to_pattern_in_parens_report(alloc, filename, inparens, *row, *col)
} }
_ => todo!("unhandled parse error: {:?}", parse_problem), _ => todo!("unhandled parse error: {:?}", parse_problem),
} }
@ -1958,14 +1956,14 @@ fn to_type_report<'a>(
use roc_parse::parser::Type; use roc_parse::parser::Type;
match parse_problem { match parse_problem {
Type::TRecord(record, row, col) => to_trecord_report(alloc, filename, &record, *row, *col), Type::TRecord(record, row, col) => to_trecord_report(alloc, filename, record, *row, *col),
Type::TTagUnion(tag_union, row, col) => { Type::TTagUnion(tag_union, row, col) => {
to_ttag_union_report(alloc, filename, &tag_union, *row, *col) to_ttag_union_report(alloc, filename, tag_union, *row, *col)
} }
Type::TInParens(tinparens, row, col) => { Type::TInParens(tinparens, row, col) => {
to_tinparens_report(alloc, filename, &tinparens, *row, *col) to_tinparens_report(alloc, filename, tinparens, *row, *col)
} }
Type::TApply(tapply, row, col) => to_tapply_report(alloc, filename, &tapply, *row, *col), Type::TApply(tapply, row, col) => to_tapply_report(alloc, filename, tapply, *row, *col),
Type::TFunctionArgument(row, col) => match what_is_next(alloc.src_lines, *row, *col) { Type::TFunctionArgument(row, col) => match what_is_next(alloc.src_lines, *row, *col) {
Next::Other(Some(',')) => { Next::Other(Some(',')) => {
@ -2856,27 +2854,27 @@ fn to_header_report<'a>(
match parse_problem { match parse_problem {
EHeader::Provides(provides, row, col) => { EHeader::Provides(provides, row, col) => {
to_provides_report(alloc, filename, &provides, *row, *col) to_provides_report(alloc, filename, provides, *row, *col)
} }
EHeader::Exposes(exposes, row, col) => { EHeader::Exposes(exposes, row, col) => {
to_exposes_report(alloc, filename, &exposes, *row, *col) to_exposes_report(alloc, filename, exposes, *row, *col)
} }
EHeader::Imports(imports, row, col) => { EHeader::Imports(imports, row, col) => {
to_imports_report(alloc, filename, &imports, *row, *col) to_imports_report(alloc, filename, imports, *row, *col)
} }
EHeader::Requires(requires, row, col) => { EHeader::Requires(requires, row, col) => {
to_requires_report(alloc, filename, &requires, *row, *col) to_requires_report(alloc, filename, requires, *row, *col)
} }
EHeader::Packages(packages, row, col) => { EHeader::Packages(packages, row, col) => {
to_packages_report(alloc, filename, &packages, *row, *col) to_packages_report(alloc, filename, packages, *row, *col)
} }
EHeader::Effects(effects, row, col) => { EHeader::Effects(effects, row, col) => {
to_effects_report(alloc, filename, &effects, *row, *col) to_effects_report(alloc, filename, effects, *row, *col)
} }
EHeader::IndentStart(row, col) => { EHeader::IndentStart(row, col) => {
@ -2988,7 +2986,7 @@ fn to_header_report<'a>(
} }
} }
EHeader::Space(error, row, col) => to_space_report(alloc, filename, &error, *row, *col), EHeader::Space(error, row, col) => to_space_report(alloc, filename, error, *row, *col),
} }
} }

View file

@ -214,7 +214,7 @@ fn report_bad_type<'b>(
alloc, alloc,
found, found,
expected_type, expected_type,
add_category(alloc, this_is, &category), add_category(alloc, this_is, category),
further_details, further_details,
), ),
]; ];
@ -1443,7 +1443,7 @@ pub fn to_doc<'b>(
Record(fields_map, ext) => { Record(fields_map, ext) => {
let mut fields = fields_map.into_iter().collect::<Vec<_>>(); let mut fields = fields_map.into_iter().collect::<Vec<_>>();
fields.sort_by(|(a, _), (b, _)| a.cmp(&b)); fields.sort_by(|(a, _), (b, _)| a.cmp(b));
report_text::record( report_text::record(
alloc, alloc,
@ -1482,7 +1482,7 @@ pub fn to_doc<'b>(
) )
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
tags.sort_by(|(a, _), (b, _)| a.cmp(&b)); tags.sort_by(|(a, _), (b, _)| a.cmp(b));
report_text::tag_union( report_text::tag_union(
alloc, alloc,
@ -1505,7 +1505,7 @@ pub fn to_doc<'b>(
) )
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
tags.sort_by(|(a, _), (b, _)| a.cmp(&b)); tags.sort_by(|(a, _), (b, _)| a.cmp(b));
report_text::recursive_tag_union( report_text::recursive_tag_union(
alloc, alloc,

View file

@ -70,7 +70,7 @@ impl<'b> Report<'b> {
pub fn render_ci(self, buf: &'b mut String, alloc: &'b RocDocAllocator<'b>) { pub fn render_ci(self, buf: &'b mut String, alloc: &'b RocDocAllocator<'b>) {
let err_msg = "<buffer is not a utf-8 encoded string>"; let err_msg = "<buffer is not a utf-8 encoded string>";
self.pretty(&alloc) self.pretty(alloc)
.1 .1
.render_raw(70, &mut CiWrite::new(buf)) .render_raw(70, &mut CiWrite::new(buf))
.expect(err_msg); .expect(err_msg);
@ -85,7 +85,7 @@ impl<'b> Report<'b> {
) { ) {
let err_msg = "<buffer is not a utf-8 encoded string>"; let err_msg = "<buffer is not a utf-8 encoded string>";
self.pretty(&alloc) self.pretty(alloc)
.1 .1
.render_raw(70, &mut ColorWrite::new(palette, buf)) .render_raw(70, &mut ColorWrite::new(palette, buf))
.expect(err_msg); .expect(err_msg);

View file

@ -36,7 +36,10 @@ pub fn infer_expr(
}; };
let (solved, _) = solve::run(&env, problems, subs, constraint); let (solved, _) = solve::run(&env, problems, subs, constraint);
let content = solved.inner().get_without_compacting(expr_var).content; let content = solved
.inner()
.get_content_without_compacting(expr_var)
.clone();
(content, solved.into_inner()) (content, solved.into_inner())
} }
@ -110,7 +113,7 @@ pub fn can_expr_with<'a>(
home: ModuleId, home: ModuleId,
expr_str: &'a str, expr_str: &'a str,
) -> Result<CanExprOut, ParseErrOut<'a>> { ) -> Result<CanExprOut, ParseErrOut<'a>> {
let loc_expr = match roc_parse::test_helpers::parse_loc_with(&arena, expr_str) { let loc_expr = match roc_parse::test_helpers::parse_loc_with(arena, expr_str) {
Ok(e) => e, Ok(e) => e,
Err(fail) => { Err(fail) => {
let interns = Interns::default(); let interns = Interns::default();
@ -142,7 +145,7 @@ pub fn can_expr_with<'a>(
let mut scope = Scope::new(home, &mut var_store); let mut scope = Scope::new(home, &mut var_store);
let dep_idents = IdentIds::exposed_builtins(0); let dep_idents = IdentIds::exposed_builtins(0);
let mut env = Env::new(home, dep_idents, &module_ids, IdentIds::default()); let mut env = Env::new(home, &dep_idents, &module_ids, IdentIds::default());
let (loc_expr, output) = canonicalize_expr( let (loc_expr, output) = canonicalize_expr(
&mut env, &mut env,
&mut var_store, &mut var_store,

View file

@ -65,7 +65,7 @@ mod test_reporting {
problems: can_problems, problems: can_problems,
.. ..
} = can_expr(arena, expr_src)?; } = can_expr(arena, expr_src)?;
let mut subs = Subs::new(var_store.into()); let mut subs = Subs::new(var_store);
for (var, name) in output.introduced_variables.name_by_var { for (var, name) in output.introduced_variables.name_by_var {
subs.rigid_var(var, name); subs.rigid_var(var, name);
@ -223,13 +223,11 @@ mod test_reporting {
list_reports(&arena, src, &mut buf, callback); list_reports(&arena, src, &mut buf, callback);
// convenient to copy-paste the generated message // convenient to copy-paste the generated message
if true { if true && buf != expected_rendering {
if buf != expected_rendering { for line in buf.split('\n') {
for line in buf.split("\n") {
println!(" {}", line); println!(" {}", line);
} }
} }
}
assert_eq!(buf, expected_rendering); assert_eq!(buf, expected_rendering);
} }
@ -247,13 +245,11 @@ mod test_reporting {
list_header_reports(&arena, src, &mut buf, callback); list_header_reports(&arena, src, &mut buf, callback);
// convenient to copy-paste the generated message // convenient to copy-paste the generated message
if true { if true && buf != expected_rendering {
if buf != expected_rendering { for line in buf.split('\n') {
for line in buf.split("\n") {
println!(" {}", line); println!(" {}", line);
} }
} }
}
assert_eq!(buf, expected_rendering); assert_eq!(buf, expected_rendering);
} }
@ -721,10 +717,10 @@ mod test_reporting {
these names seem close though: these names seem close though:
Decimal
Dec
Result Result
Num Num
Set
U8
"# "#
), ),
); );
@ -2160,8 +2156,8 @@ mod test_reporting {
This is usually a typo. Here are the `x` fields that are most similar: This is usually a typo. Here are the `x` fields that are most similar:
{ fo : Num c { fo : Num c
, foobar : Num a , foobar : Num d
, bar : Num e , bar : Num a
, baz : Num b , baz : Num b
, ... , ...
} }

View file

@ -56,10 +56,10 @@ pub fn make_solved_types(
for loc_named_var in alias.type_variables.iter() { for loc_named_var in alias.type_variables.iter() {
let (name, var) = &loc_named_var.value; let (name, var) = &loc_named_var.value;
args.push((name.clone(), SolvedType::new(&solved_subs, *var))); args.push((name.clone(), SolvedType::new(solved_subs, *var)));
} }
let solved_type = SolvedType::from_type(&solved_subs, &alias.typ); let solved_type = SolvedType::from_type(solved_subs, &alias.typ);
let solved_alias = SolvedType::Alias(*symbol, args, Box::new(solved_type)); let solved_alias = SolvedType::Alias(*symbol, args, Box::new(solved_type));
solved_types.insert(*symbol, solved_alias); solved_types.insert(*symbol, solved_alias);
@ -71,7 +71,7 @@ pub fn make_solved_types(
// other modules will generate constraints for imported values // other modules will generate constraints for imported values
// within the context of their own Subs. // within the context of their own Subs.
for (symbol, var) in exposed_vars_by_symbol.iter() { for (symbol, var) in exposed_vars_by_symbol.iter() {
let solved_type = SolvedType::new(&solved_subs, *var); let solved_type = SolvedType::new(solved_subs, *var);
solved_types.insert(*symbol, solved_type); solved_types.insert(*symbol, solved_type);
} }

View file

@ -1,12 +1,12 @@
use roc_can::constraint::Constraint::{self, *}; use roc_can::constraint::Constraint::{self, *};
use roc_can::expected::{Expected, PExpected}; use roc_can::expected::{Expected, PExpected};
use roc_collections::all::{ImMap, MutMap}; use roc_collections::all::{default_hasher, MutMap};
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use roc_region::all::{Located, Region}; use roc_region::all::{Located, Region};
use roc_types::solved_types::Solved; use roc_types::solved_types::Solved;
use roc_types::subs::{Content, Descriptor, FlatType, Mark, OptVariable, Rank, Subs, Variable}; use roc_types::subs::{Content, Descriptor, FlatType, Mark, OptVariable, Rank, Subs, Variable};
use roc_types::types::Type::{self, *}; use roc_types::types::Type::{self, *};
use roc_types::types::{Alias, Category, ErrorType, PatternCategory, RecordField}; use roc_types::types::{Alias, Category, ErrorType, PatternCategory};
use roc_unify::unify::unify; use roc_unify::unify::unify;
use roc_unify::unify::Unified::*; use roc_unify::unify::Unified::*;
@ -114,7 +114,7 @@ impl Pools {
pub fn split_last(&self) -> (&Vec<Variable>, &[Vec<Variable>]) { pub fn split_last(&self) -> (&Vec<Variable>, &[Vec<Variable>]) {
self.0 self.0
.split_last() .split_last()
.unwrap_or_else(|| panic!("Attempted to split_last() on non-empy Pools")) .unwrap_or_else(|| panic!("Attempted to split_last() on non-empty Pools"))
} }
pub fn extend_to(&mut self, n: usize) { pub fn extend_to(&mut self, n: usize) {
@ -257,7 +257,7 @@ fn solve(
} }
} }
Lookup(symbol, expectation, region) => { Lookup(symbol, expectation, region) => {
match env.vars_by_symbol.get(&symbol) { match env.vars_by_symbol.get(symbol) {
Some(var) => { Some(var) => {
// Deep copy the vars associated with this symbol before unifying them. // Deep copy the vars associated with this symbol before unifying them.
// Otherwise, suppose we have this: // Otherwise, suppose we have this:
@ -390,7 +390,7 @@ fn solve(
// If the return expression is guaranteed to solve, // If the return expression is guaranteed to solve,
// solve the assignments themselves and move on. // solve the assignments themselves and move on.
solve( solve(
&env, env,
state, state,
rank, rank,
pools, pools,
@ -413,24 +413,25 @@ fn solve(
); );
// Add a variable for each def to new_vars_by_env. // Add a variable for each def to new_vars_by_env.
let mut local_def_vars = ImMap::default(); let mut local_def_vars = Vec::with_capacity(let_con.def_types.len());
for (symbol, loc_type) in let_con.def_types.iter() { for (symbol, loc_type) in let_con.def_types.iter() {
let var = type_to_var(subs, rank, pools, cached_aliases, &loc_type.value); let var = type_to_var(subs, rank, pools, cached_aliases, &loc_type.value);
local_def_vars.insert( local_def_vars.push((
*symbol, *symbol,
Located { Located {
value: var, value: var,
region: loc_type.region, region: loc_type.region,
}, },
); ));
} }
let mut new_env = env.clone(); let mut new_env = env.clone();
for (symbol, loc_var) in local_def_vars.iter() { for (symbol, loc_var) in local_def_vars.iter() {
if !new_env.vars_by_symbol.contains_key(&symbol) { // better to ask for forgiveness than for permission
new_env.vars_by_symbol.insert(*symbol, loc_var.value); if let Some(old) = new_env.vars_by_symbol.insert(*symbol, loc_var.value) {
new_env.vars_by_symbol.insert(*symbol, old);
} }
} }
@ -485,7 +486,7 @@ fn solve(
// run solver in next pool // run solver in next pool
// Add a variable for each def to local_def_vars. // Add a variable for each def to local_def_vars.
let mut local_def_vars = ImMap::default(); let mut local_def_vars = Vec::with_capacity(let_con.def_types.len());
for (symbol, loc_type) in let_con.def_types.iter() { for (symbol, loc_type) in let_con.def_types.iter() {
let def_type = &loc_type.value; let def_type = &loc_type.value;
@ -493,13 +494,13 @@ fn solve(
let var = let var =
type_to_var(subs, next_rank, next_pools, cached_aliases, def_type); type_to_var(subs, next_rank, next_pools, cached_aliases, def_type);
local_def_vars.insert( local_def_vars.push((
*symbol, *symbol,
Located { Located {
value: var, value: var,
region: loc_type.region, region: loc_type.region,
}, },
); ));
} }
// Solve the assignments' constraints first. // Solve the assignments' constraints first.
@ -507,7 +508,7 @@ fn solve(
env: saved_env, env: saved_env,
mark, mark,
} = solve( } = solve(
&env, env,
state, state,
next_rank, next_rank,
next_pools, next_pools,
@ -527,11 +528,10 @@ fn solve(
.get(next_rank) .get(next_rank)
.iter() .iter()
.filter(|var| { .filter(|var| {
let current = subs.get_without_compacting( let current_rank =
roc_types::subs::Variable::clone(var), subs.get_rank(roc_types::subs::Variable::clone(var));
);
current.rank.into_usize() > next_rank.into_usize() current_rank.into_usize() > next_rank.into_usize()
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
@ -560,8 +560,7 @@ fn solve(
let failing: Vec<_> = rigid_vars let failing: Vec<_> = rigid_vars
.iter() .iter()
.filter(|&var| { .filter(|&var| {
!subs.redundant(*var) !subs.redundant(*var) && subs.get_rank(*var) != Rank::NONE
&& subs.get_without_compacting(*var).rank != Rank::NONE
}) })
.collect(); .collect();
@ -576,7 +575,7 @@ fn solve(
let mut new_env = env.clone(); let mut new_env = env.clone();
for (symbol, loc_var) in local_def_vars.iter() { for (symbol, loc_var) in local_def_vars.iter() {
// when there are duplicates, keep the one from `env` // when there are duplicates, keep the one from `env`
if !new_env.vars_by_symbol.contains_key(&symbol) { if !new_env.vars_by_symbol.contains_key(symbol) {
new_env.vars_by_symbol.insert(*symbol, loc_var.value); new_env.vars_by_symbol.insert(*symbol, loc_var.value);
} }
} }
@ -598,7 +597,7 @@ fn solve(
problems, problems,
cached_aliases, cached_aliases,
subs, subs,
&ret_con, ret_con,
); );
for (symbol, loc_var) in local_def_vars { for (symbol, loc_var) in local_def_vars {
@ -671,16 +670,11 @@ fn type_to_variable(
register(subs, rank, pools, content) register(subs, rank, pools, content)
} }
Record(fields, ext) => { Record(fields, ext) => {
let mut field_vars = MutMap::default(); let mut field_vars = MutMap::with_capacity_and_hasher(fields.len(), default_hasher());
for (field, field_type) in fields { for (field, field_type) in fields {
use RecordField::*; let field_var =
field_type.map(|typ| type_to_variable(subs, rank, pools, cached, typ));
let field_var = match field_type {
Required(typ) => Required(type_to_variable(subs, rank, pools, cached, typ)),
Optional(typ) => Optional(type_to_variable(subs, rank, pools, cached, typ)),
Demanded(typ) => Demanded(type_to_variable(subs, rank, pools, cached, typ)),
};
field_vars.insert(field.clone(), field_var); field_vars.insert(field.clone(), field_var);
} }
@ -695,12 +689,13 @@ fn type_to_variable(
Err((new, _)) => new, Err((new, _)) => new,
}; };
let content = Content::Structure(FlatType::Record(field_vars, new_ext_var)); let record_fields = field_vars.into_iter().collect();
let content = Content::Structure(FlatType::Record(record_fields, new_ext_var));
register(subs, rank, pools, content) register(subs, rank, pools, content)
} }
TagUnion(tags, ext) => { TagUnion(tags, ext) => {
let mut tag_vars = MutMap::default(); let mut tag_vars = MutMap::with_capacity_and_hasher(tags.len(), default_hasher());
for (tag, tag_argument_types) in tags { for (tag, tag_argument_types) in tags {
let mut tag_argument_vars = Vec::with_capacity(tag_argument_types.len()); let mut tag_argument_vars = Vec::with_capacity(tag_argument_types.len());
@ -742,7 +737,7 @@ fn type_to_variable(
debug_assert!(ext_tag_vec.is_empty()); debug_assert!(ext_tag_vec.is_empty());
let content = Content::Structure(FlatType::FunctionOrTagUnion( let content = Content::Structure(FlatType::FunctionOrTagUnion(
tag_name.clone(), Box::new(tag_name.clone()),
*symbol, *symbol,
new_ext_var, new_ext_var,
)); ));
@ -750,7 +745,7 @@ fn type_to_variable(
register(subs, rank, pools, content) register(subs, rank, pools, content)
} }
RecursiveTagUnion(rec_var, tags, ext) => { RecursiveTagUnion(rec_var, tags, ext) => {
let mut tag_vars = MutMap::default(); let mut tag_vars = MutMap::with_capacity_and_hasher(tags.len(), default_hasher());
for (tag, tag_argument_types) in tags { for (tag, tag_argument_types) in tags {
let mut tag_argument_vars = Vec::with_capacity(tag_argument_types.len()); let mut tag_argument_vars = Vec::with_capacity(tag_argument_types.len());
@ -791,51 +786,18 @@ fn type_to_variable(
} }
Alias(Symbol::BOOL_BOOL, _, _) => Variable::BOOL, Alias(Symbol::BOOL_BOOL, _, _) => Variable::BOOL,
Alias(symbol, args, alias_type) => { Alias(symbol, args, alias_type) => {
// TODO cache in uniqueness inference gives problems! all Int's get the same uniqueness var!
// Cache aliases without type arguments. Commonly used aliases like `Int` would otherwise get O(n)
// different variables (once for each occurrence). The recursion restriction is required
// for uniqueness types only: recursive aliases "introduce" an unbound uniqueness
// attribute in the body, when
//
// Peano : [ S Peano, Z ]
//
// becomes
//
// Peano : [ S (Attr u Peano), Z ]
//
// This `u` variable can be different between lists, so giving just one variable to
// this type is incorrect.
// TODO does caching work at all with uniqueness types? even Int then hides a uniqueness variable
let is_recursive = alias_type.is_recursive();
let no_args = args.is_empty();
/*
if no_args && !is_recursive {
if let Some(var) = cached.get(symbol) {
return *var;
}
}
*/
let mut arg_vars = Vec::with_capacity(args.len()); let mut arg_vars = Vec::with_capacity(args.len());
let mut new_aliases = ImMap::default();
for (arg, arg_type) in args { for (arg, arg_type) in args {
let arg_var = type_to_variable(subs, rank, pools, cached, arg_type); let arg_var = type_to_variable(subs, rank, pools, cached, arg_type);
arg_vars.push((arg.clone(), arg_var)); arg_vars.push((arg.clone(), arg_var));
new_aliases.insert(arg.clone(), arg_var);
} }
let alias_var = type_to_variable(subs, rank, pools, cached, alias_type); let alias_var = type_to_variable(subs, rank, pools, cached, alias_type);
let content = Content::Alias(*symbol, arg_vars, alias_var); let content = Content::Alias(*symbol, arg_vars, alias_var);
let result = register(subs, rank, pools, content); register(subs, rank, pools, content)
if no_args && !is_recursive {
// cached.insert(*symbol, result);
}
result
} }
HostExposedAlias { HostExposedAlias {
name: symbol, name: symbol,
@ -845,13 +807,11 @@ fn type_to_variable(
.. ..
} => { } => {
let mut arg_vars = Vec::with_capacity(args.len()); let mut arg_vars = Vec::with_capacity(args.len());
let mut new_aliases = ImMap::default();
for (arg, arg_type) in args { for (arg, arg_type) in args {
let arg_var = type_to_variable(subs, rank, pools, cached, arg_type); let arg_var = type_to_variable(subs, rank, pools, cached, arg_type);
arg_vars.push((arg.clone(), arg_var)); arg_vars.push((arg.clone(), arg_var));
new_aliases.insert(arg.clone(), arg_var);
} }
let alias_var = type_to_variable(subs, rank, pools, cached, alias_type); let alias_var = type_to_variable(subs, rank, pools, cached, alias_type);
@ -877,7 +837,7 @@ fn type_to_variable(
result result
} }
Erroneous(problem) => { Erroneous(problem) => {
let content = Content::Structure(FlatType::Erroneous(problem.clone())); let content = Content::Structure(FlatType::Erroneous(Box::new(problem.clone())));
register(subs, rank, pools, content) register(subs, rank, pools, content)
} }
@ -1023,45 +983,34 @@ fn adjust_rank(
group_rank: Rank, group_rank: Rank,
var: Variable, var: Variable,
) -> Rank { ) -> Rank {
let desc = subs.get(var); let (desc_rank, desc_mark) = subs.get_rank_mark(var);
if desc.mark == young_mark {
let Descriptor {
content,
rank: _,
mark: _,
copy,
} = desc;
if desc_mark == young_mark {
// Mark the variable as visited before adjusting content, as it may be cyclic. // Mark the variable as visited before adjusting content, as it may be cyclic.
subs.set_mark(var, visit_mark); subs.set_mark(var, visit_mark);
let max_rank = adjust_rank_content(subs, young_mark, visit_mark, group_rank, &content); // SAFETY: in this function (and functions it calls, we ONLY modify rank and mark, never content!
// hence, we can have an immutable reference to it even though we also have a mutable
// reference to the Subs as a whole. This prevents a clone of the content, which turns out
// to be quite expensive.
let content = {
let ptr = &subs.get_ref(var).content as *const _;
unsafe { &*ptr }
};
subs.set( let max_rank = adjust_rank_content(subs, young_mark, visit_mark, group_rank, content);
var,
Descriptor { subs.set_rank_mark(var, max_rank, visit_mark);
content,
rank: max_rank,
mark: visit_mark,
copy,
},
);
max_rank max_rank
} else if desc.mark == visit_mark { } else if desc_mark == visit_mark {
// nothing changes // nothing changes
desc.rank desc_rank
} else { } else {
let mut desc = desc; let min_rank = group_rank.min(desc_rank);
let min_rank = group_rank.min(desc.rank);
// TODO from elm-compiler: how can min_rank ever be group_rank? // TODO from elm-compiler: how can min_rank ever be group_rank?
desc.rank = min_rank; subs.set_rank_mark(var, min_rank, visit_mark);
desc.mark = visit_mark;
subs.set(var, desc);
min_rank min_rank
} }
@ -1131,14 +1080,9 @@ fn adjust_rank_content(
Record(fields, ext_var) => { Record(fields, ext_var) => {
let mut rank = adjust_rank(subs, young_mark, visit_mark, group_rank, *ext_var); let mut rank = adjust_rank(subs, young_mark, visit_mark, group_rank, *ext_var);
for var in fields.values() { for var in fields.iter_variables() {
rank = rank.max(adjust_rank( rank =
subs, rank.max(adjust_rank(subs, young_mark, visit_mark, group_rank, *var));
young_mark,
visit_mark,
group_rank,
var.into_inner(),
));
} }
rank rank
@ -1266,144 +1210,81 @@ fn instantiate_rigids_help(
// will not repeat this work or crawl this variable again. // will not repeat this work or crawl this variable again.
match content { match content {
Structure(flat_type) => { Structure(flat_type) => {
let new_flat_type = match flat_type { match flat_type {
Apply(symbol, args) => { Apply(_, args) => {
let args = args for var in args.into_iter() {
.into_iter() instantiate_rigids_help(subs, max_rank, pools, var);
.map(|var| instantiate_rigids_help(subs, max_rank, pools, var)) }
.collect();
Apply(symbol, args)
} }
Func(arg_vars, closure_var, ret_var) => { Func(arg_vars, closure_var, ret_var) => {
let new_ret_var = instantiate_rigids_help(subs, max_rank, pools, ret_var); instantiate_rigids_help(subs, max_rank, pools, ret_var);
let new_closure_var =
instantiate_rigids_help(subs, max_rank, pools, closure_var); instantiate_rigids_help(subs, max_rank, pools, closure_var);
let arg_vars = arg_vars
.into_iter()
.map(|var| instantiate_rigids_help(subs, max_rank, pools, var))
.collect();
Func(arg_vars, new_closure_var, new_ret_var) for var in arg_vars.into_iter() {
instantiate_rigids_help(subs, max_rank, pools, var);
}
} }
same @ EmptyRecord | same @ EmptyTagUnion | same @ Erroneous(_) => same, EmptyRecord | EmptyTagUnion | Erroneous(_) => {}
Record(fields, ext_var) => { Record(fields, ext_var) => {
let mut new_fields = MutMap::default(); for var in fields.iter_variables() {
instantiate_rigids_help(subs, max_rank, pools, *var);
for (label, field) in fields {
use RecordField::*;
let new_field = match field {
Demanded(var) => {
Demanded(instantiate_rigids_help(subs, max_rank, pools, var))
}
Required(var) => {
Required(instantiate_rigids_help(subs, max_rank, pools, var))
}
Optional(var) => {
Optional(instantiate_rigids_help(subs, max_rank, pools, var))
}
};
new_fields.insert(label, new_field);
} }
Record( instantiate_rigids_help(subs, max_rank, pools, ext_var);
new_fields,
instantiate_rigids_help(subs, max_rank, pools, ext_var),
)
} }
TagUnion(tags, ext_var) => { TagUnion(tags, ext_var) => {
let mut new_tags = MutMap::default(); for (_, vars) in tags {
for var in vars.into_iter() {
for (tag, vars) in tags { instantiate_rigids_help(subs, max_rank, pools, var);
let new_vars: Vec<Variable> = vars }
.into_iter()
.map(|var| instantiate_rigids_help(subs, max_rank, pools, var))
.collect();
new_tags.insert(tag, new_vars);
} }
TagUnion( instantiate_rigids_help(subs, max_rank, pools, ext_var);
new_tags,
instantiate_rigids_help(subs, max_rank, pools, ext_var),
)
} }
FunctionOrTagUnion(tag_name, symbol, ext_var) => FunctionOrTagUnion( FunctionOrTagUnion(_tag_name, _symbol, ext_var) => {
tag_name, instantiate_rigids_help(subs, max_rank, pools, ext_var);
symbol, }
instantiate_rigids_help(subs, max_rank, pools, ext_var),
),
RecursiveTagUnion(rec_var, tags, ext_var) => { RecursiveTagUnion(rec_var, tags, ext_var) => {
let mut new_tags = MutMap::default(); instantiate_rigids_help(subs, max_rank, pools, rec_var);
let new_rec_var = instantiate_rigids_help(subs, max_rank, pools, rec_var); for (_, vars) in tags {
for var in vars.into_iter() {
for (tag, vars) in tags { instantiate_rigids_help(subs, max_rank, pools, var);
let new_vars: Vec<Variable> = vars }
.into_iter()
.map(|var| instantiate_rigids_help(subs, max_rank, pools, var))
.collect();
new_tags.insert(tag, new_vars);
} }
RecursiveTagUnion( instantiate_rigids_help(subs, max_rank, pools, ext_var);
new_rec_var,
new_tags,
instantiate_rigids_help(subs, max_rank, pools, ext_var),
)
} }
}; };
subs.set(copy, make_descriptor(Structure(new_flat_type)));
copy
} }
FlexVar(_) | Error => copy, FlexVar(_) | Error => {}
RecursionVar { RecursionVar { structure, .. } => {
opt_name, instantiate_rigids_help(subs, max_rank, pools, structure);
structure,
} => {
let new_structure = instantiate_rigids_help(subs, max_rank, pools, structure);
subs.set(
copy,
make_descriptor(RecursionVar {
opt_name,
structure: new_structure,
}),
);
copy
} }
RigidVar(name) => { RigidVar(name) => {
// what it's all about: convert the rigid var into a flex var
subs.set(copy, make_descriptor(FlexVar(Some(name)))); subs.set(copy, make_descriptor(FlexVar(Some(name))));
copy
} }
Alias(symbol, args, real_type_var) => { Alias(_, args, real_type_var) => {
let new_args = args for (_, var) in args.into_iter() {
.into_iter() instantiate_rigids_help(subs, max_rank, pools, var);
.map(|(name, var)| (name, instantiate_rigids_help(subs, max_rank, pools, var))) }
.collect();
let new_real_type_var = instantiate_rigids_help(subs, max_rank, pools, real_type_var);
let new_content = Alias(symbol, new_args, new_real_type_var);
subs.set(copy, make_descriptor(new_content)); instantiate_rigids_help(subs, max_rank, pools, real_type_var);
copy
} }
} }
var
} }
fn deep_copy_var(subs: &mut Subs, rank: Rank, pools: &mut Pools, var: Variable) -> Variable { fn deep_copy_var(subs: &mut Subs, rank: Rank, pools: &mut Pools, var: Variable) -> Variable {
@ -1485,31 +1366,12 @@ fn deep_copy_var_help(
same @ EmptyRecord | same @ EmptyTagUnion | same @ Erroneous(_) => same, same @ EmptyRecord | same @ EmptyTagUnion | same @ Erroneous(_) => same,
Record(fields, ext_var) => { Record(mut fields, ext_var) => {
let mut new_fields = MutMap::default(); for var in fields.iter_variables_mut() {
*var = deep_copy_var_help(subs, max_rank, pools, *var);
for (label, field) in fields {
use RecordField::*;
let new_field = match field {
Demanded(var) => {
Demanded(deep_copy_var_help(subs, max_rank, pools, var))
}
Required(var) => {
Required(deep_copy_var_help(subs, max_rank, pools, var))
}
Optional(var) => {
Optional(deep_copy_var_help(subs, max_rank, pools, var))
}
};
new_fields.insert(label, new_field);
} }
Record( Record(fields, deep_copy_var_help(subs, max_rank, pools, ext_var))
new_fields,
deep_copy_var_help(subs, max_rank, pools, ext_var),
)
} }
TagUnion(tags, ext_var) => { TagUnion(tags, ext_var) => {

View file

@ -115,10 +115,10 @@ mod solve_expr {
let content = { let content = {
debug_assert!(exposed_to_host.len() == 1); debug_assert!(exposed_to_host.len() == 1);
let (_symbol, variable) = exposed_to_host.into_iter().next().unwrap(); let (_symbol, variable) = exposed_to_host.into_iter().next().unwrap();
subs.get(variable).content subs.get_content_without_compacting(variable)
}; };
let actual_str = content_to_string(content, &subs, home, &interns); let actual_str = content_to_string(content, subs, home, &interns);
// Disregard UnusedDef problems, because those are unavoidable when // Disregard UnusedDef problems, because those are unavoidable when
// returning a function from the test expression. // returning a function from the test expression.
@ -172,6 +172,21 @@ mod solve_expr {
infer_eq("0.5", "Float *"); infer_eq("0.5", "Float *");
} }
#[test]
fn dec_literal() {
infer_eq(
indoc!(
r#"
val : Dec
val = 1.2
val
"#
),
"Dec",
);
}
#[test] #[test]
fn string_literal() { fn string_literal() {
infer_eq( infer_eq(
@ -3805,7 +3820,7 @@ mod solve_expr {
} }
#[test] #[test]
fn recursive_functon_with_rigid() { fn recursive_function_with_rigid() {
infer_eq_without_problem( infer_eq_without_problem(
indoc!( indoc!(
r#" r#"
@ -4473,4 +4488,18 @@ mod solve_expr {
"RBTree {}", "RBTree {}",
); );
} }
#[test]
fn sizes() {
let query = (
std::mem::size_of::<roc_module::ident::TagName>(),
std::mem::size_of::<roc_types::subs::Descriptor>(),
std::mem::size_of::<roc_types::subs::Content>(),
std::mem::size_of::<roc_types::subs::FlatType>(),
std::mem::size_of::<roc_types::types::Problem>(),
);
// without RecordFields in FlatType assert_eq!((40, 72, 56, 48, 64), query)
assert_eq!((40, 104, 88, 80, 64), query)
}
} }

View file

@ -13,7 +13,7 @@ struct List {
} }
``` ```
On a 64-bit system, this `struct` would take up 16B in memory. On a 32-bit sysem, it would take up 8B. On a 64-bit system, this `struct` would take up 16B in memory. On a 32-bit system, it would take up 8B.
Here's what the fields mean: Here's what the fields mean:

View file

@ -37,7 +37,7 @@ fn hash_record() {
fn hash_result() { fn hash_result() {
assert_evals_to!( assert_evals_to!(
"Dict.hashTestOnly 0 (List.get [ 0x1 ] 0) ", "Dict.hashTestOnly 0 (List.get [ 0x1 ] 0) ",
6707068610910845221, 2878521786781103245,
u64 u64
); );
} }

View file

@ -3,7 +3,7 @@ mod gen_num {
use crate::assert_evals_to; use crate::assert_evals_to;
use crate::assert_llvm_evals_to; use crate::assert_llvm_evals_to;
use indoc::indoc; use indoc::indoc;
use roc_std::RocOrder; use roc_std::{RocDec, RocOrder};
#[test] #[test]
fn nat_alias() { fn nat_alias() {
@ -328,6 +328,22 @@ mod gen_num {
); );
} }
#[test]
fn dec_float_alias() {
assert_evals_to!(
indoc!(
r#"
x : Dec
x = 2.1
x
"#
),
RocDec::from_str_to_i128_unsafe("2.1"),
i128
);
}
#[test] #[test]
fn f64_float_alias() { fn f64_float_alias() {
assert_evals_to!( assert_evals_to!(
@ -543,6 +559,27 @@ mod gen_num {
); );
} }
#[test]
fn gen_add_dec() {
assert_evals_to!(
indoc!(
r#"
x : Dec
x = 2.1
y : Dec
y = 3.1
z : Dec
z = x + y
z
"#
),
RocDec::from_str_to_i128_unsafe("5.2"),
i128
);
}
#[test] #[test]
fn gen_add_f64() { fn gen_add_f64() {
assert_evals_to!( assert_evals_to!(
@ -586,6 +623,26 @@ mod gen_num {
f64 f64
); );
} }
#[test]
fn gen_div_dec() {
assert_evals_to!(
indoc!(
r#"
x : Dec
x = 10
y : Dec
y = 3
when x / y is
Ok val -> val
Err _ -> -1
"#
),
RocDec::from_str_to_i128_unsafe("3.333333333333333333"),
i128
);
}
#[test] #[test]
fn gen_int_eq() { fn gen_int_eq() {
@ -613,6 +670,44 @@ mod gen_num {
); );
} }
#[test]
fn gen_dec_eq() {
assert_evals_to!(
indoc!(
r#"
x : Dec
x = 4
y : Dec
y = 4
x == y
"#
),
true,
bool
);
}
#[test]
fn gen_dec_neq() {
assert_evals_to!(
indoc!(
r#"
x : Dec
x = 4
y : Dec
y = 5
x != y
"#
),
true,
bool
);
}
#[test] #[test]
fn gen_wrap_int_neq() { fn gen_wrap_int_neq() {
assert_evals_to!( assert_evals_to!(
@ -643,6 +738,28 @@ mod gen_num {
); );
} }
#[test]
fn gen_sub_dec() {
assert_evals_to!(
indoc!(
r#"
x : Dec
x = 1.5
y : Dec
y = 2.4
z : Dec
z = 3
(x - y) - z
"#
),
RocDec::from_str_to_i128_unsafe("-3.9"),
i128
);
}
#[test] #[test]
fn gen_sub_f64() { fn gen_sub_f64() {
assert_evals_to!( assert_evals_to!(
@ -669,6 +786,27 @@ mod gen_num {
); );
} }
#[test]
fn gen_mul_dec() {
assert_evals_to!(
indoc!(
r#"
x : Dec
x = 2
y : Dec
y = 4
z : Dec
z = 6
x * y * z
"#
),
RocDec::from_str_to_i128_unsafe("48.0"),
i128
);
}
#[test] #[test]
fn gen_mul_i64() { fn gen_mul_i64() {
assert_evals_to!( assert_evals_to!(

View file

@ -1280,7 +1280,7 @@ fn linked_list_singleton() {
} }
#[test] #[test]
fn recursive_functon_with_rigid() { fn recursive_function_with_rigid() {
assert_non_opt_evals_to!( assert_non_opt_evals_to!(
indoc!( indoc!(
r#" r#"

View file

@ -60,7 +60,7 @@ pub fn helper<'a>(
let loaded = roc_load::file::load_and_monomorphize_from_str( let loaded = roc_load::file::load_and_monomorphize_from_str(
arena, arena,
filename, filename,
&module_src, module_src,
stdlib, stdlib,
src_dir, src_dir,
exposed_types, exposed_types,
@ -211,7 +211,7 @@ pub fn helper<'a>(
// Compile and add all the Procs before adding main // Compile and add all the Procs before adding main
let env = roc_gen_llvm::llvm::build::Env { let env = roc_gen_llvm::llvm::build::Env {
arena: &arena, arena,
builder: &builder, builder: &builder,
dibuilder: &dibuilder, dibuilder: &dibuilder,
compile_unit: &compile_unit, compile_unit: &compile_unit,
@ -252,7 +252,7 @@ pub fn helper<'a>(
// Uncomment this to see the module's optimized LLVM instruction output: // Uncomment this to see the module's optimized LLVM instruction output:
// env.module.print_to_stderr(); // env.module.print_to_stderr();
let lib = module_to_dylib(&env.module, &target, opt_level) let lib = module_to_dylib(env.module, &target, opt_level)
.expect("Error loading compiled dylib for test"); .expect("Error loading compiled dylib for test");
(main_fn_name, delayed_errors.join("\n"), lib) (main_fn_name, delayed_errors.join("\n"), lib)

View file

@ -3,7 +3,7 @@
// See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check. // See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check.
#![allow(clippy::large_enum_variant)] #![allow(clippy::large_enum_variant)]
// we actually want to compare against the literal float bits // we actually want to compare against the literal float bits
#![allow(clippy::clippy::float_cmp)] #![allow(clippy::float_cmp)]
#[macro_use] #[macro_use]
extern crate pretty_assertions; extern crate pretty_assertions;
@ -100,7 +100,7 @@ fn compiles_to_ir(test_name: &str, src: &str) {
let loaded = roc_load::file::load_and_monomorphize_from_str( let loaded = roc_load::file::load_and_monomorphize_from_str(
arena, arena,
filename, filename,
&module_src, module_src,
&stdlib, &stdlib,
src_dir, src_dir,
exposed_types, exposed_types,

View file

@ -239,6 +239,16 @@ pub fn aliases() -> MutMap<Symbol, BuiltinAlias> {
}, },
); );
// Decimal : [ @Decimal ]
add_alias(
Symbol::NUM_DECIMAL,
BuiltinAlias {
region: Region::zero(),
vars: vec![],
typ: decimal_alias_content(),
},
);
// Binary64 : [ @Binary64 ] // Binary64 : [ @Binary64 ]
add_alias( add_alias(
Symbol::NUM_BINARY64, Symbol::NUM_BINARY64,
@ -269,6 +279,16 @@ pub fn aliases() -> MutMap<Symbol, BuiltinAlias> {
}, },
); );
// Dec : Float Decimal
add_alias(
Symbol::NUM_DEC,
BuiltinAlias {
region: Region::zero(),
vars: Vec::new(),
typ: float_alias_content(decimal_type()),
},
);
// F64 : Float Binary64 // F64 : Float Binary64
add_alias( add_alias(
Symbol::NUM_F64, Symbol::NUM_F64,
@ -657,6 +677,20 @@ fn unsigned8_alias_content() -> SolvedType {
single_private_tag(Symbol::NUM_AT_UNSIGNED8, vec![]) single_private_tag(Symbol::NUM_AT_UNSIGNED8, vec![])
} }
#[inline(always)]
fn decimal_alias_content() -> SolvedType {
single_private_tag(Symbol::NUM_AT_DECIMAL, vec![])
}
#[inline(always)]
pub fn decimal_type() -> SolvedType {
SolvedType::Alias(
Symbol::NUM_DECIMAL,
vec![],
Box::new(decimal_alias_content()),
)
}
#[inline(always)] #[inline(always)]
pub fn bool_type() -> SolvedType { pub fn bool_type() -> SolvedType {
SolvedType::Alias( SolvedType::Alias(

View file

@ -77,11 +77,11 @@ fn find_names_needed(
use crate::subs::FlatType::*; use crate::subs::FlatType::*;
while let Some((recursive, _chain)) = subs.occurs(variable) { while let Some((recursive, _chain)) = subs.occurs(variable) {
let content = subs.get_without_compacting(recursive).content; let rec_var = subs.fresh_unnamed_flex_var();
let content = subs.get_content_without_compacting(recursive);
match content { match content {
Content::Structure(FlatType::TagUnion(tags, ext_var)) => { Content::Structure(FlatType::TagUnion(tags, ext_var)) => {
let rec_var = subs.fresh_unnamed_flex_var();
let mut new_tags = MutMap::default(); let mut new_tags = MutMap::default();
for (label, args) in tags { for (label, args) in tags {
@ -94,7 +94,7 @@ fn find_names_needed(
new_tags.insert(label.clone(), new_args); new_tags.insert(label.clone(), new_args);
} }
let flat_type = FlatType::RecursiveTagUnion(rec_var, new_tags, ext_var); let flat_type = FlatType::RecursiveTagUnion(rec_var, new_tags, *ext_var);
subs.set_content(recursive, Content::Structure(flat_type)); subs.set_content(recursive, Content::Structure(flat_type));
} }
_ => panic!( _ => panic!(
@ -104,7 +104,7 @@ fn find_names_needed(
} }
} }
match subs.get_without_compacting(variable).content { match &subs.get_content_without_compacting(variable).clone() {
RecursionVar { opt_name: None, .. } | FlexVar(None) => { RecursionVar { opt_name: None, .. } | FlexVar(None) => {
let root = subs.get_root_key_without_compacting(variable); let root = subs.get_root_key_without_compacting(variable);
@ -133,41 +133,31 @@ fn find_names_needed(
// User-defined names are already taken. // User-defined names are already taken.
// We must not accidentally generate names that collide with them! // We must not accidentally generate names that collide with them!
names_taken.insert(name); names_taken.insert(name.clone());
} }
RigidVar(name) => { RigidVar(name) => {
// User-defined names are already taken. // User-defined names are already taken.
// We must not accidentally generate names that collide with them! // We must not accidentally generate names that collide with them!
names_taken.insert(name); names_taken.insert(name.clone());
} }
Structure(Apply(_, args)) => { Structure(Apply(_, args)) => {
for var in args { for var in args {
find_names_needed(var, subs, roots, root_appearances, names_taken); find_names_needed(*var, subs, roots, root_appearances, names_taken);
} }
} }
Structure(Func(arg_vars, _closure_var, ret_var)) => { Structure(Func(arg_vars, _closure_var, ret_var)) => {
for var in arg_vars { for var in arg_vars {
find_names_needed(var, subs, roots, root_appearances, names_taken); find_names_needed(*var, subs, roots, root_appearances, names_taken);
} }
find_names_needed(ret_var, subs, roots, root_appearances, names_taken); find_names_needed(*ret_var, subs, roots, root_appearances, names_taken);
} }
Structure(Record(fields, ext_var)) => { Structure(Record(sorted_fields, ext_var)) => {
let mut sorted_fields: Vec<_> = fields.iter().collect(); for var in sorted_fields.iter_variables() {
find_names_needed(*var, subs, roots, root_appearances, names_taken);
sorted_fields.sort_by(|(label1, _), (label2, _)| label1.cmp(label2));
for (_, field) in sorted_fields {
find_names_needed(
field.into_inner(),
subs,
roots,
root_appearances,
names_taken,
);
} }
find_names_needed(ext_var, subs, roots, root_appearances, names_taken); find_names_needed(*ext_var, subs, roots, root_appearances, names_taken);
} }
Structure(TagUnion(tags, ext_var)) => { Structure(TagUnion(tags, ext_var)) => {
let mut sorted_tags: Vec<_> = tags.iter().collect(); let mut sorted_tags: Vec<_> = tags.iter().collect();
@ -177,10 +167,10 @@ fn find_names_needed(
find_names_needed(*var, subs, roots, root_appearances, names_taken); find_names_needed(*var, subs, roots, root_appearances, names_taken);
} }
find_names_needed(ext_var, subs, roots, root_appearances, names_taken); find_names_needed(*ext_var, subs, roots, root_appearances, names_taken);
} }
Structure(FunctionOrTagUnion(_, _, ext_var)) => { Structure(FunctionOrTagUnion(_, _, ext_var)) => {
find_names_needed(ext_var, subs, roots, root_appearances, names_taken); find_names_needed(*ext_var, subs, roots, root_appearances, names_taken);
} }
Structure(RecursiveTagUnion(rec_var, tags, ext_var)) => { Structure(RecursiveTagUnion(rec_var, tags, ext_var)) => {
let mut sorted_tags: Vec<_> = tags.iter().collect(); let mut sorted_tags: Vec<_> = tags.iter().collect();
@ -190,12 +180,12 @@ fn find_names_needed(
find_names_needed(*var, subs, roots, root_appearances, names_taken); find_names_needed(*var, subs, roots, root_appearances, names_taken);
} }
find_names_needed(ext_var, subs, roots, root_appearances, names_taken); find_names_needed(*ext_var, subs, roots, root_appearances, names_taken);
find_names_needed(rec_var, subs, roots, root_appearances, names_taken); find_names_needed(*rec_var, subs, roots, root_appearances, names_taken);
} }
Alias(_symbol, args, _actual) => { Alias(_symbol, args, _actual) => {
for (_, var) in args { for (_, var) in args {
find_names_needed(var, subs, roots, root_appearances, names_taken); find_names_needed(*var, subs, roots, root_appearances, names_taken);
} }
// TODO should we also look in the actual variable? // TODO should we also look in the actual variable?
// find_names_needed(_actual, subs, roots, root_appearances, names_taken); // find_names_needed(_actual, subs, roots, root_appearances, names_taken);
@ -240,22 +230,22 @@ fn name_root(
fn set_root_name(root: Variable, name: Lowercase, subs: &mut Subs) { fn set_root_name(root: Variable, name: Lowercase, subs: &mut Subs) {
use crate::subs::Content::*; use crate::subs::Content::*;
let mut descriptor = subs.get_without_compacting(root); let old_content = subs.get_content_without_compacting(root);
match descriptor.content { match old_content {
FlexVar(None) => { FlexVar(None) => {
descriptor.content = FlexVar(Some(name)); let content = FlexVar(Some(name));
subs.set(root, descriptor); subs.set_content(root, content);
} }
RecursionVar { RecursionVar {
opt_name: None, opt_name: None,
structure, structure,
} => { } => {
descriptor.content = RecursionVar { let content = RecursionVar {
structure, structure: *structure,
opt_name: Some(name), opt_name: Some(name),
}; };
subs.set(root, descriptor); subs.set_content(root, content);
} }
RecursionVar { RecursionVar {
opt_name: Some(_existing), opt_name: Some(_existing),
@ -270,7 +260,7 @@ fn set_root_name(root: Variable, name: Lowercase, subs: &mut Subs) {
} }
pub fn content_to_string( pub fn content_to_string(
content: Content, content: &Content,
subs: &Subs, subs: &Subs,
home: ModuleId, home: ModuleId,
interns: &Interns, interns: &Interns,
@ -283,7 +273,7 @@ pub fn content_to_string(
buf buf
} }
fn write_content(env: &Env, content: Content, subs: &Subs, buf: &mut String, parens: Parens) { fn write_content(env: &Env, content: &Content, subs: &Subs, buf: &mut String, parens: Parens) {
use crate::subs::Content::*; use crate::subs::Content::*;
match content { match content {
@ -298,14 +288,14 @@ fn write_content(env: &Env, content: Content, subs: &Subs, buf: &mut String, par
Alias(symbol, args, _actual) => { Alias(symbol, args, _actual) => {
let write_parens = parens == Parens::InTypeParam && !args.is_empty(); let write_parens = parens == Parens::InTypeParam && !args.is_empty();
match symbol { match *symbol {
Symbol::NUM_NUM => { Symbol::NUM_NUM => {
debug_assert_eq!(args.len(), 1); debug_assert_eq!(args.len(), 1);
let (_, arg_var) = args let (_, arg_var) = args
.get(0) .get(0)
.expect("Num was not applied to a type argument!"); .expect("Num was not applied to a type argument!");
let content = subs.get_without_compacting(*arg_var).content; let content = subs.get_content_without_compacting(*arg_var);
match &content { match &content {
Alias(nested, _, _) => match *nested { Alias(nested, _, _) => match *nested {
@ -326,13 +316,13 @@ fn write_content(env: &Env, content: Content, subs: &Subs, buf: &mut String, par
} }
_ => write_parens!(write_parens, buf, { _ => write_parens!(write_parens, buf, {
write_symbol(env, symbol, buf); write_symbol(env, *symbol, buf);
for (_, var) in args { for (_, var) in args {
buf.push(' '); buf.push(' ');
write_content( write_content(
env, env,
subs.get_without_compacting(var).content, subs.get_content_without_compacting(*var),
subs, subs,
buf, buf,
Parens::InTypeParam, Parens::InTypeParam,
@ -342,7 +332,7 @@ fn write_content(env: &Env, content: Content, subs: &Subs, buf: &mut String, par
// useful for debugging // useful for debugging
if false { if false {
buf.push_str("[[ but really "); buf.push_str("[[ but really ");
let content = subs.get_without_compacting(_actual).content; let content = subs.get_content_without_compacting(*_actual);
write_content(env, content, subs, buf, parens); write_content(env, content, subs, buf, parens);
buf.push_str("]]"); buf.push_str("]]");
} }
@ -353,19 +343,77 @@ fn write_content(env: &Env, content: Content, subs: &Subs, buf: &mut String, par
} }
} }
fn write_flat_type(env: &Env, flat_type: FlatType, subs: &Subs, buf: &mut String, parens: Parens) { fn write_sorted_tags<'a>(
env: &Env,
subs: &'a Subs,
buf: &mut String,
tags: &MutMap<TagName, Vec<Variable>>,
ext_var: Variable,
) -> Result<(), (Variable, &'a Content)> {
// Sort the fields so they always end up in the same order.
let mut sorted_fields = Vec::with_capacity(tags.len());
for (label, vars) in tags {
sorted_fields.push((label, vars));
}
// If the `ext` contains tags, merge them into the list of tags.
// this can occur when inferring mutually recursive tags
let mut from_ext = Default::default();
let ext_content = chase_ext_tag_union(subs, ext_var, &mut from_ext);
for (tag_name, arguments) in from_ext.iter() {
sorted_fields.push((tag_name, arguments));
}
let interns = &env.interns;
let home = env.home;
sorted_fields
.sort_by(|(a, _), (b, _)| a.as_string(interns, home).cmp(&b.as_string(interns, home)));
let mut any_written_yet = false;
for (label, vars) in sorted_fields {
if any_written_yet {
buf.push_str(", ");
} else {
any_written_yet = true;
}
buf.push_str(&label.as_string(interns, home));
for var in vars {
buf.push(' ');
write_content(
env,
subs.get_content_without_compacting(*var),
subs,
buf,
Parens::InTypeParam,
);
}
}
ext_content
}
fn write_flat_type(env: &Env, flat_type: &FlatType, subs: &Subs, buf: &mut String, parens: Parens) {
use crate::subs::FlatType::*; use crate::subs::FlatType::*;
match flat_type { match flat_type {
Apply(symbol, args) => write_apply(env, symbol, args, subs, buf, parens), Apply(symbol, args) => write_apply(env, *symbol, args, subs, buf, parens),
EmptyRecord => buf.push_str(EMPTY_RECORD), EmptyRecord => buf.push_str(EMPTY_RECORD),
EmptyTagUnion => buf.push_str(EMPTY_TAG_UNION), EmptyTagUnion => buf.push_str(EMPTY_TAG_UNION),
Func(args, _closure, ret) => write_fn(env, args, ret, subs, buf, parens), Func(args, _closure, ret) => write_fn(env, args, *ret, subs, buf, parens),
Record(fields, ext_var) => { Record(fields, ext_var) => {
use crate::types::{gather_fields, RecordStructure}; use crate::types::{gather_fields, RecordStructure};
// If the `ext` has concrete fields (e.g. { foo : I64}{ bar : Bool }), merge them // If the `ext` has concrete fields (e.g. { foo : I64}{ bar : Bool }), merge them
let RecordStructure { fields, ext } = gather_fields(subs, fields, ext_var); let RecordStructure {
fields: sorted_fields,
ext,
} = gather_fields(subs, fields.clone(), *ext_var);
let ext_var = ext; let ext_var = ext;
if fields.is_empty() { if fields.is_empty() {
@ -373,12 +421,6 @@ fn write_flat_type(env: &Env, flat_type: FlatType, subs: &Subs, buf: &mut String
} else { } else {
buf.push_str("{ "); buf.push_str("{ ");
// Sort the fields so they always end up in the same order.
let mut sorted_fields = Vec::with_capacity(fields.len());
sorted_fields.extend(fields);
sorted_fields.sort_by(|(a, _), (b, _)| a.cmp(b));
let mut any_written_yet = false; let mut any_written_yet = false;
for (label, field_var) in sorted_fields { for (label, field_var) in sorted_fields {
@ -408,7 +450,7 @@ fn write_flat_type(env: &Env, flat_type: FlatType, subs: &Subs, buf: &mut String
write_content( write_content(
env, env,
subs.get_without_compacting(var).content, subs.get_content_without_compacting(var),
subs, subs,
buf, buf,
Parens::Unnecessary, Parens::Unnecessary,
@ -418,7 +460,7 @@ fn write_flat_type(env: &Env, flat_type: FlatType, subs: &Subs, buf: &mut String
buf.push_str(" }"); buf.push_str(" }");
} }
match subs.get_without_compacting(ext_var).content { match subs.get_content_without_compacting(ext_var) {
Content::Structure(EmptyRecord) => { Content::Structure(EmptyRecord) => {
// This is a closed record. We're done! // This is a closed record. We're done!
} }
@ -433,50 +475,9 @@ fn write_flat_type(env: &Env, flat_type: FlatType, subs: &Subs, buf: &mut String
} }
} }
TagUnion(tags, ext_var) => { TagUnion(tags, ext_var) => {
let interns = &env.interns;
let home = env.home;
buf.push_str("[ "); buf.push_str("[ ");
// Sort the fields so they always end up in the same order. let ext_content = write_sorted_tags(env, subs, buf, tags, *ext_var);
let mut sorted_fields = Vec::with_capacity(tags.len());
for (label, vars) in tags {
sorted_fields.push((label.clone(), vars));
}
// If the `ext` contains tags, merge them into the list of tags.
// this can occur when inferring mutually recursive tags
let ext_content = chase_ext_tag_union(subs, ext_var, &mut sorted_fields);
sorted_fields.sort_by(|(a, _), (b, _)| {
a.clone()
.as_string(interns, home)
.cmp(&b.as_string(&interns, home))
});
let mut any_written_yet = false;
for (label, vars) in sorted_fields {
if any_written_yet {
buf.push_str(", ");
} else {
any_written_yet = true;
}
buf.push_str(&label.as_string(&interns, home));
for var in vars {
buf.push(' ');
write_content(
env,
subs.get_without_compacting(var).content,
subs,
buf,
Parens::InTypeParam,
);
}
}
buf.push_str(" ]"); buf.push_str(" ]");
@ -491,17 +492,14 @@ fn write_flat_type(env: &Env, flat_type: FlatType, subs: &Subs, buf: &mut String
} }
FunctionOrTagUnion(tag_name, _, ext_var) => { FunctionOrTagUnion(tag_name, _, ext_var) => {
let interns = &env.interns;
let home = env.home;
buf.push_str("[ "); buf.push_str("[ ");
buf.push_str(&tag_name.as_string(&interns, home)); let mut tags: MutMap<TagName, _> = MutMap::default();
tags.insert(*tag_name.clone(), vec![]);
let ext_content = write_sorted_tags(env, subs, buf, &tags, *ext_var);
buf.push_str(" ]"); buf.push_str(" ]");
let mut sorted_fields = vec![(tag_name, vec![])];
let ext_content = chase_ext_tag_union(subs, ext_var, &mut sorted_fields);
if let Err((_, content)) = ext_content { if let Err((_, content)) = ext_content {
// This is an open tag union, so print the variable // This is an open tag union, so print the variable
// right after the ']' // right after the ']'
@ -513,45 +511,9 @@ fn write_flat_type(env: &Env, flat_type: FlatType, subs: &Subs, buf: &mut String
} }
RecursiveTagUnion(rec_var, tags, ext_var) => { RecursiveTagUnion(rec_var, tags, ext_var) => {
let interns = &env.interns;
let home = env.home;
buf.push_str("[ "); buf.push_str("[ ");
// Sort the fields so they always end up in the same order. let ext_content = write_sorted_tags(env, subs, buf, tags, *ext_var);
let mut sorted_fields = Vec::with_capacity(tags.len());
for (label, vars) in tags {
sorted_fields.push((label.clone(), vars));
}
// If the `ext` contains tags, merge them into the list of tags.
// this can occur when inferring mutually recursive tags
let ext_content = chase_ext_tag_union(subs, ext_var, &mut sorted_fields);
sorted_fields.sort_by(|(a, _), (b, _)| a.cmp(b));
let mut any_written_yet = false;
for (label, vars) in sorted_fields {
if any_written_yet {
buf.push_str(", ");
} else {
any_written_yet = true;
}
buf.push_str(&label.as_string(&interns, home));
for var in vars {
buf.push(' ');
write_content(
env,
subs.get_without_compacting(var).content,
subs,
buf,
Parens::InTypeParam,
);
}
}
buf.push_str(" ]"); buf.push_str(" ]");
@ -567,7 +529,7 @@ fn write_flat_type(env: &Env, flat_type: FlatType, subs: &Subs, buf: &mut String
buf.push_str(" as "); buf.push_str(" as ");
write_content( write_content(
env, env,
subs.get_without_compacting(rec_var).content, subs.get_content_without_compacting(*rec_var),
subs, subs,
buf, buf,
parens, parens,
@ -579,13 +541,13 @@ fn write_flat_type(env: &Env, flat_type: FlatType, subs: &Subs, buf: &mut String
} }
} }
pub fn chase_ext_tag_union( pub fn chase_ext_tag_union<'a>(
subs: &Subs, subs: &'a Subs,
var: Variable, var: Variable,
fields: &mut Vec<(TagName, Vec<Variable>)>, fields: &mut Vec<(TagName, Vec<Variable>)>,
) -> Result<(), (Variable, Content)> { ) -> Result<(), (Variable, &'a Content)> {
use FlatType::*; use FlatType::*;
match subs.get_without_compacting(var).content { match subs.get_content_without_compacting(var) {
Content::Structure(EmptyTagUnion) => Ok(()), Content::Structure(EmptyTagUnion) => Ok(()),
Content::Structure(TagUnion(tags, ext_var)) Content::Structure(TagUnion(tags, ext_var))
| Content::Structure(RecursiveTagUnion(_, tags, ext_var)) => { | Content::Structure(RecursiveTagUnion(_, tags, ext_var)) => {
@ -593,14 +555,14 @@ pub fn chase_ext_tag_union(
fields.push((label.clone(), vars.to_vec())); fields.push((label.clone(), vars.to_vec()));
} }
chase_ext_tag_union(subs, ext_var, fields) chase_ext_tag_union(subs, *ext_var, fields)
} }
Content::Structure(FunctionOrTagUnion(tag_name, _, ext_var)) => { Content::Structure(FunctionOrTagUnion(tag_name, _, ext_var)) => {
fields.push((tag_name, vec![])); fields.push((*tag_name.clone(), vec![]));
chase_ext_tag_union(subs, ext_var, fields) chase_ext_tag_union(subs, *ext_var, fields)
} }
Content::Alias(_, _, var) => chase_ext_tag_union(subs, var, fields), Content::Alias(_, _, var) => chase_ext_tag_union(subs, *var, fields),
content => Err((var, content)), content => Err((var, content)),
} }
@ -614,25 +576,27 @@ pub fn chase_ext_record(
use crate::subs::Content::*; use crate::subs::Content::*;
use crate::subs::FlatType::*; use crate::subs::FlatType::*;
match subs.get_without_compacting(var).content { match subs.get_content_without_compacting(var) {
Structure(Record(sub_fields, sub_ext)) => { Structure(Record(sub_fields, sub_ext)) => {
fields.extend(sub_fields.into_iter()); for (field_name, record_field) in sub_fields {
fields.insert(field_name.clone(), record_field);
}
chase_ext_record(subs, sub_ext, fields) chase_ext_record(subs, *sub_ext, fields)
} }
Structure(EmptyRecord) => Ok(()), Structure(EmptyRecord) => Ok(()),
Alias(_, _, var) => chase_ext_record(subs, var, fields), Alias(_, _, var) => chase_ext_record(subs, *var, fields),
content => Err((var, content)), content => Err((var, content.clone())),
} }
} }
fn write_apply( fn write_apply(
env: &Env, env: &Env,
symbol: Symbol, symbol: Symbol,
args: Vec<Variable>, args: &[Variable],
subs: &Subs, subs: &Subs,
buf: &mut String, buf: &mut String,
parens: Parens, parens: Parens,
@ -646,10 +610,10 @@ fn write_apply(
} }
Symbol::NUM_NUM => { Symbol::NUM_NUM => {
let arg = args let arg = args
.into_iter() .iter()
.next() .next()
.unwrap_or_else(|| panic!("Num did not have any type parameters somehow.")); .unwrap_or_else(|| panic!("Num did not have any type parameters somehow."));
let arg_content = subs.get_without_compacting(arg).content; let arg_content = subs.get_content_without_compacting(*arg);
let mut arg_param = String::new(); let mut arg_param = String::new();
let mut default_case = |subs, content| { let mut default_case = |subs, content| {
@ -690,7 +654,7 @@ fn write_apply(
buf.push(' '); buf.push(' ');
write_content( write_content(
env, env,
subs.get_without_compacting(arg).content, subs.get_content_without_compacting(*arg),
subs, subs,
buf, buf,
Parens::InTypeParam, Parens::InTypeParam,
@ -706,7 +670,7 @@ fn write_apply(
fn write_fn( fn write_fn(
env: &Env, env: &Env,
args: Vec<Variable>, args: &[Variable],
ret: Variable, ret: Variable,
subs: &Subs, subs: &Subs,
buf: &mut String, buf: &mut String,
@ -728,7 +692,7 @@ fn write_fn(
write_content( write_content(
env, env,
subs.get_without_compacting(arg).content, subs.get_content_without_compacting(*arg),
subs, subs,
buf, buf,
Parens::InFn, Parens::InFn,
@ -738,7 +702,7 @@ fn write_fn(
buf.push_str(" -> "); buf.push_str(" -> ");
write_content( write_content(
env, env,
subs.get_without_compacting(ret).content, subs.get_content_without_compacting(ret),
subs, subs,
buf, buf,
Parens::InFn, Parens::InFn,
@ -757,7 +721,7 @@ fn write_symbol(env: &Env, symbol: Symbol, buf: &mut String) {
// Don't qualify the symbol if it's in our home module, // Don't qualify the symbol if it's in our home module,
// or if it's a builtin (since all their types are always in scope) // or if it's a builtin (since all their types are always in scope)
if module_id != env.home && !module_id.is_builtin() { if module_id != env.home && !module_id.is_builtin() {
buf.push_str(module_id.to_string(&interns)); buf.push_str(module_id.to_string(interns));
buf.push('.'); buf.push('.');
} }

View file

@ -341,24 +341,27 @@ impl SolvedType {
return SolvedType::Flex(VarId::from_var(var, subs)); return SolvedType::Flex(VarId::from_var(var, subs));
} }
match subs.get_without_compacting(var).content { match subs.get_content_without_compacting(var) {
FlexVar(_) => SolvedType::Flex(VarId::from_var(var, subs)), FlexVar(_) => SolvedType::Flex(VarId::from_var(var, subs)),
RecursionVar { structure, .. } => { RecursionVar { structure, .. } => {
// TODO should there be a SolvedType RecursionVar variant? // TODO should there be a SolvedType RecursionVar variant?
Self::from_var_help(subs, recursion_vars, structure) Self::from_var_help(subs, recursion_vars, *structure)
} }
RigidVar(name) => SolvedType::Rigid(name), RigidVar(name) => SolvedType::Rigid(name.clone()),
Structure(flat_type) => Self::from_flat_type(subs, recursion_vars, flat_type), Structure(flat_type) => Self::from_flat_type(subs, recursion_vars, flat_type),
Alias(symbol, args, actual_var) => { Alias(symbol, args, actual_var) => {
let mut new_args = Vec::with_capacity(args.len()); let mut new_args = Vec::with_capacity(args.len());
for (arg_name, arg_var) in args { for (arg_name, arg_var) in args {
new_args.push((arg_name, Self::from_var_help(subs, recursion_vars, arg_var))); new_args.push((
arg_name.clone(),
Self::from_var_help(subs, recursion_vars, *arg_var),
));
} }
let aliased_to = Self::from_var_help(subs, recursion_vars, actual_var); let aliased_to = Self::from_var_help(subs, recursion_vars, *actual_var);
SolvedType::Alias(symbol, new_args, Box::new(aliased_to)) SolvedType::Alias(*symbol, new_args, Box::new(aliased_to))
} }
Error => SolvedType::Error, Error => SolvedType::Error,
} }
@ -367,7 +370,7 @@ impl SolvedType {
fn from_flat_type( fn from_flat_type(
subs: &Subs, subs: &Subs,
recursion_vars: &mut RecursionVars, recursion_vars: &mut RecursionVars,
flat_type: FlatType, flat_type: &FlatType,
) -> Self { ) -> Self {
use crate::subs::FlatType::*; use crate::subs::FlatType::*;
@ -379,17 +382,17 @@ impl SolvedType {
new_args.push(Self::from_var_help(subs, recursion_vars, var)); new_args.push(Self::from_var_help(subs, recursion_vars, var));
} }
SolvedType::Apply(symbol, new_args) SolvedType::Apply(*symbol, new_args)
} }
Func(args, closure, ret) => { Func(args, closure, ret) => {
let mut new_args = Vec::with_capacity(args.len()); let mut new_args = Vec::with_capacity(args.len());
for var in args { for var in args {
new_args.push(Self::from_var_help(subs, recursion_vars, var)); new_args.push(Self::from_var_help(subs, recursion_vars, *var));
} }
let ret = Self::from_var_help(subs, recursion_vars, ret); let ret = Self::from_var_help(subs, recursion_vars, *ret);
let closure = Self::from_var_help(subs, recursion_vars, closure); let closure = Self::from_var_help(subs, recursion_vars, *closure);
SolvedType::Func(new_args, Box::new(closure), Box::new(ret)) SolvedType::Func(new_args, Box::new(closure), Box::new(ret))
} }
@ -397,18 +400,13 @@ impl SolvedType {
let mut new_fields = Vec::with_capacity(fields.len()); let mut new_fields = Vec::with_capacity(fields.len());
for (label, field) in fields { for (label, field) in fields {
use RecordField::*; let solved_type =
field.map(|var| Self::from_var_help(subs, recursion_vars, *var));
let solved_type = match field { new_fields.push((label.clone(), solved_type));
Optional(var) => Optional(Self::from_var_help(subs, recursion_vars, var)),
Required(var) => Required(Self::from_var_help(subs, recursion_vars, var)),
Demanded(var) => Demanded(Self::from_var_help(subs, recursion_vars, var)),
};
new_fields.push((label, solved_type));
} }
let ext = Self::from_var_help(subs, recursion_vars, ext_var); let ext = Self::from_var_help(subs, recursion_vars, *ext_var);
SolvedType::Record { SolvedType::Record {
fields: new_fields, fields: new_fields,
@ -422,23 +420,23 @@ impl SolvedType {
let mut new_args = Vec::with_capacity(args.len()); let mut new_args = Vec::with_capacity(args.len());
for var in args { for var in args {
new_args.push(Self::from_var_help(subs, recursion_vars, var)); new_args.push(Self::from_var_help(subs, recursion_vars, *var));
} }
new_tags.push((tag_name, new_args)); new_tags.push((tag_name.clone(), new_args));
} }
let ext = Self::from_var_help(subs, recursion_vars, ext_var); let ext = Self::from_var_help(subs, recursion_vars, *ext_var);
SolvedType::TagUnion(new_tags, Box::new(ext)) SolvedType::TagUnion(new_tags, Box::new(ext))
} }
FunctionOrTagUnion(tag_name, symbol, ext_var) => { FunctionOrTagUnion(tag_name, symbol, ext_var) => {
let ext = Self::from_var_help(subs, recursion_vars, ext_var); let ext = Self::from_var_help(subs, recursion_vars, *ext_var);
SolvedType::FunctionOrTagUnion(tag_name, symbol, Box::new(ext)) SolvedType::FunctionOrTagUnion(*tag_name.clone(), *symbol, Box::new(ext))
} }
RecursiveTagUnion(rec_var, tags, ext_var) => { RecursiveTagUnion(rec_var, tags, ext_var) => {
recursion_vars.insert(subs, rec_var); recursion_vars.insert(subs, *rec_var);
let mut new_tags = Vec::with_capacity(tags.len()); let mut new_tags = Vec::with_capacity(tags.len());
@ -446,23 +444,23 @@ impl SolvedType {
let mut new_args = Vec::with_capacity(args.len()); let mut new_args = Vec::with_capacity(args.len());
for var in args { for var in args {
new_args.push(Self::from_var_help(subs, recursion_vars, var)); new_args.push(Self::from_var_help(subs, recursion_vars, *var));
} }
new_tags.push((tag_name, new_args)); new_tags.push((tag_name.clone(), new_args));
} }
let ext = Self::from_var_help(subs, recursion_vars, ext_var); let ext = Self::from_var_help(subs, recursion_vars, *ext_var);
SolvedType::RecursiveTagUnion( SolvedType::RecursiveTagUnion(
VarId::from_var(rec_var, subs), VarId::from_var(*rec_var, subs),
new_tags, new_tags,
Box::new(ext), Box::new(ext),
) )
} }
EmptyRecord => SolvedType::EmptyRecord, EmptyRecord => SolvedType::EmptyRecord,
EmptyTagUnion => SolvedType::EmptyTagUnion, EmptyTagUnion => SolvedType::EmptyTagUnion,
Erroneous(problem) => SolvedType::Erroneous(problem), Erroneous(problem) => SolvedType::Erroneous(*problem.clone()),
} }
} }
} }
@ -510,11 +508,11 @@ pub fn to_type(
let mut new_args = Vec::with_capacity(args.len()); let mut new_args = Vec::with_capacity(args.len());
for arg in args { for arg in args {
new_args.push(to_type(&arg, free_vars, var_store)); new_args.push(to_type(arg, free_vars, var_store));
} }
let new_ret = to_type(&ret, free_vars, var_store); let new_ret = to_type(ret, free_vars, var_store);
let new_closure = to_type(&closure, 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)) Type::Function(new_args, Box::new(new_closure), Box::new(new_ret))
} }
@ -522,13 +520,13 @@ pub fn to_type(
let mut new_args = Vec::with_capacity(args.len()); let mut new_args = Vec::with_capacity(args.len());
for arg in args { for arg in args {
new_args.push(to_type(&arg, free_vars, var_store)); new_args.push(to_type(arg, free_vars, var_store));
} }
Type::Apply(*symbol, new_args) Type::Apply(*symbol, new_args)
} }
Rigid(lowercase) => { Rigid(lowercase) => {
if let Some(var) = free_vars.named_vars.get(&lowercase) { if let Some(var) = free_vars.named_vars.get(lowercase) {
Type::Variable(*var) Type::Variable(*var)
} else { } else {
let var = var_store.fresh(); let var = var_store.fresh();
@ -549,9 +547,9 @@ pub fn to_type(
for (label, field) in fields { for (label, field) in fields {
let field_val = match field { let field_val = match field {
Required(typ) => Required(to_type(&typ, free_vars, var_store)), Required(typ) => Required(to_type(typ, free_vars, var_store)),
Optional(typ) => Optional(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)), Demanded(typ) => Demanded(to_type(typ, free_vars, var_store)),
}; };
new_fields.insert(label.clone(), field_val); new_fields.insert(label.clone(), field_val);

View file

@ -2,8 +2,9 @@ use crate::types::{name_type_var, ErrorType, Problem, RecordField, TypeExt};
use roc_collections::all::{ImMap, ImSet, MutMap, MutSet, SendMap}; use roc_collections::all::{ImMap, ImSet, MutMap, MutSet, SendMap};
use roc_module::ident::{Lowercase, TagName}; use roc_module::ident::{Lowercase, TagName};
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use std::cmp::Ordering;
use std::fmt; use std::fmt;
use std::iter::{once, Iterator}; use std::iter::{once, Extend, FromIterator, Iterator, Map, Zip};
use ven_ena::unify::{InPlace, Snapshot, UnificationTable, UnifyKey}; use ven_ena::unify::{InPlace, Snapshot, UnificationTable, UnifyKey};
#[derive(Clone, Copy, Hash, PartialEq, Eq)] #[derive(Clone, Copy, Hash, PartialEq, Eq)]
@ -339,10 +340,20 @@ impl Subs {
self.utable.probe_value_ref(key).value.mark self.utable.probe_value_ref(key).value.mark
} }
pub fn get_rank_mark(&mut self, key: Variable) -> (Rank, Mark) {
let desc = &self.utable.probe_value_ref(key).value;
(desc.rank, desc.mark)
}
pub fn get_without_compacting(&self, key: Variable) -> Descriptor { pub fn get_without_compacting(&self, key: Variable) -> Descriptor {
self.utable.probe_value_without_compacting(key) self.utable.probe_value_without_compacting(key)
} }
pub fn get_content_without_compacting(&self, key: Variable) -> &Content {
&self.utable.probe_value_ref(key).value.content
}
pub fn get_root_key(&mut self, key: Variable) -> Variable { pub fn get_root_key(&mut self, key: Variable) -> Variable {
self.utable.get_root_key(key) self.utable.get_root_key(key)
} }
@ -373,6 +384,15 @@ impl Subs {
}); });
} }
pub fn set_rank_mark(&mut self, key: Variable, rank: Rank, mark: Mark) {
let l_key = self.utable.get_root_key(key);
self.utable.update_value(l_key, |node| {
node.value.rank = rank;
node.value.mark = mark;
});
}
pub fn set_content(&mut self, key: Variable, content: Content) { pub fn set_content(&mut self, key: Variable, content: Content) {
let l_key = self.utable.get_root_key(key); let l_key = self.utable.get_root_key(key);
@ -516,7 +536,7 @@ impl From<usize> for Rank {
} }
} }
#[derive(Clone, PartialEq, Eq)] #[derive(Clone)]
pub struct Descriptor { pub struct Descriptor {
pub content: Content, pub content: Content,
pub rank: Rank, pub rank: Rank,
@ -554,7 +574,7 @@ impl From<Content> for Descriptor {
} }
} }
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug)]
pub enum Content { pub enum Content {
/// A type variable which the user did not name in an annotation, /// A type variable which the user did not name in an annotation,
/// ///
@ -593,22 +613,22 @@ impl Content {
eprintln!( eprintln!(
"{}", "{}",
crate::pretty_print::content_to_string(self.clone(), subs, home, &interns) crate::pretty_print::content_to_string(&self, subs, home, &interns)
); );
self self
} }
} }
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug)]
pub enum FlatType { pub enum FlatType {
Apply(Symbol, Vec<Variable>), Apply(Symbol, Vec<Variable>),
Func(Vec<Variable>, Variable, Variable), Func(Vec<Variable>, Variable, Variable),
Record(MutMap<Lowercase, RecordField<Variable>>, Variable), Record(RecordFields, Variable),
TagUnion(MutMap<TagName, Vec<Variable>>, Variable), TagUnion(MutMap<TagName, Vec<Variable>>, Variable),
FunctionOrTagUnion(TagName, Symbol, Variable), FunctionOrTagUnion(Box<TagName>, Symbol, Variable),
RecursiveTagUnion(Variable, MutMap<TagName, Vec<Variable>>, Variable), RecursiveTagUnion(Variable, MutMap<TagName, Vec<Variable>>, Variable),
Erroneous(Problem), Erroneous(Box<Problem>),
EmptyRecord, EmptyRecord,
EmptyTagUnion, EmptyTagUnion,
} }
@ -621,6 +641,232 @@ pub enum Builtin {
EmptyRecord, EmptyRecord,
} }
#[derive(Clone, Debug)]
pub struct RecordFields {
field_names: Vec<Lowercase>,
variables: Vec<Variable>,
field_type: Vec<RecordField<()>>,
}
impl RecordFields {
pub fn with_capacity(capacity: usize) -> Self {
Self {
field_names: Vec::with_capacity(capacity),
variables: Vec::with_capacity(capacity),
field_type: Vec::with_capacity(capacity),
}
}
pub fn len(&self) -> usize {
let answer = self.field_names.len();
debug_assert_eq!(answer, self.variables.len());
debug_assert_eq!(answer, self.field_type.len());
answer
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn iter_variables(&self) -> impl Iterator<Item = &Variable> {
self.variables.iter()
}
pub fn iter_variables_mut(&mut self) -> impl Iterator<Item = &mut Variable> {
self.variables.iter_mut()
}
pub fn iter(&self) -> impl Iterator<Item = (&Lowercase, RecordField<Variable>)> {
self.into_iter()
}
pub fn has_only_optional_fields(&self) -> bool {
self.field_type
.iter()
.all(|field| matches!(field, RecordField::Optional(_)))
}
pub fn from_vec(mut vec: Vec<(Lowercase, RecordField<Variable>)>) -> Self {
// we assume there are no duplicate field names in there
vec.sort_unstable_by(|(name1, _), (name2, _)| name1.cmp(name2));
Self::from_sorted_vec(vec)
}
pub fn from_sorted_vec(vec: Vec<(Lowercase, RecordField<Variable>)>) -> Self {
let mut result = RecordFields::with_capacity(vec.len());
result.extend(vec);
result
}
pub fn merge(self, other: Self) -> Self {
if other.is_empty() {
return self;
}
// maximum final size (if there is no overlap at all)
let final_size = self.len() + other.len();
let mut result = Self::with_capacity(final_size);
let mut it1 = self.into_iter().peekable();
let mut it2 = other.into_iter().peekable();
loop {
let which = match (it1.peek(), it2.peek()) {
(Some((l, _)), Some((r, _))) => Some(l.cmp(r)),
(Some(_), None) => Some(Ordering::Less),
(None, Some(_)) => Some(Ordering::Greater),
(None, None) => None,
};
let next_element = match which {
Some(Ordering::Less) => it1.next(),
Some(Ordering::Equal) => {
let _ = it2.next();
it1.next()
}
Some(Ordering::Greater) => it2.next(),
None => break,
};
result.extend([next_element.unwrap()]);
}
result
}
pub fn separate(self, other: Self) -> SeparateRecordFields {
let max_common = self.len().min(other.len());
let mut result = SeparateRecordFields {
only_in_1: RecordFields::with_capacity(self.len()),
only_in_2: RecordFields::with_capacity(other.len()),
in_both: Vec::with_capacity(max_common),
};
let mut it1 = self.into_iter().peekable();
let mut it2 = other.into_iter().peekable();
loop {
let which = match (it1.peek(), it2.peek()) {
(Some((l, _)), Some((r, _))) => Some(l.cmp(r)),
(Some(_), None) => Some(Ordering::Less),
(None, Some(_)) => Some(Ordering::Greater),
(None, None) => None,
};
match which {
Some(Ordering::Less) => result.only_in_1.extend(it1.next()),
Some(Ordering::Equal) => {
let (label, field1) = it1.next().unwrap();
let (_, field2) = it2.next().unwrap();
result.in_both.push((label, field1, field2));
}
Some(Ordering::Greater) => result.only_in_2.extend(it2.next()),
None => break,
};
}
result
}
}
pub struct SeparateRecordFields {
pub only_in_1: RecordFields,
pub only_in_2: RecordFields,
pub in_both: Vec<(Lowercase, RecordField<Variable>, RecordField<Variable>)>,
}
impl Extend<(Lowercase, RecordField<Variable>)> for RecordFields {
fn extend<T: IntoIterator<Item = (Lowercase, RecordField<Variable>)>>(&mut self, iter: T) {
for (name, record_field) in iter.into_iter() {
self.field_names.push(name);
self.field_type.push(record_field.map(|_| ()));
self.variables.push(record_field.into_inner());
}
}
}
impl FromIterator<(Lowercase, RecordField<Variable>)> for RecordFields {
fn from_iter<T: IntoIterator<Item = (Lowercase, RecordField<Variable>)>>(iter: T) -> Self {
let vec: Vec<_> = iter.into_iter().collect();
Self::from_vec(vec)
}
}
impl<'a> FromIterator<(&'a Lowercase, RecordField<Variable>)> for RecordFields {
fn from_iter<T: IntoIterator<Item = (&'a Lowercase, RecordField<Variable>)>>(iter: T) -> Self {
let vec: Vec<_> = iter.into_iter().map(|(a, b)| (a.clone(), b)).collect();
Self::from_vec(vec)
}
}
impl IntoIterator for RecordFields {
type Item = (Lowercase, RecordField<Variable>);
#[allow(clippy::type_complexity)]
type IntoIter = Map<
Zip<
Zip<std::vec::IntoIter<Lowercase>, std::vec::IntoIter<Variable>>,
std::vec::IntoIter<RecordField<()>>,
>,
fn(((Lowercase, Variable), RecordField<()>)) -> (Lowercase, RecordField<Variable>),
>;
fn into_iter(self) -> Self::IntoIter {
self.field_names
.into_iter()
.zip(self.variables.into_iter())
.zip(self.field_type.into_iter())
.map(record_fields_into_iterator_help)
}
}
fn record_fields_into_iterator_help(
arg: ((Lowercase, Variable), RecordField<()>),
) -> (Lowercase, RecordField<Variable>) {
let ((name, var), field_type) = arg;
(name, field_type.map(|_| var))
}
impl<'a> IntoIterator for &'a RecordFields {
type Item = (&'a Lowercase, RecordField<Variable>);
#[allow(clippy::type_complexity)]
type IntoIter = Map<
Zip<
Zip<std::slice::Iter<'a, Lowercase>, std::slice::Iter<'a, Variable>>,
std::slice::Iter<'a, RecordField<()>>,
>,
fn(
((&'a Lowercase, &Variable), &RecordField<()>),
) -> (&'a Lowercase, RecordField<Variable>),
>;
fn into_iter(self) -> Self::IntoIter {
self.field_names
.iter()
.zip(self.variables.iter())
.zip(self.field_type.iter())
.map(ref_record_fields_into_iterator_help)
}
}
fn ref_record_fields_into_iterator_help<'a>(
arg: ((&'a Lowercase, &Variable), &RecordField<()>),
) -> (&'a Lowercase, RecordField<Variable>) {
let ((name, var), field_type) = arg;
(name, field_type.map(|_| *var))
}
fn occurs( fn occurs(
subs: &Subs, subs: &Subs,
seen: &ImSet<Variable>, seen: &ImSet<Variable>,
@ -634,7 +880,7 @@ fn occurs(
if seen.contains(&root_var) { if seen.contains(&root_var) {
Some((root_var, vec![])) Some((root_var, vec![]))
} else { } else {
match subs.get_without_compacting(root_var).content { match subs.get_content_without_compacting(root_var) {
FlexVar(_) | RigidVar(_) | RecursionVar { .. } | Error => None, FlexVar(_) | RigidVar(_) | RecursionVar { .. } | Error => None,
Structure(flat_type) => { Structure(flat_type) => {
@ -645,31 +891,26 @@ fn occurs(
match flat_type { match flat_type {
Apply(_, args) => short_circuit(subs, root_var, &new_seen, args.iter()), Apply(_, args) => short_circuit(subs, root_var, &new_seen, args.iter()),
Func(arg_vars, closure_var, ret_var) => { Func(arg_vars, closure_var, ret_var) => {
let it = once(&ret_var) let it = once(ret_var)
.chain(once(&closure_var)) .chain(once(closure_var))
.chain(arg_vars.iter()); .chain(arg_vars.iter());
short_circuit(subs, root_var, &new_seen, it) short_circuit(subs, root_var, &new_seen, it)
} }
Record(vars_by_field, ext_var) => { Record(vars_by_field, ext_var) => {
let it = let it = once(ext_var).chain(vars_by_field.iter_variables());
once(&ext_var).chain(vars_by_field.values().map(|field| match field {
RecordField::Optional(var) => var,
RecordField::Required(var) => var,
RecordField::Demanded(var) => var,
}));
short_circuit(subs, root_var, &new_seen, it) short_circuit(subs, root_var, &new_seen, it)
} }
TagUnion(tags, ext_var) => { TagUnion(tags, ext_var) => {
let it = once(&ext_var).chain(tags.values().flatten()); let it = once(ext_var).chain(tags.values().flatten());
short_circuit(subs, root_var, &new_seen, it) short_circuit(subs, root_var, &new_seen, it)
} }
FunctionOrTagUnion(_, _, ext_var) => { FunctionOrTagUnion(_, _, ext_var) => {
let it = once(&ext_var); let it = once(ext_var);
short_circuit(subs, root_var, &new_seen, it) short_circuit(subs, root_var, &new_seen, it)
} }
RecursiveTagUnion(_rec_var, tags, ext_var) => { RecursiveTagUnion(_rec_var, tags, ext_var) => {
// TODO rec_var is excluded here, verify that this is correct // TODO rec_var is excluded here, verify that this is correct
let it = once(&ext_var).chain(tags.values().flatten()); let it = once(ext_var).chain(tags.values().flatten());
short_circuit(subs, root_var, &new_seen, it) short_circuit(subs, root_var, &new_seen, it)
} }
EmptyRecord | EmptyTagUnion | Erroneous(_) => None, EmptyRecord | EmptyTagUnion | Erroneous(_) => None,
@ -780,21 +1021,10 @@ fn explicit_substitute(
Record(mut vars_by_field, ext_var) => { Record(mut vars_by_field, ext_var) => {
let new_ext_var = explicit_substitute(subs, from, to, ext_var, seen); let new_ext_var = explicit_substitute(subs, from, to, ext_var, seen);
for (_, field) in vars_by_field.iter_mut() { for var in vars_by_field.variables.iter_mut() {
use RecordField::*; *var = explicit_substitute(subs, from, to, *var, seen);
}
*field = match field {
Optional(var) => {
Optional(explicit_substitute(subs, from, to, *var, seen))
}
Required(var) => {
Required(explicit_substitute(subs, from, to, *var, seen))
}
Demanded(var) => {
Demanded(explicit_substitute(subs, from, to, *var, seen))
}
};
}
subs.set_content(in_var, Structure(Record(vars_by_field, new_ext_var))); subs.set_content(in_var, Structure(Record(vars_by_field, new_ext_var)));
} }
@ -1159,6 +1389,8 @@ fn flat_type_to_err_type(
} }
FunctionOrTagUnion(tag_name, _, ext_var) => { FunctionOrTagUnion(tag_name, _, ext_var) => {
let tag_name = *tag_name;
let mut err_tags = SendMap::default(); let mut err_tags = SendMap::default();
err_tags.insert(tag_name, vec![]); err_tags.insert(tag_name, vec![]);
@ -1223,7 +1455,7 @@ fn flat_type_to_err_type(
} }
Erroneous(problem) => { Erroneous(problem) => {
state.problems.push(problem); state.problems.push(*problem);
ErrorType::Error ErrorType::Error
} }
@ -1265,8 +1497,8 @@ fn restore_content(subs: &mut Subs, content: &Content) {
EmptyTagUnion => (), EmptyTagUnion => (),
Record(fields, ext_var) => { Record(fields, ext_var) => {
for field in fields.values() { for var in fields.iter_variables() {
subs.restore(field.into_inner()); subs.restore(*var);
} }
subs.restore(*ext_var); subs.restore(*ext_var);

View file

@ -1,7 +1,7 @@
use crate::pretty_print::Parens; use crate::pretty_print::Parens;
use crate::subs::{LambdaSet, Subs, VarStore, Variable}; use crate::subs::{LambdaSet, RecordFields, Subs, VarStore, Variable};
use inlinable_string::InlinableString; use inlinable_string::InlinableString;
use roc_collections::all::{union, ImMap, ImSet, Index, MutMap, MutSet, SendMap}; use roc_collections::all::{ImMap, ImSet, Index, MutSet, SendMap};
use roc_module::ident::{ForeignSymbol, Ident, Lowercase, TagName}; use roc_module::ident::{ForeignSymbol, Ident, Lowercase, TagName};
use roc_module::low_level::LowLevel; use roc_module::low_level::LowLevel;
use roc_module::symbol::{Interns, ModuleId, Symbol}; use roc_module::symbol::{Interns, ModuleId, Symbol};
@ -406,7 +406,7 @@ impl Type {
match self { match self {
Variable(v) => { Variable(v) => {
if let Some(replacement) = substitutions.get(&v) { if let Some(replacement) = substitutions.get(v) {
*self = replacement.clone(); *self = replacement.clone();
} }
} }
@ -762,15 +762,15 @@ fn symbols_help(tipe: &Type, accum: &mut ImSet<Symbol>) {
match tipe { match tipe {
Function(args, closure, ret) => { Function(args, closure, ret) => {
symbols_help(&ret, accum); symbols_help(ret, accum);
symbols_help(&closure, accum); symbols_help(closure, accum);
args.iter().for_each(|arg| symbols_help(arg, accum)); args.iter().for_each(|arg| symbols_help(arg, accum));
} }
FunctionOrTagUnion(_, _, ext) => { FunctionOrTagUnion(_, _, ext) => {
symbols_help(&ext, accum); symbols_help(ext, accum);
} }
RecursiveTagUnion(_, tags, ext) | TagUnion(tags, ext) => { RecursiveTagUnion(_, tags, ext) | TagUnion(tags, ext) => {
symbols_help(&ext, accum); symbols_help(ext, accum);
tags.iter() tags.iter()
.map(|v| v.1.iter()) .map(|v| v.1.iter())
.flatten() .flatten()
@ -778,7 +778,7 @@ fn symbols_help(tipe: &Type, accum: &mut ImSet<Symbol>) {
} }
Record(fields, ext) => { Record(fields, ext) => {
symbols_help(&ext, accum); symbols_help(ext, accum);
fields.values().for_each(|field| { fields.values().for_each(|field| {
use RecordField::*; use RecordField::*;
@ -791,11 +791,11 @@ fn symbols_help(tipe: &Type, accum: &mut ImSet<Symbol>) {
} }
Alias(alias_symbol, _, actual_type) => { Alias(alias_symbol, _, actual_type) => {
accum.insert(*alias_symbol); accum.insert(*alias_symbol);
symbols_help(&actual_type, accum); symbols_help(actual_type, accum);
} }
HostExposedAlias { name, actual, .. } => { HostExposedAlias { name, actual, .. } => {
accum.insert(*name); accum.insert(*name);
symbols_help(&actual, accum); symbols_help(actual, accum);
} }
Apply(symbol, args) => { Apply(symbol, args) => {
accum.insert(*symbol); accum.insert(*symbol);
@ -980,7 +980,7 @@ fn variables_help_detailed(tipe: &Type, accum: &mut VariableDetail) {
} }
pub struct RecordStructure { pub struct RecordStructure {
pub fields: MutMap<Lowercase, RecordField<Variable>>, pub fields: RecordFields,
pub ext: Variable, pub ext: Variable,
} }
@ -1116,7 +1116,7 @@ pub struct Alias {
#[derive(PartialEq, Eq, Debug, Clone, Hash)] #[derive(PartialEq, Eq, Debug, Clone, Hash)]
pub enum Problem { pub enum Problem {
CanonicalizationProblem, CanonicalizationProblem,
CircularType(Symbol, ErrorType, Region), CircularType(Symbol, Box<ErrorType>, Region),
CyclicAlias(Symbol, Region, Vec<Symbol>), CyclicAlias(Symbol, Region, Vec<Symbol>),
UnrecognizedIdent(InlinableString), UnrecognizedIdent(InlinableString),
Shadowed(Region, Located<Ident>), Shadowed(Region, Located<Ident>),
@ -1522,22 +1522,76 @@ pub fn name_type_var(letters_used: u32, taken: &mut MutSet<Lowercase>) -> (Lower
pub fn gather_fields( pub fn gather_fields(
subs: &Subs, subs: &Subs,
fields: MutMap<Lowercase, RecordField<Variable>>, other_fields: RecordFields,
var: Variable, mut var: Variable,
) -> RecordStructure { ) -> RecordStructure {
use crate::subs::Content::*; use crate::subs::Content::*;
use crate::subs::FlatType::*; use crate::subs::FlatType::*;
match subs.get_without_compacting(var).content { let mut result = other_fields;
loop {
match subs.get_content_without_compacting(var) {
Structure(Record(sub_fields, sub_ext)) => { Structure(Record(sub_fields, sub_ext)) => {
gather_fields(subs, union(fields, &sub_fields), sub_ext) result = RecordFields::merge(result, sub_fields.clone());
var = *sub_ext;
} }
Alias(_, _, var) => { Alias(_, _, actual_var) => {
// TODO according to elm/compiler: "TODO may be dropping useful alias info here" // TODO according to elm/compiler: "TODO may be dropping useful alias info here"
gather_fields(subs, fields, var) var = *actual_var;
} }
_ => RecordStructure { fields, ext: var }, _ => break,
}
}
RecordStructure {
fields: result,
ext: var,
}
}
pub fn gather_fields_ref(
subs: &Subs,
other_fields: &RecordFields,
mut var: Variable,
) -> RecordStructure {
use crate::subs::Content::*;
use crate::subs::FlatType::*;
let mut from_ext = Vec::new();
loop {
match subs.get_content_without_compacting(var) {
Structure(Record(sub_fields, sub_ext)) => {
from_ext.extend(sub_fields.into_iter());
var = *sub_ext;
}
Alias(_, _, actual_var) => {
// TODO according to elm/compiler: "TODO may be dropping useful alias info here"
var = *actual_var;
}
_ => break,
}
}
if from_ext.is_empty() {
RecordStructure {
fields: other_fields.clone(),
ext: var,
}
} else {
RecordStructure {
fields: other_fields
.into_iter()
.chain(from_ext.into_iter())
.collect(),
ext: var,
}
} }
} }

View file

@ -2,8 +2,8 @@ use roc_collections::all::{default_hasher, get_shared, relative_complement, unio
use roc_module::ident::{Lowercase, TagName}; use roc_module::ident::{Lowercase, TagName};
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use roc_types::subs::Content::{self, *}; use roc_types::subs::Content::{self, *};
use roc_types::subs::{Descriptor, FlatType, Mark, OptVariable, Subs, Variable}; use roc_types::subs::{Descriptor, FlatType, Mark, OptVariable, RecordFields, Subs, Variable};
use roc_types::types::{gather_fields, ErrorType, Mismatch, RecordField, RecordStructure}; use roc_types::types::{gather_fields_ref, ErrorType, Mismatch, RecordField, RecordStructure};
macro_rules! mismatch { macro_rules! mismatch {
() => {{ () => {{
@ -177,7 +177,7 @@ fn unify_alias(
match other_content { match other_content {
FlexVar(_) => { FlexVar(_) => {
// Alias wins // Alias wins
merge(subs, &ctx, Alias(symbol, args.to_owned(), real_var)) merge(subs, ctx, Alias(symbol, args.to_owned(), real_var))
} }
RecursionVar { structure, .. } => unify_pool(subs, pool, real_var, *structure), RecursionVar { structure, .. } => unify_pool(subs, pool, real_var, *structure),
RigidVar(_) => unify_pool(subs, pool, real_var, ctx.second), RigidVar(_) => unify_pool(subs, pool, real_var, ctx.second),
@ -190,7 +190,7 @@ fn unify_alias(
} }
if problems.is_empty() { if problems.is_empty() {
problems.extend(merge(subs, &ctx, other_content.clone())); problems.extend(merge(subs, ctx, other_content.clone()));
} }
if problems.is_empty() { if problems.is_empty() {
@ -262,28 +262,27 @@ fn unify_record(
) -> Outcome { ) -> Outcome {
let fields1 = rec1.fields; let fields1 = rec1.fields;
let fields2 = rec2.fields; let fields2 = rec2.fields;
let shared_fields = get_shared(&fields1, &fields2);
// NOTE: don't use `difference` here. In contrast to Haskell, im's `difference` is symmetric
let unique_fields1 = relative_complement(&fields1, &fields2);
let unique_fields2 = relative_complement(&fields2, &fields1);
if unique_fields1.is_empty() { let separate = RecordFields::separate(fields1, fields2);
if unique_fields2.is_empty() {
let shared_fields = separate.in_both;
if separate.only_in_1.is_empty() {
if separate.only_in_2.is_empty() {
let ext_problems = unify_pool(subs, pool, rec1.ext, rec2.ext); let ext_problems = unify_pool(subs, pool, rec1.ext, rec2.ext);
if !ext_problems.is_empty() { if !ext_problems.is_empty() {
return ext_problems; return ext_problems;
} }
let other_fields = MutMap::default();
let mut field_problems = let mut field_problems =
unify_shared_fields(subs, pool, ctx, shared_fields, other_fields, rec1.ext); unify_shared_fields(subs, pool, ctx, shared_fields, OtherFields::None, rec1.ext);
field_problems.extend(ext_problems); field_problems.extend(ext_problems);
field_problems field_problems
} else { } else {
let flat_type = FlatType::Record(unique_fields2, rec2.ext); let flat_type = FlatType::Record(separate.only_in_2, rec2.ext);
let sub_record = fresh(subs, pool, ctx, Structure(flat_type)); let sub_record = fresh(subs, pool, ctx, Structure(flat_type));
let ext_problems = unify_pool(subs, pool, rec1.ext, sub_record); let ext_problems = unify_pool(subs, pool, rec1.ext, sub_record);
@ -291,16 +290,21 @@ fn unify_record(
return ext_problems; return ext_problems;
} }
let other_fields = MutMap::default(); let mut field_problems = unify_shared_fields(
let mut field_problems = subs,
unify_shared_fields(subs, pool, ctx, shared_fields, other_fields, sub_record); pool,
ctx,
shared_fields,
OtherFields::None,
sub_record,
);
field_problems.extend(ext_problems); field_problems.extend(ext_problems);
field_problems field_problems
} }
} else if unique_fields2.is_empty() { } else if separate.only_in_2.is_empty() {
let flat_type = FlatType::Record(unique_fields1, rec1.ext); let flat_type = FlatType::Record(separate.only_in_1, rec1.ext);
let sub_record = fresh(subs, pool, ctx, Structure(flat_type)); let sub_record = fresh(subs, pool, ctx, Structure(flat_type));
let ext_problems = unify_pool(subs, pool, sub_record, rec2.ext); let ext_problems = unify_pool(subs, pool, sub_record, rec2.ext);
@ -308,19 +312,29 @@ fn unify_record(
return ext_problems; return ext_problems;
} }
let other_fields = MutMap::default(); let mut field_problems = unify_shared_fields(
let mut field_problems = subs,
unify_shared_fields(subs, pool, ctx, shared_fields, other_fields, sub_record); pool,
ctx,
shared_fields,
OtherFields::None,
sub_record,
);
field_problems.extend(ext_problems); field_problems.extend(ext_problems);
field_problems field_problems
} else { } else {
let other_fields = union(unique_fields1.clone(), &unique_fields2); let it = (&separate.only_in_1)
.into_iter()
.chain((&separate.only_in_2).into_iter());
let other: RecordFields = it.collect();
let other_fields = OtherFields::Other(other);
let ext = fresh(subs, pool, ctx, Content::FlexVar(None)); let ext = fresh(subs, pool, ctx, Content::FlexVar(None));
let flat_type1 = FlatType::Record(unique_fields1, ext); let flat_type1 = FlatType::Record(separate.only_in_1, ext);
let flat_type2 = FlatType::Record(unique_fields2, ext); let flat_type2 = FlatType::Record(separate.only_in_2, ext);
let sub1 = fresh(subs, pool, ctx, Structure(flat_type1)); let sub1 = fresh(subs, pool, ctx, Structure(flat_type1));
let sub2 = fresh(subs, pool, ctx, Structure(flat_type2)); let sub2 = fresh(subs, pool, ctx, Structure(flat_type2));
@ -346,18 +360,23 @@ fn unify_record(
} }
} }
enum OtherFields {
None,
Other(RecordFields),
}
fn unify_shared_fields( fn unify_shared_fields(
subs: &mut Subs, subs: &mut Subs,
pool: &mut Pool, pool: &mut Pool,
ctx: &Context, ctx: &Context,
shared_fields: MutMap<Lowercase, (RecordField<Variable>, RecordField<Variable>)>, shared_fields: Vec<(Lowercase, RecordField<Variable>, RecordField<Variable>)>,
other_fields: MutMap<Lowercase, RecordField<Variable>>, other_fields: OtherFields,
ext: Variable, ext: Variable,
) -> Outcome { ) -> Outcome {
let mut matching_fields = MutMap::default(); let mut matching_fields = Vec::with_capacity(shared_fields.len());
let num_shared_fields = shared_fields.len(); let num_shared_fields = shared_fields.len();
for (name, (actual, expected)) in shared_fields { for (name, actual, expected) in shared_fields {
let local_problems = unify_pool(subs, pool, actual.into_inner(), expected.into_inner()); let local_problems = unify_pool(subs, pool, actual.into_inner(), expected.into_inner());
if local_problems.is_empty() { if local_problems.is_empty() {
@ -383,20 +402,38 @@ fn unify_shared_fields(
(Optional(val), Optional(_)) => Optional(val), (Optional(val), Optional(_)) => Optional(val),
}; };
let existing = matching_fields.insert(name, actual); matching_fields.push((name, actual));
debug_assert_eq!(existing, None);
} }
} }
if num_shared_fields == matching_fields.len() { if num_shared_fields == matching_fields.len() {
// pull fields in from the ext_var // pull fields in from the ext_var
let mut fields = union(matching_fields, &other_fields);
let new_ext_var = match roc_types::pretty_print::chase_ext_record(subs, ext, &mut fields) { let mut ext_fields = MutMap::default();
let new_ext_var =
match roc_types::pretty_print::chase_ext_record(subs, ext, &mut ext_fields) {
Ok(()) => Variable::EMPTY_RECORD, Ok(()) => Variable::EMPTY_RECORD,
Err((new, _)) => new, Err((new, _)) => new,
}; };
let fields: RecordFields = match other_fields {
OtherFields::None => {
if ext_fields.is_empty() {
RecordFields::from_sorted_vec(matching_fields)
} else {
matching_fields
.into_iter()
.chain(ext_fields.into_iter())
.collect()
}
}
OtherFields::Other(other_fields) => matching_fields
.into_iter()
.chain(other_fields.into_iter())
.chain(ext_fields.into_iter())
.collect(),
};
let flat_type = FlatType::Record(fields, new_ext_var); let flat_type = FlatType::Record(fields, new_ext_var);
merge(subs, ctx, Structure(flat_type)) merge(subs, ctx, Structure(flat_type))
@ -405,6 +442,39 @@ fn unify_shared_fields(
} }
} }
struct Separate<K, V> {
only_in_1: MutMap<K, V>,
only_in_2: MutMap<K, V>,
in_both: MutMap<K, (V, V)>,
}
fn separate<K, V>(tags1: MutMap<K, V>, mut tags2: MutMap<K, V>) -> Separate<K, V>
where
K: Ord + std::hash::Hash,
{
let mut only_in_1 = MutMap::with_capacity_and_hasher(tags1.len(), default_hasher());
let max_common = tags1.len().min(tags2.len());
let mut in_both = MutMap::with_capacity_and_hasher(max_common, default_hasher());
for (k, v1) in tags1.into_iter() {
match tags2.remove(&k) {
Some(v2) => {
in_both.insert(k, (v1, v2));
}
None => {
only_in_1.insert(k, v1);
}
}
}
Separate {
only_in_1,
only_in_2: tags2,
in_both,
}
}
fn unify_tag_union( fn unify_tag_union(
subs: &mut Subs, subs: &mut Subs,
pool: &mut Pool, pool: &mut Pool,
@ -415,10 +485,6 @@ fn unify_tag_union(
) -> Outcome { ) -> Outcome {
let tags1 = rec1.tags; let tags1 = rec1.tags;
let tags2 = rec2.tags; let tags2 = rec2.tags;
let shared_tags = get_shared(&tags1, &tags2);
// NOTE: don't use `difference` here. In contrast to Haskell, im's `difference` is symmetric
let unique_tags1 = relative_complement(&tags1, &tags2);
let unique_tags2 = relative_complement(&tags2, &tags1);
let recursion_var = match recursion { let recursion_var = match recursion {
(None, None) => None, (None, None) => None,
@ -426,6 +492,23 @@ fn unify_tag_union(
(Some(v1), Some(_v2)) => Some(v1), (Some(v1), Some(_v2)) => Some(v1),
}; };
// heuristic: our closure defunctionalization scheme generates a bunch of one-tag unions
// also our number types fall in this category too.
if tags1.len() == 1
&& tags2.len() == 1
&& tags1 == tags2
&& subs.get_root_key_without_compacting(rec1.ext)
== subs.get_root_key_without_compacting(rec2.ext)
{
return unify_shared_tags_merge(subs, ctx, tags1, rec1.ext, recursion_var);
}
let Separate {
only_in_1: unique_tags1,
only_in_2: unique_tags2,
in_both: shared_tags,
} = separate(tags1, tags2);
if unique_tags1.is_empty() { if unique_tags1.is_empty() {
if unique_tags2.is_empty() { if unique_tags2.is_empty() {
let ext_problems = unify_pool(subs, pool, rec1.ext, rec2.ext); let ext_problems = unify_pool(subs, pool, rec1.ext, rec2.ext);
@ -439,7 +522,7 @@ fn unify_tag_union(
pool, pool,
ctx, ctx,
shared_tags, shared_tags,
MutMap::default(), OtherTags::Empty,
rec1.ext, rec1.ext,
recursion_var, recursion_var,
); );
@ -461,7 +544,7 @@ fn unify_tag_union(
pool, pool,
ctx, ctx,
shared_tags, shared_tags,
MutMap::default(), OtherTags::Empty,
sub_record, sub_record,
recursion_var, recursion_var,
); );
@ -484,7 +567,7 @@ fn unify_tag_union(
pool, pool,
ctx, ctx,
shared_tags, shared_tags,
MutMap::default(), OtherTags::Empty,
sub_record, sub_record,
recursion_var, recursion_var,
); );
@ -493,7 +576,10 @@ fn unify_tag_union(
tag_problems tag_problems
} else { } else {
let other_tags = union(unique_tags1.clone(), &unique_tags2); let other_tags = OtherTags::Union {
tags1: unique_tags1.clone(),
tags2: unique_tags2.clone(),
};
let ext = fresh(subs, pool, ctx, Content::FlexVar(None)); let ext = fresh(subs, pool, ctx, Content::FlexVar(None));
let flat_type1 = FlatType::TagUnion(unique_tags1, ext); let flat_type1 = FlatType::TagUnion(unique_tags1, ext);
@ -686,8 +772,8 @@ fn unify_tag_union_not_recursive_recursive(
/// into it. /// into it.
#[allow(dead_code)] #[allow(dead_code)]
fn is_structure(var: Variable, subs: &mut Subs) -> bool { fn is_structure(var: Variable, subs: &mut Subs) -> bool {
match subs.get(var).content { match subs.get_content_without_compacting(var) {
Content::Alias(_, _, actual) => is_structure(actual, subs), Content::Alias(_, _, actual) => is_structure(*actual, subs),
Content::Structure(_) => true, Content::Structure(_) => true,
_ => false, _ => false,
} }
@ -772,12 +858,20 @@ fn unify_shared_tags_recursive_not_recursive(
} }
} }
enum OtherTags {
Empty,
Union {
tags1: MutMap<TagName, Vec<Variable>>,
tags2: MutMap<TagName, Vec<Variable>>,
},
}
fn unify_shared_tags( fn unify_shared_tags(
subs: &mut Subs, subs: &mut Subs,
pool: &mut Pool, pool: &mut Pool,
ctx: &Context, ctx: &Context,
shared_tags: MutMap<TagName, (Vec<Variable>, Vec<Variable>)>, shared_tags: MutMap<TagName, (Vec<Variable>, Vec<Variable>)>,
other_tags: MutMap<TagName, Vec<Variable>>, other_tags: OtherTags,
ext: Variable, ext: Variable,
recursion_var: Option<Variable>, recursion_var: Option<Variable>,
) -> Outcome { ) -> Outcome {
@ -829,6 +923,8 @@ fn unify_shared_tags(
} }
if num_shared_tags == matching_tags.len() { if num_shared_tags == matching_tags.len() {
let mut new_tags = matching_tags;
// merge fields from the ext_var into this tag union // merge fields from the ext_var into this tag union
let mut fields = Vec::new(); let mut fields = Vec::new();
let new_ext_var = match roc_types::pretty_print::chase_ext_tag_union(subs, ext, &mut fields) let new_ext_var = match roc_types::pretty_print::chase_ext_tag_union(subs, ext, &mut fields)
@ -836,18 +932,18 @@ fn unify_shared_tags(
Ok(()) => Variable::EMPTY_TAG_UNION, Ok(()) => Variable::EMPTY_TAG_UNION,
Err((new, _)) => new, Err((new, _)) => new,
}; };
let mut new_tags = union(matching_tags, &other_tags);
new_tags.extend(fields.into_iter()); new_tags.extend(fields.into_iter());
let flat_type = if let Some(rec) = recursion_var { match other_tags {
debug_assert!(is_recursion_var(subs, rec)); OtherTags::Empty => {}
FlatType::RecursiveTagUnion(rec, new_tags, new_ext_var) OtherTags::Union { tags1, tags2 } => {
} else { new_tags.reserve(tags1.len() + tags2.len());
FlatType::TagUnion(new_tags, new_ext_var) new_tags.extend(tags1);
}; new_tags.extend(tags2);
}
}
merge(subs, ctx, Structure(flat_type)) unify_shared_tags_merge(subs, ctx, new_tags, new_ext_var, recursion_var)
} else { } else {
mismatch!( mismatch!(
"Problem with Tag Union\nThere should be {:?} matching tags, but I only got \n{:?}", "Problem with Tag Union\nThere should be {:?} matching tags, but I only got \n{:?}",
@ -857,16 +953,21 @@ fn unify_shared_tags(
} }
} }
fn has_only_optional_fields<'a, I, T>(fields: &mut I) -> bool fn unify_shared_tags_merge(
where subs: &mut Subs,
I: Iterator<Item = &'a RecordField<T>>, ctx: &Context,
T: 'a, new_tags: MutMap<TagName, Vec<Variable>>,
{ new_ext_var: Variable,
fields.all(|field| match field { recursion_var: Option<Variable>,
RecordField::Required(_) => false, ) -> Outcome {
RecordField::Demanded(_) => false, let flat_type = if let Some(rec) = recursion_var {
RecordField::Optional(_) => true, debug_assert!(is_recursion_var(subs, rec));
}) FlatType::RecursiveTagUnion(rec, new_tags, new_ext_var)
} else {
FlatType::TagUnion(new_tags, new_ext_var)
};
merge(subs, ctx, Structure(flat_type))
} }
#[inline(always)] #[inline(always)]
@ -882,17 +983,17 @@ fn unify_flat_type(
match (left, right) { match (left, right) {
(EmptyRecord, EmptyRecord) => merge(subs, ctx, Structure(left.clone())), (EmptyRecord, EmptyRecord) => merge(subs, ctx, Structure(left.clone())),
(Record(fields, ext), EmptyRecord) if has_only_optional_fields(&mut fields.values()) => { (Record(fields, ext), EmptyRecord) if fields.has_only_optional_fields() => {
unify_pool(subs, pool, *ext, ctx.second) unify_pool(subs, pool, *ext, ctx.second)
} }
(EmptyRecord, Record(fields, ext)) if has_only_optional_fields(&mut fields.values()) => { (EmptyRecord, Record(fields, ext)) if fields.has_only_optional_fields() => {
unify_pool(subs, pool, ctx.first, *ext) unify_pool(subs, pool, ctx.first, *ext)
} }
(Record(fields1, ext1), Record(fields2, ext2)) => { (Record(fields1, ext1), Record(fields2, ext2)) => {
let rec1 = gather_fields(subs, fields1.clone(), *ext1); let rec1 = gather_fields_ref(subs, fields1, *ext1);
let rec2 = gather_fields(subs, fields2.clone(), *ext2); let rec2 = gather_fields_ref(subs, fields2, *ext2);
unify_record(subs, pool, ctx, rec1, rec2) unify_record(subs, pool, ctx, rec1, rec2)
} }
@ -1014,18 +1115,18 @@ fn unify_flat_type(
if tag_name_1 == tag_name_2 { if tag_name_1 == tag_name_2 {
let problems = unify_pool(subs, pool, *ext_1, *ext_2); let problems = unify_pool(subs, pool, *ext_1, *ext_2);
if problems.is_empty() { if problems.is_empty() {
let desc = subs.get(ctx.second); let content = subs.get_content_without_compacting(ctx.second).clone();
merge(subs, ctx, desc.content) merge(subs, ctx, content)
} else { } else {
problems problems
} }
} else { } else {
let mut tags1 = MutMap::default(); let mut tags1 = MutMap::default();
tags1.insert(tag_name_1.clone(), vec![]); tags1.insert(*tag_name_1.clone(), vec![]);
let union1 = gather_tags(subs, tags1, *ext_1); let union1 = gather_tags(subs, tags1, *ext_1);
let mut tags2 = MutMap::default(); let mut tags2 = MutMap::default();
tags2.insert(tag_name_2.clone(), vec![]); tags2.insert(*tag_name_2.clone(), vec![]);
let union2 = gather_tags(subs, tags2, *ext_2); let union2 = gather_tags(subs, tags2, *ext_2);
unify_tag_union(subs, pool, ctx, union1, union2, (None, None)) unify_tag_union(subs, pool, ctx, union1, union2, (None, None))
@ -1035,14 +1136,14 @@ fn unify_flat_type(
let union1 = gather_tags(subs, tags1.clone(), *ext1); let union1 = gather_tags(subs, tags1.clone(), *ext1);
let mut tags2 = MutMap::default(); let mut tags2 = MutMap::default();
tags2.insert(tag_name.clone(), vec![]); tags2.insert(*tag_name.clone(), vec![]);
let union2 = gather_tags(subs, tags2, *ext2); let union2 = gather_tags(subs, tags2, *ext2);
unify_tag_union(subs, pool, ctx, union1, union2, (None, None)) unify_tag_union(subs, pool, ctx, union1, union2, (None, None))
} }
(FunctionOrTagUnion(tag_name, _, ext1), TagUnion(tags2, ext2)) => { (FunctionOrTagUnion(tag_name, _, ext1), TagUnion(tags2, ext2)) => {
let mut tags1 = MutMap::default(); let mut tags1 = MutMap::default();
tags1.insert(tag_name.clone(), vec![]); tags1.insert(*tag_name.clone(), vec![]);
let union1 = gather_tags(subs, tags1, *ext1); let union1 = gather_tags(subs, tags1, *ext1);
let union2 = gather_tags(subs, tags2.clone(), *ext2); let union2 = gather_tags(subs, tags2.clone(), *ext2);
@ -1055,7 +1156,7 @@ fn unify_flat_type(
debug_assert!(is_recursion_var(subs, *recursion_var)); debug_assert!(is_recursion_var(subs, *recursion_var));
let mut tags2 = MutMap::default(); let mut tags2 = MutMap::default();
tags2.insert(tag_name.clone(), vec![]); tags2.insert(*tag_name.clone(), vec![]);
let union1 = gather_tags(subs, tags1.clone(), *ext1); let union1 = gather_tags(subs, tags1.clone(), *ext1);
let union2 = gather_tags(subs, tags2, *ext2); let union2 = gather_tags(subs, tags2, *ext2);
@ -1074,7 +1175,7 @@ fn unify_flat_type(
debug_assert!(is_recursion_var(subs, *recursion_var)); debug_assert!(is_recursion_var(subs, *recursion_var));
let mut tags1 = MutMap::default(); let mut tags1 = MutMap::default();
tags1.insert(tag_name.clone(), vec![]); tags1.insert(*tag_name.clone(), vec![]);
let union1 = gather_tags(subs, tags1, *ext1); let union1 = gather_tags(subs, tags1, *ext1);
let union2 = gather_tags(subs, tags2.clone(), *ext2); let union2 = gather_tags(subs, tags2.clone(), *ext2);
@ -1237,19 +1338,25 @@ fn fresh(subs: &mut Subs, pool: &mut Pool, ctx: &Context, content: Content) -> V
fn gather_tags( fn gather_tags(
subs: &mut Subs, subs: &mut Subs,
tags: MutMap<TagName, Vec<Variable>>, mut tags: MutMap<TagName, Vec<Variable>>,
var: Variable, var: Variable,
) -> TagUnionStructure { ) -> TagUnionStructure {
use roc_types::subs::Content::*; use roc_types::subs::Content::*;
use roc_types::subs::FlatType::*; use roc_types::subs::FlatType::*;
match subs.get(var).content { match subs.get_content_without_compacting(var) {
Structure(TagUnion(sub_tags, sub_ext)) => { Structure(TagUnion(sub_tags, sub_ext)) => {
gather_tags(subs, union(tags, &sub_tags), sub_ext) for (k, v) in sub_tags {
tags.insert(k.clone(), v.clone());
}
let sub_ext = *sub_ext;
gather_tags(subs, tags, sub_ext)
} }
Alias(_, _, var) => { Alias(_, _, var) => {
// TODO according to elm/compiler: "TODO may be dropping useful alias info here" // TODO according to elm/compiler: "TODO may be dropping useful alias info here"
let var = *var;
gather_tags(subs, tags, var) gather_tags(subs, tags, var)
} }
@ -1259,7 +1366,7 @@ fn gather_tags(
fn is_recursion_var(subs: &Subs, var: Variable) -> bool { fn is_recursion_var(subs: &Subs, var: Variable) -> bool {
matches!( matches!(
subs.get_without_compacting(var).content, subs.get_content_without_compacting(var),
Content::RecursionVar { .. } Content::RecursionVar { .. }
) )
} }

View file

@ -15,6 +15,7 @@ roc_can = { path = "../compiler/can" }
roc_module = { path = "../compiler/module" } roc_module = { path = "../compiler/module" }
roc_region = { path = "../compiler/region" } roc_region = { path = "../compiler/region" }
roc_types = { path = "../compiler/types" } roc_types = { path = "../compiler/types" }
roc_parse = { path = "../compiler/parse" }
roc_collections = { path = "../compiler/collections" } roc_collections = { path = "../compiler/collections" }
bumpalo = { version = "3.2", features = ["collections"] } bumpalo = { version = "3.2", features = ["collections"] }

View file

@ -1,22 +1,24 @@
extern crate pulldown_cmark; extern crate pulldown_cmark;
use roc_builtins::std::StdLib;
use roc_can::builtins::builtin_defs_map;
use roc_load::docs::{DocEntry, TypeAnnotation};
use roc_load::docs::{ModuleDocumentation, RecordField};
use roc_load::file::{LoadedModule, LoadingProblem};
use roc_module::symbol::Interns;
use std::fs;
extern crate roc_load; extern crate roc_load;
use bumpalo::Bump; use bumpalo::Bump;
use roc_builtins::std::StdLib;
use roc_can::builtins::builtin_defs_map;
use roc_can::scope::Scope; use roc_can::scope::Scope;
use roc_collections::all::MutMap; use roc_collections::all::MutMap;
use roc_load::docs::DocEntry::DocDef; use roc_load::docs::DocEntry::DocDef;
use roc_load::docs::{DocEntry, TypeAnnotation};
use roc_load::docs::{ModuleDocumentation, RecordField};
use roc_load::file::{LoadedModule, LoadingProblem};
use roc_module::symbol::{IdentIds, Interns, ModuleId};
use roc_parse::ident::{parse_ident, Ident};
use roc_parse::parser::State;
use roc_region::all::Region; use roc_region::all::Region;
use std::fs;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
pub fn generate(filenames: Vec<PathBuf>, std_lib: StdLib, build_dir: &Path) { pub fn generate(filenames: Vec<PathBuf>, std_lib: StdLib, build_dir: &Path) {
let files_docs = files_to_documentations(filenames, std_lib); let files_docs = files_to_documentations(filenames, std_lib);
let mut arena = Bump::new();
// //
// TODO: get info from a file like "elm.json" // TODO: get info from a file like "elm.json"
@ -52,22 +54,33 @@ pub fn generate(filenames: Vec<PathBuf>, std_lib: StdLib, build_dir: &Path) {
let template_html = include_str!("./static/index.html").replace( let template_html = include_str!("./static/index.html").replace(
"<!-- Module links -->", "<!-- Module links -->",
render_sidebar( render_sidebar(package.modules.iter().flat_map(|loaded_module| {
package loaded_module.documentation.values().map(move |d| {
.modules let exposed_values = loaded_module
.exposed_values
.iter() .iter()
.flat_map(|loaded_module| loaded_module.documentation.values()), .map(|symbol| symbol.ident_string(&loaded_module.interns).to_string())
) .collect::<Vec<String>>();
(exposed_values, d)
})
}))
.as_str(), .as_str(),
); );
// Write each package's module docs html file // Write each package's module docs html file
for loaded_module in package.modules.iter_mut() { for loaded_module in package.modules.iter_mut() {
let exports = loaded_module arena.reset();
.exposed_values
.iter() let mut exports: bumpalo::collections::Vec<&str> =
.map(|symbol| symbol.ident_string(&loaded_module.interns).to_string()) bumpalo::collections::Vec::with_capacity_in(loaded_module.exposed_values.len(), &arena);
.collect::<Vec<String>>();
// TODO should this also include exposed_aliases?
for symbol in loaded_module.exposed_values.iter() {
exports.push(symbol.ident_string(&loaded_module.interns));
}
let exports = exports.into_bump_slice();
for module in loaded_module.documentation.values_mut() { for module in loaded_module.documentation.values_mut() {
let module_dir = build_dir.join(module.name.replace(".", "/").as_str()); let module_dir = build_dir.join(module.name.replace(".", "/").as_str());
@ -83,7 +96,14 @@ pub fn generate(filenames: Vec<PathBuf>, std_lib: StdLib, build_dir: &Path) {
) )
.replace( .replace(
"<!-- Module Docs -->", "<!-- Module Docs -->",
render_main_content(&loaded_module.interns, &exports, module).as_str(), render_main_content(
loaded_module.module_id,
exports,
&loaded_module.dep_idents,
&loaded_module.interns,
module,
)
.as_str(),
); );
fs::write(module_dir.join("index.html"), rendered_module) fs::write(module_dir.join("index.html"), rendered_module)
@ -95,8 +115,10 @@ pub fn generate(filenames: Vec<PathBuf>, std_lib: StdLib, build_dir: &Path) {
} }
fn render_main_content( fn render_main_content(
home: ModuleId,
exposed_values: &[&str],
dep_idents: &MutMap<ModuleId, IdentIds>,
interns: &Interns, interns: &Interns,
exposed_values: &[String],
module: &mut ModuleDocumentation, module: &mut ModuleDocumentation,
) -> String { ) -> String {
let mut buf = String::new(); let mut buf = String::new();
@ -115,7 +137,7 @@ fn render_main_content(
if let DocDef(def) = entry { if let DocDef(def) = entry {
// We dont want to render entries that arent exposed // We dont want to render entries that arent exposed
should_render_entry = exposed_values.contains(&def.name); should_render_entry = exposed_values.contains(&def.name.as_str());
} }
if should_render_entry { if should_render_entry {
@ -145,7 +167,7 @@ fn render_main_content(
} }
} }
type_annotation_to_html(0, &mut content, &type_ann); type_annotation_to_html(0, &mut content, type_ann);
buf.push_str( buf.push_str(
html_node( html_node(
@ -158,12 +180,27 @@ fn render_main_content(
if let Some(docs) = &doc_def.docs { if let Some(docs) = &doc_def.docs {
buf.push_str( buf.push_str(
markdown_to_html(&mut module.scope, interns, docs.to_string()).as_str(), markdown_to_html(
home,
exposed_values,
dep_idents,
&mut module.scope,
interns,
docs.to_string(),
)
.as_str(),
); );
} }
} }
DocEntry::DetachedDoc(docs) => { DocEntry::DetachedDoc(docs) => {
let markdown = markdown_to_html(&mut module.scope, interns, docs.to_string()); let markdown = markdown_to_html(
home,
exposed_values,
dep_idents,
&mut module.scope,
interns,
docs.to_string(),
);
buf.push_str(markdown.as_str()); buf.push_str(markdown.as_str());
} }
}; };
@ -218,17 +255,17 @@ fn render_name_and_version(name: &str, version: &str) -> String {
.as_str(), .as_str(),
); );
let mut verions_href = String::new(); let mut versions_href = String::new();
verions_href.push('/'); versions_href.push('/');
verions_href.push_str(name); versions_href.push_str(name);
verions_href.push('/'); versions_href.push('/');
verions_href.push_str(version); versions_href.push_str(version);
buf.push_str( buf.push_str(
html_node( html_node(
"a", "a",
vec![("class", "version"), ("href", verions_href.as_str())], vec![("class", "version"), ("href", versions_href.as_str())],
version, version,
) )
.as_str(), .as_str(),
@ -237,10 +274,12 @@ fn render_name_and_version(name: &str, version: &str) -> String {
buf buf
} }
fn render_sidebar<'a, I: Iterator<Item = &'a ModuleDocumentation>>(modules: I) -> String { fn render_sidebar<'a, I: Iterator<Item = (Vec<String>, &'a ModuleDocumentation)>>(
modules: I,
) -> String {
let mut buf = String::new(); let mut buf = String::new();
for module in modules { for (exposed_values, module) in modules {
let mut sidebar_entry_content = String::new(); let mut sidebar_entry_content = String::new();
let name = module.name.as_str(); let name = module.name.as_str();
@ -266,6 +305,7 @@ fn render_sidebar<'a, I: Iterator<Item = &'a ModuleDocumentation>>(modules: I) -
for entry in &module.entries { for entry in &module.entries {
if let DocEntry::DocDef(doc_def) = entry { if let DocEntry::DocDef(doc_def) = entry {
if exposed_values.contains(&doc_def.name) {
let mut entry_href = String::new(); let mut entry_href = String::new();
entry_href.push_str(href.as_str()); entry_href.push_str(href.as_str());
@ -282,6 +322,7 @@ fn render_sidebar<'a, I: Iterator<Item = &'a ModuleDocumentation>>(modules: I) -
); );
} }
} }
}
entries_buf entries_buf
}; };
@ -541,7 +582,7 @@ fn should_be_multiline(type_ann: &TypeAnnotation) -> bool {
if is_multiline { if is_multiline {
break; break;
} }
is_multiline = should_be_multiline(&value); is_multiline = should_be_multiline(value);
} }
} }
@ -601,106 +642,180 @@ fn should_be_multiline(type_ann: &TypeAnnotation) -> bool {
} }
} }
pub fn insert_doc_links(scope: &mut Scope, interns: &Interns, markdown: String) -> String { struct DocUrl {
let buf = &markdown; url: String,
let mut result = String::new(); title: String,
let mut chomping_from: Option<usize> = None;
let mut chars = buf.chars().enumerate().peekable();
while let Some((index, char)) = chars.next() {
match chomping_from {
None => {
let next_is_alphabetic = match chars.peek() {
None => false,
Some((_, next_char)) => next_char.is_alphabetic(),
};
if char == '#' && next_is_alphabetic {
chomping_from = Some(index);
}
}
Some(from) => {
if !(char.is_alphabetic() || char == '.') {
let after_link = buf.chars().skip(from + buf.len());
result = buf.chars().take(from).collect();
let doc_link = make_doc_link(
scope,
interns,
&buf.chars()
.skip(from + 1)
.take(index - from - 1)
.collect::<String>(),
);
result.insert_str(from, doc_link.as_str());
let remainder = insert_doc_links(scope, interns, after_link.collect());
result.push_str(remainder.as_str());
break;
}
}
}
}
if chomping_from == None {
markdown
} else {
result
}
} }
fn make_doc_link(scope: &mut Scope, interns: &Interns, doc_item: &str) -> String { fn doc_url<'a>(
match scope.lookup(&doc_item.into(), Region::zero()) { home: ModuleId,
exposed_values: &[&str],
dep_idents: &MutMap<ModuleId, IdentIds>,
scope: &mut Scope,
interns: &'a Interns,
mut module_name: &'a str,
ident: &str,
) -> DocUrl {
if module_name.is_empty() {
// This is an unqualified lookup, so look for the ident
// in scope!
match scope.lookup(&ident.into(), Region::zero()) {
Ok(symbol) => { Ok(symbol) => {
let module_str = symbol.module_string(interns); // Get the exact module_name from scope. It could be the
// current module's name, but it also could be a different
let ident_str = symbol.ident_string(interns); // module - for example, if this is in scope from an
// unqualified import.
let mut link = String::new(); module_name = symbol.module_string(interns);
link.push('/');
link.push_str(module_str);
link.push('#');
link.push_str(ident_str);
let mut buf = String::new();
buf.push('[');
buf.push_str(doc_item);
buf.push_str("](");
buf.push_str(link.as_str());
buf.push(')');
buf
} }
Err(_) => { Err(_) => {
// TODO return Err here
panic!( panic!(
"Tried to generate an automatic link in docs for symbol `{}`, but that symbol was not in scope in this module. Scope was: {:?}", "Tried to generate an automatic link in docs for symbol `{}`, but that symbol was not in scope in this module. Scope was: {:?}",
doc_item, scope ident, scope
) );
} }
} }
} else {
match interns.module_ids.get_id(&module_name.into()) {
Some(&module_id) => {
// You can do qualified lookups on your own module, e.g.
// if I'm in the Foo module, I can do a `Foo.bar` lookup.
if module_id == home {
// Check to see if the value is exposed in this module.
// If it's not exposed, then we can't link to it!
if !exposed_values.contains(&ident) {
// TODO return Err here
panic!(
"Tried to generate an automatic link in docs for `{}.{}`, but `{}` is not declared in `{}`.",
module_name, ident, ident, module_name);
}
} else {
// This is not the home module
match dep_idents
.get(&module_id)
.and_then(|exposed_ids| exposed_ids.get_id(&ident.into()))
{
Some(_) => {
// This is a valid symbol for this dependency,
// so proceed using the current module's name.
//
// TODO: In the future, this is where we'll
// incorporate the package name into the link.
}
_ => {
// TODO return Err here
panic!(
"Tried to generate an automatic link in docs for `{}.{}`, but `{}` is not exposed in `{}`.",
module_name, ident, ident, module_name);
}
}
}
}
None => {
// TODO return Err here
panic!("Tried to generate a doc link for `{}.{}` but the `{}` module was not imported!", module_name, ident, module_name);
}
}
}
let mut url = String::new();
// Example:
//
// module_name: "Str", ident: "join" => "/Str#join"
url.push('/');
url.push_str(module_name);
url.push('#');
url.push_str(ident);
DocUrl {
url,
title: format!("Docs for {}.{}", module_name, ident),
}
} }
fn markdown_to_html(scope: &mut Scope, interns: &Interns, markdown: String) -> String { fn markdown_to_html(
use pulldown_cmark::CodeBlockKind; home: ModuleId,
use pulldown_cmark::CowStr; exposed_values: &[&str],
use pulldown_cmark::Event; dep_idents: &MutMap<ModuleId, IdentIds>,
use pulldown_cmark::Tag::*; scope: &mut Scope,
interns: &Interns,
markdown: String,
) -> String {
use pulldown_cmark::{BrokenLink, CodeBlockKind, CowStr, Event, LinkType, Tag::*};
let markdown_with_links = insert_doc_links(scope, interns, markdown); let mut arena = Bump::new();
let mut broken_link_callback = |link: BrokenLink| {
// A shortcut link - see https://spec.commonmark.org/0.30/#shortcut-reference-link -
// is something like `[foo]` in markdown. If you have a shortcut link
// without a corresponding `[foo]: https://foo.com` entry
// at the end of the document, we resolve it as an identifier based on
// what's currently in scope, so you write things like [Str.join] or
// [myFunction] and have them resolve to the docs for what you wrote.
match link.link_type {
LinkType::Shortcut => {
let state = State::new(link.reference.as_bytes());
// Reset the bump arena so we aren't constantly reallocating
// more memory.
arena.reset();
match parse_ident(&arena, state) {
Ok((_, Ident::Access { module_name, parts }, _)) => {
let mut iter = parts.iter();
match iter.next() {
Some(symbol_name) if iter.next().is_none() => {
let DocUrl { url, title } = doc_url(
home,
exposed_values,
dep_idents,
scope,
interns,
module_name,
symbol_name,
);
Some((url.into(), title.into()))
}
_ => {
// This had record field access,
// e.g. [foo.bar] - which we
// can't create a doc link to!
None
}
}
}
Ok((_, Ident::GlobalTag(type_name), _)) => {
// This looks like a global tag name, but it could
// be a type alias that's in scope, e.g. [I64]
let DocUrl { url, title } = doc_url(
home,
exposed_values,
dep_idents,
scope,
interns,
"",
type_name,
);
Some((url.into(), title.into()))
}
_ => None,
}
}
_ => None,
}
};
let markdown_options = pulldown_cmark::Options::empty(); let markdown_options = pulldown_cmark::Options::empty();
let mut docs_parser = vec![]; let mut docs_parser = vec![];
let (_, _) = pulldown_cmark::Parser::new_ext(&markdown_with_links, markdown_options).fold( let (_, _) = pulldown_cmark::Parser::new_with_broken_link_callback(
(0, 0), &markdown,
|(start_quote_count, end_quote_count), event| { markdown_options,
Some(&mut broken_link_callback),
)
.fold((0, 0), |(start_quote_count, end_quote_count), event| {
match event { match event {
// Replace this sequence (`>>>` syntax): // Replace this sequence (`>>>` syntax):
// Start(BlockQuote) // Start(BlockQuote)
@ -754,8 +869,7 @@ fn markdown_to_html(scope: &mut Scope, interns: &Interns, markdown: String) -> S
(0, 0) (0, 0)
} }
} }
}, });
);
let mut docs_html = String::new(); let mut docs_html = String::new();

View file

@ -1,26 +0,0 @@
interface Test
exposes [ singleline, multiline, multiparagraph, codeblock ]
imports []
## This is a block
Block elem : [ Block elem ]
## Single line documentation.
singleline : Bool -> Bool
## Multiline documentation.
## Without any complex syntax yet!
multiline : Bool -> Bool
## Multiparagraph documentation.
##
## Without any complex syntax yet!
multiparagraph : Bool -> Bool
## No documentation for not exposed function.
notExposed : Bool -> Bool
## Turns >>> into code block for now.
##
## >>> codeblock
codeblock : Bool -> Bool

View file

@ -1,43 +0,0 @@
#[macro_use]
extern crate pretty_assertions;
#[cfg(test)]
mod insert_doc_links {
use roc_can::env::Env;
use roc_can::scope::Scope;
use roc_collections::all::MutMap;
use roc_docs::insert_doc_links;
use roc_module::symbol::{IdentIds, Interns, ModuleIds};
use roc_types::subs::VarStore;
#[test]
fn no_doc_links() {
let home = ModuleIds::default().get_or_insert(&"Test".into());
let module_ids = ModuleIds::default();
let dep_idents = IdentIds::exposed_builtins(0);
let env = Env::new(home, dep_idents, &module_ids, IdentIds::default());
let all_ident_ids = MutMap::default();
let interns = Interns {
module_ids: env.module_ids.clone(),
all_ident_ids,
};
let var_store = &mut VarStore::default();
let scope = &mut Scope::new(home, var_store);
let markdown = r#"
# Hello
Hello thanks for using my package
"#;
assert_eq!(
markdown,
insert_doc_links(scope, &interns, markdown.to_string()),
);
}
}

View file

@ -231,6 +231,12 @@ e.g. you have a test `calculate_sum_test` that only uses the function `add`, whe
- [Boop](https://github.com/IvanMathy/Boop) scriptable scratchpad for developers. Contains collection of useful conversions: json formatting, url encoding, encode to base64... - [Boop](https://github.com/IvanMathy/Boop) scriptable scratchpad for developers. Contains collection of useful conversions: json formatting, url encoding, encode to base64...
## High performance
### Inspiration
- [10x editor](http://www.10xeditor.com/) IDE/Editor targeted at the professional developer with an emphasis on performance and scalability.
## General Thoughts/Ideas ## General Thoughts/Ideas
Thoughts and ideas possibly taken from above inspirations or separate. Thoughts and ideas possibly taken from above inspirations or separate.

94
editor/snippet-ideas.md Normal file
View file

@ -0,0 +1,94 @@
I think snippet insertion would make for an awesome demo that shows off the potential of the editor and a basic version would not be that difficult to implement.
With snippet insertion I mean the following:
say you have a list of Person records, people in scope
you press some keyboard shortcut
a text field pops up
you enter "sort"
we show autocomplete options like sort people by firstName, sort people by lastName and sort people by age. The recommendations can be that good because we know people is the only list in scope and we know which fields are in the Person record.
you navigate to sort people by age, press enter and we show in the autocomplete popup: sort people by age descending and sort people by age ascending.
you navigate to sort people by age ascending and press Enter
The correct Roc code is inserted
This is most useful for beginning Roc programmers, but I could see it saving time for experts as well.
Novice to expert programmers who are new to Roc can also perfectly describe what they want to happen but may not know the correct syntax, names of builtin functions...
Other useful snippet commands for beginning Roc programmers might be empty dict, lambda function, split strVal into chars...
Some more advanced snippets: post jsonVal to urlVal, connect to amazon S3, insert function of common algorithm like: sieve of erathostenes, greatest common divider...
This could remove the need for a lot of googling/stackoverflow, creating a delightful experience that sets us apart from other editors.
And contrary to stackoverflow/github copilot, snippets will be written by Roc experts or be easily editable by us. They can also be guaranteed to work for a specific Roc and library version because we update, version, and test them.
A nice goal to aim for is that the user never needs/wants to leave the editor to look things up.
We have way more context inside the editor so we should be able to do better than any general-purpose search engine.
I think the snippet insertion commands also set us up for quality interaction with users using voice input.
The CC0 license seems like a good fit for the snippets.
Fuzzy matching should be done to suggest a closest fuzzy match, so if the user types the snippet command `empty Map`, we should suggest `empty Dict`.
# Snippet ideas
## Pure Text Snippets
Pure text snippets are not templates and do not contain typed holes.
Fish hooks are used when subvariants should be created e.g.: <collection> means this pure text snippets should be created for all Roc collections such as Dict, Set, List...
- command: empty <collection>
+ example: empty dict >> `{::}`
- command: <common algorithm>
+ example: sieve of erathostenes >> `inserts function for sieve of erathostenes`
+ common algorithms: sieve of erathostenes, greatest common divisior, prime factorisation, A* path finding, Dijkstra's algorithm, Breadth First Search...
- command: current date/datetime
+ example: current datetime >> `now <- Time.now\n`
- command: list range 1 to 5
+ example: >> [ 1, 2, 3, 4, 5 ]
## AST aware snippets
Snippets are inserted based on type of value on which the cursor is located.
- command: <all builtins for current type>
+ example:
* We have the cursor like this `people|`
* User presses snippet shortcut or dot key
* We show a list with all builtin functions for the List type
* User chooses contains
* We change code to `List.contains people |Blank`
- command: Str to charlist
## Snippets with Typed Holes
- command: sort ^List *^ (by ^Record Field^) {ascending/descending}
+ example: sort people by age descending >> ...
- command: escape url
+ example: >> `percEncodedString = Url.percentEncode ^String^`
- command: list files in directory
+ example: >>
```
path <- File.pathFromStr ^String^
dirContents <- File.enumerateDir path
```
- command: remove/create file
- command: read/write from file
- command: concatenate strings
- we should auto create type hole commands for all builtins.
+ example: List has builtins reverse, repeat, len... generated snippet commands should be:
* reverse list > List.reverse ^List *^
* repeat list > List.repeat ^elem^ ^Nat^
* len list (fuzzy matches should be length of list)
# fuzzy matching
some pairs for fuzzy matching unit tests:
- hashmap > Dict
- map > map (function), Dict
- for > map, mapWithIndex, walk, walkBackwards, zip
- apply > map
- fold > walk, walkBackwards
- foldl > walkBackwards
- foldr > walk
- head > takeFirst
- filter > keepIf

View file

@ -66,7 +66,7 @@ impl Lines for CodeLines {
fn get_line(&self, line_nr: usize) -> UIResult<&str> { fn get_line(&self, line_nr: usize) -> UIResult<&str> {
let line_string = slice_get(line_nr, &self.lines)?; let line_string = slice_get(line_nr, &self.lines)?;
Ok(&line_string) Ok(line_string)
} }
fn line_len(&self, line_nr: usize) -> UIResult<usize> { fn line_len(&self, line_nr: usize) -> UIResult<usize> {
@ -85,7 +85,7 @@ impl Lines for CodeLines {
let mut lines = BumpString::with_capacity_in(self.nr_of_chars(), arena); let mut lines = BumpString::with_capacity_in(self.nr_of_chars(), arena);
for line in &self.lines { for line in &self.lines {
lines.push_str(&line); lines.push_str(line);
} }
lines lines

View file

@ -194,7 +194,7 @@ fn color_backtrace(backtrace: &snafu::Backtrace) -> String {
for line in backtrace_split { for line in backtrace_split {
let new_line = if line.contains("src") { let new_line = if line.contains("src") {
if !contains_one_of(&line, &irrelevant_src) { if !contains_one_of(line, &irrelevant_src) {
if let Some(prev_line) = prev_line_opt { if let Some(prev_line) = prev_line_opt {
prev_line_opt = Some(format!("{}", prev_line.truecolor(255, 30, 30))); prev_line_opt = Some(format!("{}", prev_line.truecolor(255, 30, 30)));
} }

View file

@ -129,7 +129,7 @@ impl MarkupNode {
} }
} }
let closest_ast_child = slice_get(best_index, &children_ids)?; let closest_ast_child = slice_get(best_index, children_ids)?;
let closest_ast_child_index = let closest_ast_child_index =
index_of(*closest_ast_child, &child_ids_with_ast)?; index_of(*closest_ast_child, &child_ids_with_ast)?;
@ -259,7 +259,7 @@ pub fn expr2_to_markup<'a, 'b>(
| Expr2::I128 { text, .. } | Expr2::I128 { text, .. }
| Expr2::U128 { text, .. } | Expr2::U128 { text, .. }
| Expr2::Float { text, .. } => { | Expr2::Float { text, .. } => {
let num_str = get_string(env, &text); let num_str = get_string(env, text);
new_markup_node( new_markup_node(
num_str, num_str,
@ -275,7 +275,7 @@ pub fn expr2_to_markup<'a, 'b>(
markup_node_pool, markup_node_pool,
), ),
Expr2::GlobalTag { name, .. } => new_markup_node( Expr2::GlobalTag { name, .. } => new_markup_node(
get_string(env, &name), get_string(env, name),
expr2_node_id, expr2_node_id,
HighlightStyle::Type, HighlightStyle::Type,
markup_node_pool, markup_node_pool,

View file

@ -59,7 +59,7 @@ pub fn init_model<'a>(
interns: &'a Interns, interns: &'a Interns,
code_arena: &'a Bump, code_arena: &'a Bump,
) -> EdResult<EdModel<'a>> { ) -> EdResult<EdModel<'a>> {
let mut module = EdModule::new(&code_str, env, code_arena)?; let mut module = EdModule::new(code_str, env, code_arena)?;
let ast_root_id = module.ast_root_id; let ast_root_id = module.ast_root_id;
let mut markup_node_pool = SlowPool::new(); let mut markup_node_pool = SlowPool::new();
@ -175,7 +175,7 @@ impl<'a> EdModule<'a> {
let region = Region::new(0, 0, 0, 0); let region = Region::new(0, 0, 0, 0);
let expr2_result = str_to_expr2(&ast_arena, &code_str, &mut env, &mut scope, region); let expr2_result = str_to_expr2(ast_arena, code_str, &mut env, &mut scope, region);
match expr2_result { match expr2_result {
Ok((expr2, _output)) => { Ok((expr2, _output)) => {
@ -239,7 +239,7 @@ pub mod test_ed_model {
); );
ed_model::init_model( ed_model::init_model(
&code_str, code_str,
file_path, file_path,
env, env,
&ed_model_refs.interns, &ed_model_refs.interns,

View file

@ -221,7 +221,7 @@ impl<'a> EdModel<'a> {
if self.grid_node_map.node_exists_at_pos(caret_pos) { if self.grid_node_map.node_exists_at_pos(caret_pos) {
let (expr_start_pos, expr_end_pos, ast_node_id, mark_node_id) = self let (expr_start_pos, expr_end_pos, ast_node_id, mark_node_id) = self
.grid_node_map .grid_node_map
.get_expr_start_end_pos(self.get_caret(), &self)?; .get_expr_start_end_pos(self.get_caret(), self)?;
self.set_selected_expr(expr_start_pos, expr_end_pos, ast_node_id, mark_node_id)?; self.set_selected_expr(expr_start_pos, expr_end_pos, ast_node_id, mark_node_id)?;
} else if self } else if self
@ -230,7 +230,7 @@ impl<'a> EdModel<'a> {
{ {
let (expr_start_pos, expr_end_pos, ast_node_id, mark_node_id) = self let (expr_start_pos, expr_end_pos, ast_node_id, mark_node_id) = self
.grid_node_map .grid_node_map
.get_expr_start_end_pos(self.get_caret().decrement_col(), &self)?; .get_expr_start_end_pos(self.get_caret().decrement_col(), self)?;
self.set_selected_expr(expr_start_pos, expr_end_pos, ast_node_id, mark_node_id)?; self.set_selected_expr(expr_start_pos, expr_end_pos, ast_node_id, mark_node_id)?;
} }
@ -247,7 +247,7 @@ impl<'a> EdModel<'a> {
let constrained = constrain_expr( let constrained = constrain_expr(
&arena, &arena,
&mut self.module.env, &mut self.module.env,
&expr, expr,
Expected::NoExpectation(Type2::Variable(var)), Expected::NoExpectation(Type2::Variable(var)),
Region::zero(), Region::zero(),
); );
@ -272,10 +272,10 @@ impl<'a> EdModel<'a> {
let subs = solved.inner_mut(); let subs = solved.inner_mut();
let content = subs.get(var).content; let content = subs.get_content_without_compacting(var);
PoolStr::new( PoolStr::new(
&content_to_string(content, &subs, self.module.env.home, self.interns), &content_to_string(content, subs, self.module.env.home, self.interns),
self.module.env.pool, self.module.env.pool,
) )
} }
@ -605,7 +605,7 @@ pub fn handle_new_char(received_char: &char, ed_model: &mut EdModel) -> EdResult
} }
Expr2::SmallStr(old_arr_str) => { Expr2::SmallStr(old_arr_str) => {
update_small_string( update_small_string(
&ch, old_arr_str, ed_model ch, old_arr_str, ed_model
)? )?
} }
Expr2::Str(old_pool_str) => { Expr2::Str(old_pool_str) => {
@ -2043,11 +2043,11 @@ pub mod test_ed_update {
expected_tooltip: &str, expected_tooltip: &str,
new_char: char, new_char: char,
) -> Result<(), String> { ) -> Result<(), String> {
assert_type_tooltips_seq(pre_lines, &vec![expected_tooltip], &new_char.to_string()) assert_type_tooltips_seq(pre_lines, &[expected_tooltip], &new_char.to_string())
} }
pub fn assert_type_tooltip_clean(lines: &[&str], expected_tooltip: &str) -> Result<(), String> { pub fn assert_type_tooltip_clean(lines: &[&str], expected_tooltip: &str) -> Result<(), String> {
assert_type_tooltips_seq(lines, &vec![expected_tooltip], "") assert_type_tooltips_seq(lines, &[expected_tooltip], "")
} }
// When doing ctrl+shift+up multiple times we select the surrounding expression every time, // When doing ctrl+shift+up multiple times we select the surrounding expression every time,

View file

@ -22,7 +22,7 @@ pub fn start_new_int(ed_model: &mut EdModel, digit_char: &char) -> EdResult<Inpu
curr_mark_node, curr_mark_node,
parent_id_opt, parent_id_opt,
ast_node_id, ast_node_id,
} = get_node_context(&ed_model)?; } = get_node_context(ed_model)?;
let is_blank_node = curr_mark_node.is_blank(); let is_blank_node = curr_mark_node.is_blank();

View file

@ -21,7 +21,7 @@ pub fn start_new_list(ed_model: &mut EdModel) -> EdResult<InputOutcome> {
curr_mark_node, curr_mark_node,
parent_id_opt, parent_id_opt,
ast_node_id, ast_node_id,
} = get_node_context(&ed_model)?; } = get_node_context(ed_model)?;
let is_blank_node = curr_mark_node.is_blank(); let is_blank_node = curr_mark_node.is_blank();
@ -101,7 +101,7 @@ pub fn add_blank_child(
curr_mark_node: _, curr_mark_node: _,
parent_id_opt, parent_id_opt,
ast_node_id, ast_node_id,
} = get_node_context(&ed_model)?; } = get_node_context(ed_model)?;
let trip_result: EdResult<(ExprId, ExprId, MarkNodeId)> = if let Some(parent_id) = parent_id_opt let trip_result: EdResult<(ExprId, ExprId, MarkNodeId)> = if let Some(parent_id) = parent_id_opt
{ {

View file

@ -24,7 +24,7 @@ pub fn start_new_record(ed_model: &mut EdModel) -> EdResult<InputOutcome> {
curr_mark_node, curr_mark_node,
parent_id_opt, parent_id_opt,
ast_node_id, ast_node_id,
} = get_node_context(&ed_model)?; } = get_node_context(ed_model)?;
let is_blank_node = curr_mark_node.is_blank(); let is_blank_node = curr_mark_node.is_blank();
@ -109,7 +109,7 @@ pub fn update_empty_record(
curr_mark_node, curr_mark_node,
parent_id_opt, parent_id_opt,
ast_node_id, ast_node_id,
} = get_node_context(&ed_model)?; } = get_node_context(ed_model)?;
if prev_mark_node.get_content()? == nodes::LEFT_ACCOLADE if prev_mark_node.get_content()? == nodes::LEFT_ACCOLADE
&& curr_mark_node.get_content()? == nodes::RIGHT_ACCOLADE && curr_mark_node.get_content()? == nodes::RIGHT_ACCOLADE
@ -182,7 +182,7 @@ pub fn update_record_colon(
curr_mark_node, curr_mark_node,
parent_id_opt, parent_id_opt,
ast_node_id, ast_node_id,
} = get_node_context(&ed_model)?; } = get_node_context(ed_model)?;
if let Some(parent_id) = parent_id_opt { if let Some(parent_id) = parent_id_opt {
let curr_ast_node = ed_model.module.env.pool.get(ast_node_id); let curr_ast_node = ed_model.module.env.pool.get(ast_node_id);

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