diff --git a/Cargo.lock b/Cargo.lock index f0b1a3fe97..4a3a6f031a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,7 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +version = 3 + [[package]] name = "ab_glyph" version = "0.2.11" @@ -3138,6 +3140,7 @@ dependencies = [ "roc_collections", "roc_load", "roc_module", + "roc_parse", "roc_region", "roc_types", ] @@ -3356,6 +3359,7 @@ dependencies = [ "roc_types", "roc_unify", "ven_ena", + "ven_graph", "ven_pretty", ] diff --git a/CodeOfConduct.md b/CodeOfConduct.md index d3a4ef15dd..aa99bba432 100644 --- a/CodeOfConduct.md +++ b/CodeOfConduct.md @@ -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. -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. diff --git a/Earthfile b/Earthfile index bd936d74d1..645aaa7159 100644 --- a/Earthfile +++ b/Earthfile @@ -1,4 +1,4 @@ -FROM rust:1.53-slim-buster +FROM rust:1.54-slim-buster WORKDIR /earthbuild prep-debian: @@ -101,7 +101,7 @@ check-rustfmt: RUN cargo fmt --all -- --check 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 ./ RUN typos diff --git a/cli/cli_utils/src/bench_utils.rs b/cli/cli_utils/src/bench_utils.rs index 59bcbe485a..65fec7d760 100644 --- a/cli/cli_utils/src/bench_utils.rs +++ b/cli/cli_utils/src/bench_utils.rs @@ -36,7 +36,7 @@ fn check_cmd_output( ) { let out = run_cmd( file.with_file_name(executable_filename).to_str().unwrap(), - stdin_str, + &[stdin_str], &[], ); @@ -60,7 +60,7 @@ fn bench_cmd( b.iter(|| { run_cmd( 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( } else { run_cmd( black_box(file.with_file_name(executable_filename).to_str().unwrap()), - black_box(stdin_str), + black_box(&[stdin_str]), &[], ); } diff --git a/cli/cli_utils/src/helpers.rs b/cli/cli_utils/src/helpers.rs index 6f45d89a68..e5beddfecb 100644 --- a/cli/cli_utils/src/helpers.rs +++ b/cli/cli_utils/src/helpers.rs @@ -65,7 +65,7 @@ pub fn run_roc(args: &[&str]) -> Out { } #[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); for arg in args { @@ -81,9 +81,12 @@ pub fn run_cmd(cmd_name: &str, stdin_str: &str, args: &[&str]) -> Out { { let stdin = child.stdin.as_mut().expect("Failed to open stdin"); - stdin - .write_all(stdin_str.as_bytes()) - .expect("Failed to write to stdin"); + + for stdin_str in stdin_vals { + stdin + .write_all(stdin_str.as_bytes()) + .expect("Failed to write to stdin"); + } } let output = child @@ -98,7 +101,7 @@ pub fn run_cmd(cmd_name: &str, stdin_str: &str, args: &[&str]) -> Out { } #[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. let mut cmd = Command::new("valgrind"); let named_tempfile = @@ -142,9 +145,12 @@ pub fn run_with_valgrind(stdin_str: &str, args: &[&str]) -> (Out, String) { { let stdin = child.stdin.as_mut().expect("Failed to open stdin"); - stdin - .write_all(stdin_str.as_bytes()) - .expect("Failed to write to stdin"); + + for stdin_str in stdin_vals { + stdin + .write_all(stdin_str.as_bytes()) + .expect("Failed to write to stdin"); + } } let output = child @@ -228,7 +234,7 @@ pub fn extract_valgrind_errors(xml: &str) -> Result, serde_xm } #[allow(dead_code)] -pub fn example_dir(dir_name: &str) -> PathBuf { +pub fn root_dir() -> PathBuf { let mut path = env::current_exe().ok().unwrap(); // 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 +} + +#[allow(dead_code)] +pub fn examples_dir(dir_name: &str) -> PathBuf { + let mut path = root_dir(); + // Descend into examples/{dir_name} path.push("examples"); path.push(dir_name); @@ -252,7 +265,7 @@ pub fn example_dir(dir_name: &str) -> PathBuf { #[allow(dead_code)] 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); @@ -261,19 +274,7 @@ pub fn example_file(dir_name: &str, file_name: &str) -> PathBuf { #[allow(dead_code)] pub fn fixtures_dir(dir_name: &str) -> PathBuf { - let mut path = env::current_exe().ok().unwrap(); - - // Get rid of the filename in target/debug/deps/cli_run-99c65e4e9a1fbd06 - path.pop(); - - // If we're in deps/ get rid of deps/ in target/debug/deps/ - if path.ends_with("deps") { - path.pop(); - } - - // Get rid of target/debug/ so we're back at the project root - path.pop(); - path.pop(); + let mut path = root_dir(); // Descend into cli/tests/fixtures/{dir_name} path.push("cli"); diff --git a/cli/src/build.rs b/cli/src/build.rs index d75bf0361b..6a498fc5f5 100644 --- a/cli/src/build.rs +++ b/cli/src/build.rs @@ -65,7 +65,7 @@ pub fn build_file<'a>( }; let loaded = roc_load::file::load_and_monomorphize( - &arena, + arena, roc_file_path.clone(), stdlib, src_dir.as_path(), @@ -128,11 +128,11 @@ pub fn build_file<'a>( let cwd = roc_file_path.parent().unwrap(); let binary_path = cwd.join(&*loaded.output_path); // TODO should join ".exe" on Windows let code_gen_timing = program::gen_from_mono_module( - &arena, + arena, loaded, &roc_file_path, Triple::host(), - &app_o_file, + app_o_file, opt_level, emit_debug_info, ); diff --git a/cli/src/lib.rs b/cli/src/lib.rs index c7efdbc1a5..64956980d8 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -151,8 +151,7 @@ pub fn build(target: &Triple, matches: &ArgMatches, config: BuildConfig) -> io:: LinkType::Executable }; - let path = Path::new(filename).canonicalize().unwrap(); - let src_dir = path.parent().unwrap().canonicalize().unwrap(); + let path = Path::new(filename); // Spawn the root task 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( &arena, target, diff --git a/cli/src/repl/eval.rs b/cli/src/repl/eval.rs index 7d150a4756..3f9835e196 100644 --- a/cli/src/repl/eval.rs +++ b/cli/src/repl/eval.rs @@ -1,17 +1,15 @@ use bumpalo::collections::Vec; use bumpalo::Bump; use libloading::Library; -use roc_collections::all::MutMap; 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::symbol::{Interns, ModuleId, Symbol}; use roc_mono::ir::ProcLayout; use roc_mono::layout::{union_sorted_tags_help, Builtin, Layout, UnionLayout, UnionVariant}; use roc_parse::ast::{AssignedField, Expr, StrLiteral}; use roc_region::all::{Located, Region}; -use roc_types::subs::{Content, FlatType, Subs, Variable}; -use roc_types::types::RecordField; +use roc_types::subs::{Content, FlatType, RecordFields, Subs, Variable}; struct Env<'a, 'env> { arena: &'a Bump, @@ -155,9 +153,12 @@ fn jit_to_ast_help<'a>( Content::Structure(FlatType::Record(fields, _)) => { Ok(struct_to_ast(env, ptr, field_layouts, fields)) } - Content::Structure(FlatType::EmptyRecord) => { - Ok(struct_to_ast(env, ptr, field_layouts, &MutMap::default())) - } + Content::Structure(FlatType::EmptyRecord) => Ok(struct_to_ast( + env, + ptr, + field_layouts, + &RecordFields::with_capacity(0), + )), Content::Structure(FlatType::TagUnion(tags, _)) => { debug_assert_eq!(tags.len(), 1); @@ -172,7 +173,7 @@ fn jit_to_ast_help<'a>( )) } 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(_, _, _)) => { // a function with a struct as the closure environment @@ -245,6 +246,11 @@ fn jit_to_ast_help<'a>( Builtin::Int16 => { *(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"), }; @@ -319,9 +325,9 @@ fn jit_to_ast_help<'a>( todo!("print recursive tag unions in the REPL") } 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), } @@ -429,10 +435,10 @@ fn ptr_to_ast<'a>( single_tag_union_to_ast(env, ptr, field_layouts, tag_name.clone(), payload_vars) } 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) => { - struct_to_ast(env, ptr, &[], &MutMap::default()) + struct_to_ast(env, ptr, &[], &RecordFields::with_capacity(0)) } other => { unreachable!( @@ -463,7 +469,7 @@ fn list_to_ast<'a>( let elem_var = *vars.first().unwrap(); - env.subs.get_without_compacting(elem_var).content + env.subs.get_content_without_compacting(elem_var) } other => { unreachable!( @@ -474,14 +480,14 @@ fn list_to_ast<'a>( }; 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; for index in 0..len { let offset_bytes = index * elem_size; let elem_ptr = unsafe { ptr.add(offset_bytes) }; let loc_expr = &*arena.alloc(Located { - value: ptr_to_ast(env, elem_ptr, elem_layout, &elem_content), + value: ptr_to_ast(env, elem_ptr, elem_layout, elem_content), region: Region::zero(), }); @@ -528,14 +534,14 @@ where { let arena = env.arena; 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 let mut field_ptr = ptr as *const u8; for (var, layout) in sequence { - let content = subs.get_without_compacting(var).content; - let expr = ptr_to_ast(env, field_ptr, layout, &content); + let content = subs.get_content_without_compacting(var); + let expr = ptr_to_ast(env, field_ptr, layout, content); let loc_expr = Located::at_zero(expr); output.push(&*arena.alloc(loc_expr)); @@ -551,31 +557,20 @@ fn struct_to_ast<'a>( env: &Env<'a, '_>, ptr: *const u8, field_layouts: &'a [Layout<'a>], - fields: &MutMap>, + sorted_fields: &RecordFields, ) -> Expr<'a> { let arena = env.arena; let subs = env.subs; - 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::)>>(); - - vec.sort_by(|(label1, _), (label2, _)| label1.cmp(label2)); - - vec - }; + let mut output = Vec::with_capacity_in(field_layouts.len(), arena); if sorted_fields.len() == 1 { // this is a 1-field wrapper record around another record or 1-tag tag union - let (label, field) = sorted_fields.pop().unwrap(); + let (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 { - 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(), }); @@ -601,9 +596,9 @@ fn struct_to_ast<'a>( let mut field_ptr = ptr; 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 { - value: ptr_to_ast(env, field_ptr, field_layout, &content), + value: ptr_to_ast(env, field_ptr, field_layout, content), 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 } } }), // so we need to do this recursively on the field type. let field_var = *field.as_inner(); - let field_content = env.subs.get_without_compacting(field_var).content; + let field_content = env.subs.get_content_without_compacting(field_var); let loc_expr = Located { - value: bool_to_ast(env, value, &field_content), + value: bool_to_ast(env, value, field_content), 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); 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 { - value: bool_to_ast(env, value, &content), + value: bool_to_ast(env, value, content), region: Region::zero(), }); @@ -734,9 +729,9 @@ fn bool_to_ast<'a>(env: &Env<'a, '_>, value: bool, content: &Content) -> Expr<'a } } 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 => { 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 } } }), // so we need to do this recursively on the field type. let field_var = *field.as_inner(); - let field_content = env.subs.get_without_compacting(field_var).content; + let field_content = env.subs.get_content_without_compacting(field_var); let loc_expr = Located { - value: byte_to_ast(env, value, &field_content), + value: byte_to_ast(env, value, field_content), 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); 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 { - value: byte_to_ast(env, value, &content), + value: byte_to_ast(env, value, content), region: Region::zero(), }); @@ -845,9 +840,9 @@ fn byte_to_ast<'a>(env: &Env<'a, '_>, value: u8, content: &Content) -> Expr<'a> } } 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 => { 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 } } }), // so we need to do this recursively on the field type. let field_var = *field.as_inner(); - let field_content = env.subs.get_without_compacting(field_var).content; + let field_content = env.subs.get_content_without_compacting(field_var); 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(), }; @@ -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); 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 { - value: num_to_ast(env, num_expr, &content), + value: num_to_ast(env, num_expr, content), region: Region::zero(), }); @@ -950,9 +945,9 @@ fn num_to_ast<'a>(env: &Env<'a, '_>, num_expr: Expr<'a>, content: &Content) -> E } } 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 => { panic!("Unexpected FlatType {:?} in num_to_ast", other); diff --git a/cli/src/repl/gen.rs b/cli/src/repl/gen.rs index 797fdba999..a1dd4d3372 100644 --- a/cli/src/repl/gen.rs +++ b/cli/src/repl/gen.rs @@ -154,8 +154,8 @@ pub fn gen_and_eval<'a>( // pretty-print the expr type string for later. name_all_type_vars(main_fn_var, &mut subs); - let content = subs.get(main_fn_var).content; - let expr_type_str = content_to_string(content.clone(), &subs, home, &interns); + let content = subs.get_content_without_compacting(main_fn_var); + let expr_type_str = content_to_string(content, &subs, home, &interns); let (_, main_fn_layout) = match procedures.keys().find(|(s, _)| *s == main_fn_symbol) { 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"); let res_answer = unsafe { eval::jit_to_ast( @@ -227,7 +227,7 @@ pub fn gen_and_eval<'a>( lib, main_fn_name, main_fn_layout, - &content, + content, &env.interns, home, &subs, diff --git a/cli/tests/cli_run.rs b/cli/tests/cli_run.rs index f89a64d8c3..0494d35f41 100644 --- a/cli/tests/cli_run.rs +++ b/cli/tests/cli_run.rs @@ -1,4 +1,4 @@ -// #[macro_use] +#[macro_use] extern crate pretty_assertions; extern crate bumpalo; @@ -10,12 +10,15 @@ extern crate roc_module; #[cfg(test)] mod cli_run { use cli_utils::helpers::{ - example_file, extract_valgrind_errors, fixture_file, run_cmd, run_roc, run_with_valgrind, - ValgrindError, ValgrindErrorXWhat, + example_file, examples_dir, extract_valgrind_errors, fixture_file, run_cmd, run_roc, + run_with_valgrind, ValgrindError, ValgrindErrorXWhat, }; use serial_test::serial; use std::path::Path; + #[cfg(not(debug_assertions))] + use roc_collections::all::MutMap; + #[cfg(not(target_os = "macos"))] const ALLOW_VALGRIND: bool = true; @@ -25,26 +28,18 @@ mod cli_run { #[cfg(target_os = "macos")] const ALLOW_VALGRIND: bool = false; - fn check_output( - file: &Path, - executable_filename: &str, - flags: &[&str], - expected_ending: &str, + #[derive(Debug, PartialEq, Eq)] + struct Example<'a> { + filename: &'a str, + executable_filename: &'a str, + stdin: &'a [&'a str], + expected_ending: &'a str, use_valgrind: bool, - ) { - check_output_with_stdin( - file, - "", - executable_filename, - flags, - expected_ending, - use_valgrind, - ) } fn check_output_with_stdin( file: &Path, - stdin_str: &str, + stdin: &[&str], executable_filename: &str, flags: &[&str], expected_ending: &str, @@ -59,7 +54,7 @@ mod cli_run { let out = if use_valgrind && ALLOW_VALGRIND { let (valgrind_out, raw_xml) = run_with_valgrind( - stdin_str, + stdin, &[file.with_file_name(executable_filename).to_str().unwrap()], ); @@ -101,7 +96,7 @@ mod cli_run { } else { run_cmd( file.with_file_name(executable_filename).to_str().unwrap(), - stdin_str, + stdin, &[], ) }; @@ -114,182 +109,344 @@ mod cli_run { assert!(out.status.success()); } - #[test] - #[serial(hello_world)] - fn run_hello_world() { - check_output( - &example_file("hello-world", "Hello.roc"), - "hello-world", - &[], - "Hello, World!\n", - true, - ); + /// 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] + fn $test_name() { + let dir_name = $name; + let example = $example; + let file_name = example_file(dir_name, example.filename); + + // Check with and without optimizations + check_output_with_stdin( + &file_name, + example.stdin, + example.executable_filename, + &[], + example.expected_ending, + example.use_valgrind, + ); + + check_output_with_stdin( + &file_name, + example.stdin, + example.executable_filename, + &["--optimize"], + example.expected_ending, + example.use_valgrind, + ); + } + )* + + #[test] + #[cfg(not(debug_assertions))] + fn all_examples_have_tests() { + let mut all_examples: MutMap<&str, Example<'_>> = MutMap::default(); + + $( + all_examples.insert($name, $example); + )* + + check_for_tests("../examples", &mut all_examples); + } + } } - #[test] - #[serial(hello_world)] - fn run_hello_world_optimized() { - check_output( - &example_file("hello-world", "Hello.roc"), - "hello-world", - &[], - "Hello, World!\n", - true, - ); + // 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, + // }, } - #[test] - #[serial(quicksort)] - fn run_quicksort_not_optimized() { - check_output( - &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, - ); + 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"], + benchmark.expected_ending, + benchmark.use_valgrind, + ); + } + )* + + #[test] + #[cfg(not(debug_assertions))] + fn all_benchmarks_have_tests() { + let mut all_benchmarks: MutMap<&str, Example<'_>> = MutMap::default(); + + $( + let benchmark = $benchmark; + + all_benchmarks.insert(benchmark.filename, benchmark); + )* + + check_for_benchmarks("../examples/benchmarks", &mut all_benchmarks); + } + } } - #[test] - #[serial(quicksort)] - fn run_quicksort_optimized() { - check_output( - &example_file("quicksort", "Quicksort.roc"), - "quicksort", - &["--optimize"], - "[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n", - true, - ); + benchmarks! { + nqueens => Example { + filename: "NQueens.roc", + executable_filename: "nqueens", + stdin: &["6"], + expected_ending: "4\n", + use_valgrind: true, + }, + cfold => Example { + 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] - #[serial(quicksort)] - fn run_quicksort_optimized_valgrind() { - check_output( - &example_file("quicksort", "Quicksort.roc"), - "quicksort", - &["--optimize"], - "[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n", - true, - ); + #[cfg(not(debug_assertions))] + fn check_for_tests(examples_dir: &str, all_examples: &mut MutMap<&str, Example<'_>>) { + let entries = std::fs::read_dir(examples_dir).unwrap_or_else(|err| { + panic!( + "Error trying to read {} as an examples directory: {}", + examples_dir, err + ); + }); + + 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); + }); + } + } + } + + assert_eq!(all_examples, &mut MutMap::default()); } - #[test] - #[serial(nqueens)] - fn run_nqueens_not_optimized() { - check_output_with_stdin( - &example_file("benchmarks", "NQueens.roc"), - "6", - "nqueens", - &[], - "4\n", - true, - ); - } + #[cfg(not(debug_assertions))] + fn check_for_benchmarks(benchmarks_dir: &str, all_benchmarks: &mut MutMap<&str, Example<'_>>) { + use std::ffi::OsStr; + use std::fs::File; + use std::io::Read; - #[test] - #[serial(cfold)] - fn run_cfold_not_optimized() { - check_output_with_stdin( - &example_file("benchmarks", "CFold.roc"), - "3", - "cfold", - &[], - "11 & 11\n", - true, - ); - } + let entries = std::fs::read_dir(benchmarks_dir).unwrap_or_else(|err| { + panic!( + "Error trying to read {} as a benchmark directory: {}", + benchmarks_dir, err + ); + }); - #[test] - #[serial(deriv)] - fn run_deriv_not_optimized() { - check_output_with_stdin( - &example_file("benchmarks", "Deriv.roc"), - "2", - "deriv", - &[], - "1 count: 6\n2 count: 22\n", - true, - ); - } + for entry in entries { + let entry = entry.unwrap(); + let path = entry.path(); - #[test] - #[serial(deriv)] - fn run_rbtree_insert_not_optimized() { - check_output( - &example_file("benchmarks", "RBTreeInsert.roc"), - "rbtree-insert", - &[], - "Node Black 0 {} Empty Empty\n", - true, - ); - } + if let Some("roc") = path.extension().and_then(OsStr::to_str) { + let benchmark_file_name = entry.file_name().into_string().unwrap(); - #[test] - #[serial(deriv)] - fn run_rbtree_delete_not_optimized() { - check_output_with_stdin( - &example_file("benchmarks", "RBTreeDel.roc"), - "420", - "rbtree-del", - &[], - "30\n", - true, - ); - } + // 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(); - #[test] - #[serial(astar)] - fn run_astar_optimized_1() { - check_output( - &example_file("benchmarks", "TestAStar.roc"), - "test-astar", - &[], - "True\n", - false, - ); - } + file.read_exact(buf).unwrap(); - #[test] - #[serial(base64)] - fn base64() { - check_output( - &example_file("benchmarks", "TestBase64.roc"), - "test-base64", - &[], - "encoded: SGVsbG8gV29ybGQ=\ndecoded: Hello World\n", - true, - ); - } + // 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] - #[serial(closure)] - fn closure() { - check_output( - &example_file("benchmarks", "Closure.roc"), - "closure", - &[], - "", - true, - ); + assert_eq!(all_benchmarks, &mut MutMap::default()); } - // #[test] - // #[serial(effect)] - // fn run_effect_unoptimized() { - // check_output( - // &example_file("effect", "Main.roc"), - // &[], - // "I am Dep2.str2\n", - // true, - // ); - // } - #[test] #[serial(multi_dep_str)] fn run_multi_dep_str_unoptimized() { - check_output( + check_output_with_stdin( &fixture_file("multi-dep-str", "Main.roc"), + &[], "multi-dep-str", &[], "I am Dep2.str2\n", @@ -300,8 +457,9 @@ mod cli_run { #[test] #[serial(multi_dep_str)] fn run_multi_dep_str_optimized() { - check_output( + check_output_with_stdin( &fixture_file("multi-dep-str", "Main.roc"), + &[], "multi-dep-str", &["--optimize"], "I am Dep2.str2\n", @@ -312,8 +470,9 @@ mod cli_run { #[test] #[serial(multi_dep_thunk)] fn run_multi_dep_thunk_unoptimized() { - check_output( + check_output_with_stdin( &fixture_file("multi-dep-thunk", "Main.roc"), + &[], "multi-dep-thunk", &[], "I am Dep2.value2\n", @@ -324,25 +483,13 @@ mod cli_run { #[test] #[serial(multi_dep_thunk)] fn run_multi_dep_thunk_optimized() { - check_output( + check_output_with_stdin( &fixture_file("multi-dep-thunk", "Main.roc"), + &[], "multi-dep-thunk", &["--optimize"], "I am Dep2.value2\n", 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, - ); - } } diff --git a/compiler/build/src/link.rs b/compiler/build/src/link.rs index ee1124c905..fb92d4214c 100644 --- a/compiler/build/src/link.rs +++ b/compiler/build/src/link.rs @@ -56,7 +56,7 @@ fn find_zig_str_path() -> PathBuf { } #[cfg(not(target_os = "macos"))] -fn build_zig_host( +pub fn build_zig_host( env_path: &str, env_home: &str, emit_bin: &str, @@ -86,7 +86,7 @@ fn build_zig_host( } #[cfg(target_os = "macos")] -fn build_zig_host( +pub fn build_zig_host( env_path: &str, env_home: &str, emit_bin: &str, @@ -140,7 +140,7 @@ fn build_zig_host( .args(&[ "build-obj", zig_host_src, - &emit_bin, + emit_bin, "--pkg-begin", "str", zig_str_path, @@ -333,7 +333,7 @@ fn link_linux( output_path, ), 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? // // 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 // on Catalina if it's specified with -L, so we replace it with a // 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() { format!("-L{}", big_sur_path) } else { @@ -496,7 +496,7 @@ pub fn module_to_dylib( 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 model = CodeModel::Default; let target_machine = diff --git a/compiler/build/src/program.rs b/compiler/build/src/program.rs index 82d7261950..d3e68f112f 100644 --- a/compiler/build/src/program.rs +++ b/compiler/build/src/program.rs @@ -116,9 +116,10 @@ pub fn gen_from_mono_module( } if name.starts_with("roc_builtins.dict") - || name.starts_with("dict.RocDict") || name.starts_with("roc_builtins.list") + || name.starts_with("roc_builtins.dec") || name.starts_with("list.RocList") + || name.starts_with("dict.RocDict") { 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 let ptr_bytes = target.pointer_width().unwrap().bytes() as u32; let env = roc_gen_llvm::llvm::build::Env { - arena: &arena, + arena, builder: &builder, dibuilder: &dibuilder, 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_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"); } diff --git a/compiler/builtins/bitcode/build.zig b/compiler/builtins/bitcode/build.zig index e9835e0db5..e90c8ab7fe 100644 --- a/compiler/builtins/bitcode/build.zig +++ b/compiler/builtins/bitcode/build.zig @@ -27,7 +27,6 @@ pub fn build(b: *Builder) void { llvm_obj.strip = true; llvm_obj.emit_llvm_ir = true; llvm_obj.emit_bin = false; - llvm_obj.bundle_compiler_rt = true; const ir = b.step("ir", "Build LLVM ir"); ir.dependOn(&llvm_obj.step); diff --git a/compiler/builtins/bitcode/src/dec.zig b/compiler/builtins/bitcode/src/dec.zig index 011e37cac1..aad37e91a5 100644 --- a/compiler/builtins/bitcode/src/dec.zig +++ b/compiler/builtins/bitcode/src/dec.zig @@ -1,16 +1,19 @@ const std = @import("std"); const str = @import("str.zig"); +const utils = @import("utils.zig"); const math = std.math; +const always_inline = std.builtin.CallOptions.Modifier.always_inline; const RocStr = str.RocStr; +const WithOverflow = utils.WithOverflow; -pub const RocDec = struct { +pub const RocDec = extern struct { num: i128, pub const decimal_places: u5 = 18; pub const whole_number_places: u5 = 21; 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 max: RocDec = .{ .num = math.maxInt(i128) }; @@ -22,6 +25,23 @@ pub const RocDec = struct { 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 { if (roc_str.isEmpty()) { return null; @@ -57,7 +77,7 @@ pub const RocDec = struct { var after_str_len = (length - 1) - pi; 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; @@ -74,37 +94,38 @@ pub const RocDec = struct { var result: i128 = undefined; var overflowed = @mulWithOverflow(i128, before, one_point_zero_i128, &result); if (overflowed) { - std.debug.panic("TODO runtime exception for overflow!", .{}); + @panic("TODO runtime exception for overflow!"); } before_val_i128 = result; } - var dec: ?RocDec = null; - if (before_val_i128) |before| { - if (after_val_i128) |after| { - var result: i128 = undefined; - var overflowed = @addWithOverflow(i128, before, after, &result); - if (overflowed) { - std.debug.panic("TODO runtime exception for overflow!", .{}); + const dec: RocDec = blk: { + if (before_val_i128) |before| { + if (after_val_i128) |after| { + var result: i128 = undefined; + var overflowed = @addWithOverflow(i128, before, after, &result); + if (overflowed) { + @panic("TODO runtime exception for overflow!"); + } + break :blk .{ .num = result }; + } else { + break :blk .{ .num = before }; } - dec = .{ .num = result }; + } else if (after_val_i128) |after| { + break :blk .{ .num = after }; } else { - dec = .{ .num = before }; + return null; } - } else if (after_val_i128) |after| { - dec = .{ .num = after }; - } + }; - if (dec) |d| { - if (is_negative) { - dec = d.negate(); - } + if (is_negative) { + return dec.negate(); + } else { + return dec; } - - return dec; } - fn isDigit(c: u8) bool { + inline fn isDigit(c: u8) bool { return (c -% 48) <= 9; } @@ -114,80 +135,84 @@ pub const RocDec = struct { return RocStr.init("0.0", 3); } - // Check if this Dec is negative, and if so convert to positive - // We will handle adding the '-' later - 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; + const num = self.num; + const is_negative = num < 0; - // Format the backing i128 into an array of digits (u8s) - var digit_bytes: [max_digits + 1]u8 = undefined; - var num_digits = std.fmt.formatIntBuf(digit_bytes[0..], num, 10, false, .{}); + // Format the backing i128 into an array of digit (ascii) characters (u8s) + var digit_bytes_storage: [max_digits + 1]u8 = undefined; + 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 - var before_digits_slice: []const u8 = undefined; var before_digits_offset: usize = 0; - var before_digits_adjust: u6 = 0; 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_slice = digit_bytes[0..before_digits_offset]; - } else { - before_digits_adjust = @intCast(u6, math.absInt(@intCast(i7, num_digits) - decimal_places) catch { - std.debug.panic("TODO runtime exception for overflow when getting abs", .{}); - }); - before_digits_slice = "0"; - } - // Figure out how many trailing zeros there are - // I tried to use https://ziglang.org/documentation/0.8.0/#ctz and it mostly worked, - // but was giving seemingly incorrect values for certain numbers. So instead we use - // 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; + for (digit_bytes[0..before_digits_offset]) |c| { + str_bytes[position] = c; + position += 1; } - index -= 1; - } - - // 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 - // 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_slice: []const u8 = leading_zeros[0..after_zeros_num]; - - // Get the slice for after the decimal point - var after_digits_slice: []const u8 = undefined; - if ((num_digits - before_digits_offset) == trailing_zeros) { - after_digits_slice = "0"; } else { - after_digits_slice = digit_bytes[before_digits_offset .. num_digits - trailing_zeros]; + // otherwise there are no actual digits before the decimal point + // but we format it with a '0' + str_bytes[position] = '0'; + position += 1; } - // Get the slice for the sign - const sign_slice: []const u8 = if (is_negative) "-" else leading_zeros[0..0]; + // we've done everything before the decimal point, so now we can put the decimal point in + str_bytes[position] = '.'; + position += 1; - // Hardcode adding a `1` for the '.' character - const str_len: usize = sign_slice.len + before_digits_slice.len + 1 + after_zeros_slice.len + after_digits_slice.len; + 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 + // For example, for the number 0.000123 we need to prepend 3 zeros after the decimal point + const after_zeros_num = if (num_digits < decimal_places) decimal_places - num_digits else 0; - // Join the slices together - // We do `max_digits + 2` here because we need to account for a possible sign ('-') and the dot ('.'). - // 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", .{}); - }; + var i: usize = 0; + while (i < after_zeros_num) : (i += 1) { + str_bytes[position] = '0'; + position += 1; + } - return RocStr.init(&str_bytes, str_len); + // otherwise append the decimal digits except the trailing zeros + for (digit_bytes[before_digits_offset .. num_digits - trailing_zeros]) |c| { + str_bytes[position] = c; + position += 1; + } + } + + return RocStr.init(&str_bytes, position); + } + + pub fn eq(self: RocDec, other: RocDec) bool { + return self.num == other.num; + } + + pub fn neq(self: RocDec, other: RocDec) bool { + return self.num != other.num; } pub fn negate(self: RocDec) ?RocDec { @@ -195,29 +220,41 @@ pub const RocDec = struct { 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; const overflowed = @addWithOverflow(i128, self.num, other.num, &answer); - if (!overflowed) { - return RocDec{ .num = answer }; + return .{ .value = RocDec{ .num = answer }, .has_overflowed = overflowed }; + } + + 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 { - 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; const overflowed = @subWithOverflow(i128, self.num, other.num, &answer); - if (!overflowed) { - return RocDec{ .num = answer }; + return .{ .value = RocDec{ .num = answer }, .has_overflowed = overflowed }; + } + + 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 { - 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 other_i128 = other.num; // 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 { if (other_i128 == 0) { - return .{ .num = 0 }; + return .{ .value = RocDec{ .num = 0 }, .has_overflowed = false }; } else if (other_i128 == RocDec.one_point_zero.num) { - return self; + return .{ .value = self, .has_overflowed = false }; } 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 { if (self_i128 == 0) { - return .{ .num = 0 }; + return .{ .value = RocDec{ .num = 0 }, .has_overflowed = false }; } else if (self_i128 == RocDec.one_point_zero.num) { - return other; + return .{ .value = other, .has_overflowed = false }; } 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); if (is_answer_negative) { - return .{ .num = -unsigned_answer }; + return .{ .value = RocDec{ .num = -unsigned_answer }, .has_overflowed = false }; } 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 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 @@ -292,7 +341,7 @@ pub const RocDec = struct { if (denominator_i128 == one_point_zero_i128) { return self; } 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); @@ -305,7 +354,7 @@ pub const RocDec = struct { if (numerator_i128 == one_point_zero_i128) { return other; } 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); @@ -317,13 +366,35 @@ pub const RocDec = struct { if (answer.hi == 0 and answer.lo <= math.maxInt(i128)) { unsigned_answer = @intCast(i128, answer.lo); } 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 }; } }; +// 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 { hi: 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); if (overflowed) { - std.debug.panic("TODO runtime exception for overflow!", .{}); + @panic("TODO runtime exception for overflow!"); } // Final 512bit value is d, c, b, a @@ -652,6 +723,11 @@ test "fromU64" { try expectEqual(RocDec{ .num = 25000000000000000000 }, dec); } +test "fromF64" { + var dec = RocDec.fromF64(25.5); + try expectEqual(RocDec{ .num = 25500000000000000000 }, dec.?); +} + test "fromStr: empty" { var roc_str = RocStr.init("", 0); 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()); } +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" { var dec: RocDec = .{ .num = 0 }; var res_roc_str = dec.toStr(); @@ -953,3 +1049,37 @@ test "div: 10 / 3" { 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; +} diff --git a/compiler/builtins/bitcode/src/list.zig b/compiler/builtins/bitcode/src/list.zig index 602b680d21..5259cf3e02 100644 --- a/compiler/builtins/bitcode/src/list.zig +++ b/compiler/builtins/bitcode/src/list.zig @@ -3,8 +3,6 @@ const utils = @import("utils.zig"); const RocResult = utils.RocResult; const mem = std.mem; -const TAG_WIDTH = 8; - const EqFn = fn (?[*]u8, ?[*]u8) callconv(.C) bool; const CompareFn = fn (?[*]u8, ?[*]u8, ?[*]u8) callconv(.C) u8; const Opaque = ?[*]u8; diff --git a/compiler/builtins/bitcode/src/main.zig b/compiler/builtins/bitcode/src/main.zig index 71e0ff8df8..acc259d02b 100644 --- a/compiler/builtins/bitcode/src/main.zig +++ b/compiler/builtins/bitcode/src/main.zig @@ -5,6 +5,17 @@ const testing = std.testing; // Dec Module 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 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 { exportBuiltinFn(func, "dict." ++ func_name); } - fn exportListFn(comptime func: anytype, comptime func_name: []const u8) void { 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 // https://github.com/ziglang/zig/blob/master/lib/std/std.zig#L94 test "" { 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; +} diff --git a/compiler/builtins/bitcode/src/utils.zig b/compiler/builtins/bitcode/src/utils.zig index b679c20153..7551f89de2 100644 --- a/compiler/builtins/bitcode/src/utils.zig +++ b/compiler/builtins/bitcode/src/utils.zig @@ -1,6 +1,10 @@ const std = @import("std"); 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! extern fn roc_alloc(size: usize, alignment: u32) callconv(.C) ?*c_void; diff --git a/compiler/builtins/docs/Bool.roc b/compiler/builtins/docs/Bool.roc index 077909e84d..65a8662b5a 100644 --- a/compiler/builtins/docs/Bool.roc +++ b/compiler/builtins/docs/Bool.roc @@ -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. ## 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. -## 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 ## accept arguments whose types contain functions. diff --git a/compiler/builtins/src/bitcode.rs b/compiler/builtins/src/bitcode.rs index 4ac7d0f0f3..86636724b4 100644 --- a/compiler/builtins/src/bitcode.rs +++ b/compiler/builtins/src/bitcode.rs @@ -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_SET: &str = "roc_builtins.list.set"; 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"; diff --git a/compiler/can/src/builtins.rs b/compiler/can/src/builtins.rs index b6d13be1da..4183f7cb99 100644 --- a/compiler/can/src/builtins.rs +++ b/compiler/can/src/builtins.rs @@ -455,8 +455,7 @@ fn num_add_wrap(symbol: Symbol, var_store: &mut VarStore) -> Def { num_binop(symbol, var_store, LowLevel::NumAddWrap) } -/// Num.addChecked : Num a, Num a -> Result (Num a) [ Overflow ]* -fn num_add_checked(symbol: Symbol, var_store: &mut VarStore) -> Def { +fn num_overflow_checked(symbol: Symbol, var_store: &mut VarStore, lowlevel: LowLevel) -> Def { let bool_var = var_store.fresh(); let num_var_1 = 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 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 // # 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 { loc_pattern: no_region(Pattern::Identifier(Symbol::ARG_3)), loc_expr: no_region(RunLowLevel { - op: LowLevel::NumAddChecked, + op: lowlevel, args: vec![ (num_var_1, Var(Symbol::ARG_1)), (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 fn num_sub(symbol: Symbol, var_store: &mut VarStore) -> Def { 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 ]* fn num_sub_checked(symbol: Symbol, var_store: &mut VarStore) -> Def { - let bool_var = var_store.fresh(); - 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_overflow_checked(symbol, var_store, LowLevel::NumSubChecked) } /// 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 ]* fn num_mul_checked(symbol: Symbol, var_store: &mut VarStore) -> Def { - let bool_var = var_store.fresh(); - 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_overflow_checked(symbol, var_store, LowLevel::NumMulChecked) } /// Num.isGt : Num a, Num a -> Bool diff --git a/compiler/can/src/constraint.rs b/compiler/can/src/constraint.rs index dfac99f49e..addc786a08 100644 --- a/compiler/can/src/constraint.rs +++ b/compiler/can/src/constraint.rs @@ -64,7 +64,7 @@ impl Constraint { fn subtract(declared: &Declared, detail: &VariableDetail, accum: &mut VariableDetail) { 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); } } @@ -82,11 +82,11 @@ fn subtract(declared: &Declared, detail: &VariableDetail, accum: &mut VariableDe // recursion vars should be always rigid 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); } - if !declared.rigid_vars.contains(&var) { + if !declared.rigid_vars.contains(var) { accum.recursion_variables.insert(*var); } } diff --git a/compiler/can/src/def.rs b/compiler/can/src/def.rs index b4a4652924..1c8f99f3ff 100644 --- a/compiler/can/src/def.rs +++ b/compiler/can/src/def.rs @@ -380,7 +380,7 @@ pub fn sort_can_defs( // // In the above example, `f` cannot reference `a`, and in the closure // 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 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 // 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 let Some(References { lookups, .. }) = env.closures.get(symbol) { @@ -454,7 +454,7 @@ pub fn sort_can_defs( let direct_successors = |symbol: &Symbol| -> ImSet { match refs_by_symbol.get(symbol) { 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 @@ -540,7 +540,7 @@ pub fn sort_can_defs( ), Some((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 { symbol: *symbol, @@ -662,11 +662,11 @@ fn group_to_declaration( // for a definition, so every definition is only inserted (thus typechecked and emitted) once let mut seen_pattern_regions: ImSet = ImSet::default(); - for cycle in strongly_connected_components(&group, filtered_successors) { + for cycle in strongly_connected_components(group, filtered_successors) { if cycle.len() == 1 { 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(); // Determine recursivity of closures that are not tail-recursive @@ -678,7 +678,7 @@ fn group_to_declaration( *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 is_recursive { @@ -854,7 +854,7 @@ fn canonicalize_pending_def<'a>( }; for (_, (symbol, _)) in scope.idents() { - if !vars_by_symbol.contains_key(&symbol) { + if !vars_by_symbol.contains_key(symbol) { continue; } @@ -999,7 +999,7 @@ fn canonicalize_pending_def<'a>( // // Only defs of the form (foo = ...) can be closure declarations or self tail calls. if let ( - &ast::Pattern::Identifier(ref _name), + &ast::Pattern::Identifier(_name), &Pattern::Identifier(ref defined_symbol), &Closure { 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, // 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!( "Tried to remove symbol {:?} from procedures, but it was not found: {:?}", 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 // which defined names reference each other. for (_, (symbol, region)) in scope.idents() { - if !vars_by_symbol.contains_key(&symbol) { + if !vars_by_symbol.contains_key(symbol) { continue; } @@ -1110,10 +1110,8 @@ fn canonicalize_pending_def<'a>( // identifier (e.g. `f = \x -> ...`), then this symbol can be tail-called. let outer_identifier = env.tailcallable_symbol; - if let ( - &ast::Pattern::Identifier(ref _name), - &Pattern::Identifier(ref defined_symbol), - ) = (&loc_pattern.value, &loc_can_pattern.value) + if let (&ast::Pattern::Identifier(_name), &Pattern::Identifier(ref defined_symbol)) = + (&loc_pattern.value, &loc_can_pattern.value) { 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. if let ( - &ast::Pattern::Identifier(ref _name), + &ast::Pattern::Identifier(_name), &Pattern::Identifier(ref defined_symbol), &Closure { 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, // 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!( "Tried to remove symbol {:?} from procedures, but it was not found: {:?}", symbol, env.closures @@ -1555,7 +1553,7 @@ fn correct_mutual_recursive_type_alias<'a>( let mut loc_succ = alias.typ.symbols(); // remove anything that is not defined in the current block loc_succ.retain(|key| symbols_introduced.contains(key)); - loc_succ.remove(&symbol); + loc_succ.remove(symbol); loc_succ } diff --git a/compiler/can/src/env.rs b/compiler/can/src/env.rs index b86b3f54bf..bc34d23ada 100644 --- a/compiler/can/src/env.rs +++ b/compiler/can/src/env.rs @@ -12,7 +12,7 @@ pub struct Env<'a> { /// are assumed to be relative to this path. pub home: ModuleId, - pub dep_idents: MutMap, + pub dep_idents: &'a MutMap, pub module_ids: &'a ModuleIds, @@ -40,7 +40,7 @@ pub struct Env<'a> { impl<'a> Env<'a> { pub fn new( home: ModuleId, - dep_idents: MutMap, + dep_idents: &'a MutMap, module_ids: &'a ModuleIds, exposed_ident_ids: IdentIds, ) -> Env<'a> { diff --git a/compiler/can/src/expr.rs b/compiler/can/src/expr.rs index 4116a51128..a129d74f08 100644 --- a/compiler/can/src/expr.rs +++ b/compiler/can/src/expr.rs @@ -980,7 +980,7 @@ where visited.insert(defined_symbol); for local in refs.lookups.iter() { - if !visited.contains(&local) { + if !visited.contains(local) { let other_refs: References = references_from_local(*local, visited, refs_by_def, closures); @@ -991,7 +991,7 @@ where } 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); answer = answer.union(other_refs); @@ -1022,7 +1022,7 @@ where visited.insert(call_symbol); for closed_over_local in references.lookups.iter() { - if !visited.contains(&closed_over_local) { + if !visited.contains(closed_over_local) { let other_refs = references_from_local(*closed_over_local, visited, refs_by_def, closures); @@ -1033,7 +1033,7 @@ where } 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); answer = answer.union(other_refs); diff --git a/compiler/can/src/module.rs b/compiler/can/src/module.rs index 345bb0853e..94ea585923 100644 --- a/compiler/can/src/module.rs +++ b/compiler/can/src/module.rs @@ -47,7 +47,7 @@ pub fn canonicalize_module_defs<'a, F>( home: ModuleId, module_ids: &ModuleIds, exposed_ident_ids: IdentIds, - dep_idents: MutMap, + dep_idents: &'a MutMap, aliases: MutMap, exposed_imports: MutMap, exposed_symbols: &MutSet, @@ -139,7 +139,7 @@ where } } - let (defs, _scope, output, symbols_introduced) = canonicalize_defs( + let (defs, scope, output, symbols_introduced) = canonicalize_defs( &mut env, Output::default(), var_store, diff --git a/compiler/can/src/operator.rs b/compiler/can/src/operator.rs index 4b6fbf14f2..36b790764d 100644 --- a/compiler/can/src/operator.rs +++ b/compiler/can/src/operator.rs @@ -276,7 +276,7 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located>) -> &'a }) } 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); for branch in branches.iter() { @@ -346,7 +346,7 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located>) -> &'a } If(if_thens, final_else_branch) => { // 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); @@ -363,8 +363,8 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located>) -> &'a }) } Expect(condition, continuation) => { - let desugared_condition = &*arena.alloc(desugar_expr(arena, &condition)); - let desugared_continuation = &*arena.alloc(desugar_expr(arena, &continuation)); + let desugared_condition = &*arena.alloc(desugar_expr(arena, condition)); + let desugared_continuation = &*arena.alloc(desugar_expr(arena, continuation)); arena.alloc(Located { value: Expect(desugared_condition, desugared_continuation), region: loc_expr.region, diff --git a/compiler/can/src/pattern.rs b/compiler/can/src/pattern.rs index 28ea70f9e5..92b1ca611b 100644 --- a/compiler/can/src/pattern.rs +++ b/compiler/can/src/pattern.rs @@ -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) { Err(_error) => { let problem = MalformedPatternProblem::MalformedFloat; diff --git a/compiler/can/src/scope.rs b/compiler/can/src/scope.rs index 9037d6e193..4df6919ecd 100644 --- a/compiler/can/src/scope.rs +++ b/compiler/can/src/scope.rs @@ -124,7 +124,7 @@ impl Scope { // If this IdentId was already added previously // when the value was exposed in the module header, // 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, None => all_ident_ids.add(ident.clone().into()), }; diff --git a/compiler/can/tests/helpers/mod.rs b/compiler/can/tests/helpers/mod.rs index 8540427d98..2aff0cf76a 100644 --- a/compiler/can/tests/helpers/mod.rs +++ b/compiler/can/tests/helpers/mod.rs @@ -34,7 +34,7 @@ pub struct CanExprOut { #[allow(dead_code)] 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!( "can_expr_with() got a parse error when attempting to canonicalize:\n\n{:?} {:?}", 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 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( &mut env, &mut var_store, diff --git a/compiler/can/tests/test_can.rs b/compiler/can/tests/test_can.rs index 68d3139c25..7e2d615b8a 100644 --- a/compiler/can/tests/test_can.rs +++ b/compiler/can/tests/test_can.rs @@ -145,7 +145,7 @@ mod test_can { let region = Region::zero(); assert_can( - &string.clone(), + string.clone(), RuntimeError(RuntimeError::InvalidFloat( FloatErrorKind::Error, region, @@ -658,7 +658,7 @@ mod test_can { recursive: recursion, .. }) => recursion.clone(), - Some(other @ _) => { + Some(other) => { panic!("assignment at {} is not a closure, but a {:?}", i, other) } None => { @@ -680,7 +680,7 @@ mod test_can { recursive: recursion, .. } => recursion.clone(), - other @ _ => { + other => { panic!("assignment at {} is not a closure, but a {:?}", i, other) } } diff --git a/compiler/collections/src/all.rs b/compiler/collections/src/all.rs index f473ef4e97..efd74751bb 100644 --- a/compiler/collections/src/all.rs +++ b/compiler/collections/src/all.rs @@ -79,7 +79,7 @@ where let mut buf = String::new_in(arena); if let Some(first) = strings.next() { - buf.push_str(&first); + buf.push_str(first); for string in strings { buf.reserve(join_str.len() + string.len()); @@ -133,7 +133,7 @@ where let mut answer = MutMap::default(); for (key, right_value) in map2 { - match std::collections::HashMap::get(map1, &key) { + match std::collections::HashMap::get(map1, key) { None => (), Some(left_value) => { answer.insert(key.clone(), (left_value.clone(), right_value.clone())); diff --git a/compiler/constrain/src/expr.rs b/compiler/constrain/src/expr.rs index 05dff22dd7..09020430e5 100644 --- a/compiler/constrain/src/expr.rs +++ b/compiler/constrain/src/expr.rs @@ -1446,7 +1446,7 @@ fn instantiate_rigids( let mut rigid_substitution: ImMap = ImMap::default(); 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)); } else { // It's possible to use this rigid in nested defs diff --git a/compiler/constrain/src/pattern.rs b/compiler/constrain/src/pattern.rs index 8a2f5d9250..310a7a251c 100644 --- a/compiler/constrain/src/pattern.rs +++ b/compiler/constrain/src/pattern.rs @@ -119,7 +119,7 @@ fn headers_from_annotation_help( } /// 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. pub fn constrain_pattern( env: &Env, @@ -206,7 +206,7 @@ pub fn constrain_pattern( let pat_type = Type::Variable(*var); let expected = PExpected::NoExpectation(pat_type.clone()); - if !state.headers.contains_key(&symbol) { + if !state.headers.contains_key(symbol) { state .headers .insert(*symbol, Located::at(region, pat_type.clone())); diff --git a/compiler/fmt/src/expr.rs b/compiler/fmt/src/expr.rs index ebe9036600..54ee550ef6 100644 --- a/compiler/fmt/src/expr.rs +++ b/compiler/fmt/src/expr.rs @@ -295,7 +295,7 @@ impl<'a> Formattable<'a> for Expr<'a> { items, 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), UnaryOp(sub_expr, unary_op) => { @@ -1027,7 +1027,7 @@ fn format_field_multiline<'a, T>( format_field_multiline(buf, sub_field, indent, separator_prefix); } AssignedField::SpaceAfter(sub_field, spaces) => { - // We have somethig like that: + // We have something like that: // ``` // field # comment // , otherfield diff --git a/compiler/gen_dev/src/lib.rs b/compiler/gen_dev/src/lib.rs index af414118f0..f6ee048e7b 100644 --- a/compiler/gen_dev/src/lib.rs +++ b/compiler/gen_dev/src/lib.rs @@ -74,7 +74,7 @@ where /// 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> { self.reset(); - self.load_args(&proc.args)?; + self.load_args(proc.args)?; // let start = std::time::Instant::now(); self.scan_ast(&proc.body); self.create_free_map(); diff --git a/compiler/gen_dev/tests/dev_num.rs b/compiler/gen_dev/tests/dev_num.rs index aa758c6587..010bf078d0 100644 --- a/compiler/gen_dev/tests/dev_num.rs +++ b/compiler/gen_dev/tests/dev_num.rs @@ -31,7 +31,7 @@ mod dev_num { 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!("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::MAX), f64::MAX, f64); } diff --git a/compiler/gen_dev/tests/helpers/eval.rs b/compiler/gen_dev/tests/helpers/eval.rs index f3403bb9d4..48893f63c6 100644 --- a/compiler/gen_dev/tests/helpers/eval.rs +++ b/compiler/gen_dev/tests/helpers/eval.rs @@ -49,7 +49,7 @@ pub fn helper<'a>( let loaded = roc_load::file::load_and_monomorphize_from_str( arena, filename, - &module_src, + module_src, &stdlib, src_dir, exposed_types, diff --git a/compiler/gen_llvm/src/llvm/bitcode.rs b/compiler/gen_llvm/src/llvm/bitcode.rs index b92df1f79b..bfbfaeb3b3 100644 --- a/compiler/gen_llvm/src/llvm/bitcode.rs +++ b/compiler/gen_llvm/src/llvm/bitcode.rs @@ -78,7 +78,7 @@ pub fn build_has_tag_id<'a, 'ctx, 'env>( match env.module.get_function(fn_name) { 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( env, - &fn_name, + fn_name, output_type.into(), - &argument_types, + argument_types, ); // called from zig, must use C calling convention @@ -204,7 +204,7 @@ pub fn build_transform_caller<'a, 'ctx, 'env>( function, closure_data_layout, 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( env, - &fn_name, + fn_name, env.context.void_type().into(), &(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 fn_name = layout_ids - .get(symbol, &layout) + .get(symbol, layout) .to_symbol_string(symbol, &env.interns); 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 fn_name = layout_ids - .get(symbol, &layout) + .get(symbol, layout) .to_symbol_string(symbol, &env.interns); 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( env, - &fn_name, + fn_name, env.context.i8_type().into(), &[arg_type.into(), arg_type.into(), arg_type.into()], ); diff --git a/compiler/gen_llvm/src/llvm/build.rs b/compiler/gen_llvm/src/llvm/build.rs index a6707a4884..b8f62fb2ba 100644 --- a/compiler/gen_llvm/src/llvm/build.rs +++ b/compiler/gen_llvm/src/llvm/build.rs @@ -1,6 +1,6 @@ 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::{ 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, @@ -347,7 +347,7 @@ pub fn module_from_builtins<'ctx>(ctx: &'ctx Context, module_name: &str) -> Modu // we compile the builtins into LLVM bitcode 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) .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: // // 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 i128_type = ctx.i128_type(); - let i64_type = ctx.i64_type(); - let i32_type = ctx.i32_type(); - let i16_type = ctx.i16_type(); + let i1_type = ctx.bool_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( module, @@ -418,8 +421,6 @@ fn add_intrinsics<'ctx>(ctx: &'ctx Context, module: &Module<'ctx>) { f64_type.fn_type(&[f64_type.into()], false), ); - // add with overflow - add_intrinsic(module, LLVM_SADD_WITH_OVERFLOW_I8, { let fields = [i8_type.into(), i1_type.into()]; 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) }); - 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, { let fields = [i8_type.into(), i1_type.into()]; ctx.struct_type(&fields, false) @@ -475,12 +468,6 @@ fn add_intrinsics<'ctx>(ctx: &'ctx Context, module: &Module<'ctx>) { ctx.struct_type(&fields, 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"; @@ -640,10 +627,15 @@ pub fn float_with_precision<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, value: f64, precision: &Builtin, -) -> FloatValue<'ctx> { +) -> BasicValueEnum<'ctx> { match precision { - Builtin::Float64 => env.context.f64_type().const_float(value), - Builtin::Float32 => env.context.f32_type().const_float(value), + Builtin::Decimal => call_bitcode_fn( + 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), } } @@ -662,7 +654,7 @@ pub fn build_exp_literal<'a, 'ctx, 'env>( }, 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), }, @@ -984,7 +976,7 @@ pub fn build_exp_expr<'a, 'ctx, 'env>( // The layout of the struct expects them to be dropped! let (field_expr, field_layout) = load_symbol_and_layout(scope, symbol); 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); } @@ -1195,7 +1187,7 @@ pub fn build_exp_expr<'a, 'ctx, 'env>( ) } 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); @@ -1268,7 +1260,7 @@ pub fn build_exp_expr<'a, 'ctx, 'env>( // cast the argument bytes into the desired shape for this tag 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, tag_id, arguments, - &tag_field_layouts, + tag_field_layouts, tags, reuse_allocation, parent, @@ -1499,7 +1491,7 @@ pub fn build_tag<'a, 'ctx, 'env>( union_layout, tag_id, arguments, - &tag_field_layouts, + tag_field_layouts, tags, reuse_allocation, parent, @@ -2277,7 +2269,7 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>( scope, parent, layout, - &expr, + expr, ); // 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; env.builder.build_resume(exception_object); - env.context.i64_type().const_zero().into() + env.ptr_int().const_zero().into() } Switch { @@ -2407,7 +2399,7 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>( cond_layout, 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 { cond_layout: *cond_layout, @@ -2485,7 +2477,7 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>( ); // remove this join point again - scope.join_points.remove(&id); + scope.join_points.remove(id); cont_block.move_after(phi_block).unwrap(); @@ -2835,7 +2827,7 @@ fn build_switch_ir<'a, 'ctx, 'env>( .into_int_value() } Layout::Union(variant) => { - cond_layout = Layout::Builtin(Builtin::Int64); + cond_layout = variant.tag_id_layout(); get_tag_id(env, parent, &variant, cond_value) } @@ -3129,7 +3121,7 @@ where let call_result = { let call = builder.build_invoke( function, - &arguments, + arguments, then_block, catch_block, "call_roc_function", @@ -3299,7 +3291,7 @@ fn make_exception_catching_wrapper<'a, 'ctx, 'env>( // Add main to the module. let wrapper_function = add_func( env.module, - &wrapper_function_name, + wrapper_function_name, wrapper_function_type, Linkage::External, C_CALL_CONV, @@ -3422,7 +3414,7 @@ fn build_procedures_help<'a, 'ctx, 'env>( // Add all the Proc headers to the module. // We have to do this in a separate pass first, // because their bodies may reference each other. - let 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); @@ -3436,7 +3428,7 @@ fn build_procedures_help<'a, 'ctx, 'env>( current_scope.retain_top_level_thunks_for_module(home); build_proc( - &env, + env, mod_solutions, &mut layout_ids, func_spec_solutions, @@ -3449,7 +3441,7 @@ fn build_procedures_help<'a, 'ctx, 'env>( env.dibuilder.finalize(); if fn_val.verify(true) { - function_pass.run_on(&fn_val); + function_pass.run_on(fn_val); } else { 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); 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); } @@ -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>( env: &Env<'a, 'ctx, 'env>, parent: FunctionValue<'ctx>, @@ -5273,8 +5298,6 @@ fn build_int_binop<'a, 'ctx, 'env>( match op { NumAdd => { - let context = env.context; - let intrinsic = match lhs_layout { Layout::Builtin(Builtin::Int8) => LLVM_SADD_WITH_OVERFLOW_I8, 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()]) .into_struct_value(); - let add_result = bd.build_extract_value(result, 0, "add_result").unwrap(); - 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 + throw_on_overflow(env, parent, result, "integer addition overflowed!") } NumAddWrap => bd.build_int_add(lhs, rhs, "add_int_wrap").into(), NumAddChecked => env.call_intrinsic(LLVM_SADD_WITH_OVERFLOW_I64, &[lhs.into(), rhs.into()]), NumSub => { - let context = env.context; - let intrinsic = match lhs_layout { Layout::Builtin(Builtin::Int8) => LLVM_SSUB_WITH_OVERFLOW_I8, 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()]) .into_struct_value(); - let sub_result = bd.build_extract_value(result, 0, "sub_result").unwrap(); - 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 + throw_on_overflow(env, parent, result, "integer subtraction overflowed!") } NumSubWrap => bd.build_int_sub(lhs, rhs, "sub_int").into(), NumSubChecked => env.call_intrinsic(LLVM_SSUB_WITH_OVERFLOW_I64, &[lhs.into(), rhs.into()]), NumMul => { - let context = env.context; let result = env .call_intrinsic(LLVM_SMUL_WITH_OVERFLOW_I64, &[lhs.into(), rhs.into()]) .into_struct_value(); - let mul_result = bd.build_extract_value(result, 0, "mul_result").unwrap(); - 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 + throw_on_overflow(env, parent, result, "integer multiplication overflowed!") } NumMulWrap => bd.build_int_mul(lhs, rhs, "mul_int").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(), - 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(), NumBitwiseXor => bd.build_xor(lhs, rhs, "int_bitwise_xor").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, 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); } @@ -5563,7 +5523,7 @@ fn build_float_binop<'a, 'ctx, 'env>( let result = bd.build_float_add(lhs, rhs, "add_float"); 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 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 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 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 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 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 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 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 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 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 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 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 { let width = int_type.get_bit_width(); @@ -5909,10 +5938,10 @@ fn build_float_unary_op<'a, 'ctx, 'env>( env.context.i64_type(), "num_floor", ), - NumIsFinite => call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_IS_FINITE), - NumAtan => call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_ATAN), - NumAcos => call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_ACOS), - NumAsin => call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_ASIN), + NumIsFinite => call_bitcode_fn(env, &[arg.into()], bitcode::NUM_IS_FINITE), + NumAtan => call_bitcode_fn(env, &[arg.into()], bitcode::NUM_ATAN), + NumAcos => call_bitcode_fn(env, &[arg.into()], bitcode::NUM_ACOS), + NumAsin => call_bitcode_fn(env, &[arg.into()], bitcode::NUM_ASIN), _ => { unreachable!("Unrecognized int unary operation: {:?}", op); } @@ -6078,7 +6107,7 @@ fn cxa_allocate_exception<'a, 'ctx, 'env>( let context = env.context; 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, None => { // 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 function = match module.get_function(&name) { + let function = match module.get_function(name) { Some(value) => value, None => { // 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 context = env.context; - match module.get_function(&name) { + match module.get_function(name) { Some(gvalue) => gvalue, None => { let personality_func = add_func( @@ -6200,7 +6229,7 @@ fn cxa_end_catch(env: &Env<'_, '_, '_>) { let module = env.module; let context = env.context; - let function = match module.get_function(&name) { + let function = match module.get_function(name) { Some(gvalue) => gvalue, None => { let cxa_end_catch = add_func( @@ -6228,7 +6257,7 @@ fn cxa_begin_catch<'a, 'ctx, 'env>( let module = env.module; let context = env.context; - let function = match module.get_function(&name) { + let function = match module.get_function(name) { Some(gvalue) => gvalue, None => { let u8_ptr = context.i8_type().ptr_type(AddressSpace::Generic); diff --git a/compiler/gen_llvm/src/llvm/build_dict.rs b/compiler/gen_llvm/src/llvm/build_dict.rs index 73f799466e..b056cf6b2b 100644 --- a/compiler/gen_llvm/src/llvm/build_dict.rs +++ b/compiler/gen_llvm/src/llvm/build_dict.rs @@ -64,7 +64,7 @@ pub fn dict_len<'a, 'ctx, 'env>( .build_alloca(dict_as_zig_dict.get_type(), "dict_ptr"); 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(), _ => 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 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") } @@ -140,7 +140,7 @@ pub fn dict_insert<'a, 'ctx, 'env>( dec_value_fn.as_global_value().as_pointer_value().into(), result_ptr.into(), ], - &bitcode::DICT_INSERT, + bitcode::DICT_INSERT, ); 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(), result_ptr.into(), ], - &bitcode::DICT_REMOVE, + bitcode::DICT_REMOVE, ); 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(), 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(), inc_value_fn.as_global_value().as_pointer_value().into(), ], - &bitcode::DICT_GET, + bitcode::DICT_GET, ) .into_struct_value(); @@ -415,7 +415,7 @@ pub fn dict_elements_rc<'a, 'ctx, 'env>( key_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(), list_ptr.into(), ], - &bitcode::DICT_KEYS, + bitcode::DICT_KEYS, ); 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(), output_ptr.into(), ], - &bitcode::DICT_UNION, + bitcode::DICT_UNION, ); env.builder.build_load(output_ptr, "load_output_ptr") @@ -549,7 +549,7 @@ pub fn dict_difference<'a, 'ctx, 'env>( dict2, key_layout, value_layout, - &bitcode::DICT_DIFFERENCE, + bitcode::DICT_DIFFERENCE, ) } @@ -569,7 +569,7 @@ pub fn dict_intersection<'a, 'ctx, 'env>( dict2, key_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), 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") @@ -721,7 +721,7 @@ pub fn dict_values<'a, 'ctx, 'env>( inc_value_fn.as_global_value().as_pointer_value().into(), list_ptr.into(), ], - &bitcode::DICT_VALUES, + bitcode::DICT_VALUES, ); 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(), result_alloca.into(), ], - &bitcode::SET_FROM_LIST, + bitcode::SET_FROM_LIST, ); 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 fn_name = layout_ids - .get(symbol, &layout) + .get(symbol, layout) .to_symbol_string(symbol, &env.interns); 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(); - complex_bitcast(&env.builder, dict, zig_dict_type.into(), "dict_to_zig_dict") - .into_struct_value() + complex_bitcast(env.builder, dict, zig_dict_type.into(), "dict_to_zig_dict").into_struct_value() } fn zig_dict_type<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> inkwell::types::StructType<'ctx> { diff --git a/compiler/gen_llvm/src/llvm/build_hash.rs b/compiler/gen_llvm/src/llvm/build_hash.rs index 99af6b9e11..9337e25ee1 100644 --- a/compiler/gen_llvm/src/llvm/build_hash.rs +++ b/compiler/gen_llvm/src/llvm/build_hash.rs @@ -128,8 +128,9 @@ fn hash_builtin<'a, 'ctx, 'env>( | Builtin::Float32 | Builtin::Float128 | Builtin::Float16 + | Builtin::Decimal | 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)) } Builtin::Str => { @@ -137,7 +138,7 @@ fn hash_builtin<'a, 'ctx, 'env>( call_bitcode_fn( env, &[seed.into(), build_str::str_to_i128(env, val).into()], - &bitcode::DICT_HASH_STR, + bitcode::DICT_HASH_STR, ) .into_int_value() } @@ -326,7 +327,7 @@ fn build_hash_tag<'a, 'ctx, 'env>( let symbol = Symbol::GENERIC_HASH; let fn_name = layout_ids - .get(symbol, &layout) + .get(symbol, layout) .to_symbol_string(symbol, &env.interns); let function = match env.module.get_function(fn_name.as_str()) { @@ -334,7 +335,7 @@ fn build_hash_tag<'a, 'ctx, 'env>( None => { 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( env, @@ -658,7 +659,7 @@ fn build_hash_list<'a, 'ctx, 'env>( let symbol = Symbol::GENERIC_HASH; let fn_name = layout_ids - .get(symbol, &layout) + .get(symbol, layout) .to_symbol_string(symbol, &env.interns); let function = match env.module.get_function(fn_name.as_str()) { @@ -666,7 +667,7 @@ fn build_hash_list<'a, 'ctx, 'env>( None => { 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( env, @@ -869,7 +870,7 @@ fn store_and_use_as_u8_ptr<'a, 'ctx, 'env>( value: BasicValueEnum<'ctx>, layout: &Layout<'a>, ) -> 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"); env.builder.build_store(alloc, value); @@ -894,7 +895,7 @@ fn hash_bitcode_fn<'a, 'ctx, 'env>( call_bitcode_fn( env, &[seed.into(), buffer.into(), num_bytes.into()], - &bitcode::DICT_HASH, + bitcode::DICT_HASH, ) .into_int_value() } diff --git a/compiler/gen_llvm/src/llvm/build_list.rs b/compiler/gen_llvm/src/llvm/build_list.rs index 7aa7e07ead..8c9d28663a 100644 --- a/compiler/gen_llvm/src/llvm/build_list.rs +++ b/compiler/gen_llvm/src/llvm/build_list.rs @@ -94,7 +94,7 @@ pub fn list_single<'a, 'ctx, 'env>( pass_element_as_opaque(env, element), 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>, ) -> BasicValueEnum<'ctx> { let builder = env.builder; - let ctx = env.context; // Load the usize length from the 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 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, "new_list_length", ); @@ -152,7 +151,7 @@ pub fn list_prepend<'a, 'ctx, 'env>( let index_1_ptr = unsafe { builder.build_in_bounds_gep( clone_ptr, - &[ctx.i64_type().const_int(1_u64, false)], + &[env.ptr_int().const_int(1_u64, false)], "load_index", ) }; @@ -207,7 +206,7 @@ pub fn list_join<'a, 'ctx, 'env>( env.alignment_intvalue(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), layout_width(env, &element_layout), ], - &bitcode::LIST_REVERSE, + bitcode::LIST_REVERSE, ) } @@ -292,11 +291,11 @@ pub fn list_append<'a, 'ctx, 'env>( env, &[ pass_list_as_i128(env, original_wrapper.into()), - env.alignment_intvalue(&element_layout), + env.alignment_intvalue(element_layout), pass_element_as_opaque(env, element), layout_width(env, element_layout), ], - &bitcode::LIST_APPEND, + bitcode::LIST_APPEND, ) } @@ -312,12 +311,12 @@ pub fn list_swap<'a, 'ctx, 'env>( env, &[ pass_list_as_i128(env, original_wrapper.into()), - env.alignment_intvalue(&element_layout), - layout_width(env, &element_layout), + env.alignment_intvalue(element_layout), + layout_width(env, element_layout), index_1.into(), index_2.into(), ], - &bitcode::LIST_SWAP, + bitcode::LIST_SWAP, ) } @@ -329,17 +328,17 @@ pub fn list_drop<'a, 'ctx, 'env>( count: IntValue<'ctx>, element_layout: &Layout<'a>, ) -> 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( env, &[ pass_list_as_i128(env, original_wrapper.into()), - env.alignment_intvalue(&element_layout), - layout_width(env, &element_layout), + env.alignment_intvalue(element_layout), + layout_width(env, element_layout), count.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(), length.into(), - env.alignment_intvalue(&element_layout), + env.alignment_intvalue(element_layout), index.into(), pass_element_as_opaque(env, element), 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.data_is_owned.into(), pass_as_opaque(env, default_ptr), - env.alignment_intvalue(&element_layout), + env.alignment_intvalue(element_layout), layout_width(env, element_layout), layout_width(env, default_layout), 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.data_is_owned.into(), pass_as_opaque(env, default_ptr), - env.alignment_intvalue(&element_layout), + env.alignment_intvalue(element_layout), layout_width(env, element_layout), layout_width(env, function_call_return_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, 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), roc_function_call.inc_n_data.into(), roc_function_call.data_is_owned.into(), - env.alignment_intvalue(&element_layout), + env.alignment_intvalue(element_layout), layout_width(env, element_layout), inc_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), roc_function_call.inc_n_data.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, result_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), roc_function_call.inc_n_data.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, result_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), roc_function_call.inc_n_data.into(), roc_function_call.data_is_owned.into(), - env.alignment_intvalue(&element_layout), + env.alignment_intvalue(element_layout), layout_width(env, element_layout), ], 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), roc_function_call.inc_n_data.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, return_layout), ], @@ -772,7 +771,7 @@ pub fn list_map<'a, 'ctx, 'env>( pass_as_opaque(env, roc_function_call.data), roc_function_call.inc_n_data.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, return_layout), ], @@ -874,7 +873,7 @@ pub fn list_concat<'a, 'ctx, 'env>( env.alignment_intvalue(elem_layout), layout_width(env, elem_layout), ], - &bitcode::LIST_CONCAT, + bitcode::LIST_CONCAT, ), _ => { unreachable!("Invalid List layout for List.concat {:?}", list_layout); diff --git a/compiler/gen_llvm/src/llvm/build_str.rs b/compiler/gen_llvm/src/llvm/build_str.rs index ee8576ceb8..b7bc32def6 100644 --- a/compiler/gen_llvm/src/llvm/build_str.rs +++ b/compiler/gen_llvm/src/llvm/build_str.rs @@ -27,7 +27,7 @@ pub fn str_split<'a, 'ctx, 'env>( let segment_count = call_bitcode_fn( env, &[str_i128.into(), delim_i128.into()], - &bitcode::STR_COUNT_SEGMENTS, + bitcode::STR_COUNT_SEGMENTS, ) .into_int_value(); @@ -47,7 +47,7 @@ pub fn str_split<'a, 'ctx, 'env>( call_void_bitcode_fn( env, &[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) @@ -62,7 +62,7 @@ fn str_symbol_to_i128<'a, 'ctx, 'env>( 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>( @@ -119,7 +119,7 @@ pub fn str_concat<'a, 'ctx, 'env>( call_bitcode_fn( env, &[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( env, &[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 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 env.builder @@ -171,7 +171,7 @@ pub fn str_starts_with<'a, 'ctx, 'env>( call_bitcode_fn( env, &[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( env, &[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( env, &[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( env, &[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> { 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 @@ -247,7 +247,7 @@ pub fn str_to_bytes<'a, 'ctx, 'env>( "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 } @@ -273,7 +273,7 @@ pub fn str_from_utf8<'a, 'ctx, 'env>( ), result_ptr.into(), ], - &bitcode::STR_FROM_UTF8, + bitcode::STR_FROM_UTF8, ); let record_type = env.context.struct_type( @@ -306,7 +306,7 @@ pub fn str_from_float<'a, 'ctx, 'env>( ) -> BasicValueEnum<'ctx> { 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 @@ -321,7 +321,7 @@ pub fn str_equal<'a, 'ctx, 'env>( call_bitcode_fn( env, &[str1_i128.into(), str2_i128.into()], - &bitcode::STR_EQUAL, + bitcode::STR_EQUAL, ) } diff --git a/compiler/gen_llvm/src/llvm/compare.rs b/compiler/gen_llvm/src/llvm/compare.rs index ffc0b04e99..4d0a70763c 100644 --- a/compiler/gen_llvm/src/llvm/compare.rs +++ b/compiler/gen_llvm/src/llvm/compare.rs @@ -1,5 +1,7 @@ -use crate::llvm::build::{cast_block_of_memory_to_tag, get_tag_id, FAST_CALL_CONV}; -use crate::llvm::build::{tag_pointer_clear_tag_id, Env}; +use crate::llvm::bitcode::call_bitcode_fn; +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_str::str_equal; use crate::llvm::convert::basic_type_from_layout; @@ -9,6 +11,7 @@ use inkwell::values::{ BasicValue, BasicValueEnum, FunctionValue, IntValue, PointerValue, StructValue, }; use inkwell::{AddressSpace, FloatPredicate, IntPredicate}; +use roc_builtins::bitcode; use roc_module::symbol::Symbol; 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::Decimal => call_bitcode_fn(env, &[lhs_val, rhs_val], bitcode::DEC_EQ), Builtin::Float128 => float_cmp(FloatPredicate::OEQ, "eq_f128"), Builtin::Float64 => float_cmp(FloatPredicate::OEQ, "eq_f64"), 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::Decimal => call_bitcode_fn(env, &[lhs_val, rhs_val], bitcode::DEC_NEQ), Builtin::Float128 => float_cmp(FloatPredicate::ONE, "neq_f128"), Builtin::Float64 => float_cmp(FloatPredicate::ONE, "neq_f64"), 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 fn_name = layout_ids - .get(symbol, &element_layout) + .get(symbol, element_layout) .to_symbol_string(symbol, &env.interns); let function = match env.module.get_function(fn_name.as_str()) { Some(function_value) => function_value, 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( env, @@ -423,7 +428,7 @@ fn build_list_eq_help<'a, 'ctx, 'env>( /* current_scope */ lexical_block.as_debug_info_scope(), /* inlined_at */ None, ); - builder.set_current_debug_location(&ctx, loc); + builder.set_current_debug_location(ctx, loc); } // Add args to scope @@ -631,7 +636,7 @@ fn build_struct_eq_help<'a, 'ctx, 'env>( /* current_scope */ lexical_block.as_debug_info_scope(), /* inlined_at */ None, ); - builder.set_current_debug_location(&ctx, loc); + builder.set_current_debug_location(ctx, loc); } // Add args to scope @@ -747,13 +752,13 @@ fn build_tag_eq<'a, 'ctx, 'env>( let symbol = Symbol::GENERIC_EQ; let fn_name = layout_ids - .get(symbol, &tag_layout) + .get(symbol, tag_layout) .to_symbol_string(symbol, &env.interns); let function = match env.module.get_function(fn_name.as_str()) { Some(function_value) => function_value, 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( env, @@ -812,7 +817,7 @@ fn build_tag_eq_help<'a, 'ctx, 'env>( /* current_scope */ lexical_block.as_debug_info_scope(), /* inlined_at */ None, ); - builder.set_current_debug_location(&ctx, loc); + builder.set_current_debug_location(ctx, loc); } // Add args to scope diff --git a/compiler/gen_llvm/src/llvm/convert.rs b/compiler/gen_llvm/src/llvm/convert.rs index 729c118626..df4a074e68 100644 --- a/compiler/gen_llvm/src/llvm/convert.rs +++ b/compiler/gen_llvm/src/llvm/convert.rs @@ -58,8 +58,7 @@ pub fn basic_type_from_layout<'a, 'ctx, 'env>( } } NullableUnwrapped { other_fields, .. } => { - let block = - block_of_memory_slices(env.context, &[&other_fields], env.ptr_bytes); + let block = block_of_memory_slices(env.context, &[other_fields], env.ptr_bytes); block.ptr_type(AddressSpace::Generic).into() } NonNullableUnwrapped(fields) => { @@ -97,6 +96,7 @@ pub fn basic_type_from_builtin<'a, 'ctx, 'env>( Int8 => context.i8_type().as_basic_type_enum(), Int1 => context.bool_type().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(), Float64 => context.f64_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> { 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() +} diff --git a/compiler/gen_llvm/src/llvm/refcounting.rs b/compiler/gen_llvm/src/llvm/refcounting.rs index 9c09469a97..e474dd8651 100644 --- a/compiler/gen_llvm/src/llvm/refcounting.rs +++ b/compiler/gen_llvm/src/llvm/refcounting.rs @@ -720,14 +720,14 @@ fn modify_refcount_list<'a, 'ctx, 'env>( &env.interns, "increment_list", "decrement_list", - &layout, + layout, mode, ); let function = match env.module.get_function(fn_name.as_str()) { Some(function_value) => function_value, 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); modify_refcount_list_help( @@ -857,14 +857,14 @@ fn modify_refcount_str<'a, 'ctx, 'env>( &env.interns, "increment_str", "decrement_str", - &layout, + layout, mode, ); let function = match env.module.get_function(fn_name.as_str()) { Some(function_value) => function_value, 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); modify_refcount_str_help(env, mode, layout, function_value); @@ -956,14 +956,14 @@ fn modify_refcount_dict<'a, 'ctx, 'env>( &env.interns, "increment_dict", "decrement_dict", - &layout, + layout, mode, ); let function = match env.module.get_function(fn_name.as_str()) { Some(function_value) => function_value, 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); 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. ); - let subprogram = env.new_subprogram(&fn_name); + let subprogram = env.new_subprogram(fn_name); fn_val.set_subprogram(subprogram); env.dibuilder.finalize(); diff --git a/compiler/load/src/docs.rs b/compiler/load/src/docs.rs index 1fa5db439d..4a17ba31b4 100644 --- a/compiler/load/src/docs.rs +++ b/compiler/load/src/docs.rs @@ -148,7 +148,7 @@ fn generate_entry_doc<'a>( match def { 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) { acc.push(DetachedDoc(detached_doc)); diff --git a/compiler/load/src/file.rs b/compiler/load/src/file.rs index fd8fd1dc4b..2f4caf27b9 100644 --- a/compiler/load/src/file.rs +++ b/compiler/load/src/file.rs @@ -225,7 +225,7 @@ impl<'a> Dependencies<'a> { if let Some(to_notify) = self.notifies.get(&key) { for notify_key in to_notify { let mut is_empty = false; - if let Some(waiting_for_pairs) = self.waiting_for.get_mut(¬ify_key) { + if let Some(waiting_for_pairs) = self.waiting_for.get_mut(notify_key) { waiting_for_pairs.remove(&key); is_empty = waiting_for_pairs.is_empty(); } @@ -469,7 +469,7 @@ fn start_phase<'a>( for dep_id in deps_by_name.values() { // We already verified that these are all present, // 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()); } @@ -524,6 +524,7 @@ fn start_phase<'a>( var_store, imported_modules, declarations, + dep_idents, .. } = constrained; @@ -535,7 +536,8 @@ fn start_phase<'a>( var_store, imported_modules, &mut state.exposed_types, - &state.stdlib, + state.stdlib, + dep_idents, declarations, ) } @@ -621,6 +623,7 @@ pub struct LoadedModule { pub type_problems: MutMap>, pub declarations_by_id: MutMap>, pub exposed_to_host: MutMap, + pub dep_idents: MutMap, pub exposed_aliases: MutMap, pub exposed_values: Vec, pub header_sources: MutMap)>, @@ -676,6 +679,7 @@ struct ConstrainedModule { constraint: Constraint, ident_ids: IdentIds, var_store: VarStore, + dep_idents: MutMap, module_timing: ModuleTiming, } @@ -759,6 +763,7 @@ enum Msg<'a> { solved_module: SolvedModule, solved_subs: Solved, decls: Vec, + dep_idents: MutMap, module_timing: ModuleTiming, unused_imports: MutMap, }, @@ -767,6 +772,7 @@ enum Msg<'a> { exposed_vars_by_symbol: MutMap, exposed_aliases_by_symbol: MutMap, exposed_values: Vec, + dep_idents: MutMap, documentation: MutMap, }, FoundSpecializations { @@ -985,6 +991,7 @@ enum BuildTask<'a> { constraint: Constraint, var_store: VarStore, declarations: Vec, + dep_idents: MutMap, unused_imports: MutMap, }, BuildPendingSpecializations { @@ -1516,6 +1523,7 @@ where exposed_vars_by_symbol, exposed_aliases_by_symbol, exposed_values, + dep_idents, documentation, } => { // We're done! There should be no more messages pending. @@ -1534,6 +1542,7 @@ where exposed_values, exposed_aliases_by_symbol, exposed_vars_by_symbol, + dep_idents, documentation, ))); } @@ -1635,7 +1644,7 @@ fn start_tasks<'a>( ) -> Result<(), LoadingProblem<'a>> { for (module_id, phase) in work { 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); - 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); - start_tasks(arena, &mut state, work, &injector, worker_listeners)?; + start_tasks(arena, &mut state, work, injector, worker_listeners)?; Ok(state) } @@ -1796,7 +1805,7 @@ fn update<'a>( 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) } @@ -1831,7 +1840,7 @@ fn update<'a>( .dependencies .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) } @@ -1882,7 +1891,7 @@ fn update<'a>( .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) } @@ -1892,6 +1901,7 @@ fn update<'a>( solved_module, solved_subs, decls, + dep_idents, mut module_timing, mut unused_imports, } => { @@ -1949,6 +1959,7 @@ fn update<'a>( exposed_vars_by_symbol: solved_module.exposed_vars_by_symbol, exposed_values: solved_module.exposed_symbols, exposed_aliases_by_symbol: solved_module.aliases, + dep_idents, documentation, }) .map_err(|_| LoadingProblem::MsgChannelDied)?; @@ -1986,7 +1997,7 @@ fn update<'a>( 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) @@ -2041,7 +2052,7 @@ fn update<'a>( .dependencies .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) } @@ -2143,7 +2154,7 @@ fn update<'a>( existing.extend(requested); } - start_tasks(arena, &mut state, work, &injector, worker_listeners)?; + start_tasks(arena, &mut state, work, injector, worker_listeners)?; } Ok(state) @@ -2283,6 +2294,7 @@ fn finish( exposed_values: Vec, exposed_aliases_by_symbol: MutMap, exposed_vars_by_symbol: MutMap, + dep_idents: MutMap, documentation: MutMap, ) -> LoadedModule { let module_ids = Arc::try_unwrap(state.arc_modules) @@ -2316,6 +2328,7 @@ fn finish( can_problems: state.module_cache.can_problems, type_problems: state.module_cache.type_problems, declarations_by_id: state.declarations_by_id, + dep_idents, exposed_aliases: exposed_aliases_by_symbol, exposed_values, 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 bytes = arena.alloc(bytes_vec); 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(); // Insert the first entries for this module's timings @@ -2518,7 +2531,7 @@ fn parse_header<'a>( ) -> Result<(ModuleId, Msg<'a>), LoadingProblem<'a>> { let parse_start = SystemTime::now(); 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(); // 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( arena, - &"", + "", module_ids, ident_ids_by_module, header, @@ -2825,7 +2838,7 @@ fn send_header<'a>( let name = match opt_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()), }; @@ -2901,13 +2914,13 @@ fn send_header<'a>( } if cfg!(debug_assertions) { - home.register_debug_idents(&ident_ids); + home.register_debug_idents(ident_ids); } 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(); 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 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); // 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) { - home.register_debug_idents(&ident_ids); + home.register_debug_idents(ident_ids); } 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(); while let Some(parse_entry) = parse_entries.pop() { @@ -3234,6 +3247,7 @@ impl<'a> BuildTask<'a> { imported_modules: MutMap, exposed_types: &mut SubsByModule, stdlib: &StdLib, + dep_idents: MutMap, declarations: Vec, ) -> Self { let home = module.module_id; @@ -3261,6 +3275,7 @@ impl<'a> BuildTask<'a> { constraint, var_store, declarations, + dep_idents, module_timing, unused_imports, } @@ -3276,6 +3291,7 @@ fn run_solve<'a>( constraint: Constraint, mut var_store: VarStore, decls: Vec, + dep_idents: MutMap, unused_imports: MutMap, ) -> Msg<'a> { // 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, ident_ids, decls, + dep_idents, solved_module, module_timing, unused_imports, @@ -3390,7 +3407,7 @@ fn fabricate_effects_module<'a>( 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 declared_name: ModuleName = name.into(); @@ -3464,7 +3481,7 @@ fn fabricate_effects_module<'a>( } if cfg!(debug_assertions) { - module_id.register_debug_idents(&ident_ids); + module_id.register_debug_idents(ident_ids); } ident_ids.clone() @@ -3478,7 +3495,8 @@ fn fabricate_effects_module<'a>( 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 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 .introduce( @@ -3611,6 +3629,7 @@ fn fabricate_effects_module<'a>( var_store, constraint, ident_ids: module_output.ident_ids, + dep_idents, module_timing, }; @@ -3685,12 +3704,12 @@ where let mut var_store = VarStore::default(); let canonicalized = canonicalize_module_defs( - &arena, + arena, parsed_defs, module_id, module_ids, exposed_ident_ids, - dep_idents, + &dep_idents, aliases, exposed_imports, &exposed_symbols, @@ -3712,7 +3731,7 @@ where module_output.scope, name.as_str().into(), &module_output.ident_ids, - &parsed_defs, + parsed_defs, )), }; @@ -3738,6 +3757,7 @@ where var_store, constraint, ident_ids: module_output.ident_ids, + dep_idents, module_timing, }; @@ -3761,7 +3781,7 @@ fn parse<'a>(arena: &'a Bump, header: ModuleHeader<'a>) -> Result, Loadi let parse_start = SystemTime::now(); let source = header.parse_state.bytes; 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, Err((_, fail, _)) => { return Err(LoadingProblem::ParsingFailed(fail.into_parse_problem( @@ -4210,6 +4230,7 @@ where var_store, ident_ids, declarations, + dep_idents, unused_imports, } => Ok(run_solve( module, @@ -4219,6 +4240,7 @@ where constraint, var_store, declarations, + dep_idents, unused_imports, )), BuildPendingSpecializations { diff --git a/compiler/load/tests/test_load.rs b/compiler/load/tests/test_load.rs index fe2c1caf7e..6630bcede1 100644 --- a/compiler/load/tests/test_load.rs +++ b/compiler/load/tests/test_load.rs @@ -184,12 +184,11 @@ mod test_load { expected_types: &mut HashMap<&str, &str>, ) { for (symbol, expr_var) in &def.pattern_vars { - let content = subs.get(*expr_var).content; - name_all_type_vars(*expr_var, subs); - let actual_str = content_to_string(content, subs, home, &interns); - let fully_qualified = symbol.fully_qualified(&interns, home).to_string(); + let content = subs.get_content_without_compacting(*expr_var); + 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 .remove(fully_qualified.as_str()) .unwrap_or_else(|| { diff --git a/compiler/module/src/symbol.rs b/compiler/module/src/symbol.rs index d107bdffe4..41d1c57316 100644 --- a/compiler/module/src/symbol.rs +++ b/compiler/module/src/symbol.rs @@ -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))] fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.0.fmt(f) @@ -368,7 +368,7 @@ impl<'a> PackageModuleIds<'a> { self.by_name.insert(module_name.clone(), module_id); if cfg!(debug_assertions) { - Self::insert_debug_name(module_id, &module_name); + Self::insert_debug_name(module_id, module_name); } module_id @@ -449,7 +449,7 @@ impl ModuleIds { self.by_name.insert(module_name.clone(), module_id); if cfg!(debug_assertions) { - Self::insert_debug_name(module_id, &module_name); + Self::insert_debug_name(module_id, module_name); } module_id @@ -594,13 +594,16 @@ macro_rules! define_builtins { $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); - 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 { @@ -771,6 +774,9 @@ define_builtins! { // a caller (wrapper) for comparison 21 GENERIC_COMPARE_REF: "#generic_compare_ref" + + // used to initialize parameters in borrow.rs + 22 EMPTY_PARAM: "#empty_param" } 1 NUM: "Num" => { 0 NUM_NUM: "Num" imported // the Num.Num type alias @@ -873,6 +879,9 @@ define_builtins! { 97 NUM_INT_CAST: "intCast" 98 NUM_MAX_I128: "maxI128" 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" => { diff --git a/compiler/mono/Cargo.toml b/compiler/mono/Cargo.toml index cd0e0e6d77..9bd2127016 100644 --- a/compiler/mono/Cargo.toml +++ b/compiler/mono/Cargo.toml @@ -19,6 +19,7 @@ morphic_lib = { path = "../../vendor/morphic_lib" } bumpalo = { version = "3.6.1", features = ["collections"] } hashbrown = { version = "0.11.2", features = [ "bumpalo" ] } ven_ena = { path = "../../vendor/ena" } +ven_graph = { path = "../../vendor/pathfinding" } linked-hash-map = "0.5.4" [dev-dependencies] diff --git a/compiler/mono/src/alias_analysis.rs b/compiler/mono/src/alias_analysis.rs index e8b04ca1e9..878cf7e3e2 100644 --- a/compiler/mono/src/alias_analysis.rs +++ b/compiler/mono/src/alias_analysis.rs @@ -842,7 +842,19 @@ fn lowlevel_spec( 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 => { let list = env.symbols[&arguments[0]]; @@ -853,9 +865,11 @@ fn lowlevel_spec( let _unit = builder.add_update(block, update_mode_var, cell)?; + // TODO new heap cell 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 => { match layout { @@ -887,7 +901,6 @@ fn lowlevel_spec( let cell = builder.add_get_tuple_field(block, dict, DICT_CELL_INDEX)?; let _unit = builder.add_touch(block, cell)?; - builder.add_bag_get(block, bag) } DictInsert => { @@ -904,7 +917,8 @@ fn lowlevel_spec( 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 => { // println!("missing {:?}", _other); @@ -1094,7 +1108,7 @@ fn expr_spec<'a>( let index = (*index) as u32; 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 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 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 variant_id = @@ -1220,7 +1234,7 @@ fn layout_spec_help( | UnionLayout::NullableUnwrapped { .. } | UnionLayout::NullableWrapped { .. } | 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); Ok(builder.add_named_type(MOD_APP, type_name)) @@ -1261,7 +1275,7 @@ fn builtin_spec( match builtin { 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), Dict(key_layout, value_layout) => { let value_type = layout_spec_help(builder, value_layout, when_recursive)?; diff --git a/compiler/mono/src/borrow.rs b/compiler/mono/src/borrow.rs index be0aa9bb1d..5757341f9e 100644 --- a/compiler/mono/src/borrow.rs +++ b/compiler/mono/src/borrow.rs @@ -2,7 +2,7 @@ use crate::ir::{Expr, JoinPointId, Param, Proc, ProcLayout, Stmt}; use crate::layout::Layout; use bumpalo::collections::Vec; 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::symbol::Symbol; @@ -20,8 +20,16 @@ pub fn infer_borrow<'a>( arena: &'a Bump, procs: &MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>, ) -> ParamMap<'a> { - let mut param_map = ParamMap { - items: MutMap::default(), + // intern the layouts + + 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 { @@ -33,82 +41,225 @@ pub fn infer_borrow<'a>( param_set: MutSet::default(), owned: MutMap::default(), modified: false, - param_map, arena, }; - // This is a fixed-point analysis - // - // all functions initiall own all their parameters - // through a series of checks and heuristics, some arguments are set to borrowed - // when that doesn't lead to conflicts the change is kept, otherwise it may be reverted - // - // when the signatures no longer change, the analysis stops and returns the signatures - loop { - // sort the symbols (roughly) in definition order. - // TODO in the future I think we need to do this properly, and group - // mutually recursive functions (or just make all their arguments owned) + // 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) - for (key, proc) in procs { - env.collect_proc(proc, key.1); - } + let successor_map = &make_successor_mapping(arena, procs); + let successors = move |key: &Symbol| successor_map[key].iter().copied(); - if !env.modified { - // if there were no modifications, we're done - break; - } else { - // otherwise see if there are changes after another iteration - env.modified = false; + 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); } } - env.param_map + 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 + // + // all functions initiall own all their parameters + // through a series of checks and heuristics, some arguments are set to borrowed + // when that doesn't lead to conflicts the change is kept, otherwise it may be reverted + // + // when the signatures no longer change, the analysis stops and returns the signatures + loop { + for (proc, param_offset) in group.iter() { + env.collect_proc(&mut param_map, proc, *param_offset); + } + + if !env.modified { + // if there were no modifications, we're done + break; + } else { + // otherwise see if there are changes after another iteration + env.modified = false; + } + } + } + + param_map } -#[derive(Debug, PartialEq, Eq, Hash, Clone)] -pub enum Key<'a> { - Declaration(Symbol, ProcLayout<'a>), - JoinPoint(JoinPointId), +#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] +pub struct ParamOffset(usize); + +impl From 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> { - items: MutMap, &'a [Param<'a>]>, -} - -impl<'a> IntoIterator for ParamMap<'a> { - type Item = (Key<'a>, &'a [Param<'a>]); - type IntoIter = , &'a [Param<'a>]> as IntoIterator>::IntoIter; - - fn into_iter(self) -> Self::IntoIter { - self.items.into_iter() - } -} - -impl<'a> IntoIterator for &'a ParamMap<'a> { - type Item = (&'a Key<'a>, &'a &'a [Param<'a>]); - type IntoIter = - <&'a std::collections::HashMap, &'a [Param<'a>]> as IntoIterator>::IntoIter; - - fn into_iter(self) -> Self::IntoIter { - self.items.iter() - } + /// 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. + /// + /// - the map above gives the index of the first parameter for the function + /// - the length of the ProcLayout's argument field gives the total number of parameters + /// + /// These can be read by taking a slice into this array, and can also be updated in-place + declarations: Vec<'a, Param<'a>>, + join_points: MutMap]>, } impl<'a> ParamMap<'a> { - pub fn get_symbol(&self, symbol: Symbol, layout: ProcLayout<'a>) -> Option<&'a [Param<'a>]> { - let key = Key::Declaration(symbol, layout); - - self.items.get(&key).copied() + pub fn get_param_offset(&self, symbol: Symbol, layout: ProcLayout<'a>) -> ParamOffset { + self.declaration_to_index.get_param_offset(symbol, layout) } - 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, None => unreachable!("join point not in param map: {:?}", id), } } + + pub fn iter_symbols(&'a self) -> impl Iterator { + self.declaration_to_index.elements.iter().map(|t| &t.0 .0) + } } impl<'a> ParamMap<'a> { @@ -156,11 +307,16 @@ impl<'a> ParamMap<'a> { self.visit_proc_always_owned(arena, proc, key); return; } - let already_in_there = self.items.insert( - Key::Declaration(proc.name, key.1), - Self::init_borrow_args(arena, proc.args), - ); - debug_assert!(already_in_there.is_none()); + + let index: usize = self.get_param_offset(key.0, key.1).into(); + + for (i, param) in Self::init_borrow_args(arena, proc.args) + .iter() + .copied() + .enumerate() + { + self.declarations[index + i] = param; + } self.visit_stmt(arena, proc.name, &proc.body); } @@ -171,11 +327,15 @@ impl<'a> ParamMap<'a> { proc: &Proc<'a>, key: (Symbol, ProcLayout<'a>), ) { - let already_in_there = self.items.insert( - Key::Declaration(proc.name, key.1), - Self::init_borrow_args_always_owned(arena, proc.args), - ); - debug_assert!(already_in_there.is_none()); + let index: usize = self.get_param_offset(key.0, key.1).into(); + + for (i, param) in Self::init_borrow_args_always_owned(arena, proc.args) + .iter() + .copied() + .enumerate() + { + self.declarations[index + i] = param; + } self.visit_stmt(arena, proc.name, &proc.body); } @@ -193,14 +353,8 @@ impl<'a> ParamMap<'a> { remainder: v, body: b, } => { - let already_in_there = self - .items - .insert(Key::JoinPoint(*j), Self::init_borrow_params(arena, xs)); - debug_assert!( - already_in_there.is_none(), - "join point {:?} is already defined!", - j - ); + self.join_points + .insert(*j, Self::init_borrow_params(arena, xs)); stack.push(v); stack.push(b); @@ -237,7 +391,6 @@ struct BorrowInfState<'a> { param_set: MutSet, owned: MutMap>, modified: bool, - param_map: ParamMap<'a>, arena: &'a Bump, } @@ -245,14 +398,30 @@ impl<'a> BorrowInfState<'a> { pub fn own_var(&mut self, x: Symbol) { let current = self.owned.get_mut(&self.current_proc).unwrap(); - if current.contains(&x) { - // do nothing - } else { - current.insert(x); + if current.insert(x) { + // entered if key was not yet present. If so, the set is modified, + // hence we set this flag 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 { match self.owned.get(&self.current_proc) { None => unreachable!( @@ -263,30 +432,52 @@ impl<'a> BorrowInfState<'a> { } } - fn update_param_map(&mut self, k: Key<'a>) { - let arena = self.arena; - if let Some(ps) = self.param_map.items.get(&k) { - let ps = Vec::from_iter_in( - ps.iter().map(|p| { - if !p.borrow { - p.clone() - } else if self.is_owned(p.symbol) { - self.modified = true; - let mut p = p.clone(); - p.borrow = false; + fn update_param_map_help(&mut self, ps: &[Param<'a>]) -> &'a [Param<'a>] { + let mut new_ps = Vec::with_capacity_in(ps.len(), self.arena); + new_ps.extend(ps.iter().map(|p| { + if !p.borrow { + *p + } else if self.is_owned(p.symbol) { + self.modified = true; + let mut p = *p; + p.borrow = false; - p - } else { - p.clone() - } - }), - arena, - ); + p + } else { + *p + } + })); - 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` /// If the parameter (based on the definition of `f`) is owned, /// then the argument must also be owned @@ -345,13 +536,13 @@ impl<'a> BorrowInfState<'a> { } } - /// This looks at the assignement + /// This looks at the assignment /// /// let z = e in ... /// /// and determines whether z and which of the symbols used in e /// 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::*; let crate::ir::Call { @@ -369,8 +560,7 @@ impl<'a> BorrowInfState<'a> { let top_level = ProcLayout::new(self.arena, arg_layouts, *ret_layout); // get the borrow signature of the applied function - let ps = self - .param_map + let ps = param_map .get_symbol(*name, top_level) .expect("function is defined"); @@ -386,6 +576,7 @@ impl<'a> BorrowInfState<'a> { ps.len(), arguments.len() ); + self.own_args_using_params(arguments, ps); } @@ -416,7 +607,7 @@ impl<'a> BorrowInfState<'a> { match op { 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) => { // own the list if the function wants to own the element if !function_ps[0].borrow { @@ -432,7 +623,7 @@ impl<'a> BorrowInfState<'a> { } } ListMapWithIndex => { - match self.param_map.get_symbol(arguments[1], closure_layout) { + match param_map.get_symbol(arguments[1], closure_layout) { Some(function_ps) => { // own the list if the function wants to own the element if !function_ps[1].borrow { @@ -447,7 +638,7 @@ impl<'a> BorrowInfState<'a> { 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) => { // own the lists if the function wants to own the element if !function_ps[0].borrow { @@ -465,7 +656,7 @@ impl<'a> BorrowInfState<'a> { } 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) => { // own the lists if the function wants to own the element if !function_ps[0].borrow { @@ -486,7 +677,7 @@ impl<'a> BorrowInfState<'a> { None => unreachable!(), }, ListSortWith => { - match self.param_map.get_symbol(arguments[1], closure_layout) { + match param_map.get_symbol(arguments[1], closure_layout) { Some(function_ps) => { // always own the input list self.own_var(arguments[0]); @@ -500,7 +691,7 @@ impl<'a> BorrowInfState<'a> { } } 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) => { // own the data structure if the function wants to own the element 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::*; match e { @@ -570,50 +761,44 @@ impl<'a> BorrowInfState<'a> { self.own_var(z); } - Call(call) => self.collect_call(z, call), + Call(call) => self.collect_call(param_map, z, call), Literal(_) | RuntimeErrorFunction(_) => {} StructAtIndex { structure: x, .. } => { // if the structure (record/tag/array) is owned, the extracted value is - if self.is_owned(*x) { - self.own_var(z); - } + self.if_is_owned_then_own(*x, z); // if the extracted value is owned, the structure must be too - if self.is_owned(z) { - self.own_var(*x); - } + self.if_is_owned_then_own(z, *x); } UnionAtIndex { structure: x, .. } => { // if the structure (record/tag/array) is owned, the extracted value is - if self.is_owned(*x) { - self.own_var(z); - } + self.if_is_owned_then_own(*x, z); // if the extracted value is owned, the structure must be too - if self.is_owned(z) { - self.own_var(*x); - } + self.if_is_owned_then_own(z, *x); } GetTagId { structure: x, .. } => { // if the structure (record/tag/array) is owned, the extracted value is - if self.is_owned(*x) { - self.own_var(z); - } + self.if_is_owned_then_own(*x, z); // if the extracted value is owned, the structure must be too - if self.is_owned(z) { - self.own_var(*x); - } + self.if_is_owned_then_own(z, *x); } } } #[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 ( Expr::Call(crate::ir::Call { call_type: @@ -634,7 +819,7 @@ impl<'a> BorrowInfState<'a> { if self.current_proc == *g && x == *z { // anonymous functions (for which the ps may not be known) // 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) } } @@ -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::*; match stmt { @@ -665,17 +850,35 @@ impl<'a> BorrowInfState<'a> { } => { let old = self.param_set.clone(); self.update_param_set(ys); - self.collect_stmt(v); + self.collect_stmt(param_map, v); 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) => { - self.collect_stmt(b); - self.collect_expr(*x, v); - self.preserve_tail_call(*x, v, b); + Let(x, v, _, mut b) => { + let mut stack = Vec::new_in(self.arena); + + 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 { @@ -686,17 +889,17 @@ impl<'a> BorrowInfState<'a> { fail, exception_id: _, } => { - self.collect_stmt(pass); - self.collect_stmt(fail); + self.collect_stmt(param_map, pass); + 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? // self.preserve_tail_call(*x, v, b); } 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 self.own_args_using_params(ys, ps); @@ -710,9 +913,9 @@ impl<'a> BorrowInfState<'a> { .. } => { 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"), @@ -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 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 self.owned.entry(proc.name).or_default(); - self.collect_stmt(&proc.body); - self.update_param_map(Key::Declaration(proc.name, layout)); + self.collect_stmt(param_map, &proc.body); + self.update_param_map_declaration(param_map, param_offset, proc.args.len()); self.param_set = old; } @@ -827,3 +1035,87 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] { ExpectTrue => arena.alloc_slice_copy(&[irrelevant]), } } + +fn make_successor_mapping<'a>( + arena: &'a Bump, + procs: &MutMap<(Symbol, ProcLayout<'_>), Proc<'a>>, +) -> MutMap> { + 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 + } + } + } +} diff --git a/compiler/mono/src/decision_tree.rs b/compiler/mono/src/decision_tree.rs index ef6a508c54..67a9ab8bca 100644 --- a/compiler/mono/src/decision_tree.rs +++ b/compiler/mono/src/decision_tree.rs @@ -924,7 +924,7 @@ fn pick_path<'a>(branches: &'a [Branch]) -> &'a Vec { for (path, pattern) in &branch.patterns { // NOTE we no longer check for the guard here // if !branch.guard.is_none() || needs_tests(&pattern) { - if needs_tests(&pattern) { + if needs_tests(pattern) { all_paths.push(path); } else { // do nothing @@ -996,7 +996,7 @@ where let mut min_paths = vec![first_path]; for path in all_paths { - let weight = small_defaults(branches, &path); + let weight = small_defaults(branches, path); use std::cmp::Ordering; match weight.cmp(&min_weight) { @@ -1217,15 +1217,9 @@ fn test_to_equality<'a>( cond_layout: &Layout<'a>, path: &[PathInstruction], test: Test<'a>, -) -> ( - StoresVec<'a>, - Symbol, - Symbol, - Layout<'a>, - Option>, -) { +) -> (StoresVec<'a>, Symbol, Symbol, Option>) { 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 { Test::IsCtor { tag_id, union, .. } => { @@ -1255,7 +1249,6 @@ fn test_to_equality<'a>( stores, lhs_symbol, rhs_symbol, - Layout::Builtin(Builtin::Int64), Some(ConstructorKnown::OnlyPass { scrutinee: path_symbol, layout: *cond_layout, @@ -1273,13 +1266,7 @@ fn test_to_equality<'a>( let lhs_symbol = env.unique_symbol(); stores.push((lhs_symbol, Layout::Builtin(Builtin::Int64), lhs)); - ( - stores, - lhs_symbol, - rhs_symbol, - Layout::Builtin(Builtin::Int64), - None, - ) + (stores, lhs_symbol, rhs_symbol, None) } Test::IsFloat(test_int) => { @@ -1289,13 +1276,7 @@ fn test_to_equality<'a>( let lhs_symbol = env.unique_symbol(); stores.push((lhs_symbol, Layout::Builtin(Builtin::Float64), lhs)); - ( - stores, - lhs_symbol, - rhs_symbol, - Layout::Builtin(Builtin::Float64), - None, - ) + (stores, lhs_symbol, rhs_symbol, None) } Test::IsByte { @@ -1305,13 +1286,7 @@ fn test_to_equality<'a>( let lhs_symbol = env.unique_symbol(); stores.push((lhs_symbol, Layout::Builtin(Builtin::Int8), lhs)); - ( - stores, - lhs_symbol, - rhs_symbol, - Layout::Builtin(Builtin::Int8), - None, - ) + (stores, lhs_symbol, rhs_symbol, None) } Test::IsBit(test_bit) => { @@ -1319,13 +1294,7 @@ fn test_to_equality<'a>( let lhs_symbol = env.unique_symbol(); stores.push((lhs_symbol, Layout::Builtin(Builtin::Int1), lhs)); - ( - stores, - lhs_symbol, - rhs_symbol, - Layout::Builtin(Builtin::Int1), - None, - ) + (stores, lhs_symbol, rhs_symbol, None) } Test::IsStr(test_str) => { @@ -1334,13 +1303,7 @@ fn test_to_equality<'a>( stores.push((lhs_symbol, Layout::Builtin(Builtin::Str), lhs)); - ( - stores, - lhs_symbol, - rhs_symbol, - Layout::Builtin(Builtin::Str), - None, - ) + (stores, lhs_symbol, rhs_symbol, None) } } } @@ -1349,7 +1312,6 @@ type Tests<'a> = std::vec::Vec<( bumpalo::collections::Vec<'a, (Symbol, Layout<'a>, Expr<'a>)>, Symbol, Symbol, - Layout<'a>, Option>, )>; @@ -1363,13 +1325,7 @@ fn stores_and_condition<'a>( // Assumption: there is at most 1 guard, and it is the outer layer. for (path, test) in test_chain { - tests.push(test_to_equality( - env, - cond_symbol, - &cond_layout, - &path, - test, - )) + tests.push(test_to_equality(env, cond_symbol, cond_layout, &path, test)) } tests @@ -1495,7 +1451,7 @@ fn compile_tests<'a>( fail: &'a Stmt<'a>, mut cond: 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 { None => { cond = compile_test(env, ret_layout, new_stores, lhs, rhs, fail, cond); @@ -1578,7 +1534,7 @@ fn decide_to_branching<'a>( match decider { Leaf(Jump(label)) => { 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"); Stmt::Jump(jumps[index].1, &[]) @@ -1684,7 +1640,7 @@ fn decide_to_branching<'a>( if number_of_tests == 1 { // 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( env, diff --git a/compiler/mono/src/expand_rc.rs b/compiler/mono/src/expand_rc.rs index 01ee612edd..820fcf5395 100644 --- a/compiler/mono/src/expand_rc.rs +++ b/compiler/mono/src/expand_rc.rs @@ -50,7 +50,7 @@ use roc_module::symbol::{IdentIds, ModuleId, Symbol}; // 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 -// 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 // symbol. // @@ -187,7 +187,7 @@ impl<'a, 'i> Env<'a, 'i> { pub fn unique_symbol(&mut self) -> Symbol { 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) } @@ -195,7 +195,7 @@ impl<'a, 'i> Env<'a, 'i> { fn manual_unique_symbol(home: ModuleId, ident_ids: &mut IdentIds) -> Symbol { let ident_id = ident_ids.gen_unique(); - home.register_debug_idents(&ident_ids); + home.register_debug_idents(ident_ids); Symbol::new(home, ident_id) } diff --git a/compiler/mono/src/inc_dec.rs b/compiler/mono/src/inc_dec.rs index 4cbc8b5bec..5da365c516 100644 --- a/compiler/mono/src/inc_dec.rs +++ b/compiler/mono/src/inc_dec.rs @@ -247,18 +247,16 @@ impl<'a> Context<'a> { pub fn new(arena: &'a Bump, param_map: &'a ParamMap<'a>) -> Self { let mut vars = MutMap::default(); - for (key, _) in param_map.into_iter() { - if let crate::borrow::Key::Declaration(symbol, _) = key { - vars.insert( - *symbol, - VarInfo { - reference: false, // assume function symbols are global constants - persistent: true, // assume function symbols are global constants - consume: false, // no need to consume this variable - reset: false, // reset symbols cannot be passed as function arguments - }, - ); - } + for symbol in param_map.iter_symbols() { + vars.insert( + *symbol, + VarInfo { + reference: false, // assume function symbols are global constants + persistent: true, // assume function symbols are global constants + consume: false, // no need to consume this variable + reset: false, // reset symbols cannot be passed as function arguments + }, + ); } Self { @@ -742,7 +740,7 @@ impl<'a> Context<'a> { ) -> (&'a Stmt<'a>, LiveVarSet) { 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); let new_b = match v { @@ -752,7 +750,7 @@ impl<'a> Context<'a> { | Array { elems: ys, .. } => self.add_inc_before_consume_all( ys, self.arena.alloc(Stmt::Let(z, v, l, b)), - &b_live_vars, + b_live_vars, ), 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); } -/// used to process the main function in the repl -pub fn visit_declaration<'a>( +pub fn visit_procs<'a>( arena: &'a Bump, param_map: &'a ParamMap<'a>, - stmt: &'a Stmt<'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>, + procs: &mut MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>, ) { 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) { Some(slice) => slice, None => Vec::from_iter_in( diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index 48cba39860..41bf2609c9 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -222,9 +222,7 @@ impl<'a> Proc<'a> { ) { let borrow_params = arena.alloc(crate::borrow::infer_borrow(arena, procs)); - for (key, proc) in procs.iter_mut() { - crate::inc_dec::visit_proc(arena, borrow_params, proc, key.1); - } + crate::inc_dec::visit_procs(arena, borrow_params, procs); } 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)); - for (key, proc) in result.iter_mut() { - crate::inc_dec::visit_proc(arena, borrow_params, proc, key.1); - } + crate::inc_dec::visit_procs(arena, borrow_params, &mut result); result } @@ -473,9 +469,7 @@ impl<'a> Procs<'a> { let borrow_params = arena.alloc(crate::borrow::infer_borrow(arena, &result)); - for (key, proc) in result.iter_mut() { - crate::inc_dec::visit_proc(arena, borrow_params, proc, key.1); - } + crate::inc_dec::visit_procs(arena, borrow_params, &mut result); (result, borrow_params) } @@ -815,7 +809,7 @@ impl<'a, 'i> Env<'a, 'i> { pub fn unique_symbol(&mut self) -> Symbol { 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) } @@ -848,13 +842,21 @@ impl<'a, 'i> Env<'a, 'i> { #[derive(Clone, Debug, PartialEq, Copy, Eq, Hash)] pub struct JoinPointId(pub Symbol); -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Copy, Debug, PartialEq)] pub struct Param<'a> { pub symbol: Symbol, pub borrow: bool, 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>( env: &mut Env<'a, '_>, cond_symbol: Symbol, @@ -1698,7 +1700,7 @@ fn pattern_to_when<'a>( UnsupportedPattern(region) => { // 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); (env.unique_symbol(), Located::at_zero(RuntimeError(error))) } @@ -2106,8 +2108,6 @@ fn specialize_external<'a>( let expr = Expr::UnionAtIndex { tag_id, 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, union_layout, }; @@ -2468,7 +2468,7 @@ fn specialize_solved_type<'a>( // for debugging only 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)); let raw = match attempted_layout { @@ -2509,7 +2509,7 @@ fn specialize_solved_type<'a>( debug_assert_eq!( 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)) ); @@ -2733,10 +2733,10 @@ pub fn with_hole<'a>( Layout::Builtin(float_precision_to_builtin(precision)), hole, ), - IntOrFloat::DecimalFloatType(precision) => Stmt::Let( + IntOrFloat::DecimalFloatType => Stmt::Let( assigned, Expr::Literal(Literal::Float(num as f64)), - Layout::Builtin(float_precision_to_builtin(precision)), + Layout::Builtin(Builtin::Decimal), hole, ), _ => unreachable!("unexpected float precision for integer"), @@ -2769,10 +2769,10 @@ pub fn with_hole<'a>( Layout::Builtin(float_precision_to_builtin(precision)), hole, ), - IntOrFloat::DecimalFloatType(precision) => Stmt::Let( + IntOrFloat::DecimalFloatType => Stmt::Let( assigned, Expr::Literal(Literal::Float(num as f64)), - Layout::Builtin(float_precision_to_builtin(precision)), + Layout::Builtin(Builtin::Decimal), hole, ), }, @@ -3010,7 +3010,7 @@ pub fn with_hole<'a>( let arena = env.arena; debug_assert!(!matches!( - env.subs.get_without_compacting(variant_var).content, + env.subs.get_content_without_compacting(variant_var), Content::Structure(FlatType::Func(_, _, _)) )); convert_tag_union( @@ -3035,12 +3035,15 @@ pub fn with_hole<'a>( } => { 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( env, - arg_vars, + &arg_vars, ret_var, tag_name, closure_name, @@ -3855,7 +3858,7 @@ pub fn with_hole<'a>( let mut arg_symbols = Vec::with_capacity_in(args.len(), env.arena); 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(); @@ -3885,7 +3888,7 @@ pub fn with_hole<'a>( let mut arg_symbols = Vec::with_capacity_in(args.len(), env.arena); 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(); @@ -4343,7 +4346,7 @@ fn convert_tag_union<'a>( #[allow(clippy::too_many_arguments)] fn tag_union_to_function<'a>( env: &mut Env<'a, '_>, - argument_variables: std::vec::Vec, + argument_variables: &[Variable], return_variable: Variable, tag_name: TagName, 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)); - loc_pattern_args.push((arg_var, loc_pattern)); - loc_expr_args.push((arg_var, loc_expr)); + loc_pattern_args.push((*arg_var, loc_pattern)); + loc_expr_args.push((*arg_var, loc_expr)); } let loc_body = Located::at_zero(roc_can::expr::Expr::Tag { @@ -5540,7 +5543,7 @@ fn store_pattern_help<'a>( layout_cache, outer_symbol, &layout, - &arguments, + arguments, stmt, ); } @@ -5557,7 +5560,7 @@ fn store_pattern_help<'a>( layout_cache, outer_symbol, *layout, - &arguments, + arguments, *tag_id, stmt, ); @@ -6362,18 +6365,19 @@ fn call_by_name_help<'a>( let top_level_layout = ProcLayout::new(env.arena, argument_layouts, *ret_layout); // 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 .iter() .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 let mut pattern_vars = Vec::with_capacity_in(loc_args.len(), arena); 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(_) => { pattern_vars.push(*var); } @@ -6870,7 +6874,7 @@ fn from_can_pattern_help<'a>( IntOrFloat::SignedIntType(_) => Ok(Pattern::IntLiteral(*num as i128)), IntOrFloat::UnsignedIntType(_) => Ok(Pattern::IntLiteral(*num as i128)), 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 // that, reinstate this assert! // - // dbg!(&env.subs.get_without_compacting(*field_var).content); - // dbg!(&env.subs.get_without_compacting(destruct.value.var).content); + // dbg!(&env.subs.get_content_without_compacting(*field_var)); + // dbg!(&env.subs.get_content_without_compacting(destruct.var).content); // debug_assert_eq!( // env.subs.get_root_key_without_compacting(*field_var), // env.subs.get_root_key_without_compacting(destruct.value.var) @@ -7460,7 +7464,7 @@ pub enum IntOrFloat { SignedIntType(IntPrecision), UnsignedIntType(IntPrecision), BinaryFloatType(FloatPrecision), - DecimalFloatType(FloatPrecision), + DecimalFloatType, } fn float_precision_to_builtin(precision: FloatPrecision) -> Builtin<'static> { @@ -7489,7 +7493,7 @@ pub fn num_argument_to_int_or_float( var: Variable, known_to_be_float: bool, ) -> 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(_) => 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, _, _) => { 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_BINARY32, _, _) | Content::Alias(Symbol::NUM_AT_BINARY32, _, _) => { diff --git a/compiler/mono/src/layout.rs b/compiler/mono/src/layout.rs index 48584c947d..42ba8a1038 100644 --- a/compiler/mono/src/layout.rs +++ b/compiler/mono/src/layout.rs @@ -170,7 +170,16 @@ impl<'a> UnionLayout<'a> { pub fn tag_id_builtin(&self) -> Builtin<'a> { 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(); Self::tag_id_builtin_help(union_size) @@ -460,6 +469,7 @@ pub enum Builtin<'a> { Int8, Int1, Usize, + Decimal, Float128, Float64, Float32, @@ -510,8 +520,8 @@ impl<'a> Layout<'a> { match content { FlexVar(_) | RigidVar(_) => Err(LayoutProblem::UnresolvedTypeVar(var)), RecursionVar { structure, .. } => { - let structure_content = env.subs.get_without_compacting(structure).content; - Self::new_help(env, structure, structure_content) + let structure_content = env.subs.get_content_without_compacting(structure); + Self::new_help(env, structure, structure_content.clone()) } Structure(flat_type) => layout_from_flat_type(env, flat_type), @@ -581,8 +591,8 @@ impl<'a> Layout<'a> { if env.is_seen(var) { Ok(Layout::RecursivePointer) } else { - let content = env.subs.get_without_compacting(var).content; - Self::new_help(env, var, content) + let content = env.subs.get_content_without_compacting(var); + Self::new_help(env, var, content.clone()) } } @@ -655,7 +665,7 @@ impl<'a> Layout<'a> { .max() .unwrap_or_default() // the size of the tag_id - + pointer_size + + variant.tag_id_builtin().stack_size(pointer_size) } Recursive(_) @@ -952,6 +962,7 @@ impl<'a> Builtin<'a> { const I8_SIZE: u32 = std::mem::size_of::() as u32; const I1_SIZE: u32 = std::mem::size_of::() as u32; const USIZE_SIZE: u32 = std::mem::size_of::() as u32; + const DECIMAL_SIZE: u32 = std::mem::size_of::() as u32; const F128_SIZE: u32 = 16; const F64_SIZE: u32 = std::mem::size_of::() as u32; const F32_SIZE: u32 = std::mem::size_of::() as u32; @@ -981,6 +992,7 @@ impl<'a> Builtin<'a> { Int8 => Builtin::I8_SIZE, Int1 => Builtin::I1_SIZE, Usize => Builtin::USIZE_SIZE, + Decimal => Builtin::DECIMAL_SIZE, Float128 => Builtin::F128_SIZE, Float64 => Builtin::F64_SIZE, Float32 => Builtin::F32_SIZE, @@ -1007,6 +1019,7 @@ impl<'a> Builtin<'a> { Int8 => align_of::() as u32, Int1 => align_of::() as u32, Usize => align_of::() as u32, + Decimal => align_of::() as u32, Float128 => align_of::() as u32, Float64 => align_of::() as u32, Float32 => align_of::() as u32, @@ -1022,8 +1035,8 @@ impl<'a> Builtin<'a> { use Builtin::*; match self { - Int128 | Int64 | Int32 | Int16 | Int8 | Int1 | Usize | Float128 | Float64 | Float32 - | Float16 | EmptyStr | EmptyDict | EmptyList | EmptySet => true, + Int128 | Int64 | Int32 | Int16 | Int8 | Int1 | Usize | Decimal | Float128 | Float64 + | Float32 | Float16 | EmptyStr | EmptyDict | EmptyList | EmptySet => true, Str | Dict(_, _) | Set(_) | List(_) => false, } } @@ -1033,8 +1046,8 @@ impl<'a> Builtin<'a> { use Builtin::*; match self { - Int128 | Int64 | Int32 | Int16 | Int8 | Int1 | Usize | Float128 | Float64 | Float32 - | Float16 | EmptyStr | EmptyDict | EmptyList | EmptySet => false, + Int128 | Int64 | Int32 | Int16 | Int8 | Int1 | Usize | Decimal | Float128 | Float64 + | Float32 | Float16 | EmptyStr | EmptyDict | EmptyList | EmptySet => false, List(_) => true, Str | Dict(_, _) | Set(_) => true, @@ -1057,6 +1070,7 @@ impl<'a> Builtin<'a> { Int8 => alloc.text("Int8"), Int1 => alloc.text("Int1"), Usize => alloc.text("Usize"), + Decimal => alloc.text("Decimal"), Float128 => alloc.text("Float128"), Float64 => alloc.text("Float64"), Float32 => alloc.text("Float32"), @@ -1144,6 +1158,10 @@ fn layout_from_flat_type<'a>( } // Floats + Symbol::NUM_DEC => { + debug_assert_eq!(args.len(), 0); + Ok(Layout::Builtin(Builtin::Decimal)) + } Symbol::NUM_F64 => { debug_assert_eq!(args.len(), 0); Ok(Layout::Builtin(Builtin::Float64)) @@ -1158,7 +1176,7 @@ fn layout_from_flat_type<'a>( debug_assert_eq!(args.len(), 1); 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) } @@ -1197,25 +1215,8 @@ fn layout_from_flat_type<'a>( Err(_) => unreachable!("this would have been a type error"), } - let sorted_fields = sort_record_fields_help(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; - } - } - } + // discard optional fields + let mut layouts = sort_stored_record_fields(env, fields_map); if layouts.len() == 1 { // 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)); let mut tags = MutMap::default(); - tags.insert(tag_name, vec![]); + tags.insert(*tag_name, vec![]); Ok(layout_from_tag_union(arena, tags, subs)) } @@ -1316,7 +1317,7 @@ fn layout_from_flat_type<'a>( } } else if tag_layouts.len() == 1 { // drop the tag id - UnionLayout::NonNullableUnwrapped(&tag_layouts.pop().unwrap()) + UnionLayout::NonNullableUnwrapped(tag_layouts.pop().unwrap()) } else { UnionLayout::Recursive(tag_layouts.into_bump_slice()) }; @@ -1394,6 +1395,43 @@ fn sort_record_fields_help<'a>( sorted_fields } +// drops optional fields +fn sort_stored_record_fields<'a>( + env: &mut Env<'a, '_>, + fields_map: MutMap>, +) -> 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)] pub enum UnionVariant<'a> { Never, @@ -1519,8 +1557,8 @@ pub fn union_sorted_tags<'a>( subs: &Subs, ) -> Result, LayoutProblem> { let var = - if let Content::RecursionVar { structure, .. } = subs.get_without_compacting(var).content { - structure + if let Content::RecursionVar { structure, .. } = subs.get_content_without_compacting(var) { + *structure } else { var }; @@ -1539,9 +1577,9 @@ pub fn union_sorted_tags<'a>( } fn get_recursion_var(subs: &Subs, var: Variable) -> Option { - match subs.get_without_compacting(var).content { - Content::Structure(FlatType::RecursiveTagUnion(rec_var, _, _)) => Some(rec_var), - Content::Alias(_, _, actual) => get_recursion_var(subs, actual), + match subs.get_content_without_compacting(var) { + Content::Structure(FlatType::RecursiveTagUnion(rec_var, _, _)) => Some(*rec_var), + Content::Alias(_, _, actual) => get_recursion_var(subs, *actual), _ => None, } } @@ -1868,7 +1906,7 @@ fn ext_var_is_empty_tag_union(_: &Subs, _: Variable) -> bool { unreachable!(); } -fn layout_from_num_content<'a>(content: Content) -> Result, LayoutProblem> { +fn layout_from_num_content<'a>(content: &Content) -> Result, LayoutProblem> { use roc_types::subs::Content::*; use roc_types::subs::FlatType::*; @@ -1881,7 +1919,7 @@ fn layout_from_num_content<'a>(content: Content) -> Result, LayoutPro // (e.g. for (5 + 5) assume both 5s are 64-bit integers.) Ok(Layout::Builtin(DEFAULT_NUM_BUILTIN)) } - Structure(Apply(symbol, args)) => match symbol { + Structure(Apply(symbol, args)) => match *symbol { // Ints Symbol::NUM_NAT => Ok(Layout::Builtin(Builtin::Usize)), @@ -1900,13 +1938,14 @@ fn layout_from_num_content<'a>(content: Content) -> Result, LayoutPro // Floats 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_F32 => Ok(Layout::Builtin(Builtin::Float32)), _ => { panic!( - "Invalid Num.Num type application: {:?}", - Apply(symbol, args) + "Invalid Num.Num type application: Apply({:?}, {:?})", + symbol, args ); } }, @@ -1921,19 +1960,19 @@ fn layout_from_num_content<'a>(content: Content) -> Result, LayoutPro } fn unwrap_num_tag<'a>(subs: &Subs, var: Variable) -> Result, LayoutProblem> { - match subs.get_without_compacting(var).content { + match subs.get_content_without_compacting(var) { Content::Alias(Symbol::NUM_INTEGER, args, _) => { debug_assert!(args.len() == 1); 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 { Content::Alias(symbol, args, _) => { debug_assert!(args.is_empty()); - let builtin = match symbol { + let builtin = match *symbol { Symbol::NUM_SIGNED128 => Builtin::Int128, Symbol::NUM_SIGNED64 => Builtin::Int64, Symbol::NUM_SIGNED32 => Builtin::Int32, @@ -1962,7 +2001,7 @@ fn unwrap_num_tag<'a>(subs: &Subs, var: Variable) -> Result, LayoutPr 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 { Content::Alias(Symbol::NUM_BINARY32, args, _) => { @@ -1975,6 +2014,11 @@ fn unwrap_num_tag<'a>(subs: &Subs, var: Variable) -> Result, LayoutPr 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(_) => { // default to f64 Ok(Layout::Builtin(Builtin::Float64)) @@ -1997,15 +2041,15 @@ fn dict_layout_from_key_value<'a>( key_var: Variable, value_var: Variable, ) -> Result, LayoutProblem> { - match env.subs.get_without_compacting(key_var).content { + match env.subs.get_content_without_compacting(key_var) { Content::FlexVar(_) | Content::RigidVar(_) => { // If this was still a (Dict * *) then it must have been an empty dict Ok(Layout::Builtin(Builtin::EmptyDict)) } key_content => { - let value_content = env.subs.get_without_compacting(value_var).content; - let key_layout = Layout::new_help(env, key_var, key_content)?; - let value_layout = Layout::new_help(env, value_var, value_content)?; + let value_content = env.subs.get_content_without_compacting(value_var); + let key_layout = Layout::new_help(env, key_var, key_content.clone())?; + let value_layout = Layout::new_help(env, value_var, value_content.clone())?; // This is a normal list. Ok(Layout::Builtin(Builtin::Dict( @@ -2020,7 +2064,7 @@ pub fn list_layout_from_elem<'a>( env: &mut Env<'a, '_>, elem_var: Variable, ) -> Result, LayoutProblem> { - match env.subs.get_without_compacting(elem_var).content { + match env.subs.get_content_without_compacting(elem_var) { Content::FlexVar(_) | Content::RigidVar(_) => { // If this was still a (List *) then it must have been an empty list 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. - 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; // 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. let answer = ids .toplevels_by_id - .get(&layout) + .get(layout) .copied() .unwrap_or(ids.next_id); diff --git a/compiler/mono/src/reset_reuse.rs b/compiler/mono/src/reset_reuse.rs index 3a437e9d50..10bbd05939 100644 --- a/compiler/mono/src/reset_reuse.rs +++ b/compiler/mono/src/reset_reuse.rs @@ -58,7 +58,7 @@ impl<'a, 'i> Env<'a, 'i> { fn unique_symbol(&mut self) -> Symbol { 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) } @@ -247,8 +247,6 @@ fn insert_reset<'a>( let reset_expr = Expr::Reset(x); - // const I64: Layout<'static> = Layout::Builtin(crate::layout::Builtin::Int64); - let layout = Layout::Union(union_layout); stmt = env.arena.alloc(Stmt::Let(w, reset_expr, layout, stmt)); diff --git a/compiler/mono/src/tail_recursion.rs b/compiler/mono/src/tail_recursion.rs index 0eae4df30a..f0efd1736d 100644 --- a/compiler/mono/src/tail_recursion.rs +++ b/compiler/mono/src/tail_recursion.rs @@ -35,9 +35,9 @@ pub fn make_tail_recursive<'a>( stmt: Stmt<'a>, args: &'a [(Layout<'a>, Symbol)], ) -> Stmt<'a> { - let alloced = arena.alloc(stmt); - match insert_jumps(arena, alloced, id, needle) { - None => alloced.clone(), + let allocated = arena.alloc(stmt); + match insert_jumps(arena, allocated, id, needle) { + None => allocated.clone(), Some(new) => { // jumps were inserted, we must now add a join point diff --git a/compiler/parse/src/expr.rs b/compiler/parse/src/expr.rs index 5f8f972d43..31048b4a2a 100644 --- a/compiler/parse/src/expr.rs +++ b/compiler/parse/src/expr.rs @@ -1,6 +1,6 @@ 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::ident::{lowercase_ident, parse_ident_help, Ident}; +use crate::ident::{lowercase_ident, parse_ident, Ident}; use crate::keyword; use crate::parser::{ 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 { 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. 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)] @@ -2238,7 +2238,7 @@ fn record_field_help<'a>( fn record_updateable_identifier<'a>() -> impl Parser<'a, Expr<'a>, ERecord<'a>> { specialize( |_, r, c| ERecord::Updateable(r, c), - map_with_arena!(parse_ident_help, ident_to_expr), + map_with_arena!(parse_ident, ident_to_expr), ) } diff --git a/compiler/parse/src/ident.rs b/compiler/parse/src/ident.rs index 06c9862987..2adf44fa05 100644 --- a/compiler/parse/src/ident.rs +++ b/compiler/parse/src/ident.rs @@ -138,13 +138,10 @@ macro_rules! advance_state { }; } -pub fn parse_ident_help<'a>( - arena: &'a Bump, - state: State<'a>, -) -> ParseResult<'a, Ident<'a>, EExpr<'a>> { +pub fn parse_ident<'a>(arena: &'a Bump, state: State<'a>) -> ParseResult<'a, Ident<'a>, EExpr<'a>> { let initial = state; - match parse_ident_help_help(arena, state) { + match parse_ident_help(arena, state) { Ok((progress, ident, state)) => { if let Ident::Access { module_name, parts } = ident { 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, mut state: State<'a>, ) -> ParseResult<'a, Ident<'a>, BadIdent> { diff --git a/compiler/parse/src/number_literal.rs b/compiler/parse/src/number_literal.rs index 2c1796a0b8..2386f7803a 100644 --- a/compiler/parse/src/number_literal.rs +++ b/compiler/parse/src/number_literal.rs @@ -15,7 +15,7 @@ pub fn positive_number_literal<'a>() -> impl Parser<'a, NumLiteral<'a>, Number> move |_arena, state: State<'a>| { match state.bytes.get(0) { 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 @@ -33,7 +33,7 @@ pub fn number_literal<'a>() -> impl Parser<'a, NumLiteral<'a>, Number> { parse_number_base(true, &state.bytes[1..], state) } 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 diff --git a/compiler/parse/src/pattern.rs b/compiler/parse/src/pattern.rs index 1ae58f38a1..b214d4b603 100644 --- a/compiler/parse/src/pattern.rs +++ b/compiler/parse/src/pattern.rs @@ -1,6 +1,6 @@ use crate::ast::Pattern; 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::{ backtrackable, optional, specialize, specialize_ref, word1, EPattern, PInParens, PRecord, @@ -172,8 +172,7 @@ fn loc_ident_pattern_help<'a>( let original_state = state; let (_, loc_ident, state) = - specialize(|_, r, c| EPattern::Start(r, c), loc!(parse_ident_help)) - .parse(arena, state)?; + specialize(|_, r, c| EPattern::Start(r, c), loc!(parse_ident)).parse(arena, state)?; match loc_ident.value { Ident::GlobalTag(tag) => { @@ -259,7 +258,7 @@ fn loc_ident_pattern_help<'a>( Located { region: loc_ident.region, value: Pattern::Malformed( - String::from_str_in(&malformed_str, &arena).into_bump_str(), + String::from_str_in(&malformed_str, arena).into_bump_str(), ), }, state, @@ -299,7 +298,7 @@ fn underscore_pattern_help<'a>() -> impl Parser<'a, Pattern<'a>, EPattern<'a>> { match output { Some(name) => Ok((MadeProgress, Pattern::Underscore(name), final_state)), - None => Ok((MadeProgress, Pattern::Underscore(&""), final_state)), + None => Ok((MadeProgress, Pattern::Underscore(""), final_state)), } } } diff --git a/compiler/parse/src/type_annotation.rs b/compiler/parse/src/type_annotation.rs index 6b1f718552..ac95ca185e 100644 --- a/compiler/parse/src/type_annotation.rs +++ b/compiler/parse/src/type_annotation.rs @@ -303,12 +303,12 @@ fn applied_type<'a>(min_indent: u16) -> impl Parser<'a, TypeAnnotation<'a>, Type ), |(ctor, args): (TypeAnnotation<'a>, Vec<'a, Located>>)| { match &ctor { - TypeAnnotation::Apply(ref module_name, ref name, _) => { + TypeAnnotation::Apply(module_name, name, _) => { if args.is_empty() { // ctor is already an Apply with no args, so return it directly. ctor } else { - TypeAnnotation::Apply(*module_name, *name, args.into_bump_slice()) + TypeAnnotation::Apply(module_name, name, args.into_bump_slice()) } } TypeAnnotation::Malformed(_) => ctor, @@ -371,7 +371,7 @@ fn expression<'a>(min_indent: u16) -> impl Parser<'a, Located .parse(arena, state)?; // 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.extend(rest); let output = arena.alloc(arguments); diff --git a/compiler/parse/tests/test_parse.rs b/compiler/parse/tests/test_parse.rs index 58ca1d2dee..f172d9453d 100644 --- a/compiler/parse/tests/test_parse.rs +++ b/compiler/parse/tests/test_parse.rs @@ -1609,7 +1609,7 @@ mod test_parse { #[test] fn single_underscore_closure() { 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 expected = Closure(patterns, arena.alloc(Located::new(0, 0, 6, 8, Num("42")))); let actual = parse_expr_with(&arena, "\\_ -> 42"); @@ -1629,7 +1629,7 @@ mod test_parse { 0, 1, 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 expr = Located::new(0, 0, 15, 17, Expr::Num("42")); @@ -1671,8 +1671,8 @@ mod test_parse { #[test] fn closure_with_underscores() { let arena = Bump::new(); - let underscore1 = Located::new(0, 0, 1, 2, Pattern::Underscore(&"")); - let underscore2 = Located::new(0, 0, 4, 9, Pattern::Underscore(&"name")); + let underscore1 = Located::new(0, 0, 1, 2, Pattern::Underscore("")); + let underscore2 = Located::new(0, 0, 4, 9, Pattern::Underscore("name")); let patterns = bumpalo::vec![in &arena; underscore1, underscore2]; let expected = Closure( arena.alloc(patterns), @@ -1906,7 +1906,7 @@ mod test_parse { fn underscore_backpassing() { let arena = Bump::new(); 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 num_4 = Num("4"); @@ -3531,7 +3531,6 @@ mod test_parse { match parsed { Ok((_, _, _state)) => { // dbg!(_state); - return; } Err((_, _fail, _state)) => { // dbg!(_fail, _state); @@ -3707,7 +3706,7 @@ mod test_parse { guard: None, }); 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 expr2 = Num("4"); let loc_expr2 = Located::new(2, 2, 9, 10, expr2); @@ -3752,7 +3751,7 @@ mod test_parse { guard: None, }); 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 expr2 = Num("4"); let loc_expr2 = Located::new(2, 2, 9, 10, expr2); diff --git a/compiler/reporting/src/error/parse.rs b/compiler/reporting/src/error/parse.rs index bf9da32395..1fbfac0d3a 100644 --- a/compiler/reporting/src/error/parse.rs +++ b/compiler/reporting/src/error/parse.rs @@ -159,17 +159,17 @@ fn to_syntax_report<'a>( title: "PARSE PROBLEM".to_string(), } } - Type(typ) => to_type_report(alloc, filename, &typ, 0, 0), - Pattern(pat) => to_pattern_report(alloc, filename, &pat, 0, 0), + Type(typ) => to_type_report(alloc, filename, typ, 0, 0), + Pattern(pat) => to_pattern_report(alloc, filename, pat, 0, 0), Expr(expr) => to_expr_report( alloc, filename, Context::InDef(start_row, start_col), - &expr, + expr, 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), } } @@ -205,19 +205,17 @@ fn to_expr_report<'a>( use roc_parse::parser::EExpr; match parse_problem { - 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::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::Lambda(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) + 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::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) => { let surroundings = Region::from_rows_cols(start_row, start_col, *row, *col); 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), } @@ -1557,10 +1555,10 @@ fn to_pattern_report<'a>( } } 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) => { - 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), } @@ -1958,14 +1956,14 @@ fn to_type_report<'a>( use roc_parse::parser::Type; 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) => { - 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) => { - 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) { Next::Other(Some(',')) => { @@ -2856,27 +2854,27 @@ fn to_header_report<'a>( match parse_problem { 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) => { - to_exposes_report(alloc, filename, &exposes, *row, *col) + to_exposes_report(alloc, filename, exposes, *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) => { - to_requires_report(alloc, filename, &requires, *row, *col) + to_requires_report(alloc, filename, requires, *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) => { - to_effects_report(alloc, filename, &effects, *row, *col) + to_effects_report(alloc, filename, effects, *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), } } diff --git a/compiler/reporting/src/error/type.rs b/compiler/reporting/src/error/type.rs index f4f3723257..e5c6763279 100644 --- a/compiler/reporting/src/error/type.rs +++ b/compiler/reporting/src/error/type.rs @@ -214,7 +214,7 @@ fn report_bad_type<'b>( alloc, found, expected_type, - add_category(alloc, this_is, &category), + add_category(alloc, this_is, category), further_details, ), ]; @@ -1443,7 +1443,7 @@ pub fn to_doc<'b>( Record(fields_map, ext) => { let mut fields = fields_map.into_iter().collect::>(); - fields.sort_by(|(a, _), (b, _)| a.cmp(&b)); + fields.sort_by(|(a, _), (b, _)| a.cmp(b)); report_text::record( alloc, @@ -1482,7 +1482,7 @@ pub fn to_doc<'b>( ) }) .collect::>(); - tags.sort_by(|(a, _), (b, _)| a.cmp(&b)); + tags.sort_by(|(a, _), (b, _)| a.cmp(b)); report_text::tag_union( alloc, @@ -1505,7 +1505,7 @@ pub fn to_doc<'b>( ) }) .collect::>(); - tags.sort_by(|(a, _), (b, _)| a.cmp(&b)); + tags.sort_by(|(a, _), (b, _)| a.cmp(b)); report_text::recursive_tag_union( alloc, diff --git a/compiler/reporting/src/report.rs b/compiler/reporting/src/report.rs index a96480fa78..672f1d332f 100644 --- a/compiler/reporting/src/report.rs +++ b/compiler/reporting/src/report.rs @@ -70,7 +70,7 @@ impl<'b> Report<'b> { pub fn render_ci(self, buf: &'b mut String, alloc: &'b RocDocAllocator<'b>) { let err_msg = ""; - self.pretty(&alloc) + self.pretty(alloc) .1 .render_raw(70, &mut CiWrite::new(buf)) .expect(err_msg); @@ -85,7 +85,7 @@ impl<'b> Report<'b> { ) { let err_msg = ""; - self.pretty(&alloc) + self.pretty(alloc) .1 .render_raw(70, &mut ColorWrite::new(palette, buf)) .expect(err_msg); diff --git a/compiler/reporting/tests/helpers/mod.rs b/compiler/reporting/tests/helpers/mod.rs index 3cade3ded1..7e60d02594 100644 --- a/compiler/reporting/tests/helpers/mod.rs +++ b/compiler/reporting/tests/helpers/mod.rs @@ -36,7 +36,10 @@ pub fn infer_expr( }; 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()) } @@ -110,7 +113,7 @@ pub fn can_expr_with<'a>( home: ModuleId, expr_str: &'a str, ) -> Result> { - 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, Err(fail) => { let interns = Interns::default(); @@ -142,7 +145,7 @@ pub fn can_expr_with<'a>( let mut scope = Scope::new(home, &mut var_store); let dep_idents = IdentIds::exposed_builtins(0); - let mut env = Env::new(home, dep_idents, &module_ids, IdentIds::default()); + let mut env = Env::new(home, &dep_idents, &module_ids, IdentIds::default()); let (loc_expr, output) = canonicalize_expr( &mut env, &mut var_store, diff --git a/compiler/reporting/tests/test_reporting.rs b/compiler/reporting/tests/test_reporting.rs index d3c7ce42d7..af5a4c3d36 100644 --- a/compiler/reporting/tests/test_reporting.rs +++ b/compiler/reporting/tests/test_reporting.rs @@ -65,7 +65,7 @@ mod test_reporting { problems: can_problems, .. } = 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 { subs.rigid_var(var, name); @@ -223,11 +223,9 @@ mod test_reporting { list_reports(&arena, src, &mut buf, callback); // convenient to copy-paste the generated message - if true { - if buf != expected_rendering { - for line in buf.split("\n") { - println!(" {}", line); - } + if true && buf != expected_rendering { + for line in buf.split('\n') { + println!(" {}", line); } } @@ -247,11 +245,9 @@ mod test_reporting { list_header_reports(&arena, src, &mut buf, callback); // convenient to copy-paste the generated message - if true { - if buf != expected_rendering { - for line in buf.split("\n") { - println!(" {}", line); - } + if true && buf != expected_rendering { + for line in buf.split('\n') { + println!(" {}", line); } } @@ -721,10 +717,10 @@ mod test_reporting { these names seem close though: + Decimal + Dec Result Num - Set - U8 "# ), ); @@ -2160,8 +2156,8 @@ mod test_reporting { This is usually a typo. Here are the `x` fields that are most similar: { fo : Num c - , foobar : Num a - , bar : Num e + , foobar : Num d + , bar : Num a , baz : Num b , ... } diff --git a/compiler/solve/src/module.rs b/compiler/solve/src/module.rs index f921b20709..525818e554 100644 --- a/compiler/solve/src/module.rs +++ b/compiler/solve/src/module.rs @@ -56,10 +56,10 @@ pub fn make_solved_types( for loc_named_var in alias.type_variables.iter() { 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)); solved_types.insert(*symbol, solved_alias); @@ -71,7 +71,7 @@ pub fn make_solved_types( // other modules will generate constraints for imported values // within the context of their own Subs. 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); } diff --git a/compiler/solve/src/solve.rs b/compiler/solve/src/solve.rs index a683be04f4..a4def825d0 100644 --- a/compiler/solve/src/solve.rs +++ b/compiler/solve/src/solve.rs @@ -1,12 +1,12 @@ use roc_can::constraint::Constraint::{self, *}; 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_region::all::{Located, Region}; use roc_types::solved_types::Solved; use roc_types::subs::{Content, Descriptor, FlatType, Mark, OptVariable, Rank, Subs, Variable}; 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::Unified::*; @@ -114,7 +114,7 @@ impl Pools { pub fn split_last(&self) -> (&Vec, &[Vec]) { self.0 .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) { @@ -257,7 +257,7 @@ fn solve( } } Lookup(symbol, expectation, region) => { - match env.vars_by_symbol.get(&symbol) { + match env.vars_by_symbol.get(symbol) { Some(var) => { // Deep copy the vars associated with this symbol before unifying them. // Otherwise, suppose we have this: @@ -390,7 +390,7 @@ fn solve( // If the return expression is guaranteed to solve, // solve the assignments themselves and move on. solve( - &env, + env, state, rank, pools, @@ -413,24 +413,25 @@ fn solve( ); // 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() { let var = type_to_var(subs, rank, pools, cached_aliases, &loc_type.value); - local_def_vars.insert( + local_def_vars.push(( *symbol, Located { value: var, region: loc_type.region, }, - ); + )); } let mut new_env = env.clone(); for (symbol, loc_var) in local_def_vars.iter() { - if !new_env.vars_by_symbol.contains_key(&symbol) { - new_env.vars_by_symbol.insert(*symbol, loc_var.value); + // better to ask for forgiveness than for permission + 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 // 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() { let def_type = &loc_type.value; @@ -493,13 +494,13 @@ fn solve( let var = type_to_var(subs, next_rank, next_pools, cached_aliases, def_type); - local_def_vars.insert( + local_def_vars.push(( *symbol, Located { value: var, region: loc_type.region, }, - ); + )); } // Solve the assignments' constraints first. @@ -507,7 +508,7 @@ fn solve( env: saved_env, mark, } = solve( - &env, + env, state, next_rank, next_pools, @@ -527,11 +528,10 @@ fn solve( .get(next_rank) .iter() .filter(|var| { - let current = subs.get_without_compacting( - roc_types::subs::Variable::clone(var), - ); + let current_rank = + 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::>(); @@ -560,8 +560,7 @@ fn solve( let failing: Vec<_> = rigid_vars .iter() .filter(|&var| { - !subs.redundant(*var) - && subs.get_without_compacting(*var).rank != Rank::NONE + !subs.redundant(*var) && subs.get_rank(*var) != Rank::NONE }) .collect(); @@ -576,7 +575,7 @@ fn solve( let mut new_env = env.clone(); for (symbol, loc_var) in local_def_vars.iter() { // 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); } } @@ -598,7 +597,7 @@ fn solve( problems, cached_aliases, subs, - &ret_con, + ret_con, ); for (symbol, loc_var) in local_def_vars { @@ -671,16 +670,11 @@ fn type_to_variable( register(subs, rank, pools, content) } 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 { - use RecordField::*; - - 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)), - }; + let field_var = + field_type.map(|typ| type_to_variable(subs, rank, pools, cached, typ)); field_vars.insert(field.clone(), field_var); } @@ -695,12 +689,13 @@ fn type_to_variable( 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) } 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 { 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()); let content = Content::Structure(FlatType::FunctionOrTagUnion( - tag_name.clone(), + Box::new(tag_name.clone()), *symbol, new_ext_var, )); @@ -750,7 +745,7 @@ fn type_to_variable( register(subs, rank, pools, content) } 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 { 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, 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 new_aliases = ImMap::default(); for (arg, arg_type) in args { let arg_var = type_to_variable(subs, rank, pools, cached, arg_type); arg_vars.push((arg.clone(), arg_var)); - new_aliases.insert(arg.clone(), arg_var); } let alias_var = type_to_variable(subs, rank, pools, cached, alias_type); let content = Content::Alias(*symbol, arg_vars, alias_var); - let result = register(subs, rank, pools, content); - - if no_args && !is_recursive { - // cached.insert(*symbol, result); - } - - result + register(subs, rank, pools, content) } HostExposedAlias { name: symbol, @@ -845,13 +807,11 @@ fn type_to_variable( .. } => { let mut arg_vars = Vec::with_capacity(args.len()); - let mut new_aliases = ImMap::default(); for (arg, arg_type) in args { let arg_var = type_to_variable(subs, rank, pools, cached, arg_type); arg_vars.push((arg.clone(), arg_var)); - new_aliases.insert(arg.clone(), arg_var); } let alias_var = type_to_variable(subs, rank, pools, cached, alias_type); @@ -877,7 +837,7 @@ fn type_to_variable( result } 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) } @@ -1023,45 +983,34 @@ fn adjust_rank( group_rank: Rank, var: Variable, ) -> Rank { - let desc = subs.get(var); - - if desc.mark == young_mark { - let Descriptor { - content, - rank: _, - mark: _, - copy, - } = desc; + let (desc_rank, desc_mark) = subs.get_rank_mark(var); + if desc_mark == young_mark { // Mark the variable as visited before adjusting content, as it may be cyclic. 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( - var, - Descriptor { - content, - rank: max_rank, - mark: visit_mark, - copy, - }, - ); + let max_rank = adjust_rank_content(subs, young_mark, visit_mark, group_rank, content); + + subs.set_rank_mark(var, max_rank, visit_mark); max_rank - } else if desc.mark == visit_mark { + } else if desc_mark == visit_mark { // nothing changes - desc.rank + desc_rank } 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? - desc.rank = min_rank; - desc.mark = visit_mark; - - subs.set(var, desc); + subs.set_rank_mark(var, min_rank, visit_mark); min_rank } @@ -1131,14 +1080,9 @@ fn adjust_rank_content( Record(fields, ext_var) => { let mut rank = adjust_rank(subs, young_mark, visit_mark, group_rank, *ext_var); - for var in fields.values() { - rank = rank.max(adjust_rank( - subs, - young_mark, - visit_mark, - group_rank, - var.into_inner(), - )); + for var in fields.iter_variables() { + rank = + rank.max(adjust_rank(subs, young_mark, visit_mark, group_rank, *var)); } rank @@ -1266,144 +1210,81 @@ fn instantiate_rigids_help( // will not repeat this work or crawl this variable again. match content { Structure(flat_type) => { - let new_flat_type = match flat_type { - Apply(symbol, args) => { - let args = args - .into_iter() - .map(|var| instantiate_rigids_help(subs, max_rank, pools, var)) - .collect(); - - Apply(symbol, args) + match flat_type { + Apply(_, args) => { + for var in args.into_iter() { + instantiate_rigids_help(subs, max_rank, pools, var); + } } Func(arg_vars, closure_var, ret_var) => { - let new_ret_var = instantiate_rigids_help(subs, max_rank, pools, ret_var); - let new_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(); + instantiate_rigids_help(subs, max_rank, pools, ret_var); + instantiate_rigids_help(subs, max_rank, pools, closure_var); - 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) => { - let mut new_fields = MutMap::default(); - - 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); + for var in fields.iter_variables() { + instantiate_rigids_help(subs, max_rank, pools, *var); } - Record( - new_fields, - instantiate_rigids_help(subs, max_rank, pools, ext_var), - ) + instantiate_rigids_help(subs, max_rank, pools, ext_var); } TagUnion(tags, ext_var) => { - let mut new_tags = MutMap::default(); - - for (tag, vars) in tags { - let new_vars: Vec = vars - .into_iter() - .map(|var| instantiate_rigids_help(subs, max_rank, pools, var)) - .collect(); - new_tags.insert(tag, new_vars); + for (_, vars) in tags { + for var in vars.into_iter() { + instantiate_rigids_help(subs, max_rank, pools, var); + } } - TagUnion( - new_tags, - instantiate_rigids_help(subs, max_rank, pools, ext_var), - ) + instantiate_rigids_help(subs, max_rank, pools, ext_var); } - FunctionOrTagUnion(tag_name, symbol, ext_var) => FunctionOrTagUnion( - tag_name, - symbol, - instantiate_rigids_help(subs, max_rank, pools, ext_var), - ), + FunctionOrTagUnion(_tag_name, _symbol, ext_var) => { + instantiate_rigids_help(subs, max_rank, pools, 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 (tag, vars) in tags { - let new_vars: Vec = vars - .into_iter() - .map(|var| instantiate_rigids_help(subs, max_rank, pools, var)) - .collect(); - new_tags.insert(tag, new_vars); + for (_, vars) in tags { + for var in vars.into_iter() { + instantiate_rigids_help(subs, max_rank, pools, var); + } } - RecursiveTagUnion( - new_rec_var, - new_tags, - instantiate_rigids_help(subs, max_rank, pools, ext_var), - ) + 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 { - opt_name, - structure, - } => { - let new_structure = instantiate_rigids_help(subs, max_rank, pools, structure); - - subs.set( - copy, - make_descriptor(RecursionVar { - opt_name, - structure: new_structure, - }), - ); - - copy + RecursionVar { structure, .. } => { + instantiate_rigids_help(subs, max_rank, pools, structure); } RigidVar(name) => { + // what it's all about: convert the rigid var into a flex var subs.set(copy, make_descriptor(FlexVar(Some(name)))); - - copy } - Alias(symbol, args, real_type_var) => { - let new_args = args - .into_iter() - .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); + Alias(_, args, real_type_var) => { + for (_, var) in args.into_iter() { + instantiate_rigids_help(subs, max_rank, pools, var); + } - subs.set(copy, make_descriptor(new_content)); - - copy + instantiate_rigids_help(subs, max_rank, pools, real_type_var); } } + + var } 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, - Record(fields, ext_var) => { - let mut new_fields = MutMap::default(); - - 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(mut fields, ext_var) => { + for var in fields.iter_variables_mut() { + *var = deep_copy_var_help(subs, max_rank, pools, *var); } - Record( - new_fields, - deep_copy_var_help(subs, max_rank, pools, ext_var), - ) + Record(fields, deep_copy_var_help(subs, max_rank, pools, ext_var)) } TagUnion(tags, ext_var) => { diff --git a/compiler/solve/tests/solve_expr.rs b/compiler/solve/tests/solve_expr.rs index fe3f3322eb..5c916eb2cc 100644 --- a/compiler/solve/tests/solve_expr.rs +++ b/compiler/solve/tests/solve_expr.rs @@ -115,10 +115,10 @@ mod solve_expr { let content = { debug_assert!(exposed_to_host.len() == 1); 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 // returning a function from the test expression. @@ -172,6 +172,21 @@ mod solve_expr { infer_eq("0.5", "Float *"); } + #[test] + fn dec_literal() { + infer_eq( + indoc!( + r#" + val : Dec + val = 1.2 + + val + "# + ), + "Dec", + ); + } + #[test] fn string_literal() { infer_eq( @@ -3805,7 +3820,7 @@ mod solve_expr { } #[test] - fn recursive_functon_with_rigid() { + fn recursive_function_with_rigid() { infer_eq_without_problem( indoc!( r#" @@ -4473,4 +4488,18 @@ mod solve_expr { "RBTree {}", ); } + + #[test] + fn sizes() { + let query = ( + std::mem::size_of::(), + std::mem::size_of::(), + std::mem::size_of::(), + std::mem::size_of::(), + std::mem::size_of::(), + ); + + // without RecordFields in FlatType assert_eq!((40, 72, 56, 48, 64), query) + assert_eq!((40, 104, 88, 80, 64), query) + } } diff --git a/compiler/str/README.md b/compiler/str/README.md index 2ab0c3c25d..04af2f5e9f 100644 --- a/compiler/str/README.md +++ b/compiler/str/README.md @@ -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: diff --git a/compiler/test_gen/src/gen_hash.rs b/compiler/test_gen/src/gen_hash.rs index 616b377f10..b534d8620d 100644 --- a/compiler/test_gen/src/gen_hash.rs +++ b/compiler/test_gen/src/gen_hash.rs @@ -37,7 +37,7 @@ fn hash_record() { fn hash_result() { assert_evals_to!( "Dict.hashTestOnly 0 (List.get [ 0x1 ] 0) ", - 6707068610910845221, + 2878521786781103245, u64 ); } diff --git a/compiler/test_gen/src/gen_num.rs b/compiler/test_gen/src/gen_num.rs index 8d55fdc7a8..fb1621eb8c 100644 --- a/compiler/test_gen/src/gen_num.rs +++ b/compiler/test_gen/src/gen_num.rs @@ -3,7 +3,7 @@ mod gen_num { use crate::assert_evals_to; use crate::assert_llvm_evals_to; use indoc::indoc; - use roc_std::RocOrder; + use roc_std::{RocDec, RocOrder}; #[test] 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] fn f64_float_alias() { 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] fn gen_add_f64() { assert_evals_to!( @@ -586,6 +623,26 @@ mod gen_num { 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] 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] fn gen_wrap_int_neq() { 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] fn gen_sub_f64() { 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] fn gen_mul_i64() { assert_evals_to!( diff --git a/compiler/test_gen/src/gen_primitives.rs b/compiler/test_gen/src/gen_primitives.rs index 77b4f7cb24..46d3816aa4 100644 --- a/compiler/test_gen/src/gen_primitives.rs +++ b/compiler/test_gen/src/gen_primitives.rs @@ -1280,7 +1280,7 @@ fn linked_list_singleton() { } #[test] -fn recursive_functon_with_rigid() { +fn recursive_function_with_rigid() { assert_non_opt_evals_to!( indoc!( r#" diff --git a/compiler/test_gen/src/helpers/eval.rs b/compiler/test_gen/src/helpers/eval.rs index 713f1b8a30..02436deccd 100644 --- a/compiler/test_gen/src/helpers/eval.rs +++ b/compiler/test_gen/src/helpers/eval.rs @@ -60,7 +60,7 @@ pub fn helper<'a>( let loaded = roc_load::file::load_and_monomorphize_from_str( arena, filename, - &module_src, + module_src, stdlib, src_dir, exposed_types, @@ -211,7 +211,7 @@ pub fn helper<'a>( // Compile and add all the Procs before adding main let env = roc_gen_llvm::llvm::build::Env { - arena: &arena, + arena, builder: &builder, dibuilder: &dibuilder, compile_unit: &compile_unit, @@ -252,7 +252,7 @@ pub fn helper<'a>( // Uncomment this to see the module's optimized LLVM instruction output: // env.module.print_to_stderr(); - let lib = module_to_dylib(&env.module, &target, opt_level) + let lib = module_to_dylib(env.module, &target, opt_level) .expect("Error loading compiled dylib for test"); (main_fn_name, delayed_errors.join("\n"), lib) diff --git a/compiler/test_mono/src/lib.rs b/compiler/test_mono/src/lib.rs index 403ef32904..2d05f9cd16 100644 --- a/compiler/test_mono/src/lib.rs +++ b/compiler/test_mono/src/lib.rs @@ -3,7 +3,7 @@ // See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check. #![allow(clippy::large_enum_variant)] // we actually want to compare against the literal float bits -#![allow(clippy::clippy::float_cmp)] +#![allow(clippy::float_cmp)] #[macro_use] 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( arena, filename, - &module_src, + module_src, &stdlib, src_dir, exposed_types, diff --git a/compiler/types/src/builtin_aliases.rs b/compiler/types/src/builtin_aliases.rs index 1c0217e380..4c93a949dc 100644 --- a/compiler/types/src/builtin_aliases.rs +++ b/compiler/types/src/builtin_aliases.rs @@ -239,6 +239,16 @@ pub fn aliases() -> MutMap { }, ); + // Decimal : [ @Decimal ] + add_alias( + Symbol::NUM_DECIMAL, + BuiltinAlias { + region: Region::zero(), + vars: vec![], + typ: decimal_alias_content(), + }, + ); + // Binary64 : [ @Binary64 ] add_alias( Symbol::NUM_BINARY64, @@ -269,6 +279,16 @@ pub fn aliases() -> MutMap { }, ); + // Dec : Float Decimal + add_alias( + Symbol::NUM_DEC, + BuiltinAlias { + region: Region::zero(), + vars: Vec::new(), + typ: float_alias_content(decimal_type()), + }, + ); + // F64 : Float Binary64 add_alias( Symbol::NUM_F64, @@ -657,6 +677,20 @@ fn unsigned8_alias_content() -> SolvedType { 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)] pub fn bool_type() -> SolvedType { SolvedType::Alias( diff --git a/compiler/types/src/pretty_print.rs b/compiler/types/src/pretty_print.rs index 8df08669d1..86ea524fe8 100644 --- a/compiler/types/src/pretty_print.rs +++ b/compiler/types/src/pretty_print.rs @@ -77,11 +77,11 @@ fn find_names_needed( use crate::subs::FlatType::*; 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 { Content::Structure(FlatType::TagUnion(tags, ext_var)) => { - let rec_var = subs.fresh_unnamed_flex_var(); - let mut new_tags = MutMap::default(); for (label, args) in tags { @@ -94,7 +94,7 @@ fn find_names_needed( 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)); } _ => 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) => { let root = subs.get_root_key_without_compacting(variable); @@ -133,41 +133,31 @@ fn find_names_needed( // User-defined names are already taken. // We must not accidentally generate names that collide with them! - names_taken.insert(name); + names_taken.insert(name.clone()); } RigidVar(name) => { // User-defined names are already taken. // We must not accidentally generate names that collide with them! - names_taken.insert(name); + names_taken.insert(name.clone()); } Structure(Apply(_, 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)) => { 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)) => { - let mut sorted_fields: Vec<_> = fields.iter().collect(); - - 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, - ); + Structure(Record(sorted_fields, ext_var)) => { + for var in sorted_fields.iter_variables() { + 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(TagUnion(tags, ext_var)) => { 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(ext_var, subs, roots, root_appearances, names_taken); + find_names_needed(*ext_var, subs, roots, root_appearances, names_taken); } 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)) => { 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(ext_var, subs, roots, root_appearances, names_taken); - find_names_needed(rec_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); } Alias(_symbol, args, _actual) => { 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? // 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) { 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) => { - descriptor.content = FlexVar(Some(name)); - subs.set(root, descriptor); + let content = FlexVar(Some(name)); + subs.set_content(root, content); } RecursionVar { opt_name: None, structure, } => { - descriptor.content = RecursionVar { - structure, + let content = RecursionVar { + structure: *structure, opt_name: Some(name), }; - subs.set(root, descriptor); + subs.set_content(root, content); } RecursionVar { opt_name: Some(_existing), @@ -270,7 +260,7 @@ fn set_root_name(root: Variable, name: Lowercase, subs: &mut Subs) { } pub fn content_to_string( - content: Content, + content: &Content, subs: &Subs, home: ModuleId, interns: &Interns, @@ -283,7 +273,7 @@ pub fn content_to_string( 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::*; match content { @@ -298,14 +288,14 @@ fn write_content(env: &Env, content: Content, subs: &Subs, buf: &mut String, par Alias(symbol, args, _actual) => { let write_parens = parens == Parens::InTypeParam && !args.is_empty(); - match symbol { + match *symbol { Symbol::NUM_NUM => { debug_assert_eq!(args.len(), 1); let (_, arg_var) = args .get(0) .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 { 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_symbol(env, symbol, buf); + write_symbol(env, *symbol, buf); for (_, var) in args { buf.push(' '); write_content( env, - subs.get_without_compacting(var).content, + subs.get_content_without_compacting(*var), subs, buf, Parens::InTypeParam, @@ -342,7 +332,7 @@ fn write_content(env: &Env, content: Content, subs: &Subs, buf: &mut String, par // useful for debugging if false { 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); 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>, + 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::*; 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), 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) => { use crate::types::{gather_fields, RecordStructure}; // 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; if fields.is_empty() { @@ -373,12 +421,6 @@ fn write_flat_type(env: &Env, flat_type: FlatType, subs: &Subs, buf: &mut String } else { 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; 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( env, - subs.get_without_compacting(var).content, + subs.get_content_without_compacting(var), subs, buf, Parens::Unnecessary, @@ -418,7 +460,7 @@ fn write_flat_type(env: &Env, flat_type: FlatType, subs: &Subs, buf: &mut String buf.push_str(" }"); } - match subs.get_without_compacting(ext_var).content { + match subs.get_content_without_compacting(ext_var) { Content::Structure(EmptyRecord) => { // 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) => { - let interns = &env.interns; - let home = env.home; - buf.push_str("[ "); - // 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.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, - ); - } - } + let ext_content = write_sorted_tags(env, subs, buf, tags, *ext_var); 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) => { - let interns = &env.interns; - let home = env.home; - buf.push_str("[ "); - buf.push_str(&tag_name.as_string(&interns, home)); + let mut tags: MutMap = MutMap::default(); + tags.insert(*tag_name.clone(), vec![]); + let ext_content = write_sorted_tags(env, subs, buf, &tags, *ext_var); 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 { // This is an open tag union, so print the variable // 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) => { - let interns = &env.interns; - let home = env.home; - buf.push_str("[ "); - // 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.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, - ); - } - } + let ext_content = write_sorted_tags(env, subs, buf, tags, *ext_var); 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 "); write_content( env, - subs.get_without_compacting(rec_var).content, + subs.get_content_without_compacting(*rec_var), subs, buf, 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( - subs: &Subs, +pub fn chase_ext_tag_union<'a>( + subs: &'a Subs, var: Variable, fields: &mut Vec<(TagName, Vec)>, -) -> Result<(), (Variable, Content)> { +) -> Result<(), (Variable, &'a Content)> { use FlatType::*; - match subs.get_without_compacting(var).content { + match subs.get_content_without_compacting(var) { Content::Structure(EmptyTagUnion) => Ok(()), Content::Structure(TagUnion(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())); } - chase_ext_tag_union(subs, ext_var, fields) + chase_ext_tag_union(subs, *ext_var, fields) } 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)), } @@ -614,25 +576,27 @@ pub fn chase_ext_record( use crate::subs::Content::*; use crate::subs::FlatType::*; - match subs.get_without_compacting(var).content { + match subs.get_content_without_compacting(var) { 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(()), - 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( env: &Env, symbol: Symbol, - args: Vec, + args: &[Variable], subs: &Subs, buf: &mut String, parens: Parens, @@ -646,10 +610,10 @@ fn write_apply( } Symbol::NUM_NUM => { let arg = args - .into_iter() + .iter() .next() .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 default_case = |subs, content| { @@ -690,7 +654,7 @@ fn write_apply( buf.push(' '); write_content( env, - subs.get_without_compacting(arg).content, + subs.get_content_without_compacting(*arg), subs, buf, Parens::InTypeParam, @@ -706,7 +670,7 @@ fn write_apply( fn write_fn( env: &Env, - args: Vec, + args: &[Variable], ret: Variable, subs: &Subs, buf: &mut String, @@ -728,7 +692,7 @@ fn write_fn( write_content( env, - subs.get_without_compacting(arg).content, + subs.get_content_without_compacting(*arg), subs, buf, Parens::InFn, @@ -738,7 +702,7 @@ fn write_fn( buf.push_str(" -> "); write_content( env, - subs.get_without_compacting(ret).content, + subs.get_content_without_compacting(ret), subs, buf, 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, // or if it's a builtin (since all their types are always in scope) 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('.'); } diff --git a/compiler/types/src/solved_types.rs b/compiler/types/src/solved_types.rs index fa71812f3e..a21e4c1600 100644 --- a/compiler/types/src/solved_types.rs +++ b/compiler/types/src/solved_types.rs @@ -341,24 +341,27 @@ impl SolvedType { 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)), RecursionVar { structure, .. } => { // 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), Alias(symbol, args, actual_var) => { let mut new_args = Vec::with_capacity(args.len()); 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, } @@ -367,7 +370,7 @@ impl SolvedType { fn from_flat_type( subs: &Subs, recursion_vars: &mut RecursionVars, - flat_type: FlatType, + flat_type: &FlatType, ) -> Self { use crate::subs::FlatType::*; @@ -379,17 +382,17 @@ impl SolvedType { 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) => { let mut new_args = Vec::with_capacity(args.len()); 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 closure = Self::from_var_help(subs, recursion_vars, closure); + let ret = Self::from_var_help(subs, recursion_vars, *ret); + let closure = Self::from_var_help(subs, recursion_vars, *closure); 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()); 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 { - 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)); + new_fields.push((label.clone(), 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 { fields: new_fields, @@ -422,23 +420,23 @@ impl SolvedType { let mut new_args = Vec::with_capacity(args.len()); 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)) } 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) => { - recursion_vars.insert(subs, rec_var); + recursion_vars.insert(subs, *rec_var); let mut new_tags = Vec::with_capacity(tags.len()); @@ -446,23 +444,23 @@ impl SolvedType { let mut new_args = Vec::with_capacity(args.len()); 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( - VarId::from_var(rec_var, subs), + VarId::from_var(*rec_var, subs), new_tags, Box::new(ext), ) } EmptyRecord => SolvedType::EmptyRecord, 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()); 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_closure = to_type(&closure, free_vars, var_store); + let new_ret = to_type(ret, free_vars, var_store); + let new_closure = to_type(closure, free_vars, var_store); Type::Function(new_args, Box::new(new_closure), Box::new(new_ret)) } @@ -522,13 +520,13 @@ pub fn to_type( let mut new_args = Vec::with_capacity(args.len()); 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) } 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) } else { let var = var_store.fresh(); @@ -549,9 +547,9 @@ pub fn to_type( for (label, field) in fields { let field_val = match field { - Required(typ) => Required(to_type(&typ, free_vars, var_store)), - Optional(typ) => Optional(to_type(&typ, free_vars, var_store)), - Demanded(typ) => Demanded(to_type(&typ, free_vars, var_store)), + Required(typ) => Required(to_type(typ, free_vars, var_store)), + Optional(typ) => Optional(to_type(typ, free_vars, var_store)), + Demanded(typ) => Demanded(to_type(typ, free_vars, var_store)), }; new_fields.insert(label.clone(), field_val); diff --git a/compiler/types/src/subs.rs b/compiler/types/src/subs.rs index e66ad7fb12..67f3174ed8 100644 --- a/compiler/types/src/subs.rs +++ b/compiler/types/src/subs.rs @@ -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_module::ident::{Lowercase, TagName}; use roc_module::symbol::Symbol; +use std::cmp::Ordering; 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}; #[derive(Clone, Copy, Hash, PartialEq, Eq)] @@ -339,10 +340,20 @@ impl Subs { 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 { 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 { 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) { let l_key = self.utable.get_root_key(key); @@ -516,7 +536,7 @@ impl From for Rank { } } -#[derive(Clone, PartialEq, Eq)] +#[derive(Clone)] pub struct Descriptor { pub content: Content, pub rank: Rank, @@ -554,7 +574,7 @@ impl From for Descriptor { } } -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Debug)] pub enum Content { /// A type variable which the user did not name in an annotation, /// @@ -593,22 +613,22 @@ impl Content { eprintln!( "{}", - crate::pretty_print::content_to_string(self.clone(), subs, home, &interns) + crate::pretty_print::content_to_string(&self, subs, home, &interns) ); self } } -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Debug)] pub enum FlatType { Apply(Symbol, Vec), Func(Vec, Variable, Variable), - Record(MutMap>, Variable), + Record(RecordFields, Variable), TagUnion(MutMap>, Variable), - FunctionOrTagUnion(TagName, Symbol, Variable), + FunctionOrTagUnion(Box, Symbol, Variable), RecursiveTagUnion(Variable, MutMap>, Variable), - Erroneous(Problem), + Erroneous(Box), EmptyRecord, EmptyTagUnion, } @@ -621,6 +641,232 @@ pub enum Builtin { EmptyRecord, } +#[derive(Clone, Debug)] +pub struct RecordFields { + field_names: Vec, + variables: Vec, + field_type: Vec>, +} + +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 { + self.variables.iter() + } + + pub fn iter_variables_mut(&mut self) -> impl Iterator { + self.variables.iter_mut() + } + + pub fn iter(&self) -> impl Iterator)> { + 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)>) -> 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)>) -> 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, RecordField)>, +} + +impl Extend<(Lowercase, RecordField)> for RecordFields { + fn extend)>>(&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)> for RecordFields { + fn from_iter)>>(iter: T) -> Self { + let vec: Vec<_> = iter.into_iter().collect(); + Self::from_vec(vec) + } +} + +impl<'a> FromIterator<(&'a Lowercase, RecordField)> for RecordFields { + fn from_iter)>>(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); + + #[allow(clippy::type_complexity)] + type IntoIter = Map< + Zip< + Zip, std::vec::IntoIter>, + std::vec::IntoIter>, + >, + fn(((Lowercase, Variable), RecordField<()>)) -> (Lowercase, RecordField), + >; + + 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) { + let ((name, var), field_type) = arg; + + (name, field_type.map(|_| var)) +} + +impl<'a> IntoIterator for &'a RecordFields { + type Item = (&'a Lowercase, RecordField); + + #[allow(clippy::type_complexity)] + type IntoIter = Map< + Zip< + Zip, std::slice::Iter<'a, Variable>>, + std::slice::Iter<'a, RecordField<()>>, + >, + fn( + ((&'a Lowercase, &Variable), &RecordField<()>), + ) -> (&'a Lowercase, RecordField), + >; + + 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) { + let ((name, var), field_type) = arg; + + (name, field_type.map(|_| *var)) +} + fn occurs( subs: &Subs, seen: &ImSet, @@ -634,7 +880,7 @@ fn occurs( if seen.contains(&root_var) { Some((root_var, vec![])) } else { - match subs.get_without_compacting(root_var).content { + match subs.get_content_without_compacting(root_var) { FlexVar(_) | RigidVar(_) | RecursionVar { .. } | Error => None, Structure(flat_type) => { @@ -645,31 +891,26 @@ fn occurs( match flat_type { Apply(_, args) => short_circuit(subs, root_var, &new_seen, args.iter()), Func(arg_vars, closure_var, ret_var) => { - let it = once(&ret_var) - .chain(once(&closure_var)) + let it = once(ret_var) + .chain(once(closure_var)) .chain(arg_vars.iter()); short_circuit(subs, root_var, &new_seen, it) } Record(vars_by_field, ext_var) => { - let it = - once(&ext_var).chain(vars_by_field.values().map(|field| match field { - RecordField::Optional(var) => var, - RecordField::Required(var) => var, - RecordField::Demanded(var) => var, - })); + let it = once(ext_var).chain(vars_by_field.iter_variables()); short_circuit(subs, root_var, &new_seen, it) } 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) } FunctionOrTagUnion(_, _, ext_var) => { - let it = once(&ext_var); + let it = once(ext_var); short_circuit(subs, root_var, &new_seen, it) } RecursiveTagUnion(_rec_var, tags, ext_var) => { // 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) } EmptyRecord | EmptyTagUnion | Erroneous(_) => None, @@ -780,21 +1021,10 @@ fn explicit_substitute( Record(mut vars_by_field, ext_var) => { let new_ext_var = explicit_substitute(subs, from, to, ext_var, seen); - for (_, field) in vars_by_field.iter_mut() { - use RecordField::*; - - *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)) - } - }; + for var in vars_by_field.variables.iter_mut() { + *var = explicit_substitute(subs, from, to, *var, seen); } + 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) => { + let tag_name = *tag_name; + let mut err_tags = SendMap::default(); err_tags.insert(tag_name, vec![]); @@ -1223,7 +1455,7 @@ fn flat_type_to_err_type( } Erroneous(problem) => { - state.problems.push(problem); + state.problems.push(*problem); ErrorType::Error } @@ -1265,8 +1497,8 @@ fn restore_content(subs: &mut Subs, content: &Content) { EmptyTagUnion => (), Record(fields, ext_var) => { - for field in fields.values() { - subs.restore(field.into_inner()); + for var in fields.iter_variables() { + subs.restore(*var); } subs.restore(*ext_var); diff --git a/compiler/types/src/types.rs b/compiler/types/src/types.rs index 7e0e54d3f4..fc9f123226 100644 --- a/compiler/types/src/types.rs +++ b/compiler/types/src/types.rs @@ -1,7 +1,7 @@ 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 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::low_level::LowLevel; use roc_module::symbol::{Interns, ModuleId, Symbol}; @@ -406,7 +406,7 @@ impl Type { match self { Variable(v) => { - if let Some(replacement) = substitutions.get(&v) { + if let Some(replacement) = substitutions.get(v) { *self = replacement.clone(); } } @@ -762,15 +762,15 @@ fn symbols_help(tipe: &Type, accum: &mut ImSet) { match tipe { Function(args, closure, ret) => { - symbols_help(&ret, accum); - symbols_help(&closure, accum); + symbols_help(ret, accum); + symbols_help(closure, accum); args.iter().for_each(|arg| symbols_help(arg, accum)); } FunctionOrTagUnion(_, _, ext) => { - symbols_help(&ext, accum); + symbols_help(ext, accum); } RecursiveTagUnion(_, tags, ext) | TagUnion(tags, ext) => { - symbols_help(&ext, accum); + symbols_help(ext, accum); tags.iter() .map(|v| v.1.iter()) .flatten() @@ -778,7 +778,7 @@ fn symbols_help(tipe: &Type, accum: &mut ImSet) { } Record(fields, ext) => { - symbols_help(&ext, accum); + symbols_help(ext, accum); fields.values().for_each(|field| { use RecordField::*; @@ -791,11 +791,11 @@ fn symbols_help(tipe: &Type, accum: &mut ImSet) { } Alias(alias_symbol, _, actual_type) => { accum.insert(*alias_symbol); - symbols_help(&actual_type, accum); + symbols_help(actual_type, accum); } HostExposedAlias { name, actual, .. } => { accum.insert(*name); - symbols_help(&actual, accum); + symbols_help(actual, accum); } Apply(symbol, args) => { accum.insert(*symbol); @@ -980,7 +980,7 @@ fn variables_help_detailed(tipe: &Type, accum: &mut VariableDetail) { } pub struct RecordStructure { - pub fields: MutMap>, + pub fields: RecordFields, pub ext: Variable, } @@ -1116,7 +1116,7 @@ pub struct Alias { #[derive(PartialEq, Eq, Debug, Clone, Hash)] pub enum Problem { CanonicalizationProblem, - CircularType(Symbol, ErrorType, Region), + CircularType(Symbol, Box, Region), CyclicAlias(Symbol, Region, Vec), UnrecognizedIdent(InlinableString), Shadowed(Region, Located), @@ -1522,22 +1522,76 @@ pub fn name_type_var(letters_used: u32, taken: &mut MutSet) -> (Lower pub fn gather_fields( subs: &Subs, - fields: MutMap>, - var: Variable, + other_fields: RecordFields, + mut var: Variable, ) -> RecordStructure { use crate::subs::Content::*; use crate::subs::FlatType::*; - match subs.get_without_compacting(var).content { - Structure(Record(sub_fields, sub_ext)) => { - gather_fields(subs, union(fields, &sub_fields), sub_ext) - } + let mut result = other_fields; - Alias(_, _, var) => { - // TODO according to elm/compiler: "TODO may be dropping useful alias info here" - gather_fields(subs, fields, var) - } + loop { + match subs.get_content_without_compacting(var) { + Structure(Record(sub_fields, sub_ext)) => { + result = RecordFields::merge(result, sub_fields.clone()); - _ => RecordStructure { fields, ext: var }, + var = *sub_ext; + } + + Alias(_, _, actual_var) => { + // TODO according to elm/compiler: "TODO may be dropping useful alias info here" + var = *actual_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, + } } } diff --git a/compiler/unify/src/unify.rs b/compiler/unify/src/unify.rs index 7a7ca125a0..ca915e69a9 100644 --- a/compiler/unify/src/unify.rs +++ b/compiler/unify/src/unify.rs @@ -2,8 +2,8 @@ use roc_collections::all::{default_hasher, get_shared, relative_complement, unio use roc_module::ident::{Lowercase, TagName}; use roc_module::symbol::Symbol; use roc_types::subs::Content::{self, *}; -use roc_types::subs::{Descriptor, FlatType, Mark, OptVariable, Subs, Variable}; -use roc_types::types::{gather_fields, ErrorType, Mismatch, RecordField, RecordStructure}; +use roc_types::subs::{Descriptor, FlatType, Mark, OptVariable, RecordFields, Subs, Variable}; +use roc_types::types::{gather_fields_ref, ErrorType, Mismatch, RecordField, RecordStructure}; macro_rules! mismatch { () => {{ @@ -177,7 +177,7 @@ fn unify_alias( match other_content { FlexVar(_) => { // 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), RigidVar(_) => unify_pool(subs, pool, real_var, ctx.second), @@ -190,7 +190,7 @@ fn unify_alias( } if problems.is_empty() { - problems.extend(merge(subs, &ctx, other_content.clone())); + problems.extend(merge(subs, ctx, other_content.clone())); } if problems.is_empty() { @@ -262,28 +262,27 @@ fn unify_record( ) -> Outcome { let fields1 = rec1.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() { - if unique_fields2.is_empty() { + let separate = RecordFields::separate(fields1, fields2); + + 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); if !ext_problems.is_empty() { return ext_problems; } - let other_fields = MutMap::default(); 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 } 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 ext_problems = unify_pool(subs, pool, rec1.ext, sub_record); @@ -291,16 +290,21 @@ fn unify_record( return ext_problems; } - let other_fields = MutMap::default(); - let mut field_problems = - unify_shared_fields(subs, pool, ctx, shared_fields, other_fields, sub_record); + let mut field_problems = unify_shared_fields( + subs, + pool, + ctx, + shared_fields, + OtherFields::None, + sub_record, + ); field_problems.extend(ext_problems); field_problems } - } else if unique_fields2.is_empty() { - let flat_type = FlatType::Record(unique_fields1, rec1.ext); + } else if separate.only_in_2.is_empty() { + let flat_type = FlatType::Record(separate.only_in_1, rec1.ext); let sub_record = fresh(subs, pool, ctx, Structure(flat_type)); let ext_problems = unify_pool(subs, pool, sub_record, rec2.ext); @@ -308,19 +312,29 @@ fn unify_record( return ext_problems; } - let other_fields = MutMap::default(); - let mut field_problems = - unify_shared_fields(subs, pool, ctx, shared_fields, other_fields, sub_record); + let mut field_problems = unify_shared_fields( + subs, + pool, + ctx, + shared_fields, + OtherFields::None, + sub_record, + ); field_problems.extend(ext_problems); field_problems } 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 flat_type1 = FlatType::Record(unique_fields1, ext); - let flat_type2 = FlatType::Record(unique_fields2, ext); + let flat_type1 = FlatType::Record(separate.only_in_1, ext); + let flat_type2 = FlatType::Record(separate.only_in_2, ext); let sub1 = fresh(subs, pool, ctx, Structure(flat_type1)); 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( subs: &mut Subs, pool: &mut Pool, ctx: &Context, - shared_fields: MutMap, RecordField)>, - other_fields: MutMap>, + shared_fields: Vec<(Lowercase, RecordField, RecordField)>, + other_fields: OtherFields, ext: Variable, ) -> Outcome { - let mut matching_fields = MutMap::default(); + let mut matching_fields = Vec::with_capacity(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()); if local_problems.is_empty() { @@ -383,18 +402,36 @@ fn unify_shared_fields( (Optional(val), Optional(_)) => Optional(val), }; - let existing = matching_fields.insert(name, actual); - debug_assert_eq!(existing, None); + matching_fields.push((name, actual)); } } if num_shared_fields == matching_fields.len() { // 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) { - Ok(()) => Variable::EMPTY_RECORD, - Err((new, _)) => new, + 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, + 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); @@ -405,6 +442,39 @@ fn unify_shared_fields( } } +struct Separate { + only_in_1: MutMap, + only_in_2: MutMap, + in_both: MutMap, +} + +fn separate(tags1: MutMap, mut tags2: MutMap) -> Separate +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( subs: &mut Subs, pool: &mut Pool, @@ -415,10 +485,6 @@ fn unify_tag_union( ) -> Outcome { let tags1 = rec1.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 { (None, None) => None, @@ -426,6 +492,23 @@ fn unify_tag_union( (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_tags2.is_empty() { let ext_problems = unify_pool(subs, pool, rec1.ext, rec2.ext); @@ -439,7 +522,7 @@ fn unify_tag_union( pool, ctx, shared_tags, - MutMap::default(), + OtherTags::Empty, rec1.ext, recursion_var, ); @@ -461,7 +544,7 @@ fn unify_tag_union( pool, ctx, shared_tags, - MutMap::default(), + OtherTags::Empty, sub_record, recursion_var, ); @@ -484,7 +567,7 @@ fn unify_tag_union( pool, ctx, shared_tags, - MutMap::default(), + OtherTags::Empty, sub_record, recursion_var, ); @@ -493,7 +576,10 @@ fn unify_tag_union( tag_problems } 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 flat_type1 = FlatType::TagUnion(unique_tags1, ext); @@ -686,8 +772,8 @@ fn unify_tag_union_not_recursive_recursive( /// into it. #[allow(dead_code)] fn is_structure(var: Variable, subs: &mut Subs) -> bool { - match subs.get(var).content { - Content::Alias(_, _, actual) => is_structure(actual, subs), + match subs.get_content_without_compacting(var) { + Content::Alias(_, _, actual) => is_structure(*actual, subs), Content::Structure(_) => true, _ => false, } @@ -772,12 +858,20 @@ fn unify_shared_tags_recursive_not_recursive( } } +enum OtherTags { + Empty, + Union { + tags1: MutMap>, + tags2: MutMap>, + }, +} + fn unify_shared_tags( subs: &mut Subs, pool: &mut Pool, ctx: &Context, shared_tags: MutMap, Vec)>, - other_tags: MutMap>, + other_tags: OtherTags, ext: Variable, recursion_var: Option, ) -> Outcome { @@ -829,6 +923,8 @@ fn unify_shared_tags( } if num_shared_tags == matching_tags.len() { + let mut new_tags = matching_tags; + // merge fields from the ext_var into this tag union let mut fields = Vec::new(); 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, Err((new, _)) => new, }; - - let mut new_tags = union(matching_tags, &other_tags); new_tags.extend(fields.into_iter()); - let flat_type = if let Some(rec) = recursion_var { - debug_assert!(is_recursion_var(subs, rec)); - FlatType::RecursiveTagUnion(rec, new_tags, new_ext_var) - } else { - FlatType::TagUnion(new_tags, new_ext_var) - }; + match other_tags { + OtherTags::Empty => {} + OtherTags::Union { tags1, tags2 } => { + new_tags.reserve(tags1.len() + tags2.len()); + 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 { mismatch!( "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 -where - I: Iterator>, - T: 'a, -{ - fields.all(|field| match field { - RecordField::Required(_) => false, - RecordField::Demanded(_) => false, - RecordField::Optional(_) => true, - }) +fn unify_shared_tags_merge( + subs: &mut Subs, + ctx: &Context, + new_tags: MutMap>, + new_ext_var: Variable, + recursion_var: Option, +) -> Outcome { + let flat_type = if let Some(rec) = recursion_var { + 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)] @@ -882,17 +983,17 @@ fn unify_flat_type( match (left, right) { (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) } - (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) } (Record(fields1, ext1), Record(fields2, ext2)) => { - let rec1 = gather_fields(subs, fields1.clone(), *ext1); - let rec2 = gather_fields(subs, fields2.clone(), *ext2); + let rec1 = gather_fields_ref(subs, fields1, *ext1); + let rec2 = gather_fields_ref(subs, fields2, *ext2); unify_record(subs, pool, ctx, rec1, rec2) } @@ -1014,18 +1115,18 @@ fn unify_flat_type( if tag_name_1 == tag_name_2 { let problems = unify_pool(subs, pool, *ext_1, *ext_2); if problems.is_empty() { - let desc = subs.get(ctx.second); - merge(subs, ctx, desc.content) + let content = subs.get_content_without_compacting(ctx.second).clone(); + merge(subs, ctx, content) } else { problems } } else { 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 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); 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 mut tags2 = MutMap::default(); - tags2.insert(tag_name.clone(), vec![]); + tags2.insert(*tag_name.clone(), vec![]); let union2 = gather_tags(subs, tags2, *ext2); unify_tag_union(subs, pool, ctx, union1, union2, (None, None)) } (FunctionOrTagUnion(tag_name, _, ext1), TagUnion(tags2, ext2)) => { 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 union2 = gather_tags(subs, tags2.clone(), *ext2); @@ -1055,7 +1156,7 @@ fn unify_flat_type( debug_assert!(is_recursion_var(subs, *recursion_var)); 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 union2 = gather_tags(subs, tags2, *ext2); @@ -1074,7 +1175,7 @@ fn unify_flat_type( debug_assert!(is_recursion_var(subs, *recursion_var)); 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 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( subs: &mut Subs, - tags: MutMap>, + mut tags: MutMap>, var: Variable, ) -> TagUnionStructure { use roc_types::subs::Content::*; use roc_types::subs::FlatType::*; - match subs.get(var).content { + match subs.get_content_without_compacting(var) { 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) => { // TODO according to elm/compiler: "TODO may be dropping useful alias info here" + let var = *var; gather_tags(subs, tags, var) } @@ -1259,7 +1366,7 @@ fn gather_tags( fn is_recursion_var(subs: &Subs, var: Variable) -> bool { matches!( - subs.get_without_compacting(var).content, + subs.get_content_without_compacting(var), Content::RecursionVar { .. } ) } diff --git a/docs/Cargo.toml b/docs/Cargo.toml index d57e99ece4..7cb857b8f3 100644 --- a/docs/Cargo.toml +++ b/docs/Cargo.toml @@ -15,6 +15,7 @@ roc_can = { path = "../compiler/can" } roc_module = { path = "../compiler/module" } roc_region = { path = "../compiler/region" } roc_types = { path = "../compiler/types" } +roc_parse = { path = "../compiler/parse" } roc_collections = { path = "../compiler/collections" } bumpalo = { version = "3.2", features = ["collections"] } diff --git a/docs/src/lib.rs b/docs/src/lib.rs index d99409cfbd..557c0df613 100644 --- a/docs/src/lib.rs +++ b/docs/src/lib.rs @@ -1,22 +1,24 @@ 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; use bumpalo::Bump; +use roc_builtins::std::StdLib; +use roc_can::builtins::builtin_defs_map; use roc_can::scope::Scope; use roc_collections::all::MutMap; 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 std::fs; use std::path::{Path, PathBuf}; pub fn generate(filenames: Vec, std_lib: StdLib, build_dir: &Path) { let files_docs = files_to_documentations(filenames, std_lib); + let mut arena = Bump::new(); // // TODO: get info from a file like "elm.json" @@ -52,22 +54,33 @@ pub fn generate(filenames: Vec, std_lib: StdLib, build_dir: &Path) { let template_html = include_str!("./static/index.html").replace( "", - render_sidebar( - package - .modules - .iter() - .flat_map(|loaded_module| loaded_module.documentation.values()), - ) + render_sidebar(package.modules.iter().flat_map(|loaded_module| { + loaded_module.documentation.values().map(move |d| { + let exposed_values = loaded_module + .exposed_values + .iter() + .map(|symbol| symbol.ident_string(&loaded_module.interns).to_string()) + .collect::>(); + + (exposed_values, d) + }) + })) .as_str(), ); // Write each package's module docs html file for loaded_module in package.modules.iter_mut() { - let exports = loaded_module - .exposed_values - .iter() - .map(|symbol| symbol.ident_string(&loaded_module.interns).to_string()) - .collect::>(); + arena.reset(); + + let mut exports: bumpalo::collections::Vec<&str> = + bumpalo::collections::Vec::with_capacity_in(loaded_module.exposed_values.len(), &arena); + + // 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() { let module_dir = build_dir.join(module.name.replace(".", "/").as_str()); @@ -83,7 +96,14 @@ pub fn generate(filenames: Vec, std_lib: StdLib, build_dir: &Path) { ) .replace( "", - 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) @@ -95,8 +115,10 @@ pub fn generate(filenames: Vec, std_lib: StdLib, build_dir: &Path) { } fn render_main_content( + home: ModuleId, + exposed_values: &[&str], + dep_idents: &MutMap, interns: &Interns, - exposed_values: &[String], module: &mut ModuleDocumentation, ) -> String { let mut buf = String::new(); @@ -115,7 +137,7 @@ fn render_main_content( if let DocDef(def) = entry { // 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 { @@ -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( html_node( @@ -158,12 +180,27 @@ fn render_main_content( if let Some(docs) = &doc_def.docs { 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) => { - 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()); } }; @@ -218,17 +255,17 @@ fn render_name_and_version(name: &str, version: &str) -> String { .as_str(), ); - let mut verions_href = String::new(); + let mut versions_href = String::new(); - verions_href.push('/'); - verions_href.push_str(name); - verions_href.push('/'); - verions_href.push_str(version); + versions_href.push('/'); + versions_href.push_str(name); + versions_href.push('/'); + versions_href.push_str(version); buf.push_str( html_node( "a", - vec![("class", "version"), ("href", verions_href.as_str())], + vec![("class", "version"), ("href", versions_href.as_str())], version, ) .as_str(), @@ -237,10 +274,12 @@ fn render_name_and_version(name: &str, version: &str) -> String { buf } -fn render_sidebar<'a, I: Iterator>(modules: I) -> String { +fn render_sidebar<'a, I: Iterator, &'a ModuleDocumentation)>>( + modules: I, +) -> String { let mut buf = String::new(); - for module in modules { + for (exposed_values, module) in modules { let mut sidebar_entry_content = String::new(); let name = module.name.as_str(); @@ -266,20 +305,22 @@ fn render_sidebar<'a, I: Iterator>(modules: I) - for entry in &module.entries { if let DocEntry::DocDef(doc_def) = entry { - let mut entry_href = String::new(); + if exposed_values.contains(&doc_def.name) { + let mut entry_href = String::new(); - entry_href.push_str(href.as_str()); - entry_href.push('#'); - entry_href.push_str(doc_def.name.as_str()); + entry_href.push_str(href.as_str()); + entry_href.push('#'); + entry_href.push_str(doc_def.name.as_str()); - entries_buf.push_str( - html_node( - "a", - vec![("href", entry_href.as_str())], - doc_def.name.as_str(), - ) - .as_str(), - ); + entries_buf.push_str( + html_node( + "a", + vec![("href", entry_href.as_str())], + doc_def.name.as_str(), + ) + .as_str(), + ); + } } } @@ -541,7 +582,7 @@ fn should_be_multiline(type_ann: &TypeAnnotation) -> bool { if is_multiline { break; } - is_multiline = should_be_multiline(&value); + is_multiline = should_be_multiline(value); } } @@ -601,161 +642,234 @@ fn should_be_multiline(type_ann: &TypeAnnotation) -> bool { } } -pub fn insert_doc_links(scope: &mut Scope, interns: &Interns, markdown: String) -> String { - let buf = &markdown; - let mut result = String::new(); - - let mut chomping_from: Option = 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::(), - ); - - 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 - } +struct DocUrl { + url: String, + title: String, } -fn make_doc_link(scope: &mut Scope, interns: &Interns, doc_item: &str) -> String { - match scope.lookup(&doc_item.into(), Region::zero()) { - Ok(symbol) => { - let module_str = symbol.module_string(interns); - - let ident_str = symbol.ident_string(interns); - - let mut link = String::new(); - - 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(_) => { - panic!( +fn doc_url<'a>( + home: ModuleId, + exposed_values: &[&str], + dep_idents: &MutMap, + 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) => { + // Get the exact module_name from scope. It could be the + // current module's name, but it also could be a different + // module - for example, if this is in scope from an + // unqualified import. + module_name = symbol.module_string(interns); + } + Err(_) => { + // TODO return Err here + panic!( "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 { - use pulldown_cmark::CodeBlockKind; - use pulldown_cmark::CowStr; - use pulldown_cmark::Event; - use pulldown_cmark::Tag::*; +fn markdown_to_html( + home: ModuleId, + exposed_values: &[&str], + dep_idents: &MutMap, + 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 mut docs_parser = vec![]; - let (_, _) = pulldown_cmark::Parser::new_ext(&markdown_with_links, markdown_options).fold( - (0, 0), - |(start_quote_count, end_quote_count), event| { - match event { - // Replace this sequence (`>>>` syntax): - // Start(BlockQuote) - // Start(BlockQuote) - // Start(BlockQuote) - // Start(Paragraph) - // For `Start(CodeBlock(Fenced(Borrowed("roc"))))` - Event::Start(BlockQuote) => { + let (_, _) = pulldown_cmark::Parser::new_with_broken_link_callback( + &markdown, + markdown_options, + Some(&mut broken_link_callback), + ) + .fold((0, 0), |(start_quote_count, end_quote_count), event| { + match event { + // Replace this sequence (`>>>` syntax): + // Start(BlockQuote) + // Start(BlockQuote) + // Start(BlockQuote) + // Start(Paragraph) + // For `Start(CodeBlock(Fenced(Borrowed("roc"))))` + Event::Start(BlockQuote) => { + docs_parser.push(event); + (start_quote_count + 1, 0) + } + Event::Start(Paragraph) => { + if start_quote_count == 3 { + docs_parser.pop(); + docs_parser.pop(); + docs_parser.pop(); + docs_parser.push(Event::Start(CodeBlock(CodeBlockKind::Fenced( + CowStr::Borrowed("roc"), + )))); + } else { docs_parser.push(event); - (start_quote_count + 1, 0) } - Event::Start(Paragraph) => { - if start_quote_count == 3 { - docs_parser.pop(); - docs_parser.pop(); - docs_parser.pop(); - docs_parser.push(Event::Start(CodeBlock(CodeBlockKind::Fenced( - CowStr::Borrowed("roc"), - )))); - } else { - docs_parser.push(event); - } + (0, 0) + } + // Replace this sequence (`>>>` syntax): + // End(Paragraph) + // End(BlockQuote) + // End(BlockQuote) + // End(BlockQuote) + // For `End(CodeBlock(Fenced(Borrowed("roc"))))` + Event::End(Paragraph) => { + docs_parser.push(event); + (0, 1) + } + Event::End(BlockQuote) => { + if end_quote_count == 3 { + docs_parser.pop(); + docs_parser.pop(); + docs_parser.pop(); + docs_parser.push(Event::End(CodeBlock(CodeBlockKind::Fenced( + CowStr::Borrowed("roc"), + )))); (0, 0) - } - // Replace this sequence (`>>>` syntax): - // End(Paragraph) - // End(BlockQuote) - // End(BlockQuote) - // End(BlockQuote) - // For `End(CodeBlock(Fenced(Borrowed("roc"))))` - Event::End(Paragraph) => { + } else { docs_parser.push(event); - (0, 1) - } - Event::End(BlockQuote) => { - if end_quote_count == 3 { - docs_parser.pop(); - docs_parser.pop(); - docs_parser.pop(); - docs_parser.push(Event::End(CodeBlock(CodeBlockKind::Fenced( - CowStr::Borrowed("roc"), - )))); - (0, 0) - } else { - docs_parser.push(event); - (0, end_quote_count + 1) - } - } - _ => { - docs_parser.push(event); - (0, 0) + (0, end_quote_count + 1) } } - }, - ); + _ => { + docs_parser.push(event); + (0, 0) + } + } + }); let mut docs_html = String::new(); diff --git a/docs/tests/fixtures/Interface.roc b/docs/tests/fixtures/Interface.roc deleted file mode 100644 index a0b863cbd4..0000000000 --- a/docs/tests/fixtures/Interface.roc +++ /dev/null @@ -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 diff --git a/docs/tests/insert_links.rs b/docs/tests/insert_links.rs deleted file mode 100644 index e01e3eca5b..0000000000 --- a/docs/tests/insert_links.rs +++ /dev/null @@ -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()), - ); - } -} diff --git a/editor/editor-ideas.md b/editor/editor-ideas.md index b1718ed3d2..0ef25eafcd 100644 --- a/editor/editor-ideas.md +++ b/editor/editor-ideas.md @@ -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... +## 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 Thoughts and ideas possibly taken from above inspirations or separate. diff --git a/editor/snippet-ideas.md b/editor/snippet-ideas.md new file mode 100644 index 0000000000..c8653e8378 --- /dev/null +++ b/editor/snippet-ideas.md @@ -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.: means this pure text snippets should be created for all Roc collections such as Dict, Set, List... + +- command: empty + + example: empty dict >> `{::}` +- command: + + 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: + + 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 + + diff --git a/editor/src/editor/code_lines.rs b/editor/src/editor/code_lines.rs index 0e72978a02..63f7b52472 100644 --- a/editor/src/editor/code_lines.rs +++ b/editor/src/editor/code_lines.rs @@ -66,7 +66,7 @@ impl Lines for CodeLines { fn get_line(&self, line_nr: usize) -> UIResult<&str> { let line_string = slice_get(line_nr, &self.lines)?; - Ok(&line_string) + Ok(line_string) } fn line_len(&self, line_nr: usize) -> UIResult { @@ -85,7 +85,7 @@ impl Lines for CodeLines { let mut lines = BumpString::with_capacity_in(self.nr_of_chars(), arena); for line in &self.lines { - lines.push_str(&line); + lines.push_str(line); } lines diff --git a/editor/src/editor/ed_error.rs b/editor/src/editor/ed_error.rs index 19a6124606..b534ca52bb 100644 --- a/editor/src/editor/ed_error.rs +++ b/editor/src/editor/ed_error.rs @@ -194,7 +194,7 @@ fn color_backtrace(backtrace: &snafu::Backtrace) -> String { for line in backtrace_split { 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 { prev_line_opt = Some(format!("{}", prev_line.truecolor(255, 30, 30))); } diff --git a/editor/src/editor/markup/nodes.rs b/editor/src/editor/markup/nodes.rs index 8390cd70dd..fdbfc98213 100644 --- a/editor/src/editor/markup/nodes.rs +++ b/editor/src/editor/markup/nodes.rs @@ -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 = index_of(*closest_ast_child, &child_ids_with_ast)?; @@ -259,7 +259,7 @@ pub fn expr2_to_markup<'a, 'b>( | Expr2::I128 { text, .. } | Expr2::U128 { text, .. } | Expr2::Float { text, .. } => { - let num_str = get_string(env, &text); + let num_str = get_string(env, text); new_markup_node( num_str, @@ -275,7 +275,7 @@ pub fn expr2_to_markup<'a, 'b>( markup_node_pool, ), Expr2::GlobalTag { name, .. } => new_markup_node( - get_string(env, &name), + get_string(env, name), expr2_node_id, HighlightStyle::Type, markup_node_pool, diff --git a/editor/src/editor/mvc/ed_model.rs b/editor/src/editor/mvc/ed_model.rs index b57a36df8a..6cd9373408 100644 --- a/editor/src/editor/mvc/ed_model.rs +++ b/editor/src/editor/mvc/ed_model.rs @@ -59,7 +59,7 @@ pub fn init_model<'a>( interns: &'a Interns, code_arena: &'a Bump, ) -> EdResult> { - 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 mut markup_node_pool = SlowPool::new(); @@ -175,7 +175,7 @@ impl<'a> EdModule<'a> { 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 { Ok((expr2, _output)) => { @@ -239,7 +239,7 @@ pub mod test_ed_model { ); ed_model::init_model( - &code_str, + code_str, file_path, env, &ed_model_refs.interns, diff --git a/editor/src/editor/mvc/ed_update.rs b/editor/src/editor/mvc/ed_update.rs index cf510dfd7f..9fd9caee31 100644 --- a/editor/src/editor/mvc/ed_update.rs +++ b/editor/src/editor/mvc/ed_update.rs @@ -221,7 +221,7 @@ impl<'a> EdModel<'a> { 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 .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)?; } 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 .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)?; } @@ -247,7 +247,7 @@ impl<'a> EdModel<'a> { let constrained = constrain_expr( &arena, &mut self.module.env, - &expr, + expr, Expected::NoExpectation(Type2::Variable(var)), Region::zero(), ); @@ -272,10 +272,10 @@ impl<'a> EdModel<'a> { let subs = solved.inner_mut(); - let content = subs.get(var).content; + let content = subs.get_content_without_compacting(var); 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, ) } @@ -605,7 +605,7 @@ pub fn handle_new_char(received_char: &char, ed_model: &mut EdModel) -> EdResult } Expr2::SmallStr(old_arr_str) => { update_small_string( - &ch, old_arr_str, ed_model + ch, old_arr_str, ed_model )? } Expr2::Str(old_pool_str) => { @@ -2043,11 +2043,11 @@ pub mod test_ed_update { expected_tooltip: &str, new_char: char, ) -> 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> { - 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, diff --git a/editor/src/editor/mvc/int_update.rs b/editor/src/editor/mvc/int_update.rs index 8af06a2abe..af6eb80f84 100644 --- a/editor/src/editor/mvc/int_update.rs +++ b/editor/src/editor/mvc/int_update.rs @@ -22,7 +22,7 @@ pub fn start_new_int(ed_model: &mut EdModel, digit_char: &char) -> EdResult EdResult { curr_mark_node, parent_id_opt, ast_node_id, - } = get_node_context(&ed_model)?; + } = get_node_context(ed_model)?; let is_blank_node = curr_mark_node.is_blank(); @@ -101,7 +101,7 @@ pub fn add_blank_child( curr_mark_node: _, parent_id_opt, 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 { diff --git a/editor/src/editor/mvc/record_update.rs b/editor/src/editor/mvc/record_update.rs index 9cf37c95e0..bacb17db46 100644 --- a/editor/src/editor/mvc/record_update.rs +++ b/editor/src/editor/mvc/record_update.rs @@ -24,7 +24,7 @@ pub fn start_new_record(ed_model: &mut EdModel) -> EdResult { curr_mark_node, parent_id_opt, ast_node_id, - } = get_node_context(&ed_model)?; + } = get_node_context(ed_model)?; let is_blank_node = curr_mark_node.is_blank(); @@ -109,7 +109,7 @@ pub fn update_empty_record( curr_mark_node, parent_id_opt, ast_node_id, - } = get_node_context(&ed_model)?; + } = get_node_context(ed_model)?; if prev_mark_node.get_content()? == nodes::LEFT_ACCOLADE && curr_mark_node.get_content()? == nodes::RIGHT_ACCOLADE @@ -182,7 +182,7 @@ pub fn update_record_colon( curr_mark_node, parent_id_opt, ast_node_id, - } = get_node_context(&ed_model)?; + } = get_node_context(ed_model)?; if let Some(parent_id) = parent_id_opt { let curr_ast_node = ed_model.module.env.pool.get(ast_node_id); diff --git a/editor/src/editor/mvc/string_update.rs b/editor/src/editor/mvc/string_update.rs index 3080a03e2a..b0145804ac 100644 --- a/editor/src/editor/mvc/string_update.rs +++ b/editor/src/editor/mvc/string_update.rs @@ -22,7 +22,7 @@ pub fn update_small_string( curr_mark_node: _, parent_id_opt: _, ast_node_id, - } = get_node_context(&ed_model)?; + } = get_node_context(ed_model)?; let new_input = &new_char.to_string(); @@ -84,7 +84,7 @@ pub fn update_string( curr_mark_node: _, parent_id_opt: _, ast_node_id, - } = get_node_context(&ed_model)?; + } = get_node_context(ed_model)?; // update markup let curr_mark_node_mut = ed_model.markup_node_pool.get_mut(curr_mark_node_id); @@ -129,7 +129,7 @@ pub fn start_new_string(ed_model: &mut EdModel) -> EdResult { curr_mark_node, parent_id_opt, ast_node_id, - } = get_node_context(&ed_model)?; + } = get_node_context(ed_model)?; if curr_mark_node.is_blank() { let new_expr2_node = Expr2::SmallStr(arraystring::ArrayString::new()); diff --git a/editor/src/editor/render_ast.rs b/editor/src/editor/render_ast.rs index d862b196a2..e7b01ab55b 100644 --- a/editor/src/editor/render_ast.rs +++ b/editor/src/editor/render_ast.rs @@ -108,7 +108,7 @@ fn markup_to_wgpu_helper<'a>( attributes: _, parent_id_opt: _, } => { - let highlight_color = map_get(&code_style.ed_theme.syntax_high_map, &syn_high_style)?; + let highlight_color = map_get(&code_style.ed_theme.syntax_high_map, syn_high_style)?; let glyph_text = glyph_brush::OwnedText::new(content) .with_color(colors::to_slice(*highlight_color)) @@ -127,7 +127,7 @@ fn markup_to_wgpu_helper<'a>( .with_color(colors::to_slice(colors::WHITE)) .with_scale(code_style.font_size); - let highlight_color = map_get(&code_style.ed_theme.syntax_high_map, &syn_high_style)?; + let highlight_color = map_get(&code_style.ed_theme.syntax_high_map, syn_high_style)?; let char_width = code_style.glyph_dim_rect.width; let char_height = code_style.glyph_dim_rect.height; diff --git a/editor/src/graphics/lowlevel/buffer.rs b/editor/src/graphics/lowlevel/buffer.rs index 539f52301a..f9cf9fc9a4 100644 --- a/editor/src/graphics/lowlevel/buffer.rs +++ b/editor/src/graphics/lowlevel/buffer.rs @@ -121,10 +121,10 @@ pub fn create_rect_buffers( let num_rects = { let mut quad_buffer_builder = QuadBufferBuilder::new(); for rect in rects { - quad_buffer_builder = quad_buffer_builder.push_rect(&rect); + quad_buffer_builder = quad_buffer_builder.push_rect(rect); } - let (stg_vertex, stg_index, num_indices) = quad_buffer_builder.build(&gpu_device); + let (stg_vertex, stg_index, num_indices) = quad_buffer_builder.build(gpu_device); stg_vertex.copy_to_buffer(encoder, &vertex_buffer); stg_index.copy_to_buffer(encoder, &index_buffer); diff --git a/editor/src/graphics/lowlevel/pipelines.rs b/editor/src/graphics/lowlevel/pipelines.rs index 79e3437f2e..810766ccfd 100644 --- a/editor/src/graphics/lowlevel/pipelines.rs +++ b/editor/src/graphics/lowlevel/pipelines.rs @@ -19,7 +19,7 @@ pub fn make_rect_pipeline( label: Some("Rectangle pipeline layout"), }); let pipeline = create_render_pipeline( - &gpu_device, + gpu_device, &pipeline_layout, swap_chain_descr.format, &wgpu::ShaderModuleDescriptor { @@ -42,7 +42,7 @@ pub fn create_render_pipeline( device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { label: Some("Render pipeline"), - layout: Some(&layout), + layout: Some(layout), vertex: wgpu::VertexState { module: &shader, entry_point: "vs_main", diff --git a/editor/src/graphics/primitives/text.rs b/editor/src/graphics/primitives/text.rs index 8910ceef38..4cc016d5cf 100644 --- a/editor/src/graphics/primitives/text.rs +++ b/editor/src/graphics/primitives/text.rs @@ -81,7 +81,7 @@ fn section_from_text<'a>( ..Section::default() } .add_text( - wgpu_glyph::Text::new(&text.text) + wgpu_glyph::Text::new(text.text) .with_color(Vector4::from(text.color)) .with_scale(text.size), ) @@ -156,5 +156,5 @@ pub fn build_glyph_brush( ) -> Result, InvalidFont> { let inconsolata = FontArc::try_from_slice(include_bytes!("../../../Inconsolata-Regular.ttf"))?; - Ok(GlyphBrushBuilder::using_font(inconsolata).build(&gpu_device, render_format)) + Ok(GlyphBrushBuilder::using_font(inconsolata).build(gpu_device, render_format)) } diff --git a/editor/src/lang/ast.rs b/editor/src/lang/ast.rs index 9f421b252b..e48cb42ebd 100644 --- a/editor/src/lang/ast.rs +++ b/editor/src/lang/ast.rs @@ -472,7 +472,7 @@ fn expr2_to_string_helper( Expr2::EmptyRecord => out_string.push_str("EmptyRecord"), Expr2::Record { record_var, fields } => { out_string.push_str("Record:\n"); - out_string.push_str(&var_to_string(&record_var, indent_level + 1)); + out_string.push_str(&var_to_string(record_var, indent_level + 1)); out_string.push_str(&format!("{}fields: [\n", get_spacing(indent_level + 1))); diff --git a/editor/src/lang/constrain.rs b/editor/src/lang/constrain.rs index cb1ed061bc..0dcd47965f 100644 --- a/editor/src/lang/constrain.rs +++ b/editor/src/lang/constrain.rs @@ -57,7 +57,7 @@ pub fn constrain_expr<'a>( Expr2::Blank => True, Expr2::EmptyRecord => constrain_empty_record(expected, region), Expr2::Var(symbol) => Lookup(*symbol, expected, region), - Expr2::SmallInt { var, .. } => { + Expr2::SmallInt { var, .. } | Expr2::I128 { var, .. } | Expr2::U128 { var, .. } => { let mut flex_vars = BumpVec::with_capacity_in(1, arena); flex_vars.push(*var); @@ -910,7 +910,57 @@ pub fn constrain_expr<'a>( exists(arena, flex_vars, And(cons)) } - _ => todo!("implement constraints for {:?}", expr), + + Expr2::RunLowLevel { op, args, ret_var } => { + // This is a modified version of what we do for function calls. + + // The operation's return type + let ret_type = Type2::Variable(*ret_var); + + // This will be used in the occurs check + let mut vars = BumpVec::with_capacity_in(1 + args.len(), arena); + + vars.push(*ret_var); + + let mut arg_types = BumpVec::with_capacity_in(args.len(), arena); + let mut arg_cons = BumpVec::with_capacity_in(args.len(), arena); + + for (index, node_id) in args.iter_node_ids().enumerate() { + let (arg_var, arg_id) = env.pool.get(node_id); + + vars.push(*arg_var); + + let arg_type = Type2::Variable(*arg_var); + + let reason = Reason::LowLevelOpArg { + op: *op, + arg_index: Index::zero_based(index), + }; + let expected_arg = + Expected::ForReason(reason, arg_type.shallow_clone(), Region::zero()); + let arg = env.pool.get(*arg_id); + + let arg_con = constrain_expr(arena, env, arg, expected_arg, Region::zero()); + + arg_types.push(arg_type); + arg_cons.push(arg_con); + } + + let category = Category::LowLevelOpResult(*op); + + let mut and_constraints = BumpVec::with_capacity_in(2, arena); + + and_constraints.push(And(arg_cons)); + and_constraints.push(Eq(ret_type, expected, category, region)); + + exists(arena, vars, And(and_constraints)) + } + Expr2::RuntimeError() => True, + Expr2::Closure { .. } => todo!(), + Expr2::PrivateTag { .. } => todo!(), + Expr2::InvalidLookup(_) => todo!(), + Expr2::LetRec { .. } => todo!(), + Expr2::LetFunction { .. } => todo!(), } } @@ -1038,7 +1088,7 @@ fn constrain_when_branch<'a>( } /// 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. pub fn constrain_pattern<'a>( arena: &'a Bump, @@ -1129,7 +1179,7 @@ pub fn constrain_pattern<'a>( let pat_type = Type2::Variable(*var); let expected = PExpected::NoExpectation(pat_type.shallow_clone()); - if !state.headers.contains_key(&symbol) { + if !state.headers.contains_key(symbol) { state.headers.insert(*symbol, pat_type.shallow_clone()); } diff --git a/editor/src/lang/roc_file.rs b/editor/src/lang/roc_file.rs index 98f1a25210..5379e9097a 100644 --- a/editor/src/lang/roc_file.rs +++ b/editor/src/lang/roc_file.rs @@ -37,11 +37,11 @@ impl<'a> File<'a> { let allocation = arena.alloc(bytes); let module_parse_state = parser::State::new(allocation); - let parsed_module = roc_parse::module::parse_header(&arena, module_parse_state); + let parsed_module = roc_parse::module::parse_header(arena, module_parse_state); match parsed_module { Ok((module, state)) => { - let parsed_defs = module_defs().parse(&arena, state); + let parsed_defs = module_defs().parse(arena, state); match parsed_defs { Ok((_, defs, _)) => Ok(File { diff --git a/editor/src/lang/solve.rs b/editor/src/lang/solve.rs index f248cc4999..5d44b4f2be 100644 --- a/editor/src/lang/solve.rs +++ b/editor/src/lang/solve.rs @@ -118,7 +118,7 @@ impl Pools { pub fn split_last(&self) -> (&Vec, &[Vec]) { self.0 .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) { @@ -565,11 +565,10 @@ fn solve<'a>( .get(next_rank) .iter() .filter(|var| { - let current = subs.get_without_compacting( - roc_types::subs::Variable::clone(var), - ); + let current_rank = + 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::>(); @@ -598,8 +597,7 @@ fn solve<'a>( let failing: Vec<_> = rigid_vars .iter() .filter(|&var| { - !subs.redundant(*var) - && subs.get_without_compacting(*var).rank != Rank::NONE + !subs.redundant(*var) && subs.get_rank(*var) != Rank::NONE }) .collect(); @@ -762,7 +760,9 @@ fn type_to_variable<'a>( 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) } @@ -1214,14 +1214,9 @@ fn adjust_rank_content( Record(fields, ext_var) => { let mut rank = adjust_rank(subs, young_mark, visit_mark, group_rank, *ext_var); - for var in fields.values() { - rank = rank.max(adjust_rank( - subs, - young_mark, - visit_mark, - group_rank, - var.into_inner(), - )); + for var in fields.iter_variables() { + rank = + rank.max(adjust_rank(subs, young_mark, visit_mark, group_rank, *var)); } rank @@ -1374,28 +1369,12 @@ fn instantiate_rigids_help( same @ EmptyRecord | same @ EmptyTagUnion | same @ Erroneous(_) => same, Record(fields, ext_var) => { - let mut new_fields = MutMap::default(); - - 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); + for var in fields.iter_variables() { + instantiate_rigids_help(subs, max_rank, pools, *var); } Record( - new_fields, + fields, instantiate_rigids_help(subs, max_rank, pools, ext_var), ) } @@ -1568,31 +1547,12 @@ fn deep_copy_var_help( same @ EmptyRecord | same @ EmptyTagUnion | same @ Erroneous(_) => same, - Record(fields, ext_var) => { - let mut new_fields = MutMap::default(); - - 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(mut fields, ext_var) => { + for var in fields.iter_variables_mut() { + *var = deep_copy_var_help(subs, max_rank, pools, *var); } - Record( - new_fields, - deep_copy_var_help(subs, max_rank, pools, ext_var), - ) + Record(fields, deep_copy_var_help(subs, max_rank, pools, ext_var)) } TagUnion(tags, ext_var) => { diff --git a/editor/src/ui/tooltip.rs b/editor/src/ui/tooltip.rs index 0eeed5c04b..9262a71f65 100644 --- a/editor/src/ui/tooltip.rs +++ b/editor/src/ui/tooltip.rs @@ -44,7 +44,7 @@ impl<'a> ToolTip<'a> { ) .into(), color: ui_theme.tooltip_text, - text: &self.text, + text: self.text, size: ui_theme.default_font_size, ..Default::default() } diff --git a/editor/tests/solve_expr2.rs b/editor/tests/solve_expr2.rs index a25c88b077..f4f0c4c313 100644 --- a/editor/tests/solve_expr2.rs +++ b/editor/tests/solve_expr2.rs @@ -114,14 +114,14 @@ fn infer_eq(actual: &str, expected_str: &str) { let subs = solved.inner_mut(); - let content = subs.get(var).content; + let content = subs.get_content_without_compacting(var); let interns = Interns { module_ids: env.module_ids.clone(), all_ident_ids: dep_idents, }; - let actual_str = content_to_string(content, &subs, mod_id, &interns); + let actual_str = content_to_string(content, subs, mod_id, &interns); assert_eq!(actual_str, expected_str); } @@ -328,3 +328,16 @@ fn constrain_update() { "{ name : Str }", ) } + +#[ignore = "TODO: implement builtins in the editor"] +#[test] +fn constrain_run_low_level() { + infer_eq( + indoc!( + r#" + List.map [ { name: "roc" }, { name: "bird" } ] .name + "# + ), + "List Str", + ) +} diff --git a/examples/.gitignore b/examples/.gitignore index 2a124642f7..a7b3b64e43 100644 --- a/examples/.gitignore +++ b/examples/.gitignore @@ -4,12 +4,4 @@ app libhost.a roc_app.ll roc_app.bc -benchmarks/nqueens -benchmarks/deriv -benchmarks/closure -benchmarks/cfold -benchmarks/rbtree-insert -benchmarks/rbtree-del -benchmarks/test-astar -benchmarks/test-base64 effect-example diff --git a/examples/benchmarks/.gitignore b/examples/benchmarks/.gitignore new file mode 100644 index 0000000000..e6a6950077 --- /dev/null +++ b/examples/benchmarks/.gitignore @@ -0,0 +1,16 @@ +app +*.o +*.dSYM +libhost.a +roc_app.ll +roc_app.bc +nqueens +deriv +closure +cfold +rbtree-insert +rbtree-del +rbtree-ck +test-astar +test-base64 +quicksortapp diff --git a/examples/cli/.gitignore b/examples/cli/.gitignore deleted file mode 100644 index a29ffb45c9..0000000000 --- a/examples/cli/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -echo -http-get diff --git a/examples/cli/Echo.roc b/examples/cli/Echo.roc deleted file mode 100755 index b4e0793454..0000000000 --- a/examples/cli/Echo.roc +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/env roc run - -app "echo" - packages { base: "platform" } - imports [ base.Task.{ Task, await }, base.Stdout, base.Stdin ] - provides [ main ] to base - -main : Task {} * -main = - {} <- await (Stdout.line "What's your first name?") - - firstName <- await Stdin.line - - {} <- await (Stdout.line "What's your last name?") - - lastName <- await Stdin.line - - Stdout.line "Hi, \(firstName) \(lastName)!" diff --git a/examples/cli/HttpGet.roc b/examples/cli/HttpGet.roc deleted file mode 100644 index 6e53b078fb..0000000000 --- a/examples/cli/HttpGet.roc +++ /dev/null @@ -1,16 +0,0 @@ -app "http-get" - packages { base: "platform" } - imports [ base.Task.{ Task, await }, base.Stdout, base.Stdin, base.Http ] - provides [ main ] to base - -main : Task {} * -main = - _ <- await (Stdout.line "What URL should I get?") - - url <- await Stdin.line - - result <- Task.attempt (Http.getUtf8 url) - - when result is - Ok contents -> Stdout.line "The contents of \(url) are:\n\(contents)" - Err err -> Stdout.line "Error retrieving \(url) - error was: \(err)" diff --git a/examples/cli/cli-example b/examples/cli/cli-example deleted file mode 100755 index f1effa12a1..0000000000 Binary files a/examples/cli/cli-example and /dev/null differ diff --git a/examples/cli/platform/Cargo.lock b/examples/cli/platform/Cargo.lock deleted file mode 100644 index 551e2f492b..0000000000 --- a/examples/cli/platform/Cargo.lock +++ /dev/null @@ -1,397 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "base64" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" - -[[package]] -name = "bumpalo" -version = "3.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63396b8a4b9de3f4fdfb320ab6080762242f66a8ef174c49d8e19b674db4cdbe" - -[[package]] -name = "cc" -version = "1.0.67" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3c69b077ad434294d3ce9f1f6143a2a4b89a8a2d54ef813d85003a4fd1137fd" - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "chunked_transfer" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fff857943da45f546682664a79488be82e69e43c1a7a2307679ab9afb3a66d2e" - -[[package]] -name = "errno" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa68f2fb9cae9d37c9b2b3584aba698a2e97f72d7aef7b9f7aa71d8b54ce46fe" -dependencies = [ - "errno-dragonfly", - "libc", - "winapi", -] - -[[package]] -name = "errno-dragonfly" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14ca354e36190500e1e1fb267c647932382b54053c50b14970856c0b00a35067" -dependencies = [ - "gcc", - "libc", -] - -[[package]] -name = "form_urlencoded" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" -dependencies = [ - "matches", - "percent-encoding", -] - -[[package]] -name = "gcc" -version = "0.3.55" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2" - -[[package]] -name = "host" -version = "0.1.0" -dependencies = [ - "errno", - "libc", - "roc_std", - "ureq", -] - -[[package]] -name = "idna" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89829a5d69c23d348314a7ac337fe39173b61149a9864deabd260983aed48c21" -dependencies = [ - "matches", - "unicode-bidi", - "unicode-normalization", -] - -[[package]] -name = "js-sys" -version = "0.3.50" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d99f9e3e84b8f67f846ef5b4cbbc3b1c29f6c759fcbce6f01aa0e73d932a24c" -dependencies = [ - "wasm-bindgen", -] - -[[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" - -[[package]] -name = "libc" -version = "0.2.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56d855069fafbb9b344c0f962150cd2c1187975cb1c22c1522c240d8c4986714" - -[[package]] -name = "log" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "matches" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" - -[[package]] -name = "once_cell" -version = "1.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3" - -[[package]] -name = "percent-encoding" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" - -[[package]] -name = "proc-macro2" -version = "1.0.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a152013215dca273577e18d2bf00fa862b89b24169fb78c4c95aeb07992c9cec" -dependencies = [ - "unicode-xid", -] - -[[package]] -name = "quote" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "ring" -version = "0.16.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" -dependencies = [ - "cc", - "libc", - "once_cell", - "spin", - "untrusted", - "web-sys", - "winapi", -] - -[[package]] -name = "roc_std" -version = "0.1.0" -dependencies = [ - "libc", -] - -[[package]] -name = "rustls" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "064fd21ff87c6e87ed4506e68beb42459caa4a0e2eb144932e6776768556980b" -dependencies = [ - "base64", - "log", - "ring", - "sct", - "webpki", -] - -[[package]] -name = "sct" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3042af939fca8c3453b7af0f1c66e533a15a86169e39de2657310ade8f98d3c" -dependencies = [ - "ring", - "untrusted", -] - -[[package]] -name = "spin" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" - -[[package]] -name = "syn" -version = "1.0.68" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ce15dd3ed8aa2f8eeac4716d6ef5ab58b6b9256db41d7e1a0224c2788e8fd87" -dependencies = [ - "proc-macro2", - "quote", - "unicode-xid", -] - -[[package]] -name = "tinyvec" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b5220f05bb7de7f3f53c7c065e1199b3172696fe2db9f9c4d8ad9b4ee74c342" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" - -[[package]] -name = "unicode-bidi" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" -dependencies = [ - "matches", -] - -[[package]] -name = "unicode-normalization" -version = "0.1.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07fbfce1c8a97d547e8b5334978438d9d6ec8c20e38f56d4a4374d181493eaef" -dependencies = [ - "tinyvec", -] - -[[package]] -name = "unicode-xid" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" - -[[package]] -name = "untrusted" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" - -[[package]] -name = "ureq" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fbeb1aabb07378cf0e084971a74f24241273304653184f54cdce113c0d7df1b" -dependencies = [ - "base64", - "chunked_transfer", - "log", - "once_cell", - "rustls", - "url", - "webpki", - "webpki-roots", -] - -[[package]] -name = "url" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ccd964113622c8e9322cfac19eb1004a07e636c545f325da085d5cdde6f1f8b" -dependencies = [ - "form_urlencoded", - "idna", - "matches", - "percent-encoding", -] - -[[package]] -name = "wasm-bindgen" -version = "0.2.73" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83240549659d187488f91f33c0f8547cbfef0b2088bc470c116d1d260ef623d9" -dependencies = [ - "cfg-if", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.73" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae70622411ca953215ca6d06d3ebeb1e915f0f6613e3b495122878d7ebec7dae" -dependencies = [ - "bumpalo", - "lazy_static", - "log", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.73" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e734d91443f177bfdb41969de821e15c516931c3c3db3d318fa1b68975d0f6f" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.73" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d53739ff08c8a68b0fdbcd54c372b8ab800b1449ab3c9d706503bc7dd1621b2c" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.73" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9a543ae66aa233d14bb765ed9af4a33e81b8b58d1584cf1b47ff8cd0b9e4489" - -[[package]] -name = "web-sys" -version = "0.3.50" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a905d57e488fec8861446d3393670fb50d27a262344013181c2cdf9fff5481be" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "webpki" -version = "0.21.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea" -dependencies = [ - "ring", - "untrusted", -] - -[[package]] -name = "webpki-roots" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aabe153544e473b775453675851ecc86863d2a81d786d741f6b76778f2a48940" -dependencies = [ - "webpki", -] - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/examples/cli/platform/Cargo.toml b/examples/cli/platform/Cargo.toml deleted file mode 100644 index 0780fec234..0000000000 --- a/examples/cli/platform/Cargo.toml +++ /dev/null @@ -1,17 +0,0 @@ -[package] -name = "host" -version = "0.1.0" -authors = ["The Roc Contributors"] -license = "UPL-1.0" -edition = "2018" - -[lib] -crate-type = ["staticlib"] - -[dependencies] -roc_std = { path = "../../../roc_std" } -ureq = { version = "2.1" } -libc = "0.2" -errno = "0.2" - -[workspace] diff --git a/examples/cli/platform/Http.roc b/examples/cli/platform/Http.roc deleted file mode 100644 index 447c8e6c4d..0000000000 --- a/examples/cli/platform/Http.roc +++ /dev/null @@ -1,6 +0,0 @@ -interface Http - exposes [ getUtf8 ] - imports [ fx.Effect, Task ] # TODO FIXME Task.{ Task } - -getUtf8 : Str -> Task.Task Str Str -getUtf8 = \url -> Effect.httpGetUtf8 url diff --git a/examples/cli/platform/Package-Config.roc b/examples/cli/platform/Package-Config.roc deleted file mode 100644 index 8e7cf54c0d..0000000000 --- a/examples/cli/platform/Package-Config.roc +++ /dev/null @@ -1,17 +0,0 @@ -platform rtfeldman/roc-cli - requires {}{ main : Task.Task {} * } # TODO FIXME - exposes [] # TODO FIXME actually expose modules - packages {} - imports [ Task.{ Task } ] - provides [ mainForHost ] - effects fx.Effect - { - putChar : I64 -> Effect {}, - putLine : Str -> Effect {}, - httpGetUtf8 : Str -> Effect (Result Str Str), - getLine : Effect Str - } - - -mainForHost : Task {} * as Fx -mainForHost = main diff --git a/examples/cli/platform/Stdin.roc b/examples/cli/platform/Stdin.roc deleted file mode 100644 index b46406c6de..0000000000 --- a/examples/cli/platform/Stdin.roc +++ /dev/null @@ -1,6 +0,0 @@ -interface Stdin - exposes [ line ] - imports [ fx.Effect, Task ] - -line : Task.Task Str * -line = Effect.after Effect.getLine Task.succeed # TODO FIXME Effect.getLine should suffice diff --git a/examples/cli/platform/Stdout.roc b/examples/cli/platform/Stdout.roc deleted file mode 100644 index dafc6cf0db..0000000000 --- a/examples/cli/platform/Stdout.roc +++ /dev/null @@ -1,6 +0,0 @@ -interface Stdout - exposes [ line ] - imports [ fx.Effect, Task ] # TODO FIXME Task.{ Task } - -line : Str -> Task.Task {} * -line = \str -> Effect.map (Effect.putLine str) (\_ -> Ok {}) diff --git a/examples/cli/platform/Task.roc b/examples/cli/platform/Task.roc deleted file mode 100644 index d3f996bc8a..0000000000 --- a/examples/cli/platform/Task.roc +++ /dev/null @@ -1,44 +0,0 @@ -interface Task - exposes [ Task, succeed, fail, await, map, onFail, attempt ] - imports [ fx.Effect ] - - -Task ok err : Effect.Effect (Result ok err) - - -succeed : val -> Task val * -succeed = \val -> - Effect.always (Ok val) - - -fail : err -> Task * err -fail = \val -> - Effect.always (Err val) - -attempt : Task a b, (Result a b -> Task c d) -> Task c d -attempt = \effect, transform -> - Effect.after effect \result -> - when result is - Ok ok -> transform (Ok ok) - Err err -> transform (Err err) - -await : Task a err, (a -> Task b err) -> Task b err -await = \effect, transform -> - Effect.after effect \result -> - when result is - Ok a -> transform a - Err err -> Task.fail err - -onFail : Task ok a, (a -> Task ok b) -> Task ok b -onFail = \effect, transform -> - Effect.after effect \result -> - when result is - Ok a -> Task.succeed a - Err err -> transform err - -map : Task a err, (a -> b) -> Task b err -map = \effect, transform -> - Effect.after effect \result -> - when result is - Ok a -> Task.succeed (transform a) - Err err -> Task.fail err diff --git a/examples/cli/platform/src/lib.rs b/examples/cli/platform/src/lib.rs deleted file mode 100644 index 37a120b2df..0000000000 --- a/examples/cli/platform/src/lib.rs +++ /dev/null @@ -1,256 +0,0 @@ -#![allow(non_snake_case)] - -use core::alloc::Layout; -use core::ffi::c_void; -use core::mem; -use core::mem::MaybeUninit; -use errno::{errno, Errno}; -use libc::{self, c_char, c_int}; -use roc_std::{alloca, RocCallResult, RocList, RocResult, RocStr}; - -extern "C" { - #[link_name = "roc__mainForHost_1_exposed"] - fn roc_main(output: *mut u8) -> (); - - #[link_name = "roc__mainForHost_1_size"] - fn roc_main_size() -> i64; - - #[link_name = "roc__mainForHost_1_Fx_caller"] - fn call_Fx( - flags: &'static u8, - function_pointer: *const u8, - closure_data: *const u8, - output: *mut u8, - ) -> (); - - #[allow(dead_code)] - #[link_name = "roc__mainForHost_1_Fx_size"] - fn size_Fx() -> i64; - - #[link_name = "roc__mainForHost_1_Fx_result_size"] - fn size_Fx_result() -> i64; - - fn malloc(size: usize) -> *mut c_void; - fn realloc(c_ptr: *mut c_void, size: usize) -> *mut c_void; - fn free(c_ptr: *mut c_void); -} - -#[no_mangle] -pub unsafe fn roc_alloc(size: usize, _alignment: u32) -> *mut c_void { - return malloc(size); -} - -#[no_mangle] -pub unsafe fn roc_realloc( - c_ptr: *mut c_void, - new_size: usize, - _old_size: usize, - _alignment: u32, -) -> *mut c_void { - return realloc(c_ptr, new_size); -} - -#[no_mangle] -pub unsafe fn roc_dealloc(c_ptr: *mut c_void, _alignment: u32) { - return free(c_ptr); -} - -#[no_mangle] -pub fn roc_fx_putChar(foo: i64) -> () { - let character = foo as u8 as char; - print!("{}", character); - - () -} - -#[no_mangle] -pub fn roc_fx_putLine(line: RocStr) -> () { - let bytes = line.as_slice(); - let string = unsafe { std::str::from_utf8_unchecked(bytes) }; - println!("{}", string); - - () -} - -#[no_mangle] -pub fn roc_fx_httpGetUtf8(url: RocStr) -> RocResult { - match ureq::get(unsafe { url.as_str() }).call() { - Ok(resp) => match resp.into_string() { - Ok(contents) => RocResult::Ok(RocStr::from_slice(contents.as_bytes())), // TODO make roc::Result! - // TODO turn this error into an enum! - Err(err) => RocResult::Err(RocStr::from_slice(format!("{:?}", err).as_bytes())), - }, - // TODO turn this error into an enum! - Err(err) => RocResult::Err(RocStr::from_slice(format!("{:?}", err).as_bytes())), - } -} - -#[no_mangle] -pub fn roc_fx_getLine() -> RocStr { - use std::io::{self, BufRead}; - - let stdin = io::stdin(); - let line1 = stdin.lock().lines().next().unwrap().unwrap(); - - RocStr::from_slice(line1.as_bytes()) -} - -unsafe fn call_the_closure(function_pointer: *const u8, closure_data_ptr: *const u8) -> i64 { - let size = size_Fx_result() as usize; - - alloca::with_stack_bytes(size, |buffer| { - let buffer: *mut std::ffi::c_void = buffer; - let buffer: *mut u8 = buffer as *mut u8; - - call_Fx( - &0, - function_pointer, - closure_data_ptr as *const u8, - buffer as *mut u8, - ); - - let output = &*(buffer as *mut RocCallResult<()>); - - match output.into() { - Ok(_) => 0, - Err(e) => panic!("failed with {}", e), - } - }) -} - -#[no_mangle] -pub fn rust_main() -> isize { - let size = unsafe { roc_main_size() } as usize; - let layout = Layout::array::(size).unwrap(); - - unsafe { - let buffer = std::alloc::alloc(layout); - - roc_main(buffer); - - let output = &*(buffer as *mut RocCallResult<()>); - - match output.into() { - Ok(()) => { - let function_pointer = { - // this is a pointer to the location where the function pointer is stored - // we pass just the function pointer - let temp = buffer.offset(8) as *const i64; - - (*temp) as *const u8 - }; - - let closure_data_ptr = buffer.offset(16); - - let result = - call_the_closure(function_pointer as *const u8, closure_data_ptr as *const u8); - - std::alloc::dealloc(buffer, layout); - - result - } - Err(msg) => { - std::alloc::dealloc(buffer, layout); - - panic!("Roc failed with message: {}", msg); - } - } - }; - - // Exit code - 0 -} - -/// A C file descriptor. -pub struct Fd(c_int); - -#[no_mangle] -pub unsafe fn roc_fx_open(roc_path: RocStr) -> RocResult { - const BUF_BYTES: usize = 256; - - let mut buf: MaybeUninit<[u8; BUF_BYTES]> = MaybeUninit::uninit(); - - // If the path fits in the stack-allocated buffer, we can avoid a heap - // allocation when translating our `RocStr` to a null-terminated `char*`. - let path_len = roc_path.len(); - let path_fits_in_buf = path_len > BUF_BYTES; - let c_path: *mut c_char; - - if path_fits_in_buf { - roc_path.write_c_str(buf.as_mut_ptr() as *mut u8); - - // NOTE buf may be only partially filled, so we don't `assume_init`! - c_path = buf.as_mut_ptr() as *mut c_char; - } else { - c_path = roc_alloc(path_len, mem::align_of::() as u32) as *mut c_char; - - roc_path.write_c_str(c_path as *mut u8); - } - - let fd = libc::open(c_path, libc::O_RDONLY); - - // Now that the call to `open` is done, deallocate c_path if necessary> - if !path_fits_in_buf { - roc_dealloc(c_path as *mut c_void, mem::align_of_val(&c_path) as u32); - } - - // if libc::open returned -1, that means there was an error - if fd != -1 { - RocResult::Ok(Fd(fd)) - } else { - RocResult::Err(errno()) - } -} - -#[no_mangle] -pub unsafe fn roc_fx_read(fd: Fd, bytes: usize) -> RocResult, Errno> { - const BUF_BYTES: usize = 1024; - - let mut buf: MaybeUninit<[u8; BUF_BYTES]> = MaybeUninit::uninit(); - - // We'll use our own position and libc::pread rather than using libc::read - // repeatedly and letting the fd store its own position. This way we don't - // have to worry about concurrent modifications of the fd's position. - let mut list = RocList::empty(); - let mut position: usize = 0; - - loop { - // Make sure we never read more than the buffer size, and also that - // we never read past the originally-requested number of bytes. - let bytes_to_read = BUF_BYTES.min(bytes - position); - let bytes_read = libc::pread( - fd.0, - buf.as_mut_ptr() as *mut c_void, - bytes_to_read, - position as i64, - ); - - // NOTE buf may be only partially filled, so we don't `assume_init`! - - if bytes_read == bytes_to_read as isize { - // The read was successful, and there may be more bytes to read. - // Append the bytes to the list and continue looping! - let slice = core::slice::from_raw_parts(buf.as_ptr() as *const u8, bytes_read as usize); - - list.append_slice(slice); - } else if bytes_read >= 0 { - // The read was successful, and there are no more bytes - // to read (because bytes_read was less than the requested - // bytes_to_read, but it was also not negative - which would have - // indicated an error). - let slice = core::slice::from_raw_parts(buf.as_ptr() as *const u8, bytes_read as usize); - - list.append_slice(slice); - - // We're done! - return RocResult::Ok(list); - } else { - // bytes_read was negative, so we got a read error! - break; - } - - position += bytes_read as usize; - } - - RocResult::Err(errno()) -} diff --git a/examples/custom-malloc/.gitignore b/examples/custom-malloc/.gitignore deleted file mode 100644 index eb6e180d96..0000000000 --- a/examples/custom-malloc/.gitignore +++ /dev/null @@ -1 +0,0 @@ -custom-malloc-example diff --git a/examples/custom-malloc/Main.roc b/examples/custom-malloc/Main.roc deleted file mode 100644 index ae6af0de27..0000000000 --- a/examples/custom-malloc/Main.roc +++ /dev/null @@ -1,18 +0,0 @@ -app "custom-malloc-example" - packages { base: "platform" } - imports [ base.Task.{ Task } ] - provides [ main ] to base - -main : Task.Task {} [] -main = - _ <- Task.await (Task.putLine "About to allocate a list!") - - # This is the only allocation in this Roc code! - # (The strings all get stored in the application - # binary, and are never allocated on the heap.) - list = [ 1, 2, 3, 4 ] - - if List.len list > 100 then - Task.putLine "The list was big!" - else - Task.putLine "The list was small!" diff --git a/examples/custom-malloc/platform/File.roc b/examples/custom-malloc/platform/File.roc deleted file mode 100644 index 1669382fd9..0000000000 --- a/examples/custom-malloc/platform/File.roc +++ /dev/null @@ -1,97 +0,0 @@ -interface File - exposes [ FileReadErr, FileOpenErr, FileWriteErr, DirReadErr, readUtf8, writeUtf8 ] - imports [ Task.{ Task }, fx.Effect.{ after }, Path ] - -# TODO FIXME should be able to import this as Path.{ Path }, but there's a bug. -Path : Path.Path - -# These various file errors come from the POSIX errno values - see -# http://www.virtsync.com/c-error-codes-include-errno for the actual codes, and -# https://www.gnu.org/software/libc/manual/html_node/Error-Codes.html for documentation -# -# The goal of this design is: -# * Whenever a function returns a `Task`, that task's error type represents all the errors that could happen. -# * The errors are union-friendly; if I run a task that reads, and then another that writes, I should get all the read *and* write errors. -# * To make the errors friendlier to chaining, they should always include the `Path` of the attempted operation. This way it's possible to tell which one failed after the fact. - - -## These errors can happen when opening a file, before attempting to read from -## it or write to it. The #FileReadErr and #FileWriteErr tag unions begin with -## these tags and then add more specific ones. -FileOpenErr a : - [ - FileNotFound Path, - PermissionDenied Path, - SymLinkLoop Path, - TooManyOpenFiles Path, - IoError Path, - UnknownError I64 Path, - ]a - -## Errors when attempting to read a non-directory file. -FileReadErr a : - FileOpenErr - [ - FileWasDir Path, - InvalidSeek Path, - IllegalByteSequence Path, - FileBusy Path, - ]a - -## Errors when attempting to read a directory. -DirReadErr a : - FileOpenErr - [ - FileWasNotDir Path, - ]a - -## Errors when attempting to write a non-directory file. -FileWriteErr a : - FileOpenErr - [ - FileWasDir Path, - ReadOnlyFileSystem Path, - ]a - - -## Read a file's raw bytes -#readBytes : Path -> Task (List U8) (FileReadErr *) -#readBytes = \path -> -# Effect.readBytes (Path.toStr path) - -## Read a file's bytes and interpret them as UTF-8 encoded text. -readUtf8 : Path -> Task.Task Str (FileReadErr [ BadUtf8 Str.Utf8ByteProblem Nat ]*) -readUtf8 = \path -> - Effect.map (Effect.readAllUtf8 (Path.toStr path)) \answer -> - # errno values - see - # https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/errno.h.html - when answer.errno is - 0 -> - when Str.fromUtf8 answer.bytes is - Ok str -> Ok str - Err (BadUtf8 problem index) -> Err (BadUtf8 problem index) - 1 -> Err (PermissionDenied path) - 2 -> Err (FileNotFound path) - 19 -> Err (FileWasDir path) - # TODO handle other errno scenarios that could come up - _ -> Err (UnknownError answer.errno path) - -writeUtf8 : Path, Str -> Task.Task {} (FileWriteErr [ BadThing ]*) -writeUtf8 = \path, data -> - path - |> Path.toStr - |> Effect.writeAllUtf8 data - |> Effect.map \res -> - when res.errno is - 0 -> Ok {} - _ -> Err BadThing - -## Read a file's bytes, one chunk at a time, and use it to build up a state. -## -## After each chunk is read, it gets passed to a callback which builds up a -## state - optionally while running other tasks. -#readChunks : Path, U64, state, (state, List U8 -> Task state []err) -> Task state (FileReadErr err) - -## Like #readChunks except after each chunk you can either `Continue`, -## specifying how many bytes you'd like to read next, or `Stop` early. -#readChunksOrStop : Path, U64, state, (state, List U8 -> [ Continue U64 (Task state []err), Stop (Task state []err) ]) -> Task state (FileReadErr err) diff --git a/examples/custom-malloc/platform/Package-Config.roc b/examples/custom-malloc/platform/Package-Config.roc deleted file mode 100644 index 45427ff14a..0000000000 --- a/examples/custom-malloc/platform/Package-Config.roc +++ /dev/null @@ -1,16 +0,0 @@ -platform folkertdev/foo - requires {}{ main : Task {} [] } - exposes [] - packages {} - imports [ Task ] - provides [ mainForHost ] - effects fx.Effect - { - # TODO change errno to I32 - readAllUtf8 : Str -> Effect { errno : I64, bytes : List U8 }, - writeAllUtf8 : Str, Str -> Effect { errno: I64 }, - putLine : Str -> Effect {} - } - -mainForHost : Task.Task {} [] as Fx -mainForHost = main diff --git a/examples/custom-malloc/platform/Path.roc b/examples/custom-malloc/platform/Path.roc deleted file mode 100644 index 9be077865d..0000000000 --- a/examples/custom-malloc/platform/Path.roc +++ /dev/null @@ -1,16 +0,0 @@ -interface Path - exposes [ Path, fromStr, toStr ] - imports [] - - -Path : [ @Path Str ] - - -fromStr : Str -> Result Path [ MalformedPath ]* -fromStr = \str -> - # TODO actually validate the path - may want a Parser for this! - Ok (@Path str) - -toStr : Path -> Str -toStr = \@Path str -> - str diff --git a/examples/custom-malloc/platform/Task.roc b/examples/custom-malloc/platform/Task.roc deleted file mode 100644 index 5c210afcb9..0000000000 --- a/examples/custom-malloc/platform/Task.roc +++ /dev/null @@ -1,41 +0,0 @@ -interface Task - exposes [ Task, succeed, fail, await, map, putLine, attempt ] - imports [ fx.Effect ] - - -Task ok err : Effect.Effect (Result ok err) - - -succeed : val -> Task val * -succeed = \val -> - Effect.always (Ok val) - - -fail : err -> Task * err -fail = \val -> - Effect.always (Err val) - - -await : Task a err, (a -> Task b err) -> Task b err -await = \effect, transform -> - Effect.after effect \result -> - when result is - Ok a -> transform a - Err err -> Task.fail err - -attempt : Task a b, (Result a b -> Task c d) -> Task c d -attempt = \effect, transform -> - Effect.after effect \result -> - when result is - Ok ok -> transform (Ok ok) - Err err -> transform (Err err) - -map : Task a err, (a -> b) -> Task b err -map = \effect, transform -> - Effect.after effect \result -> - when result is - Ok a -> Task.succeed (transform a) - Err err -> Task.fail err - -putLine : Str -> Task {} * -putLine = \line -> Effect.map (Effect.putLine line) (\_ -> Ok {}) diff --git a/examples/custom-malloc/platform/host.zig b/examples/custom-malloc/platform/host.zig deleted file mode 100644 index a4cce81a94..0000000000 --- a/examples/custom-malloc/platform/host.zig +++ /dev/null @@ -1,171 +0,0 @@ -const std = @import("std"); -const str = @import("str"); -const RocStr = str.RocStr; -const testing = std.testing; -const expectEqual = testing.expectEqual; -const expect = testing.expect; - -comptime { - // This is a workaround for https://github.com/ziglang/zig/issues/8218 - // which is only necessary on macOS. - // - // Once that issue is fixed, we can undo the changes in - // 177cf12e0555147faa4d436e52fc15175c2c4ff0 and go back to passing - // -fcompiler-rt in link.rs instead of doing this. Note that this - // workaround is present in many host.zig files, so make sure to undo - // it everywhere! - if (std.builtin.os.tag == .macos) { - _ = @import("compiler_rt"); - } -} - -const mem = std.mem; -const Allocator = mem.Allocator; - -extern fn roc__mainForHost_1_exposed([*]u8) void; -extern fn roc__mainForHost_1_size() i64; -extern fn roc__mainForHost_1_Fx_caller(*const u8, [*]u8, [*]u8) void; -extern fn roc__mainForHost_1_Fx_size() i64; -extern fn roc__mainForHost_1_Fx_result_size() i64; - -extern fn malloc(size: usize) callconv(.C) ?*c_void; -extern fn realloc(c_ptr: [*]align(@alignOf(u128)) u8, size: usize) callconv(.C) ?*c_void; -extern fn free(c_ptr: [*]align(@alignOf(u128)) u8) callconv(.C) void; - -const Unit = extern struct {}; - -pub export fn main() u8 { - const stdout = std.io.getStdOut().writer(); - - const size = @intCast(usize, roc__mainForHost_1_size()); - const raw_output = std.heap.c_allocator.alloc(u8, size) catch unreachable; - var output = @ptrCast([*]u8, raw_output); - - defer { - std.heap.c_allocator.free(raw_output); - } - - roc__mainForHost_1_exposed(output); - - const elements = @ptrCast([*]u64, @alignCast(8, output)); - - var flag = elements[0]; - - if (flag == 0) { - // all is well - const closure_data_pointer = @ptrCast([*]u8, output[8..size]); - - call_the_closure(closure_data_pointer); - } else { - unreachable; - } - - return 0; -} - -export fn roc_alloc(size: usize, alignment: u32) callconv(.C) ?*c_void { - const stdout = std.io.getStdOut().writer(); - - // Perform the actual malloc - const startNs = std.time.nanoTimestamp(); - const ptr = malloc(size); - const endNs = std.time.nanoTimestamp(); - - const totalMs = @divTrunc(endNs - startNs, 1000); - - stdout.print("\x1B[36m{} | \x1B[39m Custom malloc allocated {} bytes in {} ms!\n", .{ startNs, size, totalMs }) catch unreachable; - - return ptr; -} - -export fn roc_realloc(c_ptr: *c_void, new_size: usize, old_size: usize, alignment: u32) callconv(.C) ?*c_void { - return realloc(@alignCast(16, @ptrCast([*]u8, c_ptr)), new_size); -} - -export fn roc_dealloc(c_ptr: *c_void, alignment: u32) callconv(.C) void { - const stdout = std.io.getStdOut().writer(); - - // Perform the actual free - const startNs = std.time.nanoTimestamp(); - free(@alignCast(16, @ptrCast([*]u8, c_ptr))); - const endNs = std.time.nanoTimestamp(); - - const totalMs = @divTrunc(endNs - startNs, 1000); - - stdout.print("\x1B[36m{} | \x1B[39m Custom dealloc ran in {} ms!\n", .{ startNs, totalMs }) catch unreachable; -} - -fn call_the_closure(closure_data_pointer: [*]u8) void { - const size = roc__mainForHost_1_Fx_result_size(); - const raw_output = std.heap.c_allocator.alloc(u8, @intCast(usize, size)) catch unreachable; - var output = @ptrCast([*]u8, raw_output); - - defer { - std.heap.c_allocator.free(raw_output); - } - - const flags: u8 = 0; - - roc__mainForHost_1_Fx_caller(&flags, closure_data_pointer, output); - - const elements = @ptrCast([*]u64, @alignCast(8, output)); - - var flag = elements[0]; - - if (flag == 0) { - return; - } else { - unreachable; - } -} - -pub export fn roc_fx_putLine(rocPath: str.RocStr) i64 { - const stdout = std.io.getStdOut().writer(); - - const u8_ptr = rocPath.asU8ptr(); - - var i: usize = 0; - while (i < rocPath.len()) { - stdout.print("{c}", .{u8_ptr[i]}) catch unreachable; - - i += 1; - } - - stdout.print("\n", .{}) catch unreachable; - - return 0; -} - -pub const ReadResult = extern struct { - bytes: RocStr, // TODO RocList once Roc supports U8 - errno: i64, // TODO i32 when Roc supports I32 -}; - -pub const WriteResult = extern struct { - errno: i64, -}; - -pub export fn roc_fx_readAllUtf8(rocPath: RocStr) callconv(.C) ReadResult { - var dir = std.fs.cwd(); - - var content = dir.readFileAlloc(testing.allocator, rocPath.asSlice(), 1024) catch |e| switch (e) { - error.FileNotFound => return .{ .bytes = RocStr.empty(), .errno = 2 }, - error.IsDir => return .{ .bytes = RocStr.empty(), .errno = 19 }, - else => return .{ .bytes = RocStr.empty(), .errno = 9999 }, - }; - - var str_ptr = @ptrCast([*]u8, content); - var roc_str3 = RocStr.init(str_ptr, content.len); - - return .{ .bytes = roc_str3, .errno = 0 }; -} - -pub export fn roc_fx_writeAllUtf8(filePath: RocStr, content: RocStr) callconv(.C) WriteResult { - var dir = std.fs.cwd(); - - dir.writeFile(filePath.asSlice(), content.asSlice()) catch |e| switch (e) { - else => return .{ .errno = 1 }, - }; - - return .{ .errno = 0 }; -} diff --git a/examples/effect/thing/platform-dir/Package-Config.roc b/examples/effect/thing/platform-dir/Package-Config.roc index 5e9516eeba..641eec62ed 100644 --- a/examples/effect/thing/platform-dir/Package-Config.roc +++ b/examples/effect/thing/platform-dir/Package-Config.roc @@ -1,8 +1,8 @@ platform folkertdev/foo - requires {model=>Model, msg=>Msg} {main : Effect {}} + requires {} { main : Effect {} } exposes [] packages {} - imports [fx.Effect] + imports [ fx.Effect ] provides [ mainForHost ] effects fx.Effect { @@ -11,6 +11,5 @@ platform folkertdev/foo } - mainForHost : Effect.Effect {} as Fx mainForHost = main diff --git a/examples/hello-rust/.gitignore b/examples/hello-rust/.gitignore new file mode 100644 index 0000000000..6b820fd903 --- /dev/null +++ b/examples/hello-rust/.gitignore @@ -0,0 +1 @@ +hello-world diff --git a/examples/hello-rust/Hello.roc b/examples/hello-rust/Hello.roc new file mode 100644 index 0000000000..d78f48ff19 --- /dev/null +++ b/examples/hello-rust/Hello.roc @@ -0,0 +1,12 @@ +app "hello-world" + packages { base: "platform" } + imports [] + provides [ main ] to base + +greeting = + hi = "Hello" + name = "World" + + "\(hi), \(name)!\n" + +main = greeting diff --git a/examples/tea/platform/Cargo.lock b/examples/hello-rust/platform/Cargo.lock similarity index 73% rename from examples/tea/platform/Cargo.lock rename to examples/hello-rust/platform/Cargo.lock index 331efb48b9..78178675aa 100644 --- a/examples/tea/platform/Cargo.lock +++ b/examples/hello-rust/platform/Cargo.lock @@ -1,17 +1,20 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +version = 3 + [[package]] name = "host" version = "0.1.0" dependencies = [ + "libc", "roc_std", ] [[package]] name = "libc" -version = "0.2.81" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1482821306169ec4d07f6aca392a4681f66c75c9918aa49641a2595db64053cb" +checksum = "56d855069fafbb9b344c0f962150cd2c1187975cb1c22c1522c240d8c4986714" [[package]] name = "roc_std" diff --git a/examples/shared-quicksort/platform/Cargo.toml b/examples/hello-rust/platform/Cargo.toml similarity index 94% rename from examples/shared-quicksort/platform/Cargo.toml rename to examples/hello-rust/platform/Cargo.toml index b0a2a5e96e..ad2bc7c449 100644 --- a/examples/shared-quicksort/platform/Cargo.toml +++ b/examples/hello-rust/platform/Cargo.toml @@ -10,5 +10,6 @@ crate-type = ["staticlib"] [dependencies] roc_std = { path = "../../../roc_std" } +libc = "0.2" [workspace] diff --git a/examples/hello-rust/platform/Package-Config.roc b/examples/hello-rust/platform/Package-Config.roc new file mode 100644 index 0000000000..377d5c0994 --- /dev/null +++ b/examples/hello-rust/platform/Package-Config.roc @@ -0,0 +1,10 @@ +platform examples/hello-world + requires {}{ main : Str } + exposes [] + packages {} + imports [] + provides [ mainForHost ] + effects fx.Effect {} + +mainForHost : Str +mainForHost = main diff --git a/examples/cli/platform/host.c b/examples/hello-rust/platform/host.c similarity index 100% rename from examples/cli/platform/host.c rename to examples/hello-rust/platform/host.c diff --git a/examples/hello-rust/platform/src/lib.rs b/examples/hello-rust/platform/src/lib.rs new file mode 100644 index 0000000000..53156c212a --- /dev/null +++ b/examples/hello-rust/platform/src/lib.rs @@ -0,0 +1,58 @@ +#![allow(non_snake_case)] + +use core::ffi::c_void; +use core::mem::MaybeUninit; +use roc_std::{RocCallResult, RocStr}; + +extern "C" { + #[link_name = "roc__mainForHost_1_exposed"] + fn roc_main(output: *mut RocCallResult) -> (); +} + +#[no_mangle] +pub unsafe fn roc_alloc(size: usize, _alignment: u32) -> *mut c_void { + return libc::malloc(size); +} + +#[no_mangle] +pub unsafe fn roc_realloc( + c_ptr: *mut c_void, + new_size: usize, + _old_size: usize, + _alignment: u32, +) -> *mut c_void { + return libc::realloc(c_ptr, new_size); +} + +#[no_mangle] +pub unsafe fn roc_dealloc(c_ptr: *mut c_void, _alignment: u32) { + return libc::free(c_ptr); +} + +#[no_mangle] +pub fn rust_main() -> isize { + let mut call_result: MaybeUninit> = MaybeUninit::uninit(); + + unsafe { + roc_main(call_result.as_mut_ptr()); + + let output = call_result.assume_init(); + + match output.into() { + Ok(roc_str) => { + let len = roc_str.len(); + let str_bytes = roc_str.get_bytes() as *const libc::c_void; + + if libc::write(1, str_bytes, len) < 0 { + panic!("Writing to stdout failed!"); + } + } + Err(msg) => { + panic!("Roc failed with message: {}", msg); + } + } + } + + // Exit code + 0 +} diff --git a/examples/hello-world/README.md b/examples/hello-world/README.md index a2890f03ed..4aab7c1346 100644 --- a/examples/hello-world/README.md +++ b/examples/hello-world/README.md @@ -40,7 +40,7 @@ can write (it would be plain Roc code, but with some extra keywords that aren't available in normal modules - kinda like `port module` in Elm), and which describe both the Roc-host/C boundary as well as the Roc-host/Roc-app boundary. Roc application authors only care about the Roc-host/Roc-app portion, and the -host author only cares about the Roc-host/C bounary when implementing the host. +host author only cares about the Roc-host/C boundary when implementing the host. Using this glue code, the Roc compiler can generate C header files describing the boundary. This not only gets us host compatibility with C compilers, but also diff --git a/examples/hello-zig/Hello.roc b/examples/hello-zig/Hello.roc index 24a02e7823..b27d53cf8c 100644 --- a/examples/hello-zig/Hello.roc +++ b/examples/hello-zig/Hello.roc @@ -7,6 +7,6 @@ greeting = hi = "Hello" name = "World" - "\(hi), \(name)!!!!!!!!!!!!!" + "\(hi), \(name)!" main = greeting diff --git a/examples/hello-zig/README.md b/examples/hello-zig/README.md index a2890f03ed..4aab7c1346 100644 --- a/examples/hello-zig/README.md +++ b/examples/hello-zig/README.md @@ -40,7 +40,7 @@ can write (it would be plain Roc code, but with some extra keywords that aren't available in normal modules - kinda like `port module` in Elm), and which describe both the Roc-host/C boundary as well as the Roc-host/Roc-app boundary. Roc application authors only care about the Roc-host/Roc-app portion, and the -host author only cares about the Roc-host/C bounary when implementing the host. +host author only cares about the Roc-host/C boundary when implementing the host. Using this glue code, the Roc compiler can generate C header files describing the boundary. This not only gets us host compatibility with C compilers, but also diff --git a/examples/multi-module/quicksort b/examples/multi-module/quicksort deleted file mode 100755 index 74fb461398..0000000000 Binary files a/examples/multi-module/quicksort and /dev/null differ diff --git a/examples/shared-quicksort/.gitignore b/examples/shared-quicksort/.gitignore deleted file mode 100644 index 19abff6005..0000000000 --- a/examples/shared-quicksort/.gitignore +++ /dev/null @@ -1 +0,0 @@ -quicksort diff --git a/examples/shared-quicksort/Quicksort.roc b/examples/shared-quicksort/Quicksort.roc deleted file mode 100644 index a826adc511..0000000000 --- a/examples/shared-quicksort/Quicksort.roc +++ /dev/null @@ -1,68 +0,0 @@ -app "quicksort" packages { base: "./platform" } provides [ quicksort ] to base - -quicksort : List Int * -> List Int * -quicksort = \originalList -> helper originalList - -helper : List Int * -> List Int * -helper = \originalList -> - - quicksortHelp : List (Num a), Nat, Nat -> List (Num a) - quicksortHelp = \list, low, high -> - if low < high then - when partition low high list is - Pair partitionIndex partitioned -> - partitioned - |> quicksortHelp low (partitionIndex - 1) - |> quicksortHelp (partitionIndex + 1) high - else - list - - - swap : Nat, Nat, List a -> List a - swap = \i, j, list -> - when Pair (List.get list i) (List.get list j) is - Pair (Ok atI) (Ok atJ) -> - list - |> List.set i atJ - |> List.set j atI - - _ -> - [] - - partition : Nat, Nat, List (Num a) -> [ Pair Nat (List (Num a)) ] - partition = \low, high, initialList -> - when List.get initialList high is - Ok pivot -> - when partitionHelp (low - 1) low initialList high pivot is - Pair newI newList -> - Pair (newI + 1) (swap (newI + 1) high newList) - - Err _ -> - Pair (low - 1) initialList - - - partitionHelp : Nat, Nat, List (Num a), Nat, (Num a) -> [ Pair Nat (List (Num a)) ] - partitionHelp = \i, j, list, high, pivot -> - if j < high then - when List.get list j is - Ok value -> - if value <= pivot then - partitionHelp (i + 1) (j + 1) (swap (i + 1) j list) high pivot - else - partitionHelp i (j + 1) list high pivot - - Err _ -> - Pair i list - else - Pair i list - - - - result = quicksortHelp originalList 0 (List.len originalList - 1) - - if List.len originalList > 3 then - result - else - # Absolutely make the `originalList` Shared by using it again here - # but this branch is not evaluated, so should not affect performance - List.set originalList 0 (List.len originalList) diff --git a/examples/shared-quicksort/platform/Cargo.lock b/examples/shared-quicksort/platform/Cargo.lock deleted file mode 100644 index c386bb6c4a..0000000000 --- a/examples/shared-quicksort/platform/Cargo.lock +++ /dev/null @@ -1,23 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -[[package]] -name = "host" -version = "0.1.0" -dependencies = [ - "roc_std 0.1.0", -] - -[[package]] -name = "libc" -version = "0.2.79" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "roc_std" -version = "0.1.0" -dependencies = [ - "libc 0.2.79 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[metadata] -"checksum libc 0.2.79 (registry+https://github.com/rust-lang/crates.io-index)" = "2448f6066e80e3bfc792e9c98bf705b4b0fc6e8ef5b43e5889aff0eaa9c58743" diff --git a/examples/shared-quicksort/platform/Package-Config.roc b/examples/shared-quicksort/platform/Package-Config.roc deleted file mode 100644 index 25f96b0da9..0000000000 --- a/examples/shared-quicksort/platform/Package-Config.roc +++ /dev/null @@ -1,15 +0,0 @@ -platform examples/shared-quicksort - requires { quicksort : List I64 -> List I64 } - exposes [] - packages {} - imports [] - provides [ mainForHost ] - effects Effect - { - putChar : I64 -> Effect {}, - putLine : Str -> Effect {}, - getLine : Effect Str - } - -mainForHost : List I64 -> List I64 -mainForHost = \list -> quicksort list diff --git a/examples/shared-quicksort/platform/host.c b/examples/shared-quicksort/platform/host.c deleted file mode 100644 index 0378c69589..0000000000 --- a/examples/shared-quicksort/platform/host.c +++ /dev/null @@ -1,7 +0,0 @@ -#include - -extern int rust_main(); - -int main() { - return rust_main(); -} diff --git a/examples/shared-quicksort/platform/src/lib.rs b/examples/shared-quicksort/platform/src/lib.rs deleted file mode 100644 index a6d1eb4d9f..0000000000 --- a/examples/shared-quicksort/platform/src/lib.rs +++ /dev/null @@ -1,79 +0,0 @@ -#![allow(non_snake_case)] - -use roc_std::RocCallResult; -use roc_std::RocList; -use std::ffi::c_void; -use std::time::SystemTime; - -extern "C" { - #[link_name = "roc__mainForHost_1_exposed"] - fn quicksort(list: RocList, output: &mut RocCallResult>) -> (); - - fn malloc(size: usize) -> *mut c_void; - fn realloc(c_ptr: *mut c_void, size: usize) -> *mut c_void; - fn free(c_ptr: *mut c_void); -} - -#[no_mangle] -pub unsafe fn roc_alloc(size: usize, _alignment: u32) -> *mut c_void { - return malloc(size); -} - -#[no_mangle] -pub unsafe fn roc_realloc( - c_ptr: *mut c_void, - new_size: usize, - _old_size: usize, - _alignment: u32, -) -> *mut c_void { - return realloc(c_ptr, new_size); -} - -#[no_mangle] -pub unsafe fn roc_dealloc(c_ptr: *mut c_void, _alignment: u32) { - return free(c_ptr); -} - -const NUM_NUMS: usize = 100; - -#[no_mangle] -pub fn rust_main() -> isize { - let nums: RocList = { - let mut nums = Vec::with_capacity(NUM_NUMS); - - for index in 0..nums.capacity() { - let num = index as i64 % 12; - - nums.push(num); - } - - RocList::from_slice(&nums) - }; - - println!("Running Roc quicksort on {} numbers...", nums.len()); - let start_time = SystemTime::now(); - let answer = unsafe { - use std::mem::MaybeUninit; - let mut output = MaybeUninit::uninit(); - - quicksort(nums, &mut *output.as_mut_ptr()); - - match output.assume_init().into() { - Ok(value) => value, - Err(msg) => panic!("roc failed with message {}", msg), - } - }; - - let end_time = SystemTime::now(); - let duration = end_time.duration_since(start_time).unwrap(); - - println!( - "Roc quicksort took {:.4} ms to compute this answer: {:?}", - duration.as_secs_f64() * 1000.0, - // truncate the answer, so stdout is not swamped - &answer.as_slice()[0..20] - ); - - // Exit code - 0 -} diff --git a/examples/task/.gitignore b/examples/task/.gitignore deleted file mode 100644 index 395a2aa140..0000000000 --- a/examples/task/.gitignore +++ /dev/null @@ -1 +0,0 @@ -task-example diff --git a/examples/task/Main.roc b/examples/task/Main.roc deleted file mode 100644 index abffa24d8b..0000000000 --- a/examples/task/Main.roc +++ /dev/null @@ -1,19 +0,0 @@ -app "task-example" - packages { base: "platform" } - imports [ base.Task.{ Task }, base.File, base.Path ] - provides [ main ] to base - -main : Task.Task {} [] -main = - when Path.fromStr "thing.txt" is - Ok path -> - {} <- Task.await (Task.putLine "Writing to file") - - result <- Task.attempt (File.writeUtf8 path "zig is awesome") - - when result is - Ok _ -> Task.putLine "successfully wrote to file" - Err BadThing -> Task.putLine "error writing to file" - Err _ -> Task.putLine "something worse" - - _ -> Task.putLine "invalid path" diff --git a/examples/task/platform/File.roc b/examples/task/platform/File.roc deleted file mode 100644 index 1669382fd9..0000000000 --- a/examples/task/platform/File.roc +++ /dev/null @@ -1,97 +0,0 @@ -interface File - exposes [ FileReadErr, FileOpenErr, FileWriteErr, DirReadErr, readUtf8, writeUtf8 ] - imports [ Task.{ Task }, fx.Effect.{ after }, Path ] - -# TODO FIXME should be able to import this as Path.{ Path }, but there's a bug. -Path : Path.Path - -# These various file errors come from the POSIX errno values - see -# http://www.virtsync.com/c-error-codes-include-errno for the actual codes, and -# https://www.gnu.org/software/libc/manual/html_node/Error-Codes.html for documentation -# -# The goal of this design is: -# * Whenever a function returns a `Task`, that task's error type represents all the errors that could happen. -# * The errors are union-friendly; if I run a task that reads, and then another that writes, I should get all the read *and* write errors. -# * To make the errors friendlier to chaining, they should always include the `Path` of the attempted operation. This way it's possible to tell which one failed after the fact. - - -## These errors can happen when opening a file, before attempting to read from -## it or write to it. The #FileReadErr and #FileWriteErr tag unions begin with -## these tags and then add more specific ones. -FileOpenErr a : - [ - FileNotFound Path, - PermissionDenied Path, - SymLinkLoop Path, - TooManyOpenFiles Path, - IoError Path, - UnknownError I64 Path, - ]a - -## Errors when attempting to read a non-directory file. -FileReadErr a : - FileOpenErr - [ - FileWasDir Path, - InvalidSeek Path, - IllegalByteSequence Path, - FileBusy Path, - ]a - -## Errors when attempting to read a directory. -DirReadErr a : - FileOpenErr - [ - FileWasNotDir Path, - ]a - -## Errors when attempting to write a non-directory file. -FileWriteErr a : - FileOpenErr - [ - FileWasDir Path, - ReadOnlyFileSystem Path, - ]a - - -## Read a file's raw bytes -#readBytes : Path -> Task (List U8) (FileReadErr *) -#readBytes = \path -> -# Effect.readBytes (Path.toStr path) - -## Read a file's bytes and interpret them as UTF-8 encoded text. -readUtf8 : Path -> Task.Task Str (FileReadErr [ BadUtf8 Str.Utf8ByteProblem Nat ]*) -readUtf8 = \path -> - Effect.map (Effect.readAllUtf8 (Path.toStr path)) \answer -> - # errno values - see - # https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/errno.h.html - when answer.errno is - 0 -> - when Str.fromUtf8 answer.bytes is - Ok str -> Ok str - Err (BadUtf8 problem index) -> Err (BadUtf8 problem index) - 1 -> Err (PermissionDenied path) - 2 -> Err (FileNotFound path) - 19 -> Err (FileWasDir path) - # TODO handle other errno scenarios that could come up - _ -> Err (UnknownError answer.errno path) - -writeUtf8 : Path, Str -> Task.Task {} (FileWriteErr [ BadThing ]*) -writeUtf8 = \path, data -> - path - |> Path.toStr - |> Effect.writeAllUtf8 data - |> Effect.map \res -> - when res.errno is - 0 -> Ok {} - _ -> Err BadThing - -## Read a file's bytes, one chunk at a time, and use it to build up a state. -## -## After each chunk is read, it gets passed to a callback which builds up a -## state - optionally while running other tasks. -#readChunks : Path, U64, state, (state, List U8 -> Task state []err) -> Task state (FileReadErr err) - -## Like #readChunks except after each chunk you can either `Continue`, -## specifying how many bytes you'd like to read next, or `Stop` early. -#readChunksOrStop : Path, U64, state, (state, List U8 -> [ Continue U64 (Task state []err), Stop (Task state []err) ]) -> Task state (FileReadErr err) diff --git a/examples/task/platform/Package-Config.roc b/examples/task/platform/Package-Config.roc deleted file mode 100644 index 45427ff14a..0000000000 --- a/examples/task/platform/Package-Config.roc +++ /dev/null @@ -1,16 +0,0 @@ -platform folkertdev/foo - requires {}{ main : Task {} [] } - exposes [] - packages {} - imports [ Task ] - provides [ mainForHost ] - effects fx.Effect - { - # TODO change errno to I32 - readAllUtf8 : Str -> Effect { errno : I64, bytes : List U8 }, - writeAllUtf8 : Str, Str -> Effect { errno: I64 }, - putLine : Str -> Effect {} - } - -mainForHost : Task.Task {} [] as Fx -mainForHost = main diff --git a/examples/task/platform/Path.roc b/examples/task/platform/Path.roc deleted file mode 100644 index 9be077865d..0000000000 --- a/examples/task/platform/Path.roc +++ /dev/null @@ -1,16 +0,0 @@ -interface Path - exposes [ Path, fromStr, toStr ] - imports [] - - -Path : [ @Path Str ] - - -fromStr : Str -> Result Path [ MalformedPath ]* -fromStr = \str -> - # TODO actually validate the path - may want a Parser for this! - Ok (@Path str) - -toStr : Path -> Str -toStr = \@Path str -> - str diff --git a/examples/task/platform/Task.roc b/examples/task/platform/Task.roc deleted file mode 100644 index 5c210afcb9..0000000000 --- a/examples/task/platform/Task.roc +++ /dev/null @@ -1,41 +0,0 @@ -interface Task - exposes [ Task, succeed, fail, await, map, putLine, attempt ] - imports [ fx.Effect ] - - -Task ok err : Effect.Effect (Result ok err) - - -succeed : val -> Task val * -succeed = \val -> - Effect.always (Ok val) - - -fail : err -> Task * err -fail = \val -> - Effect.always (Err val) - - -await : Task a err, (a -> Task b err) -> Task b err -await = \effect, transform -> - Effect.after effect \result -> - when result is - Ok a -> transform a - Err err -> Task.fail err - -attempt : Task a b, (Result a b -> Task c d) -> Task c d -attempt = \effect, transform -> - Effect.after effect \result -> - when result is - Ok ok -> transform (Ok ok) - Err err -> transform (Err err) - -map : Task a err, (a -> b) -> Task b err -map = \effect, transform -> - Effect.after effect \result -> - when result is - Ok a -> Task.succeed (transform a) - Err err -> Task.fail err - -putLine : Str -> Task {} * -putLine = \line -> Effect.map (Effect.putLine line) (\_ -> Ok {}) diff --git a/examples/task/platform/host.zig b/examples/task/platform/host.zig deleted file mode 100644 index 3bef60306c..0000000000 --- a/examples/task/platform/host.zig +++ /dev/null @@ -1,268 +0,0 @@ -const std = @import("std"); -const str = @import("str"); -const RocStr = str.RocStr; -const testing = std.testing; -const expectEqual = testing.expectEqual; -const expect = testing.expect; - -comptime { - // This is a workaround for https://github.com/ziglang/zig/issues/8218 - // which is only necessary on macOS. - // - // Once that issue is fixed, we can undo the changes in - // 177cf12e0555147faa4d436e52fc15175c2c4ff0 and go back to passing - // -fcompiler-rt in link.rs instead of doing this. Note that this - // workaround is present in many host.zig files, so make sure to undo - // it everywhere! - if (std.builtin.os.tag == .macos) { - _ = @import("compiler_rt"); - } -} - -const mem = std.mem; -const Allocator = mem.Allocator; - -extern fn roc__mainForHost_1_exposed([*]u8) void; -extern fn roc__mainForHost_1_size() i64; -extern fn roc__mainForHost_1_Fx_caller(*const u8, *const u8, [*]u8, [*]u8) void; -extern fn roc__mainForHost_1_Fx_size() i64; -extern fn roc__mainForHost_1_Fx_result_size() i64; - -extern fn malloc(size: usize) callconv(.C) ?*c_void; -extern fn realloc(c_ptr: [*]align(@alignOf(u128)) u8, size: usize) callconv(.C) ?*c_void; -extern fn free(c_ptr: [*]align(@alignOf(u128)) u8) callconv(.C) void; - -export fn roc_alloc(size: usize, alignment: u32) callconv(.C) ?*c_void { - return malloc(size); -} - -export fn roc_realloc(c_ptr: *c_void, new_size: usize, old_size: usize, alignment: u32) callconv(.C) ?*c_void { - return realloc(@alignCast(16, @ptrCast([*]u8, c_ptr)), new_size); -} - -export fn roc_dealloc(c_ptr: *c_void, alignment: u32) callconv(.C) void { - free(@alignCast(16, @ptrCast([*]u8, c_ptr))); -} - -const Unit = extern struct {}; - -pub export fn main() u8 { - const stdout = std.io.getStdOut().writer(); - - const size = @intCast(usize, roc__mainForHost_1_size()); - const raw_output = std.heap.c_allocator.alloc(u8, size) catch unreachable; - var output = @ptrCast([*]u8, raw_output); - - defer { - std.heap.c_allocator.free(raw_output); - } - - roc__mainForHost_1_exposed(output); - - const elements = @ptrCast([*]u64, @alignCast(8, output)); - - var flag = elements[0]; - - if (flag == 0) { - // all is well - const function_pointer = @intToPtr(*const u8, elements[1]); - const closure_data_pointer = @ptrCast([*]u8, output[16..size]); - - call_the_closure(function_pointer, closure_data_pointer); - } else { - unreachable; - } - - return 0; -} - -fn call_the_closure(function_pointer: *const u8, closure_data_pointer: [*]u8) void { - const size = roc__mainForHost_1_Fx_result_size(); - const raw_output = std.heap.c_allocator.alloc(u8, @intCast(usize, size)) catch unreachable; - var output = @ptrCast([*]u8, raw_output); - - defer { - std.heap.c_allocator.free(raw_output); - } - - const flags: u8 = 0; - - roc__mainForHost_1_Fx_caller(&flags, function_pointer, closure_data_pointer, output); - - const elements = @ptrCast([*]u64, @alignCast(8, output)); - - var flag = elements[0]; - - if (flag == 0) { - return; - } else { - unreachable; - } -} - -pub export fn roc_fx_putLine(rocPath: str.RocStr) i64 { - const stdout = std.io.getStdOut().writer(); - - const u8_ptr = rocPath.asU8ptr(); - - var i: usize = 0; - while (i < rocPath.len()) { - stdout.print("{c}", .{u8_ptr[i]}) catch unreachable; - - i += 1; - } - - stdout.print("\n", .{}) catch unreachable; - - return 0; -} - -pub const ReadResult = extern struct { - bytes: RocStr, // TODO RocList once Roc supports U8 - errno: i64, // TODO i32 when Roc supports I32 -}; - -pub const WriteResult = extern struct { - errno: i64, -}; - -pub export fn roc_fx_readAllUtf8(rocPath: RocStr) callconv(.C) ReadResult { - var dir = std.fs.cwd(); - - var content = dir.readFileAlloc(testing.allocator, rocPath.asSlice(), 1024) catch |e| switch (e) { - error.FileNotFound => return .{ .bytes = RocStr.empty(), .errno = 2 }, - error.IsDir => return .{ .bytes = RocStr.empty(), .errno = 19 }, - else => return .{ .bytes = RocStr.empty(), .errno = 9999 }, - }; - - var str_ptr = @ptrCast([*]u8, content); - var roc_str3 = RocStr.init(str_ptr, content.len); - - return .{ .bytes = roc_str3, .errno = 0 }; -} - -pub export fn roc_fx_writeAllUtf8(filePath: RocStr, content: RocStr) callconv(.C) WriteResult { - var dir = std.fs.cwd(); - - dir.writeFile(filePath.asSlice(), content.asSlice()) catch |e| switch (e) { - else => return .{ .errno = 1 }, - }; - - return .{ .errno = 0 }; -} - -pub fn roc_fx_readAllUtf8_that_does_not_work(rocPath: *RocStr) ReadResult { - const allocator = std.heap.c_allocator; - - // fopen wants a C string, so stack-allocate one using rocPath's contents - const len = rocPath.len() + 1; - - var raw = allocator.alloc(u8, len) catch unreachable; - var path: [*:0]u8 = @ptrCast([*:0]u8, raw); - rocPath.memcpy(path, len); - path[len] = 0; // nul-terminate the path, since it's a C string - - // Open the file - const file = fopen(path, "r") orelse { - return ReadResult{ .bytes = RocStr.empty(), .errno = errno }; - }; - - // Now that the file has been opened, make sure we always (try to) close it - // before returning, even if something went wrong while reading it. - defer { - if (fclose(file) != 0) { - return ReadResult{ .bytes = RocStr.empty(), .errno = errno }; - } - } - - // Next we'll count the total number of bytes in the file, which we need - // to know so we can allocate a correctly-sized buffer to read into. - - // First, seek to the end of the file - if (fseek(file, 0, SEEK_END) != 0) { - return ReadResult{ .bytes = RocStr.empty(), .errno = errno }; - } - - // Now the current file position (which ftell returns) will be the end of - // the file - which will be equal to the total number of bytes in the file. - const totalBytes: c_long = ftell(file); - - // In the highly unusal case that there are no bytes to read, return early. - if (totalBytes <= 0) { - // If the file was empty, return an empty list. - if (totalBytes == 0) { - return ReadResult{ .bytes = RocStr.empty(), .errno = 0 }; - } - - // ftell returns -1 on error, so return an error here - return ReadResult{ .bytes = RocStr.empty(), .errno = errno }; - } - - // Rewind to the beginning of the file, so we can start actually reading. - if (fseek(file, 0, SEEK_SET) != 0) { - return ReadResult{ .bytes = RocStr.empty(), .errno = errno }; - } - - // Allocate enough bytes for the contents of the file, plus the refcount. - const refcountBytes = @sizeOf(usize); - var buffer: [*]u8 = malloc(totalBytes + refcountBytes) orelse { - // If allocation failed, throw a runtime exception for Roc to catch. - - // fclose the file explicitly before throwing, because libunwind - // will disregard our defer block. (TODO verify this!) - // - // Silently ignore fclose errors here, because we're about to throw an - // allocation failure exception; fclose failures won't affect that. - fclose(file); - - // TODO use libunwind to throw an exception here - // TODO set an "allocation failed" exception object for `catch` to receive - // TODO write a test for this which simulates allocation failure - }; - - // Initialize the refcount to a positive number - meaning it's actually - // a capacity value, which is appropriate since we return a Unique value. - @ptrCast(buffer, [*]usize)[0] = totalBytes; - - // The buffer pointer should point to the first byte *after* the refcount - buffer += refcountBytes; - - // Read the bytes into the buffer. - const bytesRead = fread(buffer, 1, totalBytes, file); - - // fread indicates an error by returning a number that's different from - // the number of elements we requested to read - if (bytesRead != totalBytes) { - return ReadResult{ .bytes = RocStr.empty(), .errno = errno }; - } - - // Explicitly return errno = 0 to indicate there was no error. - // - // (We don't want to read from the errno global here because it might have - // a nonzero value leftover from previous unrelated operations.) - return ReadResult{ .bytes = RocStr.init(buffer, totalBytes), .errno = 0 }; -} - -// const c = @cImport({ -// @cInclude("stdio.h"); -// @cInclude("stdlib.h"); -// }); -// -// extern var errno: c_int; -// -// const FILE = extern struct { -// unused: u8, -// }; - -// extern "c" fn fopen(filename: [*:0]const u8, modes: [*:0]const u8) ?*FILE; -//extern "c" fn fopen(filename: [*:0]const u8, modes: [*:0]const u8) ?*FILE; -//extern "c" fn fclose(stream: *FILE) c_int; -//extern "c" fn fseek(stream: *FILE, offset: c_long, origin: c_int) c_int; - -// extern fn fopen([*:0]const u8, [*:0]const u8) ?*FILE; -// extern fn fseek(*FILE, c_long, c_int) c_int; - -//extern fn fopen([*c]const u8, [*c]const u8) [*c]FILE; -// extern fn ftell([*c]FILE) c_long; -// extern fn fread([*c]u8, size_t, size_t, [*c]FILE) size_t; -// extern fn fclose([*c]FILE) c_int; diff --git a/examples/tea/Main.roc b/examples/tea/Main.roc deleted file mode 100644 index 0019e85630..0000000000 --- a/examples/tea/Main.roc +++ /dev/null @@ -1,31 +0,0 @@ -app "effect-example" - packages { base: "platform" } - imports [base.Cmd] - provides [ main ] to base - -Model : I64 - -Msg : [ Line Str ] - -main = { init, update } - -init : {} -> { model : Model, cmd : Cmd.Cmd Msg } -init = \{} -> - cmd = - Cmd.after (Cmd.putLine "Type a thing, and I'll say it back") \{} -> - Cmd.getLine (\l -> Line l) - - { model: 42, cmd } - - -update : Msg, Model -> { model : Model, cmd : Cmd.Cmd Msg } -update = \msg, model -> - when msg is - Line line -> - cmd = - Cmd.after (Cmd.putLine "You said:") \{} -> - Cmd.after (Cmd.putLine line) \{} -> - Cmd.after (Cmd.putLine "Type another thing, and I'll say it back") \{} -> - Cmd.getLine (\l -> Line l) - - { model: model + 1, cmd } diff --git a/examples/tea/platform/Cargo.toml b/examples/tea/platform/Cargo.toml deleted file mode 100644 index b0a2a5e96e..0000000000 --- a/examples/tea/platform/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -[package] -name = "host" -version = "0.1.0" -authors = ["The Roc Contributors"] -license = "UPL-1.0" -edition = "2018" - -[lib] -crate-type = ["staticlib"] - -[dependencies] -roc_std = { path = "../../../roc_std" } - -[workspace] diff --git a/examples/tea/platform/Cmd.roc b/examples/tea/platform/Cmd.roc deleted file mode 100644 index cb0a78b1cc..0000000000 --- a/examples/tea/platform/Cmd.roc +++ /dev/null @@ -1,24 +0,0 @@ -interface Cmd - exposes [ Cmd, none, map, putLine, getLine, always, after ] - imports [ Effect ] - -Cmd a : Effect.Effect a - -none : Cmd {} -none = Effect.always {} - -always : {} -> Cmd {} -always = \x -> Effect.always x - -getLine : (Str -> msg) -> Cmd msg -getLine = \toMsg -> - Effect.map Effect.getLine toMsg - -putLine : Str -> Cmd {} -putLine = \line -> Effect.putLine line - -map : Cmd a, (a -> b) -> Cmd b -map = \cmd, transform -> Effect.map cmd transform - -after : Cmd a, (a -> Cmd b) -> Cmd b -after = \cmd, transform -> Effect.after cmd transform diff --git a/examples/tea/platform/Package-Config.roc b/examples/tea/platform/Package-Config.roc deleted file mode 100644 index 4cd85a5fd5..0000000000 --- a/examples/tea/platform/Package-Config.roc +++ /dev/null @@ -1,19 +0,0 @@ -platform folkertdev/foo - requires {}{foo:Str} - exposes [] - packages {} - imports [Cmd] - provides [ mainForHost ] - effects fx.Effect - { - putChar : I64 -> Effect {}, - putLine : Str -> Effect {}, - getLine : Effect Str - } - -mainForHost : - { - init : ({} -> { model: I64 as Model, cmd : (Cmd.Cmd [ Line Str ]) as Fx }) as Init, - update : ([ Line Str ], I64 -> { model: I64, cmd : Cmd.Cmd [ Line Str ] } ) as Update - } -mainForHost = main diff --git a/examples/tea/platform/host.c b/examples/tea/platform/host.c deleted file mode 100644 index 0378c69589..0000000000 --- a/examples/tea/platform/host.c +++ /dev/null @@ -1,7 +0,0 @@ -#include - -extern int rust_main(); - -int main() { - return rust_main(); -} diff --git a/examples/tea/platform/src/lib.rs b/examples/tea/platform/src/lib.rs deleted file mode 100644 index 3ed1a9e7cb..0000000000 --- a/examples/tea/platform/src/lib.rs +++ /dev/null @@ -1,327 +0,0 @@ -#![allow(non_snake_case)] - -use roc_std::RocCallResult; -use roc_std::RocStr; -use std::alloc::Layout; -use std::ffi::c_void; -use std::time::SystemTime; - -type Model = *const u8; - -extern "C" { - #[link_name = "roc__mainForHost_1_exposed"] - fn roc_main(output: *mut u8) -> (); - - #[link_name = "roc__mainForHost_1_size"] - fn roc_main_size() -> i64; - - #[link_name = "roc__mainForHost_1_Init_caller"] - fn _call_Init( - flags: &(), - function_pointer: *const u8, - closure_data: *const u8, - output: *mut u8, - ) -> (); - - #[link_name = "roc__mainForHost_1_Init_size"] - fn size_Init() -> i64; - - #[link_name = "roc__mainForHost_1_Init_result_size"] - fn size_Init_result() -> i64; - - #[link_name = "roc__mainForHost_1_Update_caller"] - fn call_Update( - msg: Msg, - model: Model, - function_pointer: *const u8, - closure_data: *const u8, - output: *mut u8, - ) -> (); - - #[link_name = "roc__mainForHost_1_Update_size"] - fn size_Update() -> i64; - - #[link_name = "roc__mainForHost_1_Update_result_size"] - fn size_Update_result() -> i64; - - #[link_name = "roc__mainForHost_1_Fx_caller"] - fn _call_Fx( - unit: &(), - function_pointer: *const u8, - closure_data: *const u8, - output: *mut u8, - ) -> (); - - #[link_name = "roc__mainForHost_1_Fx_size"] - fn size_Fx() -> i64; - - #[link_name = "roc__mainForHost_1_Fx_result_size"] - fn size_Fx_result() -> i64; - - #[link_name = "roc__mainForHost_1_Model_size"] - fn size_Model() -> i64; - - fn malloc(size: usize) -> *mut c_void; - fn realloc(c_ptr: *mut c_void, size: usize) -> *mut c_void; - fn free(c_ptr: *mut c_void); -} - -#[no_mangle] -pub unsafe fn roc_alloc(size: usize, _alignment: u32) -> *mut c_void { - return malloc(size); -} - -#[no_mangle] -pub unsafe fn roc_realloc( - c_ptr: *mut c_void, - new_size: usize, - _old_size: usize, - _alignment: u32, -) -> *mut c_void { - return realloc(c_ptr, new_size); -} - -#[no_mangle] -pub unsafe fn roc_dealloc(c_ptr: *mut c_void, _alignment: u32) { - return free(c_ptr); -} - -unsafe fn call_Fx(function_pointer: *const u8, closure_data: *const u8, output: *mut u8) -> () { - // Fx (or Cmd on the roc side) is a thunk, so we know its first argument is a (pointer to) unit - _call_Fx(&(), function_pointer, closure_data, output) -} - -unsafe fn call_Init(function_pointer: *const u8, closure_data: *const u8, output: *mut u8) -> () { - // for now, we hardcode flags to be `()` (or `{}` on the roc side) - let flags = (); - _call_Init(&flags, function_pointer, closure_data, output) -} - -#[no_mangle] -pub fn roc_fx_putChar(foo: i64) -> () { - let character = foo as u8 as char; - print!("{}", character); - - () -} - -#[no_mangle] -pub fn roc_fx_putLine(line: RocStr) -> () { - println!("{}", unsafe { line.as_str() }); - - () -} - -#[no_mangle] -pub fn roc_fx_getLine() -> RocStr { - use std::io::{self, BufRead}; - - let stdin = io::stdin(); - let line1 = stdin.lock().lines().next().unwrap().unwrap(); - - RocStr::from_slice_with_capacity(line1.as_bytes(), line1.len()) -} - -unsafe fn run_fx(function_pointer: *const u8, closure_data_ptr: *const u8) -> Msg { - let size = size_Fx_result() as usize; - - let layout = Layout::array::(size).unwrap(); - let buffer = std::alloc::alloc(layout); - - call_Fx( - function_pointer, - closure_data_ptr as *const u8, - buffer as *mut u8, - ); - - let output = &*(buffer as *mut RocCallResult<()>); - - match output.into() { - Ok(()) => Msg { msg: buffer.add(8) }, - - Err(e) => panic!("failed with {}", e), - } -} - -struct Msg { - msg: *mut u8, -} - -impl Msg { - unsafe fn alloc(size: usize) -> Self { - let size = size_Fx_result() as usize; - let layout = Layout::array::(size).unwrap(); - let msg = std::alloc::alloc(layout); - - Self { msg } - } -} - -impl Drop for Msg { - fn drop(&mut self) { - unsafe { - let size = size_Fx_result() as usize; - let layout = Layout::array::(size).unwrap(); - std::alloc::dealloc(self.msg.offset(-8), layout); - } - } -} - -struct ModelCmd { - buffer: *mut u8, - cmd_fn_ptr_ptr: *const u8, - cmd_closure_data_ptr: *const u8, - model: *const u8, -} - -impl ModelCmd { - unsafe fn alloc() -> Self { - let size = 8 + size_Fx() as usize + size_Model() as usize; - - let layout = Layout::array::(size).unwrap(); - let buffer = std::alloc::alloc(layout); - - let cmd_fn_ptr_ptr = buffer.add(8); - let cmd_closure_data_ptr = buffer.add(8 + 8); - let model = buffer.add(8 + size_Fx() as usize); - - Self { - buffer, - cmd_fn_ptr_ptr, - cmd_closure_data_ptr, - model, - } - } -} - -impl Drop for ModelCmd { - fn drop(&mut self) { - unsafe { - let size = 8 + size_Fx() as usize + size_Model() as usize; - let layout = Layout::array::(size).unwrap(); - std::alloc::dealloc(self.buffer, layout); - } - } -} - -unsafe fn run_init( - function_pointer: *const u8, - closure_data_ptr: *const u8, -) -> Result { - debug_assert_eq!(size_Init_result(), 8 + size_Fx() + size_Model()); - let size = size_Init_result() as usize; - - let model_cmd = ModelCmd::alloc(); - let buffer = model_cmd.buffer; - - call_Init(function_pointer, 0 as *const u8, buffer as *mut u8); - - // cmd < model, so the command comes first - let output = &*(buffer as *mut RocCallResult<()>); - - match output.into() { - Ok(_) => Ok(model_cmd), - Err(e) => Err(e.to_string()), - } -} - -unsafe fn run_update( - msg: Msg, - model: Model, - function_pointer: *const u8, - closure_data_ptr: *const u8, -) -> Result { - debug_assert_eq!(size_Update_result(), 8 + size_Fx() + size_Model()); - let size = size_Update_result() as usize; - - let model_cmd = ModelCmd::alloc(); - let buffer = model_cmd.buffer; - - call_Update( - msg, - model, - function_pointer, - closure_data_ptr, - buffer as *mut u8, - ); - - // cmd < model, so the command comes first - let output = &*(buffer as *mut RocCallResult<()>); - - match output.into() { - Ok(_) => Ok(model_cmd), - Err(e) => Err(e.to_string()), - } -} - -fn run_roc() -> Result<(), String> { - let size = unsafe { roc_main_size() } as usize; - let layout = Layout::array::(size).unwrap(); - unsafe { - let buffer = std::alloc::alloc(layout); - - roc_main(buffer); - - let output = &*(buffer as *mut RocCallResult<(*const u8, *const u8)>); - - match output.into() { - Ok((init_fn_ptr, update_fn_ptr)) => { - //let closure_data_ptr = buffer.offset(16); - let closure_data_ptr = 0 as *const u8; - - let model_cmd = - &mut run_init(init_fn_ptr as *const u8, closure_data_ptr as *const u8).unwrap(); - - for _ in 0..5 { - let model = model_cmd.model; - let cmd_fn_ptr = *(model_cmd.cmd_fn_ptr_ptr as *const usize) as *const u8; - let msg = run_fx(cmd_fn_ptr, model_cmd.cmd_closure_data_ptr); - - let mut result = run_update( - msg, - model, - update_fn_ptr as *const u8, - closure_data_ptr as *const u8, - ) - .unwrap(); - - std::mem::swap(model_cmd, &mut result); - - // implictly drops `result` and `msg` - } - - std::alloc::dealloc(buffer, layout); - Ok(()) - } - Err(msg) => { - std::alloc::dealloc(buffer, layout); - - Err(msg.to_string()) - } - } - } -} - -#[no_mangle] -pub fn rust_main() -> isize { - let start_time = SystemTime::now(); - - let end_time = SystemTime::now(); - let duration = end_time.duration_since(start_time).unwrap(); - - match run_roc() { - Ok(answer) => { - println!( - "Roc closure took {:.4} ms to compute this answer: {:?}", - duration.as_secs_f64() * 1000.0, - answer - ); - } - Err(e) => { - eprintln!("Roc failed with message {:?}", e); - } - } - - // Exit code - 0 -} diff --git a/nix/sources.json b/nix/sources.json new file mode 100644 index 0000000000..7f02e9546f --- /dev/null +++ b/nix/sources.json @@ -0,0 +1,26 @@ +{ + "niv": { + "branch": "master", + "description": "Easy dependency management for Nix projects", + "homepage": "https://github.com/nmattia/niv", + "owner": "nmattia", + "repo": "niv", + "rev": "e0ca65c81a2d7a4d82a189f1e23a48d59ad42070", + "sha256": "1pq9nh1d8nn3xvbdny8fafzw87mj7gsmp6pxkdl65w2g18rmcmzx", + "type": "tarball", + "url": "https://github.com/nmattia/niv/archive/e0ca65c81a2d7a4d82a189f1e23a48d59ad42070.tar.gz", + "url_template": "https://github.com///archive/.tar.gz" + }, + "nixpkgs": { + "branch": "nixpkgs-unstable", + "description": "Nix Packages collection", + "homepage": "", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "1441fa74d213d7cc120d9d7d49e540c1fc59bc58", + "sha256": "152qb7ch0r4bidik33zd0a9wl0929zr0dqs5l5ksm7vh3assc7sc", + "type": "tarball", + "url": "https://github.com/NixOS/nixpkgs/archive/1441fa74d213d7cc120d9d7d49e540c1fc59bc58.tar.gz", + "url_template": "https://github.com///archive/.tar.gz" + } +} diff --git a/nix/sources.nix b/nix/sources.nix new file mode 100644 index 0000000000..1938409ddd --- /dev/null +++ b/nix/sources.nix @@ -0,0 +1,174 @@ +# This file has been generated by Niv. + +let + + # + # The fetchers. fetch_ fetches specs of type . + # + + fetch_file = pkgs: name: spec: + let + name' = sanitizeName name + "-src"; + in + if spec.builtin or true then + builtins_fetchurl { inherit (spec) url sha256; name = name'; } + else + pkgs.fetchurl { inherit (spec) url sha256; name = name'; }; + + fetch_tarball = pkgs: name: spec: + let + name' = sanitizeName name + "-src"; + in + if spec.builtin or true then + builtins_fetchTarball { name = name'; inherit (spec) url sha256; } + else + pkgs.fetchzip { name = name'; inherit (spec) url sha256; }; + + fetch_git = name: spec: + let + ref = + if spec ? ref then spec.ref else + if spec ? branch then "refs/heads/${spec.branch}" else + if spec ? tag then "refs/tags/${spec.tag}" else + abort "In git source '${name}': Please specify `ref`, `tag` or `branch`!"; + in + builtins.fetchGit { url = spec.repo; inherit (spec) rev; inherit ref; }; + + fetch_local = spec: spec.path; + + fetch_builtin-tarball = name: throw + ''[${name}] The niv type "builtin-tarball" is deprecated. You should instead use `builtin = true`. + $ niv modify ${name} -a type=tarball -a builtin=true''; + + fetch_builtin-url = name: throw + ''[${name}] The niv type "builtin-url" will soon be deprecated. You should instead use `builtin = true`. + $ niv modify ${name} -a type=file -a builtin=true''; + + # + # Various helpers + # + + # https://github.com/NixOS/nixpkgs/pull/83241/files#diff-c6f540a4f3bfa4b0e8b6bafd4cd54e8bR695 + sanitizeName = name: + ( + concatMapStrings (s: if builtins.isList s then "-" else s) + ( + builtins.split "[^[:alnum:]+._?=-]+" + ((x: builtins.elemAt (builtins.match "\\.*(.*)" x) 0) name) + ) + ); + + # The set of packages used when specs are fetched using non-builtins. + mkPkgs = sources: system: + let + sourcesNixpkgs = + import (builtins_fetchTarball { inherit (sources.nixpkgs) url sha256; }) { inherit system; }; + hasNixpkgsPath = builtins.any (x: x.prefix == "nixpkgs") builtins.nixPath; + hasThisAsNixpkgsPath = == ./.; + in + if builtins.hasAttr "nixpkgs" sources + then sourcesNixpkgs + else if hasNixpkgsPath && ! hasThisAsNixpkgsPath then + import {} + else + abort + '' + Please specify either (through -I or NIX_PATH=nixpkgs=...) or + add a package called "nixpkgs" to your sources.json. + ''; + + # The actual fetching function. + fetch = pkgs: name: spec: + + if ! builtins.hasAttr "type" spec then + abort "ERROR: niv spec ${name} does not have a 'type' attribute" + else if spec.type == "file" then fetch_file pkgs name spec + else if spec.type == "tarball" then fetch_tarball pkgs name spec + else if spec.type == "git" then fetch_git name spec + else if spec.type == "local" then fetch_local spec + else if spec.type == "builtin-tarball" then fetch_builtin-tarball name + else if spec.type == "builtin-url" then fetch_builtin-url name + else + abort "ERROR: niv spec ${name} has unknown type ${builtins.toJSON spec.type}"; + + # If the environment variable NIV_OVERRIDE_${name} is set, then use + # the path directly as opposed to the fetched source. + replace = name: drv: + let + saneName = stringAsChars (c: if isNull (builtins.match "[a-zA-Z0-9]" c) then "_" else c) name; + ersatz = builtins.getEnv "NIV_OVERRIDE_${saneName}"; + in + if ersatz == "" then drv else + # this turns the string into an actual Nix path (for both absolute and + # relative paths) + if builtins.substring 0 1 ersatz == "/" then /. + ersatz else /. + builtins.getEnv "PWD" + "/${ersatz}"; + + # Ports of functions for older nix versions + + # a Nix version of mapAttrs if the built-in doesn't exist + mapAttrs = builtins.mapAttrs or ( + f: set: with builtins; + listToAttrs (map (attr: { name = attr; value = f attr set.${attr}; }) (attrNames set)) + ); + + # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/lists.nix#L295 + range = first: last: if first > last then [] else builtins.genList (n: first + n) (last - first + 1); + + # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L257 + stringToCharacters = s: map (p: builtins.substring p 1 s) (range 0 (builtins.stringLength s - 1)); + + # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L269 + stringAsChars = f: s: concatStrings (map f (stringToCharacters s)); + concatMapStrings = f: list: concatStrings (map f list); + concatStrings = builtins.concatStringsSep ""; + + # https://github.com/NixOS/nixpkgs/blob/8a9f58a375c401b96da862d969f66429def1d118/lib/attrsets.nix#L331 + optionalAttrs = cond: as: if cond then as else {}; + + # fetchTarball version that is compatible between all the versions of Nix + builtins_fetchTarball = { url, name ? null, sha256 }@attrs: + let + inherit (builtins) lessThan nixVersion fetchTarball; + in + if lessThan nixVersion "1.12" then + fetchTarball ({ inherit url; } // (optionalAttrs (!isNull name) { inherit name; })) + else + fetchTarball attrs; + + # fetchurl version that is compatible between all the versions of Nix + builtins_fetchurl = { url, name ? null, sha256 }@attrs: + let + inherit (builtins) lessThan nixVersion fetchurl; + in + if lessThan nixVersion "1.12" then + fetchurl ({ inherit url; } // (optionalAttrs (!isNull name) { inherit name; })) + else + fetchurl attrs; + + # Create the final "sources" from the config + mkSources = config: + mapAttrs ( + name: spec: + if builtins.hasAttr "outPath" spec + then abort + "The values in sources.json should not have an 'outPath' attribute" + else + spec // { outPath = replace name (fetch config.pkgs name spec); } + ) config.sources; + + # The "config" used by the fetchers + mkConfig = + { sourcesFile ? if builtins.pathExists ./sources.json then ./sources.json else null + , sources ? if isNull sourcesFile then {} else builtins.fromJSON (builtins.readFile sourcesFile) + , system ? builtins.currentSystem + , pkgs ? mkPkgs sources system + }: rec { + # The sources, i.e. the attribute set of spec name to spec + inherit sources; + + # The "pkgs" (evaluated nixpkgs) to use for e.g. non-builtin fetchers + inherit pkgs; + }; + +in +mkSources (mkConfig {}) // { __functor = _: settings: mkSources (mkConfig settings); } diff --git a/nix/zig.nix b/nix/zig.nix index 0f18ce1672..55fd1680e8 100644 --- a/nix/zig.nix +++ b/nix/zig.nix @@ -3,11 +3,7 @@ let version = "0.8.0"; - osName = - if pkgs.stdenv.isDarwin then - "macos" - else - "linux"; + osName = if pkgs.stdenv.isDarwin then "macos" else "linux"; splitSystem = builtins.split "-" builtins.currentSystem; arch = builtins.elemAt splitSystem 0; @@ -15,20 +11,17 @@ let archiveName = "zig-${osName}-${arch}-${version}"; - # If you're system is not aarch64, we assume it's x86_64 - sha256 = - if pkgs.stdenv.isDarwin then - if isAarch64 then - "b32d13f66d0e1ff740b3326d66a469ee6baddbd7211fa111c066d3bd57683111" - else - "279f9360b5cb23103f0395dc4d3d0d30626e699b1b4be55e98fd985b62bc6fbe" + # If your system is not aarch64, we assume it's x86_64 + sha256 = if pkgs.stdenv.isDarwin then + if isAarch64 then + "b32d13f66d0e1ff740b3326d66a469ee6baddbd7211fa111c066d3bd57683111" else - if isAarch64 then - "ee204ca2c2037952cf3f8b10c609373a08a291efa4af7b3c73be0f2b27720470" - else - "502625d3da3ae595c5f44a809a87714320b7a40e6dff4a895b5fa7df3391d01e"; -in -pkgs.stdenv.mkDerivation { + "279f9360b5cb23103f0395dc4d3d0d30626e699b1b4be55e98fd985b62bc6fbe" + else if isAarch64 then + "ee204ca2c2037952cf3f8b10c609373a08a291efa4af7b3c73be0f2b27720470" + else + "502625d3da3ae595c5f44a809a87714320b7a40e6dff4a895b5fa7df3391d01e"; +in pkgs.stdenv.mkDerivation { pname = "zig"; version = version; src = pkgs.fetchurl { diff --git a/roc-for-elm-programmers.md b/roc-for-elm-programmers.md index 575eb461e3..c624fd8985 100644 --- a/roc-for-elm-programmers.md +++ b/roc-for-elm-programmers.md @@ -437,7 +437,7 @@ when error is # File.WriteErr possibilities DirectoryNotFound -> ... - WriteAcessDenied -> ... + WriteAccessDenied -> ... DiskFull -> ... ``` @@ -819,7 +819,7 @@ Dict.fromList [ KV "Sam" True, KV "Ali" False KV firstName False This works, but is not nearly as nice to read. -Additionally, map literals can compile direcly to efficient initialization code +Additionally, map literals can compile directly to efficient initialization code without needing to (hopefully be able to) optimize away the intermediate `List` involved in `fromList`. @@ -969,7 +969,7 @@ test "it works" This is convenient with higher-order functions which take a function as their final argument. Since many Roc functions have the same type as Elm functions except with their arguments flipped, this means it's possible to end a lot -of expessions with anonymous functions - e.g. +of expressions with anonymous functions - e.g. ```elm modifiedNums = @@ -1357,7 +1357,7 @@ are all unions containing a single tag. That means they hold no information at r that is, discarded prior to code generation. During code generation, Roc treats `Quantity [ Km ] Int` as equivalent to `Quantity Int`. -Then, becaue `Quantity Int` is an alias for `[ Quantity Int ]`, it will unbox again +Then, because `Quantity Int` is an alias for `[ Quantity Int ]`, it will unbox again and reduce that all the way down to to `Int`. This means that, just like phantom *types*, phantom *values* affect type checking diff --git a/roc_std/src/lib.rs b/roc_std/src/lib.rs index b54fd6c7b9..1f4484eb24 100644 --- a/roc_std/src/lib.rs +++ b/roc_std/src/lib.rs @@ -1,5 +1,6 @@ #![crate_type = "lib"] #![no_std] +use core::convert::From; use core::ffi::c_void; use core::{fmt, mem, ptr}; @@ -391,6 +392,14 @@ impl RocStr { } } + pub fn get_bytes(&self) -> *const u8 { + if self.is_small_str() { + self.get_small_str_ptr() + } else { + self.elements + } + } + pub fn storage(&self) -> Option { use core::cmp::Ordering::*; @@ -694,3 +703,72 @@ impl<'a, T: Sized + Copy> From<&'a RocCallResult> for Result { } } } + +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +pub struct RocDec(pub i128); + +impl RocDec { + pub const MIN: Self = Self(i128::MIN); + pub const MAX: Self = Self(i128::MAX); + + pub const DECIMAL_PLACES: u32 = 18; + + pub const ONE_POINT_ZERO: i128 = 10i128.pow(Self::DECIMAL_PLACES); + + #[allow(clippy::should_implement_trait)] + pub fn from_str(value: &str) -> Option { + // Split the string into the parts before and after the "." + let mut parts = value.split('.'); + + let before_point = match parts.next() { + Some(answer) => answer, + None => { + return None; + } + }; + + let after_point = match parts.next() { + Some(answer) if answer.len() <= Self::DECIMAL_PLACES as usize => answer, + _ => { + return None; + } + }; + + // There should have only been one "." in the string! + if parts.next().is_some() { + return None; + } + + // Calculate the low digits - the ones after the decimal point. + let lo = match after_point.parse::() { + Ok(answer) => { + // Translate e.g. the 1 from 0.1 into 10000000000000000000 + // by "restoring" the elided trailing zeroes to the number! + let trailing_zeroes = Self::DECIMAL_PLACES as usize - after_point.len(); + let lo = answer * 10i128.pow(trailing_zeroes as u32); + + if !before_point.starts_with('-') { + lo + } else { + -lo + } + } + Err(_) => { + return None; + } + }; + + // Calculate the high digits - the ones before the decimal point. + match before_point.parse::() { + Ok(answer) => match answer.checked_mul(10i128.pow(Self::DECIMAL_PLACES)) { + Some(hi) => hi.checked_add(lo).map(RocDec), + None => None, + }, + Err(_) => None, + } + } + + pub fn from_str_to_i128_unsafe(val: &str) -> i128 { + RocDec::from_str(val).unwrap().0 + } +} diff --git a/shell.nix b/shell.nix index 878ea9ce69..44666067d0 100644 --- a/shell.nix +++ b/shell.nix @@ -1,34 +1,21 @@ -{}: +{ }: let - # Look here for information about how pin version of nixpkgs - # → https://nixos.wiki/wiki/FAQ/Pinning_Nixpkgs - # TODO: We should probably use flakes at somepoint - pkgs = import ( - builtins.fetchGit { - # name = "nixpkgs-2021-04-23"; - url = "https://github.com/nixos/nixpkgs/"; - ref = "refs/heads/nixpkgs-unstable"; - rev = "51bb9f3e9ab6161a3bf7746e20b955712cef618b"; - } - ) {}; + sources = import nix/sources.nix { }; + pkgs = import sources.nixpkgs { }; - darwinInputs = - with pkgs; - lib.optionals stdenv.isDarwin ( - with pkgs.darwin.apple_sdk.frameworks; [ - AppKit - CoreFoundation - CoreServices - CoreVideo - Foundation - Metal - Security - ] - ); + darwinInputs = with pkgs; + lib.optionals stdenv.isDarwin (with pkgs.darwin.apple_sdk.frameworks; [ + AppKit + CoreFoundation + CoreServices + CoreVideo + Foundation + Metal + Security + ]); - linuxInputs = - with pkgs; + linuxInputs = with pkgs; lib.optionals stdenv.isLinux [ valgrind vulkan-headers @@ -46,7 +33,7 @@ let zig = import ./nix/zig.nix { inherit pkgs; }; - inputs = with pkgs;[ + inputs = with pkgs; [ # build libraries rustc cargo @@ -72,28 +59,26 @@ let # faster builds - see https://github.com/rtfeldman/roc/blob/trunk/BUILDING_FROM_SOURCE.md#use-lld-for-the-linker llvmPkgs.lld - ]; -in -pkgs.mkShell - { - buildInputs = inputs ++ darwinInputs ++ linuxInputs; - # Additional Env vars - LLVM_SYS_120_PREFIX = "${llvmPkgs.llvm.dev}"; - LD_LIBRARY_PATH = - with pkgs; - lib.makeLibraryPath - ( - [ - pkg-config - stdenv.cc.cc.lib - llvmPkgs.libcxx - llvmPkgs.libcxxabi - libunwind - libffi - ncurses - zlib - ] - ++ linuxInputs - ); - } + # meta-tools + # note: niv manages its own nixpkgs so it doesn't need pkgs.callPackage. Do + # `cachix use niv` to get cached builds! + (import sources.niv { }).niv + ]; +in pkgs.mkShell { + buildInputs = inputs ++ darwinInputs ++ linuxInputs; + + # Additional Env vars + LLVM_SYS_120_PREFIX = "${llvmPkgs.llvm.dev}"; + LD_LIBRARY_PATH = with pkgs; + lib.makeLibraryPath ([ + pkg-config + stdenv.cc.cc.lib + llvmPkgs.libcxx + llvmPkgs.libcxxabi + libunwind + libffi + ncurses + zlib + ] ++ linuxInputs); +} diff --git a/vendor/morphic_lib/src/api.rs b/vendor/morphic_lib/src/api.rs index f45c2dbd53..6dc57d1c6f 100644 --- a/vendor/morphic_lib/src/api.rs +++ b/vendor/morphic_lib/src/api.rs @@ -1612,7 +1612,7 @@ fn hash_bstr(hasher: &mut Sha256, bstr: &[u8]) { fn hash_func_name(mod_: ModName, func: FuncName) -> FuncSpec { let mut hasher = Sha256::new(); - hash_bstr(&mut hasher, &mod_.0); - hash_bstr(&mut hasher, &func.0); + hash_bstr(&mut hasher, mod_.0); + hash_bstr(&mut hasher, func.0); FuncSpec(hasher.finalize().into()) } diff --git a/vendor/morphic_lib/src/preprocess.rs b/vendor/morphic_lib/src/preprocess.rs index 161d61ca9f..f52e0f648b 100644 --- a/vendor/morphic_lib/src/preprocess.rs +++ b/vendor/morphic_lib/src/preprocess.rs @@ -679,8 +679,8 @@ fn preprocess_block_expr( values_in_scope, continuations_in_scope, block, - &api_node.op, - &api_node.inputs, + api_node.op, + api_node.inputs, ) .map_err(Error::annotate_binding(BindingLocation::Value( api_value_id, @@ -1149,7 +1149,7 @@ fn preprocess_func_def( let (final_block, ret_val) = preprocess_block_expr( tc, ctx, - &api_builder, + api_builder, body_types, &mut graph_builder, &mut values_in_scope, @@ -1183,7 +1183,7 @@ fn preprocess_const_def( let (final_block, ret_val) = preprocess_block_expr( tc, ctx, - &api_builder, + api_builder, body_types, &mut graph_builder, &mut values_in_scope,