From 25513d7eda0828841df000a50382d62d7bc570f3 Mon Sep 17 00:00:00 2001 From: Chad Stearns Date: Sat, 7 Mar 2020 14:43:47 -0500 Subject: [PATCH 001/428] Restoring report test file --- Cargo.lock | 7 +++++++ compiler/reporting/Cargo.toml | 8 ++++++++ 2 files changed, 15 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index cef8ec9214..cb297331b0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -955,15 +955,22 @@ version = "0.1.0" name = "roc_reporting" version = "0.1.0" dependencies = [ + "bumpalo", "indoc", "maplit", "pretty_assertions", "quickcheck", "quickcheck_macros", + "roc_builtins", + "roc_can", "roc_collections", + "roc_constrain", + "roc_load", "roc_module", + "roc_parse", "roc_problem", "roc_region", + "roc_solve", "roc_types", ] diff --git a/compiler/reporting/Cargo.toml b/compiler/reporting/Cargo.toml index ca4e7016f2..11f3598b23 100644 --- a/compiler/reporting/Cargo.toml +++ b/compiler/reporting/Cargo.toml @@ -10,10 +10,18 @@ roc_region = { path = "../region" } roc_module = { path = "../module" } roc_problem = { path = "../problem" } roc_types = { path = "../types" } +roc_load = { path = "../load" } +roc_can = { path = "../can" } [dev-dependencies] +roc_constrain = { path = "../constrain" } +roc_builtins = { path = "../builtins" } +roc_problem = { path = "../problem" } +roc_parse = { path = "../parse" } +roc_solve = { path = "../solve" } pretty_assertions = "0.5.1 " maplit = "1.0.1" indoc = "0.3.3" quickcheck = "0.8" quickcheck_macros = "0.8" +bumpalo = "2.6" From 922e5d5af3175ce8ed73e2aeec0a4b7fb86a915e Mon Sep 17 00:00:00 2001 From: Chad Stearns Date: Sat, 7 Mar 2020 14:44:11 -0500 Subject: [PATCH 002/428] Bringing in reporting test files --- compiler/reporting/tests/helpers/mod.rs | 457 +++++++++++++++++++++ compiler/reporting/tests/test_reporting.rs | 172 ++++++++ 2 files changed, 629 insertions(+) create mode 100644 compiler/reporting/tests/helpers/mod.rs create mode 100644 compiler/reporting/tests/test_reporting.rs diff --git a/compiler/reporting/tests/helpers/mod.rs b/compiler/reporting/tests/helpers/mod.rs new file mode 100644 index 0000000000..ddf047813d --- /dev/null +++ b/compiler/reporting/tests/helpers/mod.rs @@ -0,0 +1,457 @@ +extern crate bumpalo; + +use self::bumpalo::Bump; +use roc_builtins::unique::uniq_stdlib; +use roc_can::constraint::Constraint; +use roc_can::env::Env; +use roc_can::expected::Expected; +use roc_can::expr::{canonicalize_expr, Expr, Output}; +use roc_can::operator; +use roc_can::scope::Scope; +use roc_collections::all::{ImMap, ImSet, MutMap, SendMap, SendSet}; +use roc_constrain::expr::constrain_expr; +use roc_constrain::module::{constrain_imported_values, load_builtin_aliases, Import}; +use roc_module::ident::Ident; +use roc_module::symbol::{IdentIds, Interns, ModuleId, ModuleIds, Symbol}; +use roc_parse::ast::{self, Attempting}; +use roc_parse::blankspace::space0_before; +use roc_parse::parser::{loc, Fail, Parser, State}; +use roc_problem::can::Problem; +use roc_region::all::{Located, Region}; +use roc_solve::solve; +use roc_types::subs::{Content, Subs, VarStore, Variable}; +use roc_types::types::Type; +use std::hash::Hash; +use std::path::{Path, PathBuf}; + +pub fn test_home() -> ModuleId { + ModuleIds::default().get_or_insert(&"Test".into()) +} + +#[allow(dead_code)] +pub fn infer_expr( + subs: Subs, + problems: &mut Vec, + constraint: &Constraint, + expr_var: Variable, +) -> (Content, Subs) { + let env = solve::Env { + aliases: MutMap::default(), + vars_by_symbol: SendMap::default(), + }; + let (solved, _) = solve::run(&env, problems, subs, constraint); + + let content = solved.inner().get_without_compacting(expr_var).content; + + (content, solved.into_inner()) +} + +/// Used in the with_larger_debug_stack() function, for tests that otherwise +/// run out of stack space in debug builds (but don't in --release builds) +#[allow(dead_code)] +const EXPANDED_STACK_SIZE: usize = 4 * 1024 * 1024; + +/// Without this, some tests pass in `cargo test --release` but fail without +/// the --release flag because they run out of stack space. This increases +/// stack size for debug builds only, while leaving the stack space at the default +/// amount for release builds. +#[allow(dead_code)] +#[cfg(debug_assertions)] +pub fn with_larger_debug_stack(run_test: F) +where + F: FnOnce() -> (), + F: Send, + F: 'static, +{ + std::thread::Builder::new() + .stack_size(EXPANDED_STACK_SIZE) + .spawn(run_test) + .expect("Error while spawning expanded dev stack size thread") + .join() + .expect("Error while joining expanded dev stack size thread") +} + +/// In --release builds, don't increase the stack size. Run the test normally. +/// This way, we find out if any of our tests are blowing the stack even after +/// optimizations in release builds. +#[allow(dead_code)] +#[cfg(not(debug_assertions))] +#[inline(always)] +pub fn with_larger_debug_stack(run_test: F) +where + F: FnOnce() -> (), + F: Send, + F: 'static, +{ + run_test() +} + +#[allow(dead_code)] +pub fn parse_with<'a>(arena: &'a Bump, input: &'a str) -> Result, Fail> { + parse_loc_with(arena, input).map(|loc_expr| loc_expr.value) +} + +#[allow(dead_code)] +pub fn parse_loc_with<'a>(arena: &'a Bump, input: &'a str) -> Result>, Fail> { + let state = State::new(&input, Attempting::Module); + let parser = space0_before(loc(roc_parse::expr::expr(0)), 0); + let answer = parser.parse(&arena, state); + + answer + .map(|(loc_expr, _)| loc_expr) + .map_err(|(fail, _)| fail) +} + +#[allow(dead_code)] +pub fn can_expr(expr_str: &str) -> CanExprOut { + can_expr_with(&Bump::new(), test_home(), expr_str) +} + +#[allow(dead_code)] +pub fn uniq_expr( + expr_str: &str, +) -> ( + Located, + Output, + Vec, + Subs, + Variable, + Constraint, + ModuleId, + Interns, +) { + let declared_idents: &ImMap = &ImMap::default(); + + uniq_expr_with(&Bump::new(), expr_str, declared_idents) +} + +#[allow(dead_code)] +pub fn uniq_expr_with( + arena: &Bump, + expr_str: &str, + declared_idents: &ImMap, +) -> ( + Located, + Output, + Vec, + Subs, + Variable, + Constraint, + ModuleId, + Interns, +) { + let home = test_home(); + let CanExprOut { + loc_expr, + output, + problems, + var_store: old_var_store, + var, + interns, + .. + } = can_expr_with(arena, home, expr_str); + + // double check + let var_store = VarStore::new(old_var_store.fresh()); + + let expected2 = Expected::NoExpectation(Type::Variable(var)); + let constraint = roc_constrain::uniq::constrain_declaration( + home, + &var_store, + Region::zero(), + &loc_expr, + declared_idents, + expected2, + ); + + let stdlib = uniq_stdlib(); + + let types = stdlib.types; + let imports: Vec<_> = types + .iter() + .map(|(symbol, (solved_type, region))| Import { + loc_symbol: Located::at(*region, *symbol), + solved_type: solved_type, + }) + .collect(); + + // load builtin values + + // TODO what to do with those rigids? + let (_introduced_rigids, constraint) = + constrain_imported_values(imports, constraint, &var_store); + + // load builtin types + let mut constraint = load_builtin_aliases(&stdlib.aliases, constraint, &var_store); + + constraint.instantiate_aliases(&var_store); + + let subs2 = Subs::new(var_store.into()); + + ( + loc_expr, output, problems, subs2, var, constraint, home, interns, + ) +} + +pub struct CanExprOut { + pub loc_expr: Located, + pub output: Output, + pub problems: Vec, + pub home: ModuleId, + pub interns: Interns, + pub var_store: VarStore, + pub var: Variable, + pub constraint: Constraint, +} + +#[allow(dead_code)] +pub fn can_expr_with(arena: &Bump, home: ModuleId, expr_str: &str) -> CanExprOut { + let loc_expr = 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 + ) + }); + + let var_store = VarStore::default(); + let var = var_store.fresh(); + let expected = Expected::NoExpectation(Type::Variable(var)); + let module_ids = ModuleIds::default(); + + // Desugar operators (convert them to Apply calls, taking into account + // operator precedence and associativity rules), before doing other canonicalization. + // + // If we did this *during* canonicalization, then each time we + // visited a BinOp node we'd recursively try to apply this to each of its nested + // operators, and then again on *their* nested operators, ultimately applying the + // rules multiple times unnecessarily. + let loc_expr = operator::desugar_expr(arena, &loc_expr); + + let mut scope = Scope::new(home); + let dep_idents = IdentIds::exposed_builtins(0); + let mut env = Env::new(home, dep_idents, &module_ids, IdentIds::default()); + let (loc_expr, output) = canonicalize_expr( + &mut env, + &var_store, + &mut scope, + Region::zero(), + &loc_expr.value, + ); + + let constraint = constrain_expr( + &roc_constrain::expr::Env { + rigids: ImMap::default(), + home, + }, + loc_expr.region, + &loc_expr.value, + expected, + ); + + let types = roc_builtins::std::types(); + + let imports: Vec<_> = types + .iter() + .map(|(symbol, (solved_type, region))| Import { + loc_symbol: Located::at(*region, *symbol), + solved_type: solved_type, + }) + .collect(); + + //load builtin values + let (_introduced_rigids, constraint) = + constrain_imported_values(imports, constraint, &var_store); + + //load builtin types + let mut constraint = + load_builtin_aliases(&roc_builtins::std::aliases(), constraint, &var_store); + + constraint.instantiate_aliases(&var_store); + + let mut all_ident_ids = MutMap::default(); + + // When pretty printing types, we may need the exposed builtins, + // so include them in the Interns we'll ultimately return. + for (module_id, ident_ids) in IdentIds::exposed_builtins(0) { + all_ident_ids.insert(module_id, ident_ids); + } + + all_ident_ids.insert(home, env.ident_ids); + + let interns = Interns { + module_ids: env.module_ids.clone(), + all_ident_ids, + }; + + CanExprOut { + loc_expr, + output, + problems: env.problems, + home: env.home, + var_store, + interns, + var, + constraint, + } +} + +#[allow(dead_code)] +pub fn mut_map_from_pairs(pairs: I) -> MutMap +where + I: IntoIterator, + K: Hash + Eq, +{ + let mut answer = MutMap::default(); + + for (key, value) in pairs { + answer.insert(key, value); + } + + answer +} + +#[allow(dead_code)] +pub fn im_map_from_pairs(pairs: I) -> ImMap +where + I: IntoIterator, + K: Hash + Eq + Clone, + V: Clone, +{ + let mut answer = ImMap::default(); + + for (key, value) in pairs { + answer.insert(key, value); + } + + answer +} + +#[allow(dead_code)] +pub fn send_set_from(elems: I) -> SendSet +where + I: IntoIterator, + V: Hash + Eq + Clone, +{ + let mut answer = SendSet::default(); + + for elem in elems { + answer.insert(elem); + } + + answer +} + +#[allow(dead_code)] +pub fn fixtures_dir<'a>() -> PathBuf { + Path::new("tests").join("fixtures").join("build") +} + +#[allow(dead_code)] +pub fn builtins_dir<'a>() -> PathBuf { + PathBuf::new().join("builtins") +} + +// Check constraints +// +// Keep track of the used (in types or expectations) variables, and the declared variables (in +// flex_vars or rigid_vars fields of LetConstraint. These roc_collections should match: no duplicates +// and no variables that are used but not declared are allowed. +// +// There is one exception: the initial variable (that stores the type of the whole expression) is +// never declared, but is used. +#[allow(dead_code)] +pub fn assert_correct_variable_usage(constraint: &Constraint) { + // variables declared in constraint (flex_vars or rigid_vars) + // and variables actually used in constraints + let (declared, used) = variable_usage(constraint); + + let used: ImSet = used.clone().into(); + let mut decl: ImSet = declared.rigid_vars.clone().into(); + + for var in declared.flex_vars.clone() { + decl.insert(var); + } + + let diff = used.clone().relative_complement(decl); + + // NOTE: this checks whether we're using variables that are not declared. For recursive type + // definitions, their rigid types are declared twice, which is correct! + if !diff.is_empty() { + println!("VARIABLE USAGE PROBLEM"); + + println!("used: {:?}", &used); + println!("rigids: {:?}", &declared.rigid_vars); + println!("flexs: {:?}", &declared.flex_vars); + + println!("difference: {:?}", &diff); + + panic!("variable usage problem (see stdout for details)"); + } +} + +#[derive(Default)] +pub struct SeenVariables { + pub rigid_vars: Vec, + pub flex_vars: Vec, +} + +pub fn variable_usage(con: &Constraint) -> (SeenVariables, Vec) { + let mut declared = SeenVariables::default(); + let mut used = ImSet::default(); + variable_usage_help(con, &mut declared, &mut used); + + used.remove(unsafe { &Variable::unsafe_test_debug_variable(1) }); + used.remove(unsafe { &Variable::unsafe_test_debug_variable(2) }); + used.remove(unsafe { &Variable::unsafe_test_debug_variable(3) }); + + let mut used_vec: Vec = used.into_iter().collect(); + used_vec.sort(); + + declared.rigid_vars.sort(); + declared.flex_vars.sort(); + + (declared, used_vec) +} + +fn variable_usage_help(con: &Constraint, declared: &mut SeenVariables, used: &mut ImSet) { + use Constraint::*; + + match con { + True | SaveTheEnvironment => (), + Eq(tipe, expectation, _) => { + for v in tipe.variables() { + used.insert(v); + } + + for v in expectation.get_type_ref().variables() { + used.insert(v); + } + } + Lookup(_, expectation, _) => { + for v in expectation.get_type_ref().variables() { + used.insert(v); + } + } + Pattern(_, _, tipe, pexpectation) => { + for v in tipe.variables() { + used.insert(v); + } + + for v in pexpectation.get_type_ref().variables() { + used.insert(v); + } + } + Let(letcon) => { + declared.rigid_vars.extend(letcon.rigid_vars.clone()); + declared.flex_vars.extend(letcon.flex_vars.clone()); + + variable_usage_help(&letcon.defs_constraint, declared, used); + variable_usage_help(&letcon.ret_constraint, declared, used); + } + And(constraints) => { + for sub in constraints { + variable_usage_help(sub, declared, used); + } + } + } +} diff --git a/compiler/reporting/tests/test_reporting.rs b/compiler/reporting/tests/test_reporting.rs new file mode 100644 index 0000000000..ef8e2232b3 --- /dev/null +++ b/compiler/reporting/tests/test_reporting.rs @@ -0,0 +1,172 @@ +#[macro_use] +extern crate pretty_assertions; +#[macro_use] +extern crate indoc; +extern crate bumpalo; +extern crate roc_reporting; +mod helpers; + + +#[cfg(test)] +mod test_report { + use std::path::PathBuf; + use roc_reporting::report::{ReportText, Report}; + use roc_types::types; + use roc_types::subs::Subs; + use roc_module::symbol::{ModuleId, Interns}; + use roc_types::pretty_print::name_all_type_vars; + // use roc_region::all; + use roc_reporting::report::ReportText::{Plain, EmText, Url, Region}; + use crate::helpers::{assert_correct_variable_usage, can_expr, infer_expr, CanExprOut}; + + // use roc_problem::can; + fn to_simple_report(text: ReportText) -> Report { + let mut filename = PathBuf::new(); + filename.push(r"\code\proj\Main.roc"); + Report { + text: text, + filename: filename, + } + } + + fn infer_expr_help( + expr_src: &str, + ) -> ( + Vec, + Vec, + Subs, + ModuleId, + Interns, + ) { + let CanExprOut { + output, + var_store, + var, + constraint, + home, + interns, + problems: can_problems, + .. + } = can_expr(expr_src); + let mut subs = Subs::new(var_store.into()); + + assert_correct_variable_usage(&constraint); + + for (var, name) in output.ftv { + subs.rigid_var(var, name); + } + + let mut unify_problems = Vec::new(); + let (_content, solved) = infer_expr(subs, &mut unify_problems, &constraint, var); + let mut subs = solved; + + name_all_type_vars(var, &mut subs); + + (unify_problems, can_problems, subs, home, interns) + } + + fn report_renders_as(src: &str, report: Report, expected_rendering: &str) { + let (_type_problems, can_problems, mut subs, home, interns) = infer_expr_help(src); + let mut buf = String::new(); + let src_lines: Vec<&str> = src.split('\n').collect(); + + dbg!("canonicalization problems: {:?}", can_problems); + + report + .text + .render_ci(&mut buf, &mut subs, home, &src_lines, &interns); + + assert_eq!(buf, expected_rendering); + } + + #[test] + fn report_plain() { + report_renders_as( + indoc!( + r#" + x = 1 + y = 2 + + x + "# + ), + to_simple_report(Plain(Box::from("y"))), + "y", + ); + } + + #[test] + fn report_emphasized_text() { + report_renders_as( + indoc!( + r#" + x = 1 + y = 2 + + x + "# + ), + to_simple_report(EmText(Box::from("y"))), + "*y*", + ); + } + + #[test] + fn report_url() { + report_renders_as( + indoc!( + r#" + x = 1 + y = 2 + + x + "# + ), + to_simple_report(Url(Box::from("y"))), + "", + ); + } + + // #[test] + // fn report_symbol() { + // report_renders_as( + // indoc!( + // r#" + // x = 1 + // y = 2 + // + // x + // "# + // ), + // to_simple_report(Value(Symbol::new("Test" ))), + // "x", + // ); + // } + + #[test] + fn report_region() { + report_renders_as( + indoc!( + r#" + x = 1 + y = 2 + + x + "# + ), + to_simple_report(Region(roc_region::all::Region { + start_line: 1, + end_line: 4, + start_col: 0, + end_col: 0, + })), + indoc!( + r#" + 1 | y = 2 + 2 | + 3 | x + "# + ), + ); + } +} \ No newline at end of file From a165248a176d39b300560628c72eb833d8359ad8 Mon Sep 17 00:00:00 2001 From: Chad Stearns Date: Sat, 7 Mar 2020 14:46:08 -0500 Subject: [PATCH 003/428] Cargo fmt --- compiler/reporting/tests/test_reporting.rs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/compiler/reporting/tests/test_reporting.rs b/compiler/reporting/tests/test_reporting.rs index ef8e2232b3..8ecd8d4583 100644 --- a/compiler/reporting/tests/test_reporting.rs +++ b/compiler/reporting/tests/test_reporting.rs @@ -6,18 +6,17 @@ extern crate bumpalo; extern crate roc_reporting; mod helpers; - #[cfg(test)] mod test_report { - use std::path::PathBuf; - use roc_reporting::report::{ReportText, Report}; - use roc_types::types; - use roc_types::subs::Subs; - use roc_module::symbol::{ModuleId, Interns}; + use roc_module::symbol::{Interns, ModuleId}; + use roc_reporting::report::{Report, ReportText}; use roc_types::pretty_print::name_all_type_vars; + use roc_types::subs::Subs; + use roc_types::types; + use std::path::PathBuf; // use roc_region::all; - use roc_reporting::report::ReportText::{Plain, EmText, Url, Region}; use crate::helpers::{assert_correct_variable_usage, can_expr, infer_expr, CanExprOut}; + use roc_reporting::report::ReportText::{EmText, Plain, Region, Url}; // use roc_problem::can; fn to_simple_report(text: ReportText) -> Report { @@ -169,4 +168,4 @@ mod test_report { ), ); } -} \ No newline at end of file +} From d589f6b2df232122ef2bee45f73c37739bf9e732 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sat, 7 Mar 2020 15:54:46 -0500 Subject: [PATCH 004/428] Fix infer_expr_help in test_reporting --- compiler/reporting/tests/test_reporting.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/compiler/reporting/tests/test_reporting.rs b/compiler/reporting/tests/test_reporting.rs index 8ecd8d4583..bf654b4ffb 100644 --- a/compiler/reporting/tests/test_reporting.rs +++ b/compiler/reporting/tests/test_reporting.rs @@ -51,13 +51,12 @@ mod test_report { assert_correct_variable_usage(&constraint); - for (var, name) in output.ftv { + for (var, name) in output.introduced_variables.name_by_var { subs.rigid_var(var, name); } let mut unify_problems = Vec::new(); - let (_content, solved) = infer_expr(subs, &mut unify_problems, &constraint, var); - let mut subs = solved; + let (_content, mut subs) = infer_expr(subs, &mut unify_problems, &constraint, var); name_all_type_vars(var, &mut subs); From 704b81d67c54b690aba6699ef7f49b04549ef9bb Mon Sep 17 00:00:00 2001 From: Chad Stearns Date: Sun, 8 Mar 2020 00:39:28 -0500 Subject: [PATCH 005/428] Restore report tests and also add more basic tests --- compiler/reporting/src/report.rs | 17 ++- compiler/reporting/tests/test_reporting.rs | 121 ++++++++++++--------- 2 files changed, 80 insertions(+), 58 deletions(-) diff --git a/compiler/reporting/src/report.rs b/compiler/reporting/src/report.rs index d40e9fcfe5..47743afae5 100644 --- a/compiler/reporting/src/report.rs +++ b/compiler/reporting/src/report.rs @@ -89,10 +89,19 @@ impl ReportText { } Type(content) => buf.push_str(content_to_string(content, subs, home, interns).as_str()), Region(region) => { - panic!( - "TODO convert these source lines and this region into a String: {:?}, {:?}", - src_lines, region - ); + for i in region.start_line..region.end_line { + buf.push_str(i.to_string().as_str()); + buf.push_str(" |"); + + let line = src_lines[i as usize]; + + if !line.is_empty() { + buf.push(' '); + buf.push_str(src_lines[i as usize]); + } + + buf.push('\n'); + } } Docs(_) => { panic!("TODO implment docs"); diff --git a/compiler/reporting/tests/test_reporting.rs b/compiler/reporting/tests/test_reporting.rs index bf654b4ffb..d214e8c6ad 100644 --- a/compiler/reporting/tests/test_reporting.rs +++ b/compiler/reporting/tests/test_reporting.rs @@ -4,10 +4,12 @@ extern crate pretty_assertions; extern crate indoc; extern crate bumpalo; extern crate roc_reporting; + mod helpers; #[cfg(test)] mod test_report { + use crate::helpers::test_home; use roc_module::symbol::{Interns, ModuleId}; use roc_reporting::report::{Report, ReportText}; use roc_types::pretty_print::name_all_type_vars; @@ -16,7 +18,10 @@ mod test_report { use std::path::PathBuf; // use roc_region::all; use crate::helpers::{assert_correct_variable_usage, can_expr, infer_expr, CanExprOut}; - use roc_reporting::report::ReportText::{EmText, Plain, Region, Url}; + use roc_reporting::report::ReportText::{EmText, Plain, Region, Type, Url, Value}; + use roc_types::subs::Content::{FlexVar, RigidVar, Structure}; + use roc_types::subs::FlatType::{EmptyRecord, Erroneous, Record}; + use roc_types::types::Problem::CanonicalizationProblem; // use roc_problem::can; fn to_simple_report(text: ReportText) -> Report { @@ -63,13 +68,11 @@ mod test_report { (unify_problems, can_problems, subs, home, interns) } - fn report_renders_as(src: &str, report: Report, expected_rendering: &str) { - let (_type_problems, can_problems, mut subs, home, interns) = infer_expr_help(src); + fn report_renders_as_from_src(src: &str, report: Report, expected_rendering: &str) { + let (_type_problems, _can_problems, mut subs, home, interns) = infer_expr_help(src); let mut buf = String::new(); let src_lines: Vec<&str> = src.split('\n').collect(); - dbg!("canonicalization problems: {:?}", can_problems); - report .text .render_ci(&mut buf, &mut subs, home, &src_lines, &interns); @@ -77,9 +80,8 @@ mod test_report { assert_eq!(buf, expected_rendering); } - #[test] - fn report_plain() { - report_renders_as( + fn report_renders_as(report: Report, expected_rendering: &str) { + report_renders_as_from_src( indoc!( r#" x = 1 @@ -88,68 +90,79 @@ mod test_report { x "# ), - to_simple_report(Plain(Box::from("y"))), - "y", - ); + report, + expected_rendering, + ) + } + + #[test] + fn report_plain() { + report_renders_as(to_simple_report(Plain(Box::from("y"))), "y"); } #[test] fn report_emphasized_text() { - report_renders_as( - indoc!( - r#" - x = 1 - y = 2 - - x - "# - ), - to_simple_report(EmText(Box::from("y"))), - "*y*", - ); + report_renders_as(to_simple_report(EmText(Box::from("y"))), "*y*"); } #[test] fn report_url() { - report_renders_as( - indoc!( - r#" - x = 1 - y = 2 - - x - "# - ), - to_simple_report(Url(Box::from("y"))), - "", - ); + report_renders_as(to_simple_report(Url(Box::from("y"))), ""); } - // #[test] - // fn report_symbol() { - // report_renders_as( - // indoc!( - // r#" - // x = 1 - // y = 2 - // - // x - // "# - // ), - // to_simple_report(Value(Symbol::new("Test" ))), - // "x", - // ); - // } + #[test] + fn report_symbol() { + let src: &str = indoc!( + r#" + x = 1 + y = 2 + + x + "# + ); + + let (_type_problems, _can_problems, mut subs, home, interns) = infer_expr_help(src); + + let mut buf = String::new(); + let src_lines: Vec<&str> = src.split('\n').collect(); + + to_simple_report(Value(interns.symbol(test_home(), "x".into()))) + .text + .render_ci(&mut buf, &mut subs, home, &src_lines, &interns); + + assert_eq!(buf, "x"); + } + + #[test] + fn report_wildcard() { + report_renders_as(to_simple_report(Type(FlexVar(None))), "*"); + } + + #[test] + fn report_flex_var() { + report_renders_as(to_simple_report(Type(FlexVar(Some("msg".into())))), "msg"); + } + + #[test] + fn report_rigid_var() { + report_renders_as(to_simple_report(Type(RigidVar("a".into()))), "a"); + } + + #[test] + fn report_empty_record() { + report_renders_as(to_simple_report(Type(Structure(EmptyRecord))), "{}"); + } #[test] fn report_region() { - report_renders_as( + report_renders_as_from_src( indoc!( r#" x = 1 y = 2 + f = \a -> a + 4 - x + f x "# ), to_simple_report(Region(roc_region::all::Region { @@ -161,8 +174,8 @@ mod test_report { indoc!( r#" 1 | y = 2 - 2 | - 3 | x + 2 | f = \a -> a + 4 + 3 | "# ), ); From dcfa1172277732f8e80428ead1e5fd7023a1b051 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sun, 8 Mar 2020 14:45:18 -0400 Subject: [PATCH 006/428] Basic test_mono and test_opt --- compiler/mono/tests/helpers/mod.rs | 457 +++++++++++++++++++++++++++++ compiler/mono/tests/test_mono.rs | 63 ++++ compiler/mono/tests/test_opt.rs | 50 ++++ 3 files changed, 570 insertions(+) create mode 100644 compiler/mono/tests/helpers/mod.rs create mode 100644 compiler/mono/tests/test_mono.rs create mode 100644 compiler/mono/tests/test_opt.rs diff --git a/compiler/mono/tests/helpers/mod.rs b/compiler/mono/tests/helpers/mod.rs new file mode 100644 index 0000000000..ddf047813d --- /dev/null +++ b/compiler/mono/tests/helpers/mod.rs @@ -0,0 +1,457 @@ +extern crate bumpalo; + +use self::bumpalo::Bump; +use roc_builtins::unique::uniq_stdlib; +use roc_can::constraint::Constraint; +use roc_can::env::Env; +use roc_can::expected::Expected; +use roc_can::expr::{canonicalize_expr, Expr, Output}; +use roc_can::operator; +use roc_can::scope::Scope; +use roc_collections::all::{ImMap, ImSet, MutMap, SendMap, SendSet}; +use roc_constrain::expr::constrain_expr; +use roc_constrain::module::{constrain_imported_values, load_builtin_aliases, Import}; +use roc_module::ident::Ident; +use roc_module::symbol::{IdentIds, Interns, ModuleId, ModuleIds, Symbol}; +use roc_parse::ast::{self, Attempting}; +use roc_parse::blankspace::space0_before; +use roc_parse::parser::{loc, Fail, Parser, State}; +use roc_problem::can::Problem; +use roc_region::all::{Located, Region}; +use roc_solve::solve; +use roc_types::subs::{Content, Subs, VarStore, Variable}; +use roc_types::types::Type; +use std::hash::Hash; +use std::path::{Path, PathBuf}; + +pub fn test_home() -> ModuleId { + ModuleIds::default().get_or_insert(&"Test".into()) +} + +#[allow(dead_code)] +pub fn infer_expr( + subs: Subs, + problems: &mut Vec, + constraint: &Constraint, + expr_var: Variable, +) -> (Content, Subs) { + let env = solve::Env { + aliases: MutMap::default(), + vars_by_symbol: SendMap::default(), + }; + let (solved, _) = solve::run(&env, problems, subs, constraint); + + let content = solved.inner().get_without_compacting(expr_var).content; + + (content, solved.into_inner()) +} + +/// Used in the with_larger_debug_stack() function, for tests that otherwise +/// run out of stack space in debug builds (but don't in --release builds) +#[allow(dead_code)] +const EXPANDED_STACK_SIZE: usize = 4 * 1024 * 1024; + +/// Without this, some tests pass in `cargo test --release` but fail without +/// the --release flag because they run out of stack space. This increases +/// stack size for debug builds only, while leaving the stack space at the default +/// amount for release builds. +#[allow(dead_code)] +#[cfg(debug_assertions)] +pub fn with_larger_debug_stack(run_test: F) +where + F: FnOnce() -> (), + F: Send, + F: 'static, +{ + std::thread::Builder::new() + .stack_size(EXPANDED_STACK_SIZE) + .spawn(run_test) + .expect("Error while spawning expanded dev stack size thread") + .join() + .expect("Error while joining expanded dev stack size thread") +} + +/// In --release builds, don't increase the stack size. Run the test normally. +/// This way, we find out if any of our tests are blowing the stack even after +/// optimizations in release builds. +#[allow(dead_code)] +#[cfg(not(debug_assertions))] +#[inline(always)] +pub fn with_larger_debug_stack(run_test: F) +where + F: FnOnce() -> (), + F: Send, + F: 'static, +{ + run_test() +} + +#[allow(dead_code)] +pub fn parse_with<'a>(arena: &'a Bump, input: &'a str) -> Result, Fail> { + parse_loc_with(arena, input).map(|loc_expr| loc_expr.value) +} + +#[allow(dead_code)] +pub fn parse_loc_with<'a>(arena: &'a Bump, input: &'a str) -> Result>, Fail> { + let state = State::new(&input, Attempting::Module); + let parser = space0_before(loc(roc_parse::expr::expr(0)), 0); + let answer = parser.parse(&arena, state); + + answer + .map(|(loc_expr, _)| loc_expr) + .map_err(|(fail, _)| fail) +} + +#[allow(dead_code)] +pub fn can_expr(expr_str: &str) -> CanExprOut { + can_expr_with(&Bump::new(), test_home(), expr_str) +} + +#[allow(dead_code)] +pub fn uniq_expr( + expr_str: &str, +) -> ( + Located, + Output, + Vec, + Subs, + Variable, + Constraint, + ModuleId, + Interns, +) { + let declared_idents: &ImMap = &ImMap::default(); + + uniq_expr_with(&Bump::new(), expr_str, declared_idents) +} + +#[allow(dead_code)] +pub fn uniq_expr_with( + arena: &Bump, + expr_str: &str, + declared_idents: &ImMap, +) -> ( + Located, + Output, + Vec, + Subs, + Variable, + Constraint, + ModuleId, + Interns, +) { + let home = test_home(); + let CanExprOut { + loc_expr, + output, + problems, + var_store: old_var_store, + var, + interns, + .. + } = can_expr_with(arena, home, expr_str); + + // double check + let var_store = VarStore::new(old_var_store.fresh()); + + let expected2 = Expected::NoExpectation(Type::Variable(var)); + let constraint = roc_constrain::uniq::constrain_declaration( + home, + &var_store, + Region::zero(), + &loc_expr, + declared_idents, + expected2, + ); + + let stdlib = uniq_stdlib(); + + let types = stdlib.types; + let imports: Vec<_> = types + .iter() + .map(|(symbol, (solved_type, region))| Import { + loc_symbol: Located::at(*region, *symbol), + solved_type: solved_type, + }) + .collect(); + + // load builtin values + + // TODO what to do with those rigids? + let (_introduced_rigids, constraint) = + constrain_imported_values(imports, constraint, &var_store); + + // load builtin types + let mut constraint = load_builtin_aliases(&stdlib.aliases, constraint, &var_store); + + constraint.instantiate_aliases(&var_store); + + let subs2 = Subs::new(var_store.into()); + + ( + loc_expr, output, problems, subs2, var, constraint, home, interns, + ) +} + +pub struct CanExprOut { + pub loc_expr: Located, + pub output: Output, + pub problems: Vec, + pub home: ModuleId, + pub interns: Interns, + pub var_store: VarStore, + pub var: Variable, + pub constraint: Constraint, +} + +#[allow(dead_code)] +pub fn can_expr_with(arena: &Bump, home: ModuleId, expr_str: &str) -> CanExprOut { + let loc_expr = 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 + ) + }); + + let var_store = VarStore::default(); + let var = var_store.fresh(); + let expected = Expected::NoExpectation(Type::Variable(var)); + let module_ids = ModuleIds::default(); + + // Desugar operators (convert them to Apply calls, taking into account + // operator precedence and associativity rules), before doing other canonicalization. + // + // If we did this *during* canonicalization, then each time we + // visited a BinOp node we'd recursively try to apply this to each of its nested + // operators, and then again on *their* nested operators, ultimately applying the + // rules multiple times unnecessarily. + let loc_expr = operator::desugar_expr(arena, &loc_expr); + + let mut scope = Scope::new(home); + let dep_idents = IdentIds::exposed_builtins(0); + let mut env = Env::new(home, dep_idents, &module_ids, IdentIds::default()); + let (loc_expr, output) = canonicalize_expr( + &mut env, + &var_store, + &mut scope, + Region::zero(), + &loc_expr.value, + ); + + let constraint = constrain_expr( + &roc_constrain::expr::Env { + rigids: ImMap::default(), + home, + }, + loc_expr.region, + &loc_expr.value, + expected, + ); + + let types = roc_builtins::std::types(); + + let imports: Vec<_> = types + .iter() + .map(|(symbol, (solved_type, region))| Import { + loc_symbol: Located::at(*region, *symbol), + solved_type: solved_type, + }) + .collect(); + + //load builtin values + let (_introduced_rigids, constraint) = + constrain_imported_values(imports, constraint, &var_store); + + //load builtin types + let mut constraint = + load_builtin_aliases(&roc_builtins::std::aliases(), constraint, &var_store); + + constraint.instantiate_aliases(&var_store); + + let mut all_ident_ids = MutMap::default(); + + // When pretty printing types, we may need the exposed builtins, + // so include them in the Interns we'll ultimately return. + for (module_id, ident_ids) in IdentIds::exposed_builtins(0) { + all_ident_ids.insert(module_id, ident_ids); + } + + all_ident_ids.insert(home, env.ident_ids); + + let interns = Interns { + module_ids: env.module_ids.clone(), + all_ident_ids, + }; + + CanExprOut { + loc_expr, + output, + problems: env.problems, + home: env.home, + var_store, + interns, + var, + constraint, + } +} + +#[allow(dead_code)] +pub fn mut_map_from_pairs(pairs: I) -> MutMap +where + I: IntoIterator, + K: Hash + Eq, +{ + let mut answer = MutMap::default(); + + for (key, value) in pairs { + answer.insert(key, value); + } + + answer +} + +#[allow(dead_code)] +pub fn im_map_from_pairs(pairs: I) -> ImMap +where + I: IntoIterator, + K: Hash + Eq + Clone, + V: Clone, +{ + let mut answer = ImMap::default(); + + for (key, value) in pairs { + answer.insert(key, value); + } + + answer +} + +#[allow(dead_code)] +pub fn send_set_from(elems: I) -> SendSet +where + I: IntoIterator, + V: Hash + Eq + Clone, +{ + let mut answer = SendSet::default(); + + for elem in elems { + answer.insert(elem); + } + + answer +} + +#[allow(dead_code)] +pub fn fixtures_dir<'a>() -> PathBuf { + Path::new("tests").join("fixtures").join("build") +} + +#[allow(dead_code)] +pub fn builtins_dir<'a>() -> PathBuf { + PathBuf::new().join("builtins") +} + +// Check constraints +// +// Keep track of the used (in types or expectations) variables, and the declared variables (in +// flex_vars or rigid_vars fields of LetConstraint. These roc_collections should match: no duplicates +// and no variables that are used but not declared are allowed. +// +// There is one exception: the initial variable (that stores the type of the whole expression) is +// never declared, but is used. +#[allow(dead_code)] +pub fn assert_correct_variable_usage(constraint: &Constraint) { + // variables declared in constraint (flex_vars or rigid_vars) + // and variables actually used in constraints + let (declared, used) = variable_usage(constraint); + + let used: ImSet = used.clone().into(); + let mut decl: ImSet = declared.rigid_vars.clone().into(); + + for var in declared.flex_vars.clone() { + decl.insert(var); + } + + let diff = used.clone().relative_complement(decl); + + // NOTE: this checks whether we're using variables that are not declared. For recursive type + // definitions, their rigid types are declared twice, which is correct! + if !diff.is_empty() { + println!("VARIABLE USAGE PROBLEM"); + + println!("used: {:?}", &used); + println!("rigids: {:?}", &declared.rigid_vars); + println!("flexs: {:?}", &declared.flex_vars); + + println!("difference: {:?}", &diff); + + panic!("variable usage problem (see stdout for details)"); + } +} + +#[derive(Default)] +pub struct SeenVariables { + pub rigid_vars: Vec, + pub flex_vars: Vec, +} + +pub fn variable_usage(con: &Constraint) -> (SeenVariables, Vec) { + let mut declared = SeenVariables::default(); + let mut used = ImSet::default(); + variable_usage_help(con, &mut declared, &mut used); + + used.remove(unsafe { &Variable::unsafe_test_debug_variable(1) }); + used.remove(unsafe { &Variable::unsafe_test_debug_variable(2) }); + used.remove(unsafe { &Variable::unsafe_test_debug_variable(3) }); + + let mut used_vec: Vec = used.into_iter().collect(); + used_vec.sort(); + + declared.rigid_vars.sort(); + declared.flex_vars.sort(); + + (declared, used_vec) +} + +fn variable_usage_help(con: &Constraint, declared: &mut SeenVariables, used: &mut ImSet) { + use Constraint::*; + + match con { + True | SaveTheEnvironment => (), + Eq(tipe, expectation, _) => { + for v in tipe.variables() { + used.insert(v); + } + + for v in expectation.get_type_ref().variables() { + used.insert(v); + } + } + Lookup(_, expectation, _) => { + for v in expectation.get_type_ref().variables() { + used.insert(v); + } + } + Pattern(_, _, tipe, pexpectation) => { + for v in tipe.variables() { + used.insert(v); + } + + for v in pexpectation.get_type_ref().variables() { + used.insert(v); + } + } + Let(letcon) => { + declared.rigid_vars.extend(letcon.rigid_vars.clone()); + declared.flex_vars.extend(letcon.flex_vars.clone()); + + variable_usage_help(&letcon.defs_constraint, declared, used); + variable_usage_help(&letcon.ret_constraint, declared, used); + } + And(constraints) => { + for sub in constraints { + variable_usage_help(sub, declared, used); + } + } + } +} diff --git a/compiler/mono/tests/test_mono.rs b/compiler/mono/tests/test_mono.rs new file mode 100644 index 0000000000..423b3bad43 --- /dev/null +++ b/compiler/mono/tests/test_mono.rs @@ -0,0 +1,63 @@ +#[macro_use] +extern crate pretty_assertions; +#[macro_use] +extern crate indoc; + +extern crate bumpalo; +extern crate roc_mono; + +mod helpers; + +// Test monomorphization +#[cfg(test)] +mod test_mono { + use crate::helpers::{can_expr, infer_expr, CanExprOut}; + use bumpalo::Bump; + use roc_collections::all::MutMap; + use roc_mono::expr::Expr::{self, *}; + use roc_types::subs::Subs; + + // HELPERS + + fn compiles_to(src: &str, expected: Expr<'_>) { + let arena = Bump::new(); + let CanExprOut { + loc_expr, + var_store, + var, + constraint, + home, + mut interns, + .. + } = can_expr(src); + let subs = Subs::new(var_store.into()); + let mut unify_problems = Vec::new(); + let (_content, subs) = infer_expr(subs, &mut unify_problems, &constraint, var); + + // Compile and add all the Procs before adding main + let mut procs = MutMap::default(); + let mut ident_ids = interns.all_ident_ids.remove(&home).unwrap(); + + // Populate Procs and Subs, and get the low-level Expr from the canonical Expr + let mono_expr = Expr::new( + &arena, + &subs, + loc_expr.value, + &mut procs, + home, + &mut ident_ids, + ); + + assert_eq!(mono_expr, expected); + } + + #[test] + fn int_literal() { + compiles_to("5", Int(5)); + } + + #[test] + fn float_literal() { + compiles_to("0.5", Float(0.5)); + } +} diff --git a/compiler/mono/tests/test_opt.rs b/compiler/mono/tests/test_opt.rs new file mode 100644 index 0000000000..88cdc373a3 --- /dev/null +++ b/compiler/mono/tests/test_opt.rs @@ -0,0 +1,50 @@ +#[macro_use] +extern crate pretty_assertions; +#[macro_use] +extern crate indoc; + +extern crate bumpalo; +extern crate roc_mono; + +mod helpers; + +// Test optimizations +#[cfg(test)] +mod test_opt { + use crate::helpers::uniq_expr; + use bumpalo::Bump; + use roc_collections::all::MutMap; + use roc_mono::expr::Expr::{self, *}; + + // HELPERS + + fn compiles_to(src: &str, expected: Expr<'_>) { + let arena = Bump::new(); + let (loc_expr, _, _problems, subs, _, _, home, mut interns) = uniq_expr(src); + // Compile and add all the Procs before adding main + let mut procs = MutMap::default(); + let mut ident_ids = interns.all_ident_ids.remove(&home).unwrap(); + + // Populate Procs and Subs, and get the low-level Expr from the canonical Expr + let mono_expr = Expr::new( + &arena, + &subs, + loc_expr.value, + &mut procs, + home, + &mut ident_ids, + ); + + assert_eq!(mono_expr, expected); + } + + #[test] + fn int_literal() { + compiles_to("5", Int(5)); + } + + #[test] + fn float_literal() { + compiles_to("0.5", Float(0.5)); + } +} From 99359221ab27420c50e54a5eb6667b43d8288c28 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sun, 8 Mar 2020 15:09:01 -0400 Subject: [PATCH 007/428] Add List.set test to test_mono --- compiler/mono/tests/test_mono.rs | 38 ++++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/compiler/mono/tests/test_mono.rs b/compiler/mono/tests/test_mono.rs index 423b3bad43..9af7867198 100644 --- a/compiler/mono/tests/test_mono.rs +++ b/compiler/mono/tests/test_mono.rs @@ -1,7 +1,7 @@ #[macro_use] extern crate pretty_assertions; -#[macro_use] -extern crate indoc; +// #[macro_use] +// extern crate indoc; extern crate bumpalo; extern crate roc_mono; @@ -14,7 +14,9 @@ mod test_mono { use crate::helpers::{can_expr, infer_expr, CanExprOut}; use bumpalo::Bump; use roc_collections::all::MutMap; + use roc_module::symbol::Symbol; use roc_mono::expr::Expr::{self, *}; + use roc_mono::layout::{Builtin, Layout}; use roc_types::subs::Subs; // HELPERS @@ -60,4 +62,36 @@ mod test_mono { fn float_literal() { compiles_to("0.5", Float(0.5)); } + + #[test] + fn set_unique_int_list() { + compiles_to( + "List.getUnsafe (List.set [ 12, 9, 7, 3 ] 1 42) 1", + CallByName( + Symbol::LIST_GET_UNSAFE, + &vec![ + ( + CallByName( + Symbol::LIST_SET, + &vec![ + ( + Array { + elem_layout: Layout::Builtin(Builtin::Int64), + elems: &vec![Int(12), Int(9), Int(7), Int(3)], + }, + Layout::Builtin(Builtin::List(&Layout::Builtin( + Builtin::Int64, + ))), + ), + (Int(1), Layout::Builtin(Builtin::Int64)), + (Int(42), Layout::Builtin(Builtin::Int64)), + ], + ), + Layout::Builtin(Builtin::List(&Layout::Builtin(Builtin::Int64))), + ), + (Int(1), Layout::Builtin(Builtin::Int64)), + ], + ), + ); + } } From d878ef86aa33dd24849f453c44ab346da88a545c Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sun, 8 Mar 2020 15:18:39 -0400 Subject: [PATCH 008/428] Add List.set_in_place test to test_opt --- compiler/mono/tests/test_opt.rs | 47 ++++++++++++++++++++++++++++++--- 1 file changed, 43 insertions(+), 4 deletions(-) diff --git a/compiler/mono/tests/test_opt.rs b/compiler/mono/tests/test_opt.rs index 88cdc373a3..9560758cdd 100644 --- a/compiler/mono/tests/test_opt.rs +++ b/compiler/mono/tests/test_opt.rs @@ -1,7 +1,7 @@ #[macro_use] extern crate pretty_assertions; -#[macro_use] -extern crate indoc; +// #[macro_use] +// extern crate indoc; extern crate bumpalo; extern crate roc_mono; @@ -11,16 +11,22 @@ mod helpers; // Test optimizations #[cfg(test)] mod test_opt { - use crate::helpers::uniq_expr; + use crate::helpers::{infer_expr, uniq_expr}; use bumpalo::Bump; use roc_collections::all::MutMap; + use roc_module::symbol::Symbol; use roc_mono::expr::Expr::{self, *}; + use roc_mono::layout::{Builtin, Layout}; // HELPERS fn compiles_to(src: &str, expected: Expr<'_>) { let arena = Bump::new(); - let (loc_expr, _, _problems, subs, _, _, home, mut interns) = uniq_expr(src); + let (loc_expr, _, _problems, subs, var, constraint, home, mut interns) = uniq_expr(src); + + let mut unify_problems = Vec::new(); + let (_content, subs) = infer_expr(subs, &mut unify_problems, &constraint, var); + // Compile and add all the Procs before adding main let mut procs = MutMap::default(); let mut ident_ids = interns.all_ident_ids.remove(&home).unwrap(); @@ -47,4 +53,37 @@ mod test_opt { fn float_literal() { compiles_to("0.5", Float(0.5)); } + + #[test] + fn set_unique_int_list() { + // This should optimize List.set to List.set_in_place + compiles_to( + "List.getUnsafe (List.set [ 12, 9, 7, 3 ] 1 42) 1", + CallByName( + Symbol::LIST_GET_UNSAFE, + &vec![ + ( + CallByName( + Symbol::LIST_SET_IN_PLACE, + &vec![ + ( + Array { + elem_layout: Layout::Builtin(Builtin::Int64), + elems: &vec![Int(12), Int(9), Int(7), Int(3)], + }, + Layout::Builtin(Builtin::List(&Layout::Builtin( + Builtin::Int64, + ))), + ), + (Int(1), Layout::Builtin(Builtin::Int64)), + (Int(42), Layout::Builtin(Builtin::Int64)), + ], + ), + Layout::Builtin(Builtin::List(&Layout::Builtin(Builtin::Int64))), + ), + (Int(1), Layout::Builtin(Builtin::Int64)), + ], + ), + ); + } } From a9cf87e67e739f40cdf7e95ebcf335c5979bc666 Mon Sep 17 00:00:00 2001 From: Chad Stearns Date: Sun, 8 Mar 2020 16:48:08 -0400 Subject: [PATCH 009/428] Satisfy clippy --- compiler/reporting/tests/test_reporting.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/compiler/reporting/tests/test_reporting.rs b/compiler/reporting/tests/test_reporting.rs index d214e8c6ad..3184e47a30 100644 --- a/compiler/reporting/tests/test_reporting.rs +++ b/compiler/reporting/tests/test_reporting.rs @@ -20,8 +20,7 @@ mod test_report { use crate::helpers::{assert_correct_variable_usage, can_expr, infer_expr, CanExprOut}; use roc_reporting::report::ReportText::{EmText, Plain, Region, Type, Url, Value}; use roc_types::subs::Content::{FlexVar, RigidVar, Structure}; - use roc_types::subs::FlatType::{EmptyRecord, Erroneous, Record}; - use roc_types::types::Problem::CanonicalizationProblem; + use roc_types::subs::FlatType::{EmptyRecord, Record}; // use roc_problem::can; fn to_simple_report(text: ReportText) -> Report { From b4e028a39cb7a7aa94433332b75163c9f3d2737c Mon Sep 17 00:00:00 2001 From: Chad Stearns Date: Sun, 8 Mar 2020 17:05:06 -0400 Subject: [PATCH 010/428] Upgrade to correct bumpalo version in reporting Cargo --- compiler/reporting/Cargo.toml | 2 +- compiler/reporting/tests/test_reporting.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/reporting/Cargo.toml b/compiler/reporting/Cargo.toml index 11f3598b23..514933f6d6 100644 --- a/compiler/reporting/Cargo.toml +++ b/compiler/reporting/Cargo.toml @@ -24,4 +24,4 @@ maplit = "1.0.1" indoc = "0.3.3" quickcheck = "0.8" quickcheck_macros = "0.8" -bumpalo = "2.6" +bumpalo = { version = "3.2", features = ["collections"] } diff --git a/compiler/reporting/tests/test_reporting.rs b/compiler/reporting/tests/test_reporting.rs index 3184e47a30..6d0ef66cf8 100644 --- a/compiler/reporting/tests/test_reporting.rs +++ b/compiler/reporting/tests/test_reporting.rs @@ -20,7 +20,7 @@ mod test_report { use crate::helpers::{assert_correct_variable_usage, can_expr, infer_expr, CanExprOut}; use roc_reporting::report::ReportText::{EmText, Plain, Region, Type, Url, Value}; use roc_types::subs::Content::{FlexVar, RigidVar, Structure}; - use roc_types::subs::FlatType::{EmptyRecord, Record}; + use roc_types::subs::FlatType::EmptyRecord; // use roc_problem::can; fn to_simple_report(text: ReportText) -> Report { From 6ae1ee373e82a47fd06bf052524b0d85faa5dc47 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sun, 8 Mar 2020 17:20:54 -0400 Subject: [PATCH 011/428] Use a tag on rtfeldman/inkwell --- Cargo.lock | 4 ++-- compiler/gen/Cargo.toml | 23 +++++++++++++++++------ 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f644507d58..0fc3211e9b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -337,7 +337,7 @@ dependencies = [ [[package]] name = "inkwell" version = "0.1.0" -source = "git+https://github.com/TheDan64/inkwell?branch=llvm8-0#647a7cbfc2a6be3b41b8de3b1c63ec1fe4e79267" +source = "git+https://github.com/rtfeldman/inkwell?tag=llvm8-0.release1#647a7cbfc2a6be3b41b8de3b1c63ec1fe4e79267" dependencies = [ "either", "inkwell_internals", @@ -351,7 +351,7 @@ dependencies = [ [[package]] name = "inkwell_internals" version = "0.1.0" -source = "git+https://github.com/TheDan64/inkwell?branch=llvm8-0#647a7cbfc2a6be3b41b8de3b1c63ec1fe4e79267" +source = "git+https://github.com/rtfeldman/inkwell?tag=llvm8-0.release1#647a7cbfc2a6be3b41b8de3b1c63ec1fe4e79267" dependencies = [ "proc-macro2 0.4.30", "quote 0.6.13", diff --git a/compiler/gen/Cargo.toml b/compiler/gen/Cargo.toml index d4a4e158cb..20838969b7 100644 --- a/compiler/gen/Cargo.toml +++ b/compiler/gen/Cargo.toml @@ -20,13 +20,24 @@ im = "14" # im and im-rc should always have the same version! im-rc = "14" # im and im-rc should always have the same version! bumpalo = { version = "3.2", features = ["collections"] } inlinable_string = "0.1.0" -# NOTE: Breaking API changes get pushed directly to this Inkwell branch, so be -# very careful when running `cargo update` to get a new revision into Cargo.lock. +# NOTE: rtfeldman/inkwell is a fork of TheDan64/inkwell which does not change anything. # -# We have to depend on `branch` instead of a specific `rev` here because, although -# `rev` works locally, it causes an error on GitHub Actions. (It's unclear why, -# but after several hours of trying unsuccessfully to fix it, `branch` is it.) -inkwell = { git = "https://github.com/TheDan64/inkwell", branch = "llvm8-0" } +# The reason for this fork is that the way Inkwell is designed, you have to use +# a particular branch (e.g. "llvm8-0") in Cargo.toml. That would be fine, except that +# breaking changes get pushed directly to that branch, which breaks our build +# without warning. +# +# We tried referencing a specific rev on TheDan64/inkwell directly (instead of branch), +# but although that worked locally, it did not work on GitHub Actions. (After a few +# hours of investigation, gave up trying to figure out why.) So this is the workaround: +# having an immutable tag on the rtfeldman/inkwell fork which points to +# a particular "release" of Inkwell. +# +# When we want to update Inkwell, we can sync up rtfeldman/inkwell to the latest +# commit of TheDan64/inkwell, push a new tag which points to the latest commit, +# change the tag value in this Cargo.toml to point to that tag, and `cargo update`. +# This way, GitHub Actions works and nobody's builds get broken. +inkwell = { git = "https://github.com/rtfeldman/inkwell", tag = "llvm8-0.release1" } target-lexicon = "0.10" # NOTE: we must use the same version of target-lexicon as cranelift! cranelift = "0.59" # All cranelift crates should have the same version! cranelift-simplejit = "0.59" # All cranelift crates should have the same version! From 2bad39e8b95a48ec4e698bef709e51b5e16bc04f Mon Sep 17 00:00:00 2001 From: Folkert Date: Mon, 9 Mar 2020 23:40:18 +0100 Subject: [PATCH 012/428] convert enum tag unions to mono --- compiler/mono/src/expr.rs | 19 ++++++++ compiler/mono/src/layout.rs | 40 ++++++++++++++++- compiler/mono/tests/test_mono.rs | 74 +++++++++++++++++++++++++++++--- 3 files changed, 124 insertions(+), 9 deletions(-) diff --git a/compiler/mono/src/expr.rs b/compiler/mono/src/expr.rs index 90a07c791f..d7d8940339 100644 --- a/compiler/mono/src/expr.rs +++ b/compiler/mono/src/expr.rs @@ -353,6 +353,25 @@ fn from_can<'a>( elems: elems.into_bump_slice(), } } + + Tag { + variant_var, + ext_var, + name, + arguments, + } => { + let subs = &env.subs; + let arena = env.arena; + + match Layout::from_var(arena, variant_var, subs) { + Ok(Layout::Builtin(Builtin::Bool(_smaller, larger))) => Expr::Bool(name == larger), + Ok(Layout::Builtin(Builtin::Byte(tags))) => match tags.get(&name) { + Some(v) => Expr::Byte(*v), + None => panic!("Tag name is not part of the type"), + }, + _ => panic!(), + } + } other => panic!("TODO convert canonicalized {:?} to ll::Expr", other), } } diff --git a/compiler/mono/src/layout.rs b/compiler/mono/src/layout.rs index ed2aa49fee..180cf39a7f 100644 --- a/compiler/mono/src/layout.rs +++ b/compiler/mono/src/layout.rs @@ -19,6 +19,8 @@ pub enum Layout<'a> { pub enum Builtin<'a> { Int64, Float64, + Bool(TagName, TagName), + Byte(MutMap), Str, Map(&'a Layout<'a>, &'a Layout<'a>), Set(&'a Layout<'a>), @@ -81,6 +83,8 @@ impl<'a> Layout<'a> { impl<'a> Builtin<'a> { const I64_SIZE: u32 = std::mem::size_of::() as u32; const F64_SIZE: u32 = std::mem::size_of::() as u32; + const BOOL_SIZE: u32 = std::mem::size_of::() as u32; + const BYTE_SIZE: u32 = std::mem::size_of::() as u32; /// Number of machine words in an empty one of these const STR_WORDS: u32 = 3; @@ -94,6 +98,8 @@ impl<'a> Builtin<'a> { match self { Int64 => Builtin::I64_SIZE, Float64 => Builtin::F64_SIZE, + Bool(_, _) => Builtin::BOOL_SIZE, + Byte(_) => Builtin::BYTE_SIZE, Str => Builtin::STR_WORDS * pointer_size, Map(_, _) => Builtin::MAP_WORDS * pointer_size, Set(_) => Builtin::SET_WORDS * pointer_size, @@ -243,7 +249,34 @@ fn layout_from_flat_type<'a>( } } _ => { - panic!("TODO handle a tag union with mutliple tags: {:?}", tags); + // Check if we can turn this tag union into an enum + // TODO rather than the arguments being empty, check whether their layout has size 0. + if tags.len() <= 256 && tags.iter().all(|(_, args)| args.is_empty()) { + if tags.len() <= 2 { + // Up to 2 enum tags can be stored (in theory) in one bit + let mut it = tags.keys(); + let a: TagName = it.next().unwrap().clone(); + let b: TagName = it.next().unwrap().clone(); + + if a < b { + Ok(Layout::Builtin(Builtin::Bool(a, b))) + } else { + Ok(Layout::Builtin(Builtin::Bool(b, a))) + } + } else { + // up to 256 enum tags can be stored in a byte + let mut counter = 0u8; + let mut tag_to_u8 = MutMap::default(); + + for (name, _) in tags { + tag_to_u8.insert(name, counter); + counter += 1; + } + Ok(Layout::Builtin(Builtin::Byte(tag_to_u8))) + } + } else { + panic!("TODO handle a tag union with mutliple tags: {:?}", tags); + } } } } @@ -301,13 +334,15 @@ fn flatten_union( match subs.get_without_compacting(ext_var).content { Structure(EmptyTagUnion) => (), - Structure(TagUnion(new_tags, new_ext_var)) => { + Structure(TagUnion(new_tags, new_ext_var)) + | Structure(RecursiveTagUnion(_, new_tags, new_ext_var)) => { for (tag_name, vars) in new_tags { tags.insert(tag_name, vars); } flatten_union(tags, new_ext_var, subs) } + Alias(_, _, actual) => flatten_union(tags, actual, subs), invalid => { panic!("Compiler error: flatten_union got an ext_var in a tag union that wasn't itself a tag union; instead, it was: {:?}", invalid); } @@ -329,6 +364,7 @@ fn flatten_record(fields: &mut MutMap, ext_var: Variable, s flatten_record(fields, new_ext_var, subs) } + Alias(_, _, actual) => flatten_record(fields, actual, subs), invalid => { panic!("Compiler error: flatten_record encountered an ext_var in a record that wasn't itself a record; instead, it was: {:?}", invalid); } diff --git a/compiler/mono/tests/test_mono.rs b/compiler/mono/tests/test_mono.rs index 9af7867198..4f3c74d893 100644 --- a/compiler/mono/tests/test_mono.rs +++ b/compiler/mono/tests/test_mono.rs @@ -11,10 +11,11 @@ mod helpers; // Test monomorphization #[cfg(test)] mod test_mono { - use crate::helpers::{can_expr, infer_expr, CanExprOut}; + use crate::helpers::{can_expr, infer_expr, test_home, CanExprOut}; use bumpalo::Bump; use roc_collections::all::MutMap; - use roc_module::symbol::Symbol; + use roc_module::ident::TagName::*; + use roc_module::symbol::{Interns, Symbol}; use roc_mono::expr::Expr::{self, *}; use roc_mono::layout::{Builtin, Layout}; use roc_types::subs::Subs; @@ -22,6 +23,13 @@ mod test_mono { // HELPERS fn compiles_to(src: &str, expected: Expr<'_>) { + compiles_to_with_interns(src, |_| expected) + } + + fn compiles_to_with_interns<'a, F>(src: &str, get_expected: F) + where + F: FnOnce(Interns) -> Expr<'a>, + { let arena = Bump::new(); let CanExprOut { loc_expr, @@ -50,7 +58,7 @@ mod test_mono { &mut ident_ids, ); - assert_eq!(mono_expr, expected); + assert_eq!(mono_expr, get_expected(interns)); } #[test] @@ -63,10 +71,62 @@ mod test_mono { compiles_to("0.5", Float(0.5)); } + // #[test] + // fn bool_literal() { + // compiles_to_with_interns( + // r#" + // x : Bool + // x = True + // + // x + // "#, + // |interns| { + // let home = test_home(); + // let var_x = interns.symbol(home, "x".into()); + // + // let stores = [( + // var_x, + // Layout::Builtin(Builtin::Bool(Global("False".into()), Global("True".into()))), + // Bool(true), + // )]; + // + // let load = Load(var_x); + // + // Store(&stores, &load) + // }, + // ); + // } + + // #[test] + // fn two_element_enum() { + // compiles_to( + // r#" + // x : [ Yes, No ] + // x = No + // + // x + // "#, + // Int(32), + // ); + // } + // + // #[test] + // fn three_element_enum() { + // compiles_to( + // r#" + // # this test is brought to you by fruits.com! + // x : [ Apple, Orange, Banana ] + // x = Orange + // + // x + // "#, + // Int(32), + // ); + // } + #[test] fn set_unique_int_list() { - compiles_to( - "List.getUnsafe (List.set [ 12, 9, 7, 3 ] 1 42) 1", + compiles_to("List.getUnsafe (List.set [ 12, 9, 7, 3 ] 1 42) 1", { CallByName( Symbol::LIST_GET_UNSAFE, &vec![ @@ -91,7 +151,7 @@ mod test_mono { ), (Int(1), Layout::Builtin(Builtin::Int64)), ], - ), - ); + ) + }); } } From 5237408b1538c132a2eb1661ea3b14f866ce131f Mon Sep 17 00:00:00 2001 From: Folkert Date: Mon, 9 Mar 2020 23:51:48 +0100 Subject: [PATCH 013/428] do what clippy wants --- compiler/gen/src/crane/convert.rs | 2 ++ compiler/gen/src/llvm/convert.rs | 2 ++ compiler/mono/src/expr.rs | 5 +---- compiler/mono/src/layout.rs | 6 ++---- 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/compiler/gen/src/crane/convert.rs b/compiler/gen/src/crane/convert.rs index 8f0d1451f5..4039fabb68 100644 --- a/compiler/gen/src/crane/convert.rs +++ b/compiler/gen/src/crane/convert.rs @@ -27,6 +27,8 @@ pub fn type_from_layout(cfg: TargetFrontendConfig, layout: &Layout<'_>) -> Type Builtin(builtin) => match builtin { Int64 => types::I64, Float64 => types::F64, + Bool(_, _) => types::B1, + Byte(_) => types::I8, Str | Map(_, _) | Set(_) | List(_) => cfg.pointer_type(), }, } diff --git a/compiler/gen/src/llvm/convert.rs b/compiler/gen/src/llvm/convert.rs index 00b1bd703c..b039348868 100644 --- a/compiler/gen/src/llvm/convert.rs +++ b/compiler/gen/src/llvm/convert.rs @@ -50,6 +50,8 @@ pub fn basic_type_from_layout<'ctx>( Builtin(builtin) => match builtin { Int64 => context.i64_type().as_basic_type_enum(), Float64 => context.f64_type().as_basic_type_enum(), + Bool(_, _) => context.bool_type().as_basic_type_enum(), + Byte(_) => context.i8_type().as_basic_type_enum(), Str => context .i8_type() .ptr_type(AddressSpace::Generic) diff --git a/compiler/mono/src/expr.rs b/compiler/mono/src/expr.rs index d7d8940339..c38f257e01 100644 --- a/compiler/mono/src/expr.rs +++ b/compiler/mono/src/expr.rs @@ -355,10 +355,7 @@ fn from_can<'a>( } Tag { - variant_var, - ext_var, - name, - arguments, + variant_var, name, .. } => { let subs = &env.subs; let arena = env.arena; diff --git a/compiler/mono/src/layout.rs b/compiler/mono/src/layout.rs index 180cf39a7f..f28d69c142 100644 --- a/compiler/mono/src/layout.rs +++ b/compiler/mono/src/layout.rs @@ -265,12 +265,10 @@ fn layout_from_flat_type<'a>( } } else { // up to 256 enum tags can be stored in a byte - let mut counter = 0u8; let mut tag_to_u8 = MutMap::default(); - for (name, _) in tags { - tag_to_u8.insert(name, counter); - counter += 1; + for (counter, (name, _)) in tags.into_iter().enumerate() { + tag_to_u8.insert(name, counter as u8); } Ok(Layout::Builtin(Builtin::Byte(tag_to_u8))) } From 0a8e360e19b71c30e64fe5ae710c04d27e343bd9 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Mon, 9 Mar 2020 22:59:07 -0400 Subject: [PATCH 014/428] Get bool_literal compiling --- compiler/mono/tests/test_mono.rs | 52 +++++++++++++++++--------------- 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/compiler/mono/tests/test_mono.rs b/compiler/mono/tests/test_mono.rs index 4f3c74d893..b89c7994bb 100644 --- a/compiler/mono/tests/test_mono.rs +++ b/compiler/mono/tests/test_mono.rs @@ -71,31 +71,33 @@ mod test_mono { compiles_to("0.5", Float(0.5)); } - // #[test] - // fn bool_literal() { - // compiles_to_with_interns( - // r#" - // x : Bool - // x = True - // - // x - // "#, - // |interns| { - // let home = test_home(); - // let var_x = interns.symbol(home, "x".into()); - // - // let stores = [( - // var_x, - // Layout::Builtin(Builtin::Bool(Global("False".into()), Global("True".into()))), - // Bool(true), - // )]; - // - // let load = Load(var_x); - // - // Store(&stores, &load) - // }, - // ); - // } + #[test] + fn bool_literal() { + let arena = Bump::new(); + + compiles_to_with_interns( + r#" + x : Bool + x = True + + x + "#, + |interns| { + let home = test_home(); + let var_x = interns.symbol(home, "x".into()); + + let stores = [( + var_x, + Layout::Builtin(Builtin::Bool(Global("False".into()), Global("True".into()))), + Bool(true), + )]; + + let load = Load(var_x); + + Store(arena.alloc(stores), arena.alloc(load)) + }, + ); + } // #[test] // fn two_element_enum() { From c7daad8404d60c5263d75df81f736ea5837ac205 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Mon, 9 Mar 2020 23:01:14 -0400 Subject: [PATCH 015/428] Restore home's ident_ids after creating Expr --- compiler/mono/tests/test_mono.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/compiler/mono/tests/test_mono.rs b/compiler/mono/tests/test_mono.rs index b89c7994bb..325f257363 100644 --- a/compiler/mono/tests/test_mono.rs +++ b/compiler/mono/tests/test_mono.rs @@ -58,6 +58,9 @@ mod test_mono { &mut ident_ids, ); + // Put this module's ident_ids back in the interns + interns.all_ident_ids.insert(home, ident_ids); + assert_eq!(mono_expr, get_expected(interns)); } From f556f195d3d21e894f8ff0513a9f77d2116ba7c6 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Mon, 9 Mar 2020 19:46:28 -0400 Subject: [PATCH 016/428] Handle more mono::Expr cases --- compiler/gen/src/crane/convert.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/compiler/gen/src/crane/convert.rs b/compiler/gen/src/crane/convert.rs index 8f0d1451f5..24465d521f 100644 --- a/compiler/gen/src/crane/convert.rs +++ b/compiler/gen/src/crane/convert.rs @@ -27,7 +27,9 @@ pub fn type_from_layout(cfg: TargetFrontendConfig, layout: &Layout<'_>) -> Type Builtin(builtin) => match builtin { Int64 => types::I64, Float64 => types::F64, - Str | Map(_, _) | Set(_) | List(_) => cfg.pointer_type(), + Str | EmptyStr | Map(_, _) | EmptyMap | Set(_) | EmptySet | List(_) | EmptyList => { + cfg.pointer_type() + } }, } } From 3789402a327c4a7743537cd0908bc97cd19520c4 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Tue, 10 Mar 2020 02:56:15 -0400 Subject: [PATCH 017/428] Rename List.length to List.len --- .../load/tests/fixtures/build/interface_with_deps/AStar.roc | 2 +- compiler/solve/tests/test_uniq_solve.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/compiler/load/tests/fixtures/build/interface_with_deps/AStar.roc b/compiler/load/tests/fixtures/build/interface_with_deps/AStar.roc index be06fef3c2..0901f80fe9 100644 --- a/compiler/load/tests/fixtures/build/interface_with_deps/AStar.roc +++ b/compiler/load/tests/fixtures/build/interface_with_deps/AStar.roc @@ -63,7 +63,7 @@ updateCost = \current, neighbour, model -> newCosts = Map.insert model.costs neighbour distanceTo distanceTo = reconstructPath newCameFrom neighbour - |> List.length + |> List.len |> Num.toFloat newModel = { model & costs : newCosts , cameFrom : newCameFrom } diff --git a/compiler/solve/tests/test_uniq_solve.rs b/compiler/solve/tests/test_uniq_solve.rs index 1feccf7e37..3d6dcba690 100644 --- a/compiler/solve/tests/test_uniq_solve.rs +++ b/compiler/solve/tests/test_uniq_solve.rs @@ -2206,7 +2206,7 @@ mod test_uniq_solve { newCosts = Map.insert model.costs neighbour distanceTo distanceTo = reconstructPath newCameFrom neighbour - |> List.length + |> List.len |> Num.toFloat newModel = { model & costs : newCosts , cameFrom : newCameFrom } @@ -2291,7 +2291,7 @@ mod test_uniq_solve { newCosts = Map.insert model.costs neighbour distanceTo distanceTo = reconstructPath newCameFrom neighbour - |> List.length + |> List.len |> Num.toFloat newModel = { model & costs : newCosts , cameFrom : newCameFrom } From 8450597a0711d716059bd48d1728952e882238d4 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Tue, 10 Mar 2020 02:26:07 -0400 Subject: [PATCH 018/428] Try an alloca approach for structs --- compiler/builtins/src/std.rs | 6 +- compiler/builtins/src/unique.rs | 6 +- compiler/gen/src/llvm/build.rs | 150 ++++++++++++++++++++++++------- compiler/gen/src/llvm/convert.rs | 14 ++- compiler/gen/tests/test_gen.rs | 13 ++- compiler/module/src/symbol.rs | 4 +- compiler/uniq/src/sharing.rs | 2 +- 7 files changed, 153 insertions(+), 42 deletions(-) diff --git a/compiler/builtins/src/std.rs b/compiler/builtins/src/std.rs index 26cb4074c5..29ee1285b3 100644 --- a/compiler/builtins/src/std.rs +++ b/compiler/builtins/src/std.rs @@ -367,7 +367,7 @@ pub fn types() -> MutMap { // isEmpty : List * -> Bool add_type( - Symbol::LIST_ISEMPTY, + Symbol::LIST_IS_EMPTY, SolvedType::Func( vec![SolvedType::Apply( Symbol::LIST_LIST, @@ -442,9 +442,9 @@ pub fn types() -> MutMap { ), ); - // length : List a -> Int + // len : List * -> Int add_type( - Symbol::LIST_LENGTH, + Symbol::LIST_LEN, SolvedType::Func(vec![list_type(flex(TVAR1))], Box::new(int_type())), ); diff --git a/compiler/builtins/src/unique.rs b/compiler/builtins/src/unique.rs index 199bd14eda..d755b33f50 100644 --- a/compiler/builtins/src/unique.rs +++ b/compiler/builtins/src/unique.rs @@ -434,13 +434,13 @@ pub fn types() -> MutMap { // isEmpty : Attr u (List *) -> Attr v Bool add_type( - Symbol::LIST_ISEMPTY, + Symbol::LIST_IS_EMPTY, unique_function(vec![list_type(UVAR1, TVAR1)], bool_type(UVAR2)), ); - // length : List a -> Int + // len : List * -> Int add_type( - Symbol::LIST_LENGTH, + Symbol::LIST_LEN, unique_function(vec![list_type(UVAR1, TVAR1)], int_type(UVAR2)), ); diff --git a/compiler/gen/src/llvm/build.rs b/compiler/gen/src/llvm/build.rs index 6dbfc17981..898b81bce4 100644 --- a/compiler/gen/src/llvm/build.rs +++ b/compiler/gen/src/llvm/build.rs @@ -3,12 +3,12 @@ use bumpalo::Bump; use inkwell::builder::Builder; use inkwell::context::Context; use inkwell::module::{Linkage, Module}; -use inkwell::types::BasicTypeEnum; +use inkwell::types::{BasicTypeEnum, PointerType, StructType}; use inkwell::values::BasicValueEnum::{self, *}; use inkwell::values::{FunctionValue, IntValue, PointerValue}; -use inkwell::{FloatPredicate, IntPredicate}; +use inkwell::{AddressSpace, FloatPredicate, IntPredicate}; -use crate::llvm::convert::{basic_type_from_layout, get_fn_type}; +use crate::llvm::convert::{basic_type_from_layout, get_array_type, get_fn_type}; use roc_collections::all::ImMap; use roc_module::symbol::{Interns, Symbol}; use roc_mono::expr::{Expr, Proc, Procs}; @@ -206,24 +206,34 @@ pub fn build_expr<'a, 'ctx, 'env>( } } Array { elem_layout, elems } => { + let ctx = env.context; + let elem_type = basic_type_from_layout(ctx, elem_layout); + if elems.is_empty() { - panic!("TODO build an empty string in LLVM"); + let array_type = get_array_type(&elem_type, 0); + let ptr_type = array_type.ptr_type(AddressSpace::Generic); + let struct_type = array_wrapper(ctx, ptr_type); + let zero = BasicValueEnum::IntValue(ctx.i32_type().const_zero()); + let val = struct_type.const_named_struct(&[ + BasicValueEnum::PointerValue(ptr_type.const_null()), // pointer + zero, // length + zero, // capacity + ]); + + BasicValueEnum::StructValue(val) } else { - let elem_bytes = elem_layout.stack_size(env.pointer_bytes) as u64; - let bytes_len = elem_bytes * (elems.len() + 1) as u64/* TODO drop the +1 when we have structs and this is no longer a NUL-terminated CString.*/; - - let ctx = env.context; let builder = env.builder; + let len_u64 = elems.len() as u64; + let elem_bytes = elem_layout.stack_size(env.pointer_bytes) as u64; + let bytes_len = elem_bytes * len_u64; - let elem_type = basic_type_from_layout(ctx, elem_layout); - let nul_terminator = elem_type.into_int_type().const_zero(); let len = ctx.i32_type().const_int(bytes_len, false); let ptr = env .builder - .build_array_malloc(elem_type, len, "str_ptr") + .build_array_malloc(elem_type, len, "create_list_ptr") .unwrap(); - // Copy the bytes from the string literal into the array + // Copy the elements from the list literal into the array for (index, elem) in elems.iter().enumerate() { let offset = ctx.i32_type().const_int(elem_bytes * index as u64, false); let elem_ptr = unsafe { builder.build_gep(ptr, &[offset], "elem") }; @@ -233,15 +243,32 @@ pub fn build_expr<'a, 'ctx, 'env>( builder.build_store(elem_ptr, val); } - // Add a NUL terminator at the end. - // TODO: Instead of NUL-terminating, return a struct - // with the pointer and also the length and capacity. - let index = ctx.i32_type().const_int(bytes_len as u64 - 1, false); - let elem_ptr = unsafe { builder.build_gep(ptr, &[index], "nul_terminator") }; + let struct_type = array_wrapper(ctx, ptr.get_type()); + let len = BasicValueEnum::IntValue(ctx.i32_type().const_int(len_u64, false)); + let tuple_ptr = builder.build_alloca(struct_type, "create_list_tuple"); - builder.build_store(elem_ptr, nul_terminator); + { + let field_ptr = + unsafe { builder.build_struct_gep(tuple_ptr, 0, "list_tuple_ptr") }; - BasicValueEnum::PointerValue(ptr) + builder.build_store(field_ptr, BasicValueEnum::PointerValue(ptr)); + } + + { + let field_ptr = + unsafe { builder.build_struct_gep(tuple_ptr, 1, "list_tuple_len") }; + + builder.build_store(field_ptr, len); + } + + { + let field_ptr = + unsafe { builder.build_struct_gep(tuple_ptr, 2, "list_tuple_capacity") }; + + builder.build_store(field_ptr, len); + } + + BasicValueEnum::PointerValue(tuple_ptr) } } _ => { @@ -578,38 +605,91 @@ fn call_with_args<'a, 'ctx, 'env>( BasicValueEnum::IntValue(int_val) } + Symbol::LIST_LEN => { + debug_assert!(args.len() == 1); + + let tuple_ptr = args[0].into_pointer_value(); + let builder = env.builder; + let list_len_ptr = unsafe { builder.build_struct_gep(tuple_ptr, 1, "list_tuple_len") }; + + // Get the 32-bit int length and cast it to a 64-bit int + let i32_val = builder.build_load(list_len_ptr, "List.len").into_int_value(); + + BasicValueEnum::IntValue(builder.build_int_cast(i32_val, env.context.i64_type(), "i32_to_i64")) + } + Symbol::LIST_IS_EMPTY => { + debug_assert!(args.len() == 1); + + let list_struct = args[0].into_struct_value(); + let builder = env.builder; + let list_len = builder.build_extract_value(list_struct, 1, "unwrapped_list_len").unwrap().into_int_value(); + let zero = env.context.i32_type().const_zero(); + let answer = builder.build_int_compare(IntPredicate::EQ, list_len, zero, "is_zero"); + + BasicValueEnum::IntValue(answer) + } Symbol::LIST_GET_UNSAFE => { + let builder = env.builder; + + // List.get : List elem, Int -> Result elem [ OutOfBounds ]* debug_assert!(args.len() == 2); - let list_ptr = args[0].into_pointer_value(); + let tuple_ptr = args[0].into_pointer_value(); let elem_index = args[1].into_int_value(); - let builder = env.builder; - let elem_bytes = 8; // TODO Look this up instead of hardcoding it! - let elem_size = env.context.i64_type().const_int(elem_bytes, false); - let offset = builder.build_int_mul(elem_index, elem_size, "MUL_OFFSET"); + // Slot 1 in the array is the length + let _list_len = unsafe { builder.build_struct_gep(tuple_ptr, 1, "list_tuple_len") }; - let elem_ptr = unsafe { builder.build_gep(list_ptr, &[offset], "elem") }; + // TODO here, check to see if the requested index exceeds the length of the array. + + // Slot 0 in the tuple struct is the pointer to the array data + let array_ptr_field = unsafe { builder.build_struct_gep(tuple_ptr, 0, "list_tuple_ptr") }; + let array_data_ptr = builder.build_load(array_ptr_field, "get_array_data").into_pointer_value(); + + let elem_bytes = 8; // TODO Look this size up instead of hardcoding it! + let elem_size = env.context.i64_type().const_int(elem_bytes, false); + + // Calculate the offset at runtime by multiplying the index by the size of an element. + let offset_bytes = builder.build_int_mul(elem_index, elem_size, "mul_offset"); + + // We already checked the bounds earlier. + let elem_ptr = unsafe { builder.build_in_bounds_gep(array_data_ptr, &[offset_bytes], "elem") }; builder.build_load(elem_ptr, "List.get") } Symbol::LIST_SET /* TODO clone first for LIST_SET! */ | Symbol::LIST_SET_IN_PLACE => { + let builder = env.builder; + debug_assert!(args.len() == 3); - let list_ptr = args[0].into_pointer_value(); + let tuple_ptr = args[0].into_pointer_value(); let elem_index = args[1].into_int_value(); let elem = args[2]; - let builder = env.builder; - let elem_bytes = 8; // TODO Look this up instead of hardcoding it! + // Slot 1 in the array is the length + let _list_len = unsafe { builder.build_struct_gep(tuple_ptr, 1, "list_tuple_len") }; + + // TODO here, check to see if the requested index exceeds the length of the array. + // If so, bail out and return the list unaltered. + + // Slot 0 in the tuple struct is the pointer to the array data + let array_ptr_field = unsafe { builder.build_struct_gep(tuple_ptr, 0, "list_tuple_ptr") }; + let array_data_ptr = builder.build_load(array_ptr_field, "get_array_data").into_pointer_value(); + + let elem_bytes = 8; // TODO Look this size up instead of hardcoding it! let elem_size = env.context.i64_type().const_int(elem_bytes, false); - let offset = builder.build_int_mul(elem_index, elem_size, "MUL_OFFSET"); - let elem_ptr = unsafe { builder.build_gep(list_ptr, &[offset], "elem") }; + // Calculate the offset at runtime by multiplying the index by the size of an element. + let offset_bytes = builder.build_int_mul(elem_index, elem_size, "mul_offset"); + // We already checked the bounds earlier. + let elem_ptr = unsafe { builder.build_in_bounds_gep(array_data_ptr, &[offset_bytes], "elem") }; + + // Mutate the array in-place. builder.build_store(elem_ptr, elem); - list_ptr.into() + // Return a pointer to the wrapper tuple. + tuple_ptr.into() } _ => { let fn_val = env @@ -625,3 +705,11 @@ fn call_with_args<'a, 'ctx, 'env>( } } } + +/// (pointer: usize, length: u32, capacity: u32) +fn array_wrapper<'ctx>(ctx: &'ctx Context, ptr_type: PointerType<'ctx>) -> StructType<'ctx> { + let ptr_type_enum = BasicTypeEnum::PointerType(ptr_type); + let u32_type = BasicTypeEnum::IntType(ctx.i32_type()); + + ctx.struct_type(&[ptr_type_enum, u32_type, u32_type], false) +} diff --git a/compiler/gen/src/llvm/convert.rs b/compiler/gen/src/llvm/convert.rs index 00b1bd703c..71363f3b5e 100644 --- a/compiler/gen/src/llvm/convert.rs +++ b/compiler/gen/src/llvm/convert.rs @@ -1,6 +1,6 @@ use inkwell::context::Context; use inkwell::types::BasicTypeEnum::{self, *}; -use inkwell::types::{BasicType, FunctionType}; +use inkwell::types::{ArrayType, BasicType, FunctionType}; use inkwell::AddressSpace; use roc_mono::layout::Layout; @@ -20,6 +20,18 @@ pub fn get_fn_type<'ctx>( } } +/// TODO could this be added to Inkwell itself as a method on BasicValueEnum? +pub fn get_array_type<'ctx>(bt_enum: &BasicTypeEnum<'ctx>, size: u32) -> ArrayType<'ctx> { + match bt_enum { + ArrayType(typ) => typ.array_type(size), + IntType(typ) => typ.array_type(size), + FloatType(typ) => typ.array_type(size), + PointerType(typ) => typ.array_type(size), + StructType(typ) => typ.array_type(size), + VectorType(typ) => typ.array_type(size), + } +} + pub fn basic_type_from_layout<'ctx>( context: &'ctx Context, layout: &Layout<'_>, diff --git a/compiler/gen/tests/test_gen.rs b/compiler/gen/tests/test_gen.rs index ab6953fa74..9a31766a86 100644 --- a/compiler/gen/tests/test_gen.rs +++ b/compiler/gen/tests/test_gen.rs @@ -274,7 +274,7 @@ mod test_gen { builder.build_return(Some(&ret)); // Uncomment this to see the module's un-optimized LLVM instruction output: - // env.module.print_to_stderr(); + env.module.print_to_stderr(); if main_fn.verify(true) { fpm.run_on(&main_fn); @@ -482,6 +482,17 @@ mod test_gen { assert_evals_to!("1234.0", 1234.0, f64); } + #[test] + fn int_list_len() { + // assert_evals_to!("List.len [ 12, 9, 6, 3 ]", 4, i64); + assert_llvm_evals_to!("List.len [ 12, 9, 6, 3 ]", 4, i64, |x| x); + } + + // #[test] + // fn int_list_is_empty() { + // assert_evals_to!("List.is_empty [ 12, 9, 6, 3 ]", 0, i32, |x| x); + // } + #[test] fn get_int_list() { assert_evals_to!("List.getUnsafe [ 12, 9, 6, 3 ] 1", 9, i64); diff --git a/compiler/module/src/symbol.rs b/compiler/module/src/symbol.rs index dea7b585d6..9a119e3c78 100644 --- a/compiler/module/src/symbol.rs +++ b/compiler/module/src/symbol.rs @@ -603,13 +603,13 @@ define_builtins! { 6 LIST: "List" => { 0 LIST_LIST: "List" imported // the List.List type alias 1 LIST_AT_LIST: "@List" // the List.@List private tag - 2 LIST_ISEMPTY: "isEmpty" + 2 LIST_IS_EMPTY: "isEmpty" 3 LIST_GET: "get" 4 LIST_SET: "set" 5 LIST_SET_IN_PLACE: "set_in_place" 6 LIST_PUSH: "push" 7 LIST_MAP: "map" - 8 LIST_LENGTH: "length" + 8 LIST_LEN: "len" 9 LIST_FOLDL: "foldl" 10 LIST_FOLDR: "foldr" 11 LIST_GET_UNSAFE: "getUnsafe" // TODO remove once we can code gen Result diff --git a/compiler/uniq/src/sharing.rs b/compiler/uniq/src/sharing.rs index a4eead5afe..46ea47507e 100644 --- a/compiler/uniq/src/sharing.rs +++ b/compiler/uniq/src/sharing.rs @@ -734,7 +734,7 @@ fn special_case_builtins( annotate_usage(&loc_value.value, usage); } - Symbol::LIST_ISEMPTY => { + Symbol::LIST_IS_EMPTY | Symbol::LIST_LEN => { debug_assert!(loc_args.len() == 1); let loc_list = &loc_args[0].1; From 8da7f262375ab4992283a21437b8944effbe069c Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Tue, 10 Mar 2020 02:25:34 -0400 Subject: [PATCH 019/428] Try a different Struct approach --- compiler/gen/src/llvm/build.rs | 97 ++++++++++++++++---------------- compiler/gen/src/llvm/convert.rs | 35 +++++++++--- compiler/gen/tests/test_gen.rs | 6 ++ compiler/mono/src/layout.rs | 23 ++++++-- 4 files changed, 98 insertions(+), 63 deletions(-) diff --git a/compiler/gen/src/llvm/build.rs b/compiler/gen/src/llvm/build.rs index 898b81bce4..04a9ec0eee 100644 --- a/compiler/gen/src/llvm/build.rs +++ b/compiler/gen/src/llvm/build.rs @@ -3,12 +3,14 @@ use bumpalo::Bump; use inkwell::builder::Builder; use inkwell::context::Context; use inkwell::module::{Linkage, Module}; -use inkwell::types::{BasicTypeEnum, PointerType, StructType}; +use inkwell::types::BasicTypeEnum; use inkwell::values::BasicValueEnum::{self, *}; use inkwell::values::{FunctionValue, IntValue, PointerValue}; use inkwell::{AddressSpace, FloatPredicate, IntPredicate}; -use crate::llvm::convert::{basic_type_from_layout, get_array_type, get_fn_type}; +use crate::llvm::convert::{ + basic_type_from_layout, collection_wrapper, get_array_type, get_fn_type, +}; use roc_collections::all::ImMap; use roc_module::symbol::{Interns, Symbol}; use roc_mono::expr::{Expr, Proc, Procs}; @@ -208,30 +210,37 @@ pub fn build_expr<'a, 'ctx, 'env>( Array { elem_layout, elems } => { let ctx = env.context; let elem_type = basic_type_from_layout(ctx, elem_layout); + let builder = env.builder; if elems.is_empty() { let array_type = get_array_type(&elem_type, 0); let ptr_type = array_type.ptr_type(AddressSpace::Generic); - let struct_type = array_wrapper(ctx, ptr_type); - let zero = BasicValueEnum::IntValue(ctx.i32_type().const_zero()); - let val = struct_type.const_named_struct(&[ - BasicValueEnum::PointerValue(ptr_type.const_null()), // pointer - zero, // length - zero, // capacity - ]); + let struct_type = collection_wrapper(ctx, ptr_type); + let struct_val = struct_type.const_zero(); - BasicValueEnum::StructValue(val) + // The first field in the struct should be the pointer. + builder + .build_insert_value( + struct_val, + BasicValueEnum::PointerValue(ptr_type.const_null()), + 0, + "insert_ptr", + ) + .unwrap(); + + BasicValueEnum::StructValue(struct_val) } else { - let builder = env.builder; let len_u64 = elems.len() as u64; let elem_bytes = elem_layout.stack_size(env.pointer_bytes) as u64; - let bytes_len = elem_bytes * len_u64; - let len = ctx.i32_type().const_int(bytes_len, false); - let ptr = env - .builder - .build_array_malloc(elem_type, len, "create_list_ptr") - .unwrap(); + let ptr = { + let bytes_len = elem_bytes * len_u64; + let len = ctx.i32_type().const_int(bytes_len, false); + + env.builder + .build_array_malloc(elem_type, len, "create_list_ptr") + .unwrap() + }; // Copy the elements from the list literal into the array for (index, elem) in elems.iter().enumerate() { @@ -243,32 +252,29 @@ pub fn build_expr<'a, 'ctx, 'env>( builder.build_store(elem_ptr, val); } - let struct_type = array_wrapper(ctx, ptr.get_type()); - let len = BasicValueEnum::IntValue(ctx.i32_type().const_int(len_u64, false)); - let tuple_ptr = builder.build_alloca(struct_type, "create_list_tuple"); + let ptr_val = BasicValueEnum::PointerValue(ptr); + let struct_type = collection_wrapper(ctx, ptr.get_type()); + let len = dbg!(BasicValueEnum::IntValue( + ctx.i32_type().const_int(len_u64, false) + )); + let struct_val = struct_type.const_zero(); - { - let field_ptr = - unsafe { builder.build_struct_gep(tuple_ptr, 0, "list_tuple_ptr") }; + // Field 0: pointer + builder + .build_insert_value(struct_val, ptr_val, 0, "insert_ptr") + .unwrap(); - builder.build_store(field_ptr, BasicValueEnum::PointerValue(ptr)); - } + // Field 1: length + builder + .build_insert_value(struct_val, len, 1, "insert_len") + .unwrap(); - { - let field_ptr = - unsafe { builder.build_struct_gep(tuple_ptr, 1, "list_tuple_len") }; + // Field 2: capacity (initially set to length) + builder + .build_insert_value(struct_val, len, 2, "insert_capacity") + .unwrap(); - builder.build_store(field_ptr, len); - } - - { - let field_ptr = - unsafe { builder.build_struct_gep(tuple_ptr, 2, "list_tuple_capacity") }; - - builder.build_store(field_ptr, len); - } - - BasicValueEnum::PointerValue(tuple_ptr) + BasicValueEnum::StructValue(struct_val) } } _ => { @@ -608,12 +614,11 @@ fn call_with_args<'a, 'ctx, 'env>( Symbol::LIST_LEN => { debug_assert!(args.len() == 1); - let tuple_ptr = args[0].into_pointer_value(); + let tuple_struct = dbg!(args[0].into_struct_value()); let builder = env.builder; - let list_len_ptr = unsafe { builder.build_struct_gep(tuple_ptr, 1, "list_tuple_len") }; // Get the 32-bit int length and cast it to a 64-bit int - let i32_val = builder.build_load(list_len_ptr, "List.len").into_int_value(); + let i32_val = dbg!(builder.build_extract_value(tuple_struct, 1, "unwrapped_list_len").unwrap().into_int_value()); BasicValueEnum::IntValue(builder.build_int_cast(i32_val, env.context.i64_type(), "i32_to_i64")) } @@ -705,11 +710,3 @@ fn call_with_args<'a, 'ctx, 'env>( } } } - -/// (pointer: usize, length: u32, capacity: u32) -fn array_wrapper<'ctx>(ctx: &'ctx Context, ptr_type: PointerType<'ctx>) -> StructType<'ctx> { - let ptr_type_enum = BasicTypeEnum::PointerType(ptr_type); - let u32_type = BasicTypeEnum::IntType(ctx.i32_type()); - - ctx.struct_type(&[ptr_type_enum, u32_type, u32_type], false) -} diff --git a/compiler/gen/src/llvm/convert.rs b/compiler/gen/src/llvm/convert.rs index 71363f3b5e..38a7891dc9 100644 --- a/compiler/gen/src/llvm/convert.rs +++ b/compiler/gen/src/llvm/convert.rs @@ -1,6 +1,6 @@ use inkwell::context::Context; use inkwell::types::BasicTypeEnum::{self, *}; -use inkwell::types::{ArrayType, BasicType, FunctionType}; +use inkwell::types::{ArrayType, BasicType, FunctionType, PointerType, StructType}; use inkwell::AddressSpace; use roc_mono::layout::Layout; @@ -62,15 +62,36 @@ pub fn basic_type_from_layout<'ctx>( Builtin(builtin) => match builtin { Int64 => context.i64_type().as_basic_type_enum(), Float64 => context.f64_type().as_basic_type_enum(), - Str => context + Str | EmptyStr => context .i8_type() .ptr_type(AddressSpace::Generic) .as_basic_type_enum(), - Map(_, _) => panic!("TODO layout_to_basic_type for Builtin::Map"), - Set(_) => panic!("TODO layout_to_basic_type for Builtin::Set"), - List(elem_layout) => basic_type_from_layout(context, elem_layout) - .ptr_type(AddressSpace::Generic) - .as_basic_type_enum(), + Map(_, _) | EmptyMap => panic!("TODO layout_to_basic_type for Builtin::Map"), + Set(_) | EmptySet => panic!("TODO layout_to_basic_type for Builtin::Set"), + List(elem_layout) => { + let ptr_type = + basic_type_from_layout(context, elem_layout).ptr_type(AddressSpace::Generic); + + collection_wrapper(context, ptr_type).into() + } + EmptyList => { + let array_type = + get_array_type(&context.opaque_struct_type("empty_list_elem").into(), 0); + let ptr_type = array_type.ptr_type(AddressSpace::Generic); + + collection_wrapper(context, ptr_type).into() + } }, } } + +/// (pointer: usize, length: u32, capacity: u32) +pub fn collection_wrapper<'ctx>( + ctx: &'ctx Context, + ptr_type: PointerType<'ctx>, +) -> StructType<'ctx> { + let ptr_type_enum = BasicTypeEnum::PointerType(ptr_type); + let u32_type = BasicTypeEnum::IntType(ctx.i32_type()); + + ctx.struct_type(&[ptr_type_enum, u32_type, u32_type], false) +} diff --git a/compiler/gen/tests/test_gen.rs b/compiler/gen/tests/test_gen.rs index 9a31766a86..dda852a5d7 100644 --- a/compiler/gen/tests/test_gen.rs +++ b/compiler/gen/tests/test_gen.rs @@ -482,6 +482,12 @@ mod test_gen { assert_evals_to!("1234.0", 1234.0, f64); } + #[test] + fn empty_list_len() { + // assert_evals_to!("List.len [ 12, 9, 6, 3 ]", 4, i64); + assert_llvm_evals_to!("List.len []", 0, i64, |x| x); + } + #[test] fn int_list_len() { // assert_evals_to!("List.len [ 12, 9, 6, 3 ]", 4, i64); diff --git a/compiler/mono/src/layout.rs b/compiler/mono/src/layout.rs index ed2aa49fee..b56a5a6b4c 100644 --- a/compiler/mono/src/layout.rs +++ b/compiler/mono/src/layout.rs @@ -23,6 +23,10 @@ pub enum Builtin<'a> { Map(&'a Layout<'a>, &'a Layout<'a>), Set(&'a Layout<'a>), List(&'a Layout<'a>), + EmptyStr, + EmptyList, + EmptyMap, + EmptySet, } impl<'a> Layout<'a> { @@ -94,10 +98,10 @@ impl<'a> Builtin<'a> { match self { Int64 => Builtin::I64_SIZE, Float64 => Builtin::F64_SIZE, - Str => Builtin::STR_WORDS * pointer_size, - Map(_, _) => Builtin::MAP_WORDS * pointer_size, - Set(_) => Builtin::SET_WORDS * pointer_size, - List(_) => Builtin::LIST_WORDS * pointer_size, + Str | EmptyStr => Builtin::STR_WORDS * pointer_size, + Map(_, _) | EmptyMap => Builtin::MAP_WORDS * pointer_size, + Set(_) | EmptySet => Builtin::SET_WORDS * pointer_size, + List(_) | EmptyList => Builtin::LIST_WORDS * pointer_size, } } } @@ -131,9 +135,16 @@ fn layout_from_flat_type<'a>( } Symbol::STR_STR => Ok(Layout::Builtin(Builtin::Str)), Symbol::LIST_LIST => { - let elem_layout = Layout::from_var(arena, args[0], subs)?; + use roc_types::subs::Content::*; - Ok(Layout::Builtin(Builtin::List(arena.alloc(elem_layout)))) + match subs.get_without_compacting(args[0]).content { + FlexVar(_) | RigidVar(_) => Ok(Layout::Builtin(Builtin::EmptyList)), + content => { + let elem_layout = Layout::from_content(arena, content, subs)?; + + Ok(Layout::Builtin(Builtin::List(arena.alloc(elem_layout)))) + } + } } Symbol::ATTR_ATTR => { debug_assert!(args.len() == 2); From 913f18226bf6e47ad39a0e8571dd9b684932d371 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Tue, 10 Mar 2020 02:40:08 -0400 Subject: [PATCH 020/428] Got LLVM structs working --- compiler/gen/src/llvm/build.rs | 19 ++++++++--------- compiler/gen/tests/test_gen.rs | 38 +++++++++++++++++++++++++++++++++- 2 files changed, 46 insertions(+), 11 deletions(-) diff --git a/compiler/gen/src/llvm/build.rs b/compiler/gen/src/llvm/build.rs index 04a9ec0eee..5adb87e925 100644 --- a/compiler/gen/src/llvm/build.rs +++ b/compiler/gen/src/llvm/build.rs @@ -216,19 +216,18 @@ pub fn build_expr<'a, 'ctx, 'env>( let array_type = get_array_type(&elem_type, 0); let ptr_type = array_type.ptr_type(AddressSpace::Generic); let struct_type = collection_wrapper(ctx, ptr_type); - let struct_val = struct_type.const_zero(); // The first field in the struct should be the pointer. - builder + let struct_val = builder .build_insert_value( - struct_val, + struct_type.const_zero(), BasicValueEnum::PointerValue(ptr_type.const_null()), 0, "insert_ptr", ) .unwrap(); - BasicValueEnum::StructValue(struct_val) + BasicValueEnum::StructValue(struct_val.into_struct_value()) } else { let len_u64 = elems.len() as u64; let elem_bytes = elem_layout.stack_size(env.pointer_bytes) as u64; @@ -257,24 +256,24 @@ pub fn build_expr<'a, 'ctx, 'env>( let len = dbg!(BasicValueEnum::IntValue( ctx.i32_type().const_int(len_u64, false) )); - let struct_val = struct_type.const_zero(); + let mut struct_val; // Field 0: pointer - builder - .build_insert_value(struct_val, ptr_val, 0, "insert_ptr") + struct_val = builder + .build_insert_value(struct_type.const_zero(), ptr_val, 0, "insert_ptr") .unwrap(); // Field 1: length - builder + struct_val = builder .build_insert_value(struct_val, len, 1, "insert_len") .unwrap(); // Field 2: capacity (initially set to length) - builder + struct_val = builder .build_insert_value(struct_val, len, 2, "insert_capacity") .unwrap(); - BasicValueEnum::StructValue(struct_val) + BasicValueEnum::StructValue(struct_val.into_struct_value()) } } _ => { diff --git a/compiler/gen/tests/test_gen.rs b/compiler/gen/tests/test_gen.rs index dda852a5d7..bb24825d50 100644 --- a/compiler/gen/tests/test_gen.rs +++ b/compiler/gen/tests/test_gen.rs @@ -494,6 +494,42 @@ mod test_gen { assert_llvm_evals_to!("List.len [ 12, 9, 6, 3 ]", 4, i64, |x| x); } + #[test] + fn loaded_int_list_len() { + assert_llvm_evals_to!( + indoc!( + r#" + nums = [ 2, 4, 6 ] + + List.len nums + "# + ), + 3, + i64, + |x| x + ); + } + + #[test] + fn fn_int_list_len() { + assert_llvm_evals_to!( + indoc!( + r#" + # TODO remove this annotation once monomorphization works! + getLen : List Int -> Int + getLen = \list -> List.len list + + nums = [ 2, 4, 6 ] + + getLen nums + "# + ), + 3, + i64, + |x| x + ); + } + // #[test] // fn int_list_is_empty() { // assert_evals_to!("List.is_empty [ 12, 9, 6, 3 ]", 0, i32, |x| x); @@ -510,7 +546,7 @@ mod test_gen { } #[test] - fn set_shared_int_list() { + fn get_shared_int_list() { assert_evals_to!( indoc!( r#" From 21d6d1cbd9009edf80abf4ab51e1b184c1a14347 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Tue, 10 Mar 2020 02:43:53 -0400 Subject: [PATCH 021/428] Disable empty_list_len for now --- compiler/gen/tests/test_gen.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/compiler/gen/tests/test_gen.rs b/compiler/gen/tests/test_gen.rs index bb24825d50..c3dd62907a 100644 --- a/compiler/gen/tests/test_gen.rs +++ b/compiler/gen/tests/test_gen.rs @@ -482,11 +482,10 @@ mod test_gen { assert_evals_to!("1234.0", 1234.0, f64); } - #[test] - fn empty_list_len() { - // assert_evals_to!("List.len [ 12, 9, 6, 3 ]", 4, i64); - assert_llvm_evals_to!("List.len []", 0, i64, |x| x); - } + // #[test] + // fn empty_list_len() { + // assert_evals_to!("List.len []", 0, i64); + // } #[test] fn int_list_len() { From 28b50c00c00b5d159d3ec891789dd76cbf958b47 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Tue, 10 Mar 2020 02:46:51 -0400 Subject: [PATCH 022/428] Clean up some comments --- compiler/gen/src/llvm/build.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/compiler/gen/src/llvm/build.rs b/compiler/gen/src/llvm/build.rs index 5adb87e925..038ae3a669 100644 --- a/compiler/gen/src/llvm/build.rs +++ b/compiler/gen/src/llvm/build.rs @@ -613,12 +613,13 @@ fn call_with_args<'a, 'ctx, 'env>( Symbol::LIST_LEN => { debug_assert!(args.len() == 1); - let tuple_struct = dbg!(args[0].into_struct_value()); + let tuple_struct = args[0].into_struct_value(); let builder = env.builder; - // Get the 32-bit int length and cast it to a 64-bit int - let i32_val = dbg!(builder.build_extract_value(tuple_struct, 1, "unwrapped_list_len").unwrap().into_int_value()); + // Get the 32-bit int length + let i32_val = builder.build_extract_value(tuple_struct, 1, "unwrapped_list_len").unwrap().into_int_value(); + // cast the 32-bit length to a 64-bit int BasicValueEnum::IntValue(builder.build_int_cast(i32_val, env.context.i64_type(), "i32_to_i64")) } Symbol::LIST_IS_EMPTY => { From 8da2bebcdeca6ef3422b80842da37f2c1d5f4749 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Tue, 10 Mar 2020 02:49:49 -0400 Subject: [PATCH 023/428] Remove some dbg! statements --- compiler/gen/src/llvm/build.rs | 10 ++++------ compiler/gen/tests/test_gen.rs | 2 +- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/compiler/gen/src/llvm/build.rs b/compiler/gen/src/llvm/build.rs index 038ae3a669..a4e23ebffd 100644 --- a/compiler/gen/src/llvm/build.rs +++ b/compiler/gen/src/llvm/build.rs @@ -253,9 +253,7 @@ pub fn build_expr<'a, 'ctx, 'env>( let ptr_val = BasicValueEnum::PointerValue(ptr); let struct_type = collection_wrapper(ctx, ptr.get_type()); - let len = dbg!(BasicValueEnum::IntValue( - ctx.i32_type().const_int(len_u64, false) - )); + let len = BasicValueEnum::IntValue(ctx.i32_type().const_int(len_u64, false)); let mut struct_val; // Field 0: pointer @@ -639,16 +637,16 @@ fn call_with_args<'a, 'ctx, 'env>( // List.get : List elem, Int -> Result elem [ OutOfBounds ]* debug_assert!(args.len() == 2); - let tuple_ptr = args[0].into_pointer_value(); + let tuple_struct = args[0].into_struct_value(); let elem_index = args[1].into_int_value(); // Slot 1 in the array is the length - let _list_len = unsafe { builder.build_struct_gep(tuple_ptr, 1, "list_tuple_len") }; + let _list_len = builder.build_extract_value(tuple_struct, 1, "unwrapped_list_len").unwrap().into_int_value(); // TODO here, check to see if the requested index exceeds the length of the array. // Slot 0 in the tuple struct is the pointer to the array data - let array_ptr_field = unsafe { builder.build_struct_gep(tuple_ptr, 0, "list_tuple_ptr") }; + let array_ptr_field = builder.build_extract_value(tuple_struct, 1, "unwrapped_list_len").unwrap().into_pointer_value(); let array_data_ptr = builder.build_load(array_ptr_field, "get_array_data").into_pointer_value(); let elem_bytes = 8; // TODO Look this size up instead of hardcoding it! diff --git a/compiler/gen/tests/test_gen.rs b/compiler/gen/tests/test_gen.rs index c3dd62907a..90d0b05ab3 100644 --- a/compiler/gen/tests/test_gen.rs +++ b/compiler/gen/tests/test_gen.rs @@ -274,7 +274,7 @@ mod test_gen { builder.build_return(Some(&ret)); // Uncomment this to see the module's un-optimized LLVM instruction output: - env.module.print_to_stderr(); + // env.module.print_to_stderr(); if main_fn.verify(true) { fpm.run_on(&main_fn); From 246199ec3e42f308a2e113e04ab0fa47c402861d Mon Sep 17 00:00:00 2001 From: Folkert Date: Tue, 10 Mar 2020 12:01:13 +0100 Subject: [PATCH 024/428] fixes after review --- compiler/mono/src/layout.rs | 24 ++++++++- compiler/mono/tests/test_mono.rs | 83 ++++++++++++++++++++++---------- 2 files changed, 79 insertions(+), 28 deletions(-) diff --git a/compiler/mono/src/layout.rs b/compiler/mono/src/layout.rs index f28d69c142..b1f66cfb59 100644 --- a/compiler/mono/src/layout.rs +++ b/compiler/mono/src/layout.rs @@ -5,6 +5,8 @@ use roc_module::ident::{Lowercase, TagName}; use roc_module::symbol::Symbol; use roc_types::subs::{Content, FlatType, Subs, Variable}; +const POINTER_SIZE: u32 = std::mem::size_of::() as u32; + /// Types for code gen must be monomorphic. No type variables allowed! #[derive(Clone, Debug, PartialEq, Eq)] pub enum Layout<'a> { @@ -250,8 +252,26 @@ fn layout_from_flat_type<'a>( } _ => { // Check if we can turn this tag union into an enum - // TODO rather than the arguments being empty, check whether their layout has size 0. - if tags.len() <= 256 && tags.iter().all(|(_, args)| args.is_empty()) { + // The arguments of all tags must have size 0. + // That is trivially the case when there are no arguments + // + // [ Orange, Apple, Banana ] + // + // But when one-tag tag unions are optimized away, we can also use an enum for + // + // [ Foo [ Unit ], Bar [ Unit ] ] + let arguments_have_size_0 = || { + tags.iter().all(|(_, args)| { + args.iter().all(|var| { + Layout::from_var(arena, *var, subs) + .map(|v| v.stack_size(POINTER_SIZE)) + == Ok(0) + }) + }) + }; + + // up to 256 enum keys can be stored in a byte + if tags.len() <= std::u8::MAX as usize + 1 && arguments_have_size_0() { if tags.len() <= 2 { // Up to 2 enum tags can be stored (in theory) in one bit let mut it = tags.keys(); diff --git a/compiler/mono/tests/test_mono.rs b/compiler/mono/tests/test_mono.rs index 325f257363..60eb043761 100644 --- a/compiler/mono/tests/test_mono.rs +++ b/compiler/mono/tests/test_mono.rs @@ -102,32 +102,63 @@ mod test_mono { ); } - // #[test] - // fn two_element_enum() { - // compiles_to( - // r#" - // x : [ Yes, No ] - // x = No - // - // x - // "#, - // Int(32), - // ); - // } - // - // #[test] - // fn three_element_enum() { - // compiles_to( - // r#" - // # this test is brought to you by fruits.com! - // x : [ Apple, Orange, Banana ] - // x = Orange - // - // x - // "#, - // Int(32), - // ); - // } + #[test] + fn two_element_enum() { + let arena = Bump::new(); + + compiles_to_with_interns( + r#" + x : [ Yes, No ] + x = No + + x + "#, + |interns| { + let home = test_home(); + let var_x = interns.symbol(home, "x".into()); + + let stores = [( + var_x, + Layout::Builtin(Builtin::Bool(Global("No".into()), Global("Yes".into()))), + Bool(false), + )]; + + let load = Load(var_x); + + Store(arena.alloc(stores), arena.alloc(load)) + }, + ); + } + #[test] + fn three_element_enum() { + let arena = Bump::new(); + + compiles_to_with_interns( + r#" + # this test is brought to you by fruits.com! + x : [ Apple, Orange, Banana ] + x = Orange + + x + "#, + |interns| { + let home = test_home(); + let var_x = interns.symbol(home, "x".into()); + + let mut fruits = MutMap::default(); + + fruits.insert(Global("Banana".into()), 0); + fruits.insert(Global("Orange".into()), 1); + fruits.insert(Global("Apple".into()), 2); + + let stores = [(var_x, Layout::Builtin(Builtin::Byte(fruits)), Byte(1))]; + + let load = Load(var_x); + + Store(arena.alloc(stores), arena.alloc(load)) + }, + ); + } #[test] fn set_unique_int_list() { From fef590b9f7d07b514a2d65ae9a9454a980c3dafd Mon Sep 17 00:00:00 2001 From: Folkert Date: Tue, 10 Mar 2020 13:44:54 +0100 Subject: [PATCH 025/428] remove unneeded variable check in reporting --- compiler/reporting/tests/helpers/mod.rs | 107 +-------------------- compiler/reporting/tests/test_reporting.rs | 4 +- 2 files changed, 2 insertions(+), 109 deletions(-) diff --git a/compiler/reporting/tests/helpers/mod.rs b/compiler/reporting/tests/helpers/mod.rs index ddf047813d..9ca85d8c4e 100644 --- a/compiler/reporting/tests/helpers/mod.rs +++ b/compiler/reporting/tests/helpers/mod.rs @@ -8,7 +8,7 @@ use roc_can::expected::Expected; use roc_can::expr::{canonicalize_expr, Expr, Output}; use roc_can::operator; use roc_can::scope::Scope; -use roc_collections::all::{ImMap, ImSet, MutMap, SendMap, SendSet}; +use roc_collections::all::{ImMap, MutMap, SendMap, SendSet}; use roc_constrain::expr::constrain_expr; use roc_constrain::module::{constrain_imported_values, load_builtin_aliases, Import}; use roc_module::ident::Ident; @@ -350,108 +350,3 @@ pub fn fixtures_dir<'a>() -> PathBuf { pub fn builtins_dir<'a>() -> PathBuf { PathBuf::new().join("builtins") } - -// Check constraints -// -// Keep track of the used (in types or expectations) variables, and the declared variables (in -// flex_vars or rigid_vars fields of LetConstraint. These roc_collections should match: no duplicates -// and no variables that are used but not declared are allowed. -// -// There is one exception: the initial variable (that stores the type of the whole expression) is -// never declared, but is used. -#[allow(dead_code)] -pub fn assert_correct_variable_usage(constraint: &Constraint) { - // variables declared in constraint (flex_vars or rigid_vars) - // and variables actually used in constraints - let (declared, used) = variable_usage(constraint); - - let used: ImSet = used.clone().into(); - let mut decl: ImSet = declared.rigid_vars.clone().into(); - - for var in declared.flex_vars.clone() { - decl.insert(var); - } - - let diff = used.clone().relative_complement(decl); - - // NOTE: this checks whether we're using variables that are not declared. For recursive type - // definitions, their rigid types are declared twice, which is correct! - if !diff.is_empty() { - println!("VARIABLE USAGE PROBLEM"); - - println!("used: {:?}", &used); - println!("rigids: {:?}", &declared.rigid_vars); - println!("flexs: {:?}", &declared.flex_vars); - - println!("difference: {:?}", &diff); - - panic!("variable usage problem (see stdout for details)"); - } -} - -#[derive(Default)] -pub struct SeenVariables { - pub rigid_vars: Vec, - pub flex_vars: Vec, -} - -pub fn variable_usage(con: &Constraint) -> (SeenVariables, Vec) { - let mut declared = SeenVariables::default(); - let mut used = ImSet::default(); - variable_usage_help(con, &mut declared, &mut used); - - used.remove(unsafe { &Variable::unsafe_test_debug_variable(1) }); - used.remove(unsafe { &Variable::unsafe_test_debug_variable(2) }); - used.remove(unsafe { &Variable::unsafe_test_debug_variable(3) }); - - let mut used_vec: Vec = used.into_iter().collect(); - used_vec.sort(); - - declared.rigid_vars.sort(); - declared.flex_vars.sort(); - - (declared, used_vec) -} - -fn variable_usage_help(con: &Constraint, declared: &mut SeenVariables, used: &mut ImSet) { - use Constraint::*; - - match con { - True | SaveTheEnvironment => (), - Eq(tipe, expectation, _) => { - for v in tipe.variables() { - used.insert(v); - } - - for v in expectation.get_type_ref().variables() { - used.insert(v); - } - } - Lookup(_, expectation, _) => { - for v in expectation.get_type_ref().variables() { - used.insert(v); - } - } - Pattern(_, _, tipe, pexpectation) => { - for v in tipe.variables() { - used.insert(v); - } - - for v in pexpectation.get_type_ref().variables() { - used.insert(v); - } - } - Let(letcon) => { - declared.rigid_vars.extend(letcon.rigid_vars.clone()); - declared.flex_vars.extend(letcon.flex_vars.clone()); - - variable_usage_help(&letcon.defs_constraint, declared, used); - variable_usage_help(&letcon.ret_constraint, declared, used); - } - And(constraints) => { - for sub in constraints { - variable_usage_help(sub, declared, used); - } - } - } -} diff --git a/compiler/reporting/tests/test_reporting.rs b/compiler/reporting/tests/test_reporting.rs index 6d0ef66cf8..7744a01432 100644 --- a/compiler/reporting/tests/test_reporting.rs +++ b/compiler/reporting/tests/test_reporting.rs @@ -17,7 +17,7 @@ mod test_report { use roc_types::types; use std::path::PathBuf; // use roc_region::all; - use crate::helpers::{assert_correct_variable_usage, can_expr, infer_expr, CanExprOut}; + use crate::helpers::{can_expr, infer_expr, CanExprOut}; use roc_reporting::report::ReportText::{EmText, Plain, Region, Type, Url, Value}; use roc_types::subs::Content::{FlexVar, RigidVar, Structure}; use roc_types::subs::FlatType::EmptyRecord; @@ -53,8 +53,6 @@ mod test_report { } = can_expr(expr_src); let mut subs = Subs::new(var_store.into()); - assert_correct_variable_usage(&constraint); - for (var, name) in output.introduced_variables.name_by_var { subs.rigid_var(var, name); } From 7fea957979853d715405fafba2f87de5a987ba13 Mon Sep 17 00:00:00 2001 From: Folkert Date: Tue, 10 Mar 2020 14:50:35 +0100 Subject: [PATCH 026/428] hardcode Bool --- compiler/can/src/expr.rs | 2 ++ compiler/can/src/num.rs | 2 ++ compiler/constrain/src/expr.rs | 11 ++--------- compiler/constrain/src/uniq.rs | 11 ++--------- compiler/solve/src/solve.rs | 2 ++ compiler/solve/tests/helpers/mod.rs | 7 ++++--- compiler/solve/tests/test_uniq_solve.rs | 2 +- compiler/types/src/subs.rs | 23 +++++++++++++++++++---- 8 files changed, 34 insertions(+), 26 deletions(-) diff --git a/compiler/can/src/expr.rs b/compiler/can/src/expr.rs index 6e34a59aed..29c8ae8b73 100644 --- a/compiler/can/src/expr.rs +++ b/compiler/can/src/expr.rs @@ -33,6 +33,8 @@ pub struct Output { #[derive(Clone, Debug, PartialEq)] pub enum Expr { // Literals + + // Int and Float store a variable to generate better error messages Int(Variable, i64), Float(Variable, f64), Str(Box), diff --git a/compiler/can/src/num.rs b/compiler/can/src/num.rs index a06ad4242f..90c2e0c0a7 100644 --- a/compiler/can/src/num.rs +++ b/compiler/can/src/num.rs @@ -12,6 +12,7 @@ pub fn int_expr_from_result( result: Result, env: &mut Env, ) -> Expr { + // Int stores a variable to generate better error messages match result { Ok(int) => Expr::Int(var_store.fresh(), int), Err(raw) => { @@ -30,6 +31,7 @@ pub fn float_expr_from_result( result: Result, env: &mut Env, ) -> Expr { + // Float stores a variable to generate better error messages match result { Ok(float) => Expr::Float(var_store.fresh(), float), Err(raw) => { diff --git a/compiler/constrain/src/expr.rs b/compiler/constrain/src/expr.rs index c0fd8a6263..59e385a8ec 100644 --- a/compiler/constrain/src/expr.rs +++ b/compiler/constrain/src/expr.rs @@ -10,7 +10,7 @@ use roc_can::expr::Expr::{self, *}; use roc_can::expr::Field; use roc_can::pattern::Pattern; use roc_collections::all::{ImMap, SendMap}; -use roc_module::ident::{Lowercase, TagName}; +use roc_module::ident::Lowercase; use roc_module::symbol::{ModuleId, Symbol}; use roc_region::all::{Located, Region}; use roc_types::subs::Variable; @@ -318,14 +318,7 @@ pub fn constrain_expr( branches, final_else, } => { - // TODO use Bool alias here, so we don't allocate this type every time - let bool_type = Type::TagUnion( - vec![ - (TagName::Global("True".into()), vec![]), - (TagName::Global("False".into()), vec![]), - ], - Box::new(Type::EmptyTagUnion), - ); + let bool_type = Type::Variable(Variable::BOOL); let expect_bool = Expected::ForReason(Reason::IfCondition, bool_type, region); let mut branch_cons = Vec::with_capacity(2 * branches.len() + 2); diff --git a/compiler/constrain/src/uniq.rs b/compiler/constrain/src/uniq.rs index e0d3556d65..c5513e7623 100644 --- a/compiler/constrain/src/uniq.rs +++ b/compiler/constrain/src/uniq.rs @@ -7,7 +7,7 @@ use roc_can::expected::{Expected, PExpected}; use roc_can::expr::{Expr, Field}; use roc_can::pattern::{Pattern, RecordDestruct}; use roc_collections::all::{ImMap, ImSet, SendMap}; -use roc_module::ident::{Ident, Lowercase, TagName}; +use roc_module::ident::{Ident, Lowercase}; use roc_module::symbol::{ModuleId, Symbol}; use roc_region::all::{Located, Region}; use roc_types::boolean_algebra::{Atom, Bool}; @@ -761,14 +761,7 @@ pub fn constrain_expr( final_else, } => { // TODO use Bool alias here, so we don't allocate this type every time - let bool_type = Type::TagUnion( - vec![ - (TagName::Global("True".into()), vec![]), - (TagName::Global("False".into()), vec![]), - ], - Box::new(Type::EmptyTagUnion), - ); - + let bool_type = Type::Variable(Variable::BOOL); let mut branch_cons = Vec::with_capacity(2 * branches.len() + 2); let mut cond_uniq_vars = Vec::with_capacity(branches.len() + 2); diff --git a/compiler/solve/src/solve.rs b/compiler/solve/src/solve.rs index 2d72e657c9..791c03ba43 100644 --- a/compiler/solve/src/solve.rs +++ b/compiler/solve/src/solve.rs @@ -546,6 +546,7 @@ fn type_to_variable( register(subs, rank, pools, content) } + Alias(Symbol::BOOL_BOOL, _, _) => Variable::BOOL, Alias(symbol, args, alias_type) => { // Cache aliases without type arguments. Commonly used aliases like `Int` would otherwise get O(n) // different variables (once for each occurence). The recursion restriction is required @@ -560,6 +561,7 @@ fn type_to_variable( // // 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 { diff --git a/compiler/solve/tests/helpers/mod.rs b/compiler/solve/tests/helpers/mod.rs index ddf047813d..292809793c 100644 --- a/compiler/solve/tests/helpers/mod.rs +++ b/compiler/solve/tests/helpers/mod.rs @@ -400,9 +400,10 @@ pub fn variable_usage(con: &Constraint) -> (SeenVariables, Vec) { let mut used = ImSet::default(); variable_usage_help(con, &mut declared, &mut used); - used.remove(unsafe { &Variable::unsafe_test_debug_variable(1) }); - used.remove(unsafe { &Variable::unsafe_test_debug_variable(2) }); - used.remove(unsafe { &Variable::unsafe_test_debug_variable(3) }); + // ..= because there is an extra undeclared variable that contains the type of the full expression + for i in 0..=Variable::RESERVED { + used.remove(unsafe { &Variable::unsafe_test_debug_variable(i as u32) }); + } let mut used_vec: Vec = used.into_iter().collect(); used_vec.sort(); diff --git a/compiler/solve/tests/test_uniq_solve.rs b/compiler/solve/tests/test_uniq_solve.rs index 1feccf7e37..a956321ed2 100644 --- a/compiler/solve/tests/test_uniq_solve.rs +++ b/compiler/solve/tests/test_uniq_solve.rs @@ -2113,7 +2113,7 @@ mod test_uniq_solve { f "# ), - "Attr * (Attr (* | a | b) { p : (Attr b *), q : (Attr a *) }* -> Attr * Int)", + "Attr * (Attr (* | a | b) { p : (Attr a *), q : (Attr b *) }* -> Attr * Int)", ); } diff --git a/compiler/types/src/subs.rs b/compiler/types/src/subs.rs index b4b063a9a3..8d1fd0439e 100644 --- a/compiler/types/src/subs.rs +++ b/compiler/types/src/subs.rs @@ -151,9 +151,10 @@ impl Variable { pub const EMPTY_RECORD: Variable = Variable(1); pub const EMPTY_TAG_UNION: Variable = Variable(2); + pub const BOOL: Variable = Variable(3); + pub const RESERVED: usize = 4; - // variables 1 and 2 are reserved for EmptyRecord and EmptyTagUnion - const FIRST_USER_SPACE_VAR: Variable = Variable(3); + const FIRST_USER_SPACE_VAR: Variable = Variable(Self::RESERVED as u32); /// # Safety /// @@ -228,8 +229,22 @@ impl Subs { subs.utable.new_key(flex_var_descriptor()); } - subs.set_content(Variable(1), Content::Structure(FlatType::EmptyRecord)); - subs.set_content(Variable(2), Content::Structure(FlatType::EmptyTagUnion)); + subs.set_content( + Variable::EMPTY_RECORD, + Content::Structure(FlatType::EmptyRecord), + ); + subs.set_content( + Variable::EMPTY_TAG_UNION, + Content::Structure(FlatType::EmptyTagUnion), + ); + + subs.set_content(Variable::BOOL, { + let mut tags = MutMap::default(); + tags.insert(TagName::Global("False".into()), vec![]); + tags.insert(TagName::Global("True".into()), vec![]); + + Content::Structure(FlatType::TagUnion(tags, Variable::EMPTY_TAG_UNION)) + }); subs } From 657901d7604b1f19b7f638db102f8a6c0bed5efe Mon Sep 17 00:00:00 2001 From: Folkert Date: Tue, 10 Mar 2020 15:16:44 +0100 Subject: [PATCH 027/428] bubble up pointer_size --- compiler/gen/tests/test_gen.rs | 15 +++++--- compiler/mono/src/expr.rs | 43 +++++++++++---------- compiler/mono/src/layout.rs | 64 ++++++++++++++++++++------------ compiler/mono/tests/test_mono.rs | 4 ++ compiler/mono/tests/test_opt.rs | 4 ++ 5 files changed, 81 insertions(+), 49 deletions(-) diff --git a/compiler/gen/tests/test_gen.rs b/compiler/gen/tests/test_gen.rs index ab6953fa74..464f680ddd 100644 --- a/compiler/gen/tests/test_gen.rs +++ b/compiler/gen/tests/test_gen.rs @@ -37,6 +37,9 @@ mod test_gen { use std::mem; use std::os::raw::c_char; + // Pointer size on 64-bit platforms + const POINTER_SIZE: u32 = std::mem::size_of::() as u32; + macro_rules! assert_crane_evals_to { ($src:expr, $expected:expr, $ty:ty, $transform:expr) => { let arena = Bump::new(); @@ -57,7 +60,7 @@ mod test_gen { let main_fn_name = "$Test.main"; // Compute main_fn_ret_type before moving subs to Env - let layout = Layout::from_content(&arena, content, &subs) + let layout = Layout::from_content(&arena, content, &subs, POINTER_SIZE) .unwrap_or_else(|err| panic!("Code gen error in test: could not convert content to layout. Err was {:?} and Subs were {:?}", err, subs)); let main_ret_type = type_from_layout(cfg, &layout); @@ -72,7 +75,7 @@ mod test_gen { let mut ident_ids = env.interns.all_ident_ids.remove(&home).unwrap(); // Populate Procs and Subs, and get the low-level Expr from the canonical Expr - let mono_expr = Expr::new(&arena, &subs, loc_expr.value, &mut procs, home, &mut ident_ids); + let mono_expr = Expr::new(&arena, &subs, loc_expr.value, &mut procs, home, &mut ident_ids, POINTER_SIZE); // Put this module's ident_ids back in the interns env.interns.all_ident_ids.insert(home, ident_ids); @@ -195,7 +198,7 @@ mod test_gen { fpm.initialize(); // Compute main_fn_type before moving subs to Env - let layout = Layout::from_content(&arena, content, &subs) + let layout = Layout::from_content(&arena, content, &subs, POINTER_SIZE) .unwrap_or_else(|err| panic!("Code gen error in test: could not convert to layout. Err was {:?} and Subs were {:?}", err, subs)); let main_fn_type = basic_type_from_layout(&context, &layout) .fn_type(&[], false); @@ -221,7 +224,7 @@ mod test_gen { let mut ident_ids = env.interns.all_ident_ids.remove(&home).unwrap(); // Populate Procs and get the low-level Expr from the canonical Expr - let main_body = Expr::new(&arena, &subs, loc_expr.value, &mut procs, home, &mut ident_ids); + let main_body = Expr::new(&arena, &subs, loc_expr.value, &mut procs, home, &mut ident_ids, POINTER_SIZE); // Put this module's ident_ids back in the interns, so we can use them in Env. env.interns.all_ident_ids.insert(home, ident_ids); @@ -330,7 +333,7 @@ mod test_gen { fpm.initialize(); // Compute main_fn_type before moving subs to Env - let layout = Layout::from_content(&arena, content, &subs) + let layout = Layout::from_content(&arena, content, &subs, POINTER_SIZE) .unwrap_or_else(|err| panic!("Code gen error in test: could not convert to layout. Err was {:?} and Subs were {:?}", err, subs)); let main_fn_type = basic_type_from_layout(&context, &layout) .fn_type(&[], false); @@ -356,7 +359,7 @@ mod test_gen { let mut ident_ids = env.interns.all_ident_ids.remove(&home).unwrap(); // Populate Procs and get the low-level Expr from the canonical Expr - let main_body = Expr::new(&arena, &subs, loc_expr.value, &mut procs, home, &mut ident_ids); + let main_body = Expr::new(&arena, &subs, loc_expr.value, &mut procs, home, &mut ident_ids, POINTER_SIZE); // Put this module's ident_ids back in the interns, so we can use them in Env. env.interns.all_ident_ids.insert(home, ident_ids); diff --git a/compiler/mono/src/expr.rs b/compiler/mono/src/expr.rs index c38f257e01..31cee11277 100644 --- a/compiler/mono/src/expr.rs +++ b/compiler/mono/src/expr.rs @@ -24,6 +24,7 @@ struct Env<'a, 'i> { pub subs: &'a Subs, pub home: ModuleId, pub ident_ids: &'i mut IdentIds, + pub pointer_size: u32, } #[derive(Clone, Debug, PartialEq)] @@ -119,12 +120,14 @@ impl<'a> Expr<'a> { procs: &mut Procs<'a>, home: ModuleId, ident_ids: &mut IdentIds, + pointer_size: u32, ) -> Self { let mut env = Env { arena, subs, home, ident_ids, + pointer_size, }; from_can(&mut env, can_expr, procs, None) @@ -256,8 +259,8 @@ fn from_can<'a>( args.push(from_can(env, loc_arg.value, procs, None)); } - let layout = - Layout::from_var(env.arena, fn_var, env.subs).unwrap_or_else(|err| { + let layout = Layout::from_var(env.arena, fn_var, env.subs, env.pointer_size) + .unwrap_or_else(|err| { panic!("TODO turn fn_var into a RuntimeError {:?}", err) }); Expr::CallByPointer(&*env.arena.alloc(ptr), args.into_bump_slice(), layout) @@ -283,7 +286,7 @@ fn from_can<'a>( field_bodies.push((label, expr)); } - let struct_layout = match Layout::from_var(arena, ext_var, subs) { + let struct_layout = match Layout::from_var(arena, ext_var, subs, env.pointer_size) { Ok(layout) => layout, Err(()) => { // Invalid field! @@ -306,7 +309,7 @@ fn from_can<'a>( let subs = env.subs; let arena = env.arena; - let struct_layout = match Layout::from_var(arena, ext_var, subs) { + let struct_layout = match Layout::from_var(arena, ext_var, subs, env.pointer_size) { Ok(layout) => layout, Err(()) => { // Invalid field! @@ -314,7 +317,7 @@ fn from_can<'a>( } }; - let field_layout = match Layout::from_var(arena, field_var, subs) { + let field_layout = match Layout::from_var(arena, field_var, subs, env.pointer_size) { Ok(layout) => layout, Err(()) => { // Invalid field! @@ -335,7 +338,7 @@ fn from_can<'a>( } => { let subs = env.subs; let arena = env.arena; - let elem_layout = match Layout::from_var(arena, elem_var, subs) { + let elem_layout = match Layout::from_var(arena, elem_var, subs, env.pointer_size) { Ok(layout) => layout, Err(()) => { panic!("TODO gracefully handle List with invalid element layout"); @@ -360,7 +363,7 @@ fn from_can<'a>( let subs = &env.subs; let arena = env.arena; - match Layout::from_var(arena, variant_var, subs) { + match Layout::from_var(arena, variant_var, subs, env.pointer_size) { Ok(Layout::Builtin(Builtin::Bool(_smaller, larger))) => Expr::Bool(name == larger), Ok(Layout::Builtin(Builtin::Byte(tags))) => match tags.get(&name) { Some(v) => Expr::Byte(*v), @@ -386,7 +389,7 @@ fn add_closure<'a>( let mut proc_args = Vec::with_capacity_in(loc_args.len(), arena); for (arg_var, loc_arg) in loc_args.iter() { - let layout = match Layout::from_var(arena, *arg_var, subs) { + let layout = match Layout::from_var(arena, *arg_var, subs, env.pointer_size) { Ok(layout) => layout, Err(()) => { // Invalid closure! @@ -406,7 +409,7 @@ fn add_closure<'a>( proc_args.push((layout, arg_name)); } - let ret_layout = Layout::from_var(arena, ret_var, subs) + let ret_layout = Layout::from_var(arena, ret_var, subs, env.pointer_size) .unwrap_or_else(|err| panic!("TODO handle invalid function {:?}", err)); let proc = Proc { @@ -431,7 +434,7 @@ fn store_pattern<'a>( ) { use roc_can::pattern::Pattern::*; - let layout = match Layout::from_var(env.arena, var, env.subs) { + let layout = match Layout::from_var(env.arena, var, env.subs, env.pointer_size) { Ok(layout) => layout, Err(()) => { panic!("TODO gen a runtime error here"); @@ -525,8 +528,8 @@ fn from_can_when<'a>( let cond_rhs = arena.alloc(Expr::Int(*int)); let pass = arena.alloc(from_can(env, loc_then.value, procs, None)); let fail = arena.alloc(from_can(env, loc_else.value, procs, None)); - let ret_layout = - Layout::from_var(arena, expr_var, env.subs).unwrap_or_else(|err| { + let ret_layout = Layout::from_var(arena, expr_var, env.subs, env.pointer_size) + .unwrap_or_else(|err| { panic!("TODO turn this into a RuntimeError {:?}", err) }); @@ -544,8 +547,8 @@ fn from_can_when<'a>( let cond_rhs = arena.alloc(Expr::Float(*float)); let pass = arena.alloc(from_can(env, loc_then.value, procs, None)); let fail = arena.alloc(from_can(env, loc_else.value, procs, None)); - let ret_layout = - Layout::from_var(arena, expr_var, env.subs).unwrap_or_else(|err| { + let ret_layout = Layout::from_var(arena, expr_var, env.subs, env.pointer_size) + .unwrap_or_else(|err| { panic!("TODO turn this into a RuntimeError {:?}", err) }); @@ -568,7 +571,7 @@ fn from_can_when<'a>( let arena = env.arena; let cond = from_can(env, loc_cond.value, procs, None); let subs = &env.subs; - let layout = Layout::from_var(arena, cond_var, subs) + let layout = Layout::from_var(arena, cond_var, subs, env.pointer_size) .unwrap_or_else(|_| panic!("TODO generate a runtime error in from_can_when here!")); // We can Switch on integers and tags, because they both have @@ -649,12 +652,12 @@ fn from_can_when<'a>( debug_assert!(opt_default_branch.is_some()); let default_branch = opt_default_branch.unwrap(); - let cond_layout = - Layout::from_var(arena, cond_var, env.subs).unwrap_or_else(|err| { + let cond_layout = Layout::from_var(arena, cond_var, env.subs, env.pointer_size) + .unwrap_or_else(|err| { panic!("TODO turn cond_layout into a RuntimeError {:?}", err) }); - let ret_layout = - Layout::from_var(arena, expr_var, env.subs).unwrap_or_else(|err| { + let ret_layout = Layout::from_var(arena, expr_var, env.subs, env.pointer_size) + .unwrap_or_else(|err| { panic!("TODO turn ret_layout into a RuntimeError {:?}", err) }); @@ -694,7 +697,7 @@ fn call_by_name<'a>( let arena = env.arena; for (var, loc_arg) in loc_args { - let layout = Layout::from_var(arena, var, subs) + let layout = Layout::from_var(arena, var, subs, env.pointer_size) .unwrap_or_else(|err| panic!("TODO gracefully handle bad layout: {:?}", err)); args.push((from_can(env, loc_arg.value, procs, None), layout)); diff --git a/compiler/mono/src/layout.rs b/compiler/mono/src/layout.rs index b1f66cfb59..08a1136ec9 100644 --- a/compiler/mono/src/layout.rs +++ b/compiler/mono/src/layout.rs @@ -5,8 +5,6 @@ use roc_module::ident::{Lowercase, TagName}; use roc_module::symbol::Symbol; use roc_types::subs::{Content, FlatType, Subs, Variable}; -const POINTER_SIZE: u32 = std::mem::size_of::() as u32; - /// Types for code gen must be monomorphic. No type variables allowed! #[derive(Clone, Debug, PartialEq, Eq)] pub enum Layout<'a> { @@ -33,20 +31,30 @@ impl<'a> Layout<'a> { /// Returns Err(()) if given an error, or Ok(Layout) if given a non-erroneous Structure. /// Panics if given a FlexVar or RigidVar, since those should have been /// monomorphized away already! - pub fn from_var(arena: &'a Bump, var: Variable, subs: &Subs) -> Result { + pub fn from_var( + arena: &'a Bump, + var: Variable, + subs: &Subs, + pointer_size: u32, + ) -> Result { let content = subs.get_without_compacting(var).content; - Self::from_content(arena, content, subs) + Self::from_content(arena, content, subs, pointer_size) } - pub fn from_content(arena: &'a Bump, content: Content, subs: &Subs) -> Result { + pub fn from_content( + arena: &'a Bump, + content: Content, + subs: &Subs, + pointer_size: u32, + ) -> Result { use roc_types::subs::Content::*; match content { var @ FlexVar(_) | var @ RigidVar(_) => { panic!("Layout::from_content encountered an unresolved {:?}", var); } - Structure(flat_type) => layout_from_flat_type(arena, flat_type, subs), + Structure(flat_type) => layout_from_flat_type(arena, flat_type, subs, pointer_size), Alias(Symbol::INT_INT, args, _) => { debug_assert!(args.is_empty()); @@ -56,9 +64,12 @@ impl<'a> Layout<'a> { debug_assert!(args.is_empty()); Ok(Layout::Builtin(Builtin::Float64)) } - Alias(_, _, var) => { - Self::from_content(arena, subs.get_without_compacting(var).content, subs) - } + Alias(_, _, var) => Self::from_content( + arena, + subs.get_without_compacting(var).content, + subs, + pointer_size, + ), Error => Err(()), } } @@ -114,6 +125,7 @@ fn layout_from_flat_type<'a>( arena: &'a Bump, flat_type: FlatType, subs: &Subs, + pointer_size: u32, ) -> Result, ()> { use roc_types::subs::FlatType::*; @@ -139,7 +151,7 @@ fn layout_from_flat_type<'a>( } Symbol::STR_STR => Ok(Layout::Builtin(Builtin::Str)), Symbol::LIST_LIST => { - let elem_layout = Layout::from_var(arena, args[0], subs)?; + let elem_layout = Layout::from_var(arena, args[0], subs, pointer_size)?; Ok(Layout::Builtin(Builtin::List(arena.alloc(elem_layout)))) } @@ -153,7 +165,7 @@ fn layout_from_flat_type<'a>( // For now, layout is unaffected by uniqueness. // (Incorporating refcounting may change this.) // Unwrap and continue - Layout::from_var(arena, wrapped_var, subs) + Layout::from_var(arena, wrapped_var, subs, pointer_size) } _ => { panic!("TODO layout_from_flat_type for {:?}", Apply(symbol, args)); @@ -166,11 +178,16 @@ fn layout_from_flat_type<'a>( for arg_var in args { let arg_content = subs.get_without_compacting(arg_var).content; - fn_args.push(Layout::from_content(arena, arg_content, subs)?); + fn_args.push(Layout::from_content( + arena, + arg_content, + subs, + pointer_size, + )?); } let ret_content = subs.get_without_compacting(ret_var).content; - let ret = Layout::from_content(arena, ret_content, subs)?; + let ret = Layout::from_content(arena, ret_content, subs, pointer_size)?; Ok(Layout::FunctionPointer( fn_args.into_bump_slice(), @@ -180,7 +197,7 @@ fn layout_from_flat_type<'a>( Record(mut fields, ext_var) => { flatten_record(&mut fields, ext_var, subs); let ext_content = subs.get_without_compacting(ext_var).content; - let ext_layout = match Layout::from_content(arena, ext_content, subs) { + let ext_layout = match Layout::from_content(arena, ext_content, subs, pointer_size) { Ok(layout) => layout, Err(()) => { // Invalid record! @@ -208,13 +225,14 @@ fn layout_from_flat_type<'a>( for (label, field_var) in fields { let field_content = subs.get_without_compacting(field_var).content; - let field_layout = match Layout::from_content(arena, field_content, subs) { - Ok(layout) => layout, - Err(()) => { - // Invalid field! - panic!("TODO gracefully handle record with invalid field.var"); - } - }; + let field_layout = + match Layout::from_content(arena, field_content, subs, pointer_size) { + Ok(layout) => layout, + Err(()) => { + // Invalid field! + panic!("TODO gracefully handle record with invalid field.var"); + } + }; field_layouts.push((label.clone(), field_layout)); } @@ -263,8 +281,8 @@ fn layout_from_flat_type<'a>( let arguments_have_size_0 = || { tags.iter().all(|(_, args)| { args.iter().all(|var| { - Layout::from_var(arena, *var, subs) - .map(|v| v.stack_size(POINTER_SIZE)) + Layout::from_var(arena, *var, subs, pointer_size) + .map(|v| v.stack_size(pointer_size)) == Ok(0) }) }) diff --git a/compiler/mono/tests/test_mono.rs b/compiler/mono/tests/test_mono.rs index 60eb043761..04e8156933 100644 --- a/compiler/mono/tests/test_mono.rs +++ b/compiler/mono/tests/test_mono.rs @@ -48,6 +48,9 @@ mod test_mono { let mut procs = MutMap::default(); let mut ident_ids = interns.all_ident_ids.remove(&home).unwrap(); + // assume 64-bit pointers + let pointer_size = std::mem::size_of::() as u32; + // Populate Procs and Subs, and get the low-level Expr from the canonical Expr let mono_expr = Expr::new( &arena, @@ -56,6 +59,7 @@ mod test_mono { &mut procs, home, &mut ident_ids, + pointer_size, ); // Put this module's ident_ids back in the interns diff --git a/compiler/mono/tests/test_opt.rs b/compiler/mono/tests/test_opt.rs index 9560758cdd..a7c6cdebc1 100644 --- a/compiler/mono/tests/test_opt.rs +++ b/compiler/mono/tests/test_opt.rs @@ -31,6 +31,9 @@ mod test_opt { let mut procs = MutMap::default(); let mut ident_ids = interns.all_ident_ids.remove(&home).unwrap(); + // assume 64-bit pointers + let pointer_size = std::mem::size_of::() as u32; + // Populate Procs and Subs, and get the low-level Expr from the canonical Expr let mono_expr = Expr::new( &arena, @@ -39,6 +42,7 @@ mod test_opt { &mut procs, home, &mut ident_ids, + pointer_size, ); assert_eq!(mono_expr, expected); From 62f34c41cc1082243279aafae48144f2b696962a Mon Sep 17 00:00:00 2001 From: Folkert Date: Tue, 10 Mar 2020 15:21:39 +0100 Subject: [PATCH 028/428] hardcode Bool alias previously it would use the tag union, which would result in slightly worse error messages down the line --- compiler/solve/tests/test_uniq_solve.rs | 3 ++- compiler/types/src/subs.rs | 11 ++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/compiler/solve/tests/test_uniq_solve.rs b/compiler/solve/tests/test_uniq_solve.rs index a956321ed2..685bf20988 100644 --- a/compiler/solve/tests/test_uniq_solve.rs +++ b/compiler/solve/tests/test_uniq_solve.rs @@ -2113,7 +2113,8 @@ mod test_uniq_solve { f "# ), - "Attr * (Attr (* | a | b) { p : (Attr a *), q : (Attr b *) }* -> Attr * Int)", + "Attr * (Attr (* | a | b) { p : (Attr b *), q : (Attr a *) }* -> Attr * Int)", + // "Attr * (Attr (* | a | b) { p : (Attr a *), q : (Attr b *) }* -> Attr * Int)", ); } diff --git a/compiler/types/src/subs.rs b/compiler/types/src/subs.rs index 8d1fd0439e..9ac431e55c 100644 --- a/compiler/types/src/subs.rs +++ b/compiler/types/src/subs.rs @@ -151,8 +151,9 @@ impl Variable { pub const EMPTY_RECORD: Variable = Variable(1); pub const EMPTY_TAG_UNION: Variable = Variable(2); - pub const BOOL: Variable = Variable(3); - pub const RESERVED: usize = 4; + const BOOL_ENUM: Variable = Variable(3); + pub const BOOL: Variable = Variable(4); + pub const RESERVED: usize = 5; const FIRST_USER_SPACE_VAR: Variable = Variable(Self::RESERVED as u32); @@ -238,7 +239,7 @@ impl Subs { Content::Structure(FlatType::EmptyTagUnion), ); - subs.set_content(Variable::BOOL, { + subs.set_content(Variable::BOOL_ENUM, { let mut tags = MutMap::default(); tags.insert(TagName::Global("False".into()), vec![]); tags.insert(TagName::Global("True".into()), vec![]); @@ -246,6 +247,10 @@ impl Subs { Content::Structure(FlatType::TagUnion(tags, Variable::EMPTY_TAG_UNION)) }); + subs.set_content(Variable::BOOL, { + Content::Alias(Symbol::BOOL_BOOL, vec![], Variable::BOOL_ENUM) + }); + subs } From fd1c29fb352b75287c6c483411b30de355b7b609 Mon Sep 17 00:00:00 2001 From: Folkert Date: Tue, 10 Mar 2020 20:54:33 +0100 Subject: [PATCH 029/428] convert Tag to mono --- compiler/mono/src/expr.rs | 50 ++++++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 16 deletions(-) diff --git a/compiler/mono/src/expr.rs b/compiler/mono/src/expr.rs index 31cee11277..a5699d3117 100644 --- a/compiler/mono/src/expr.rs +++ b/compiler/mono/src/expr.rs @@ -90,7 +90,6 @@ pub enum Expr<'a> { }, Tag { tag_layout: Layout<'a>, - ext_layout: Layout<'a>, name: TagName, arguments: &'a [Expr<'a>], }, @@ -300,6 +299,40 @@ fn from_can<'a>( } } + Tag { + variant_var, + name, + arguments: args, + .. + } => { + let arena = env.arena; + + match Layout::from_var(arena, variant_var, &env.subs, env.pointer_size) { + Ok(Layout::Builtin(Builtin::Bool(_smaller, larger))) => Expr::Bool(name == larger), + Ok(Layout::Builtin(Builtin::Byte(tags))) => match tags.get(&name) { + Some(v) => Expr::Byte(*v), + None => panic!("Tag name is not part of the type"), + }, + Ok(layout) => { + let mut arguments = Vec::with_capacity_in(args.len(), arena); + + for (_, arg) in args { + arguments.push(from_can(env, arg.value, procs, None)); + } + + Expr::Tag { + tag_layout: layout, + name, + arguments: arguments.into_bump_slice(), + } + } + Err(()) => { + // Invalid field! + panic!("TODO gracefully handle Access with invalid struct_layout"); + } + } + } + Access { ext_var, field_var, @@ -357,21 +390,6 @@ fn from_can<'a>( } } - Tag { - variant_var, name, .. - } => { - let subs = &env.subs; - let arena = env.arena; - - match Layout::from_var(arena, variant_var, subs, env.pointer_size) { - Ok(Layout::Builtin(Builtin::Bool(_smaller, larger))) => Expr::Bool(name == larger), - Ok(Layout::Builtin(Builtin::Byte(tags))) => match tags.get(&name) { - Some(v) => Expr::Byte(*v), - None => panic!("Tag name is not part of the type"), - }, - _ => panic!(), - } - } other => panic!("TODO convert canonicalized {:?} to ll::Expr", other), } } From 3ecbe0325cf7381e9f57b06a82a1eacce06e0197 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Tue, 10 Mar 2020 20:40:53 -0400 Subject: [PATCH 030/428] Plain number literals (e.g. `5`) are now `Num *` --- compiler/can/src/def.rs | 7 +- compiler/can/src/expr.rs | 7 +- compiler/can/src/num.rs | 21 +++ compiler/can/src/operator.rs | 4 +- compiler/can/src/pattern.rs | 15 +- compiler/can/tests/test_canonicalize.rs | 19 ++- compiler/constrain/src/expr.rs | 8 + compiler/constrain/src/pattern.rs | 15 +- compiler/constrain/src/uniq.rs | 7 + compiler/fmt/src/expr.rs | 6 +- compiler/fmt/src/pattern.rs | 2 +- compiler/mono/src/expr.rs | 31 +++- compiler/mono/src/layout.rs | 4 + compiler/parse/src/ast.rs | 6 +- compiler/parse/src/expr.rs | 4 +- compiler/parse/src/number_literal.rs | 10 +- compiler/parse/tests/test_parse.rs | 202 ++++++++++++------------ compiler/solve/tests/test_solve.rs | 151 +++++++++--------- compiler/types/src/types.rs | 4 +- compiler/uniq/src/sharing.rs | 1 + roc-for-elm-programmers.md | 44 +++--- 21 files changed, 345 insertions(+), 223 deletions(-) diff --git a/compiler/can/src/def.rs b/compiler/can/src/def.rs index 86511b947f..13a0431cfc 100644 --- a/compiler/can/src/def.rs +++ b/compiler/can/src/def.rs @@ -674,7 +674,12 @@ fn pattern_to_vars_by_symbol( } } - IntLiteral(_) | FloatLiteral(_) | StrLiteral(_) | Underscore | UnsupportedPattern(_) => {} + NumLiteral(_, _) + | IntLiteral(_) + | FloatLiteral(_) + | StrLiteral(_) + | Underscore + | UnsupportedPattern(_) => {} Shadowed(_, _) => {} } diff --git a/compiler/can/src/expr.rs b/compiler/can/src/expr.rs index 6e34a59aed..31aab318fe 100644 --- a/compiler/can/src/expr.rs +++ b/compiler/can/src/expr.rs @@ -3,7 +3,7 @@ use crate::def::{can_defs_with_return, Def}; use crate::env::Env; use crate::num::{ finish_parsing_base, finish_parsing_float, finish_parsing_int, float_expr_from_result, - int_expr_from_result, + int_expr_from_result, num_expr_from_result, }; use crate::pattern::{canonicalize_pattern, Pattern}; use crate::procedure::References; @@ -33,6 +33,7 @@ pub struct Output { #[derive(Clone, Debug, PartialEq)] pub enum Expr { // Literals + Num(Variable, i64), Int(Variable, i64), Float(Variable, f64), Str(Box), @@ -144,8 +145,8 @@ pub fn canonicalize_expr<'a>( use Expr::*; let (expr, output) = match expr { - ast::Expr::Int(string) => { - let answer = int_expr_from_result(var_store, finish_parsing_int(*string), env); + ast::Expr::Num(string) => { + let answer = num_expr_from_result(var_store, finish_parsing_int(*string), env); (answer, Output::default()) } diff --git a/compiler/can/src/num.rs b/compiler/can/src/num.rs index a06ad4242f..5cbf469607 100644 --- a/compiler/can/src/num.rs +++ b/compiler/can/src/num.rs @@ -6,6 +6,27 @@ use roc_problem::can::RuntimeError::*; use roc_types::subs::VarStore; use std::i64; +#[inline(always)] +pub fn num_expr_from_result( + var_store: &VarStore, + result: Result, + env: &mut Env, +) -> Expr { + match result { + Ok(int) => Expr::Num(var_store.fresh(), int), + Err(raw) => { + // (Num *) compiles to Int if it doesn't + // get specialized to something else first, + // so use int's overflow bounds here. + let runtime_error = IntOutsideRange(raw.into()); + + env.problem(Problem::RuntimeError(runtime_error.clone())); + + Expr::RuntimeError(runtime_error) + } + } +} + #[inline(always)] pub fn int_expr_from_result( var_store: &VarStore, diff --git a/compiler/can/src/operator.rs b/compiler/can/src/operator.rs index 2b71344c9d..c4cd0cb0e8 100644 --- a/compiler/can/src/operator.rs +++ b/compiler/can/src/operator.rs @@ -62,8 +62,8 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located>) -> &'a match &loc_expr.value { Float(_) | Nested(Float(_)) - | Int(_) - | Nested(Int(_)) + | Num(_) + | Nested(Num(_)) | NonBase10Int { .. } | Nested(NonBase10Int { .. }) | Str(_) diff --git a/compiler/can/src/pattern.rs b/compiler/can/src/pattern.rs index 5c8af6318f..34e3af98fe 100644 --- a/compiler/can/src/pattern.rs +++ b/compiler/can/src/pattern.rs @@ -16,6 +16,7 @@ pub enum Pattern { Identifier(Symbol), AppliedTag(Variable, TagName, Vec<(Variable, Located)>), IntLiteral(i64), + NumLiteral(Variable, i64), FloatLiteral(f64), StrLiteral(Box), RecordDestructure(Variable, Vec>), @@ -61,7 +62,12 @@ pub fn symbols_from_pattern_help(pattern: &Pattern, symbols: &mut Vec) { } } - IntLiteral(_) | FloatLiteral(_) | StrLiteral(_) | Underscore | UnsupportedPattern(_) => {} + NumLiteral(_, _) + | IntLiteral(_) + | FloatLiteral(_) + | StrLiteral(_) + | Underscore + | UnsupportedPattern(_) => {} Shadowed(_, _) => {} } @@ -155,12 +161,12 @@ pub fn canonicalize_pattern<'a>( ptype @ DefExpr | ptype @ TopLevelDef => unsupported_pattern(env, ptype, region), }, - IntLiteral(string) => match pattern_type { + NumLiteral(string) => match pattern_type { WhenBranch => { let int = finish_parsing_int(string) .unwrap_or_else(|_| panic!("TODO handle malformed int pattern")); - Pattern::IntLiteral(int) + Pattern::NumLiteral(var_store.fresh(), int) } ptype @ DefExpr | ptype @ TopLevelDef | ptype @ FunctionArg => { unsupported_pattern(env, ptype, region) @@ -353,7 +359,8 @@ fn add_bindings_from_patterns( answer.push((*symbol, *region)); } } - IntLiteral(_) + NumLiteral(_, _) + | IntLiteral(_) | FloatLiteral(_) | StrLiteral(_) | Underscore diff --git a/compiler/can/tests/test_canonicalize.rs b/compiler/can/tests/test_canonicalize.rs index bf9f11b37e..33e7f1bed8 100644 --- a/compiler/can/tests/test_canonicalize.rs +++ b/compiler/can/tests/test_canonicalize.rs @@ -40,6 +40,7 @@ mod test_canonicalize { } } } + fn assert_can_int(input: &str, expected: i64) { let arena = Bump::new(); let actual_out = can_expr_with(&arena, test_home(), input); @@ -54,6 +55,20 @@ mod test_canonicalize { } } + fn assert_can_num(input: &str, expected: i64) { + let arena = Bump::new(); + let actual_out = can_expr_with(&arena, test_home(), input); + + match actual_out.loc_expr.value { + Expr::Num(_, actual) => { + assert_eq!(expected, actual); + } + actual => { + panic!("Expected a Num, but got: {:?}", actual); + } + } + } + // NUMBER LITERALS #[test] @@ -98,12 +113,12 @@ mod test_canonicalize { #[test] fn zero() { - assert_can_int("0", 0); + assert_can_num("0", 0); } #[test] fn minus_zero() { - assert_can_int("-0", 0); + assert_can_num("-0", 0); } #[test] diff --git a/compiler/constrain/src/expr.rs b/compiler/constrain/src/expr.rs index c0fd8a6263..af5266d0da 100644 --- a/compiler/constrain/src/expr.rs +++ b/compiler/constrain/src/expr.rs @@ -80,6 +80,14 @@ pub fn constrain_expr( ) -> Constraint { match expr { Int(var, _) => int_literal(*var, expected, region), + Num(var, _) => exists( + vec![*var], + Eq( + Type::Apply(Symbol::NUM_NUM, vec![Type::Variable(*var)]), + expected, + region, + ), + ), Float(var, _) => float_literal(*var, expected, region), EmptyRecord => constrain_empty_record(region, expected), Expr::Record(stored_var, fields) => { diff --git a/compiler/constrain/src/pattern.rs b/compiler/constrain/src/pattern.rs index 5008871644..5702a22d81 100644 --- a/compiler/constrain/src/pattern.rs +++ b/compiler/constrain/src/pattern.rs @@ -53,6 +53,7 @@ fn headers_from_annotation_help( Underscore | Shadowed(_, _) | UnsupportedPattern(_) + | NumLiteral(_, _) | IntLiteral(_) | FloatLiteral(_) | StrLiteral(_) => true, @@ -124,10 +125,22 @@ pub fn constrain_pattern( }, ); } + + NumLiteral(var, _) => { + state.vars.push(*var); + + state.constraints.push(Constraint::Pattern( + region, + PatternCategory::Num, + builtins::builtin_type(Symbol::NUM_NUM, vec![Type::Variable(*var)]), + expected, + )); + } + IntLiteral(_) => { state.constraints.push(Constraint::Pattern( region, - PatternCategory::Int, + PatternCategory::Float, builtins::builtin_type(Symbol::INT_INT, vec![]), expected, )); diff --git a/compiler/constrain/src/uniq.rs b/compiler/constrain/src/uniq.rs index e0d3556d65..9f3ba2871a 100644 --- a/compiler/constrain/src/uniq.rs +++ b/compiler/constrain/src/uniq.rs @@ -154,6 +154,10 @@ fn constrain_pattern( ); } + NumLiteral(_, _) => { + panic!("TODO uniq constraint for NumLiteral"); + } + IntLiteral(_) => { let (num_uvar, int_uvar, num_type) = unique_int(var_store); state.constraints.push(exists( @@ -339,6 +343,9 @@ pub fn constrain_expr( pub use roc_can::expr::Expr::*; match expr { + Num(_var, _) => { + panic!("TODO uniq::constrain_expr for Num literal"); + } Int(var, _) => { let (num_uvar, int_uvar, num_type) = unique_int(var_store); diff --git a/compiler/fmt/src/expr.rs b/compiler/fmt/src/expr.rs index dd5a3d0a71..107fc2d6cc 100644 --- a/compiler/fmt/src/expr.rs +++ b/compiler/fmt/src/expr.rs @@ -89,7 +89,7 @@ pub fn fmt_expr<'a>( } buf.push_str("\"\"\""); } - Int(string) | Float(string) | GlobalTag(string) | PrivateTag(string) => { + Num(string) | Float(string) | GlobalTag(string) | PrivateTag(string) => { buf.push_str(string) } NonBase10Int { @@ -432,7 +432,7 @@ pub fn is_multiline_pattern<'a>(pattern: &'a Pattern<'a>) -> bool { | Pattern::Apply(_, _) | Pattern::RecordDestructure(_) | Pattern::RecordField(_, _) - | Pattern::IntLiteral(_) + | Pattern::NumLiteral(_) | Pattern::NonBase10Literal { .. } | Pattern::FloatLiteral(_) | Pattern::StrLiteral(_) @@ -456,7 +456,7 @@ pub fn is_multiline_expr<'a>(expr: &'a Expr<'a>) -> bool { // These expressions never have newlines Float(_) - | Int(_) + | Num(_) | NonBase10Int { .. } | Str(_) | Access(_, _) diff --git a/compiler/fmt/src/pattern.rs b/compiler/fmt/src/pattern.rs index b7f06d2ce4..44a8cca814 100644 --- a/compiler/fmt/src/pattern.rs +++ b/compiler/fmt/src/pattern.rs @@ -56,7 +56,7 @@ pub fn fmt_pattern<'a>( fmt_pattern(buf, &loc_pattern.value, indent, true, only_comments); } - IntLiteral(string) => buf.push_str(string), + NumLiteral(string) => buf.push_str(string), NonBase10Literal { base, string, diff --git a/compiler/mono/src/expr.rs b/compiler/mono/src/expr.rs index 90a07c791f..ef1c2bdedf 100644 --- a/compiler/mono/src/expr.rs +++ b/compiler/mono/src/expr.rs @@ -141,8 +141,23 @@ fn from_can<'a>( use roc_can::pattern::Pattern::*; match can_expr { - Int(_, val) => Expr::Int(val), - Float(_, val) => Expr::Float(val), + Num(var, num) => match env.subs.get_without_compacting(var).content { + Content::Alias(Symbol::INT_INTEGER, args, _) => { + debug_assert!(args.len() == 0); + Expr::Int(num) + } + Content::FlexVar(_) => { + // If this was still a (Num *), assume compiling it to an Int + Expr::Int(num) + } + Content::Alias(Symbol::FLOAT_FLOATINGPOINT, args, _) => { + debug_assert!(args.len() == 0); + Expr::Float(num as f64) + } + other => panic!("Unrecognized Num type argument Content: {:?}", other), + }, + Int(_, num) => Expr::Int(num), + Float(_, num) => Expr::Float(num), Str(string) | BlockStr(string) => Expr::Str(env.arena.alloc(string)), Var(symbol) => Expr::Load(symbol), LetNonRec(def, ret_expr, _, _) => { @@ -353,7 +368,7 @@ fn from_can<'a>( elems: elems.into_bump_slice(), } } - other => panic!("TODO convert canonicalized {:?} to ll::Expr", other), + other => panic!("TODO convert canonicalized {:?} to mono::Expr", other), } } @@ -579,6 +594,16 @@ fn from_can_when<'a>( let mono_expr = from_can(env, loc_expr.value, procs, None); match &loc_when_pat.value { + NumLiteral(var, num) => { + panic!( + "TODO check if this var is an Int; if so, it's jumpable! var: {:?}, num: {:?}", + var, num + ); + // Switch only compares the condition to the + // alternatives based on their bit patterns, + // so casting from i64 to u64 makes no difference here. + // jumpable_branches.push((*int as u64, mono_expr)); + } IntLiteral(int) => { // Switch only compares the condition to the // alternatives based on their bit patterns, diff --git a/compiler/mono/src/layout.rs b/compiler/mono/src/layout.rs index ed2aa49fee..8fda76a46b 100644 --- a/compiler/mono/src/layout.rs +++ b/compiler/mono/src/layout.rs @@ -357,6 +357,10 @@ fn unwrap_num_tag<'a>(subs: &Subs, var: Variable) -> Result, ()> { debug_assert!(args.is_empty()); Ok(Layout::Builtin(Builtin::Float64)) } + Content::FlexVar(_) => { + // If this was still a (Num *) then default to compiling it to i64 + Ok(Layout::Builtin(Builtin::Int64)) + } other => { panic!("TODO non structure Num.@Num flat_type {:?}", other); } diff --git a/compiler/parse/src/ast.rs b/compiler/parse/src/ast.rs index aa130a1faa..42867a2dd9 100644 --- a/compiler/parse/src/ast.rs +++ b/compiler/parse/src/ast.rs @@ -93,7 +93,7 @@ pub struct WhenPattern<'a> { pub enum Expr<'a> { // Number Literals Float(&'a str), - Int(&'a str), + Num(&'a str), NonBase10Int { string: &'a str, base: Base, @@ -324,7 +324,7 @@ pub enum Pattern<'a> { Nested(&'a Pattern<'a>), // Literal - IntLiteral(&'a str), + NumLiteral(&'a str), NonBase10Literal { string: &'a str, base: Base, @@ -425,7 +425,7 @@ impl<'a> Pattern<'a> { (Nested(x), Nested(y)) => x.equivalent(y), // Literal - (IntLiteral(x), IntLiteral(y)) => x == y, + (NumLiteral(x), NumLiteral(y)) => x == y, ( NonBase10Literal { string: string_x, diff --git a/compiler/parse/src/expr.rs b/compiler/parse/src/expr.rs index 7ba7444988..dfaf6b1434 100644 --- a/compiler/parse/src/expr.rs +++ b/compiler/parse/src/expr.rs @@ -299,7 +299,7 @@ fn expr_to_pattern<'a>(arena: &'a Bump, expr: &Expr<'a>) -> Result, } Expr::Float(string) => Ok(Pattern::FloatLiteral(string)), - Expr::Int(string) => Ok(Pattern::IntLiteral(string)), + Expr::Num(string) => Ok(Pattern::NumLiteral(string)), Expr::NonBase10Int { string, base, @@ -551,7 +551,7 @@ fn annotation_or_alias<'a>( QualifiedIdentifier { .. } => { panic!("TODO gracefully handle trying to annotate a qualified identifier, e.g. `Foo.bar : ...`"); } - IntLiteral(_) + NumLiteral(_) | NonBase10Literal { .. } | FloatLiteral(_) | StrLiteral(_) diff --git a/compiler/parse/src/number_literal.rs b/compiler/parse/src/number_literal.rs index f14db3d145..64aeb676cd 100644 --- a/compiler/parse/src/number_literal.rs +++ b/compiler/parse/src/number_literal.rs @@ -36,7 +36,7 @@ where { use self::LiteralType::*; - let mut typ = Int; + let mut typ = Num; // We already parsed 1 character (which may have been a minus sign). let mut bytes_parsed = 1; @@ -71,8 +71,8 @@ where } else { return err_unexpected(); } - } else if next_ch == 'b' && typ == Int { - // We have to check for typ == Int because otherwise we get a false + } else if next_ch == 'b' && typ == Num { + // We have to check for typ == Num because otherwise we get a false // positive here when parsing a hex literal that happens to have // a 'b' in it, e.g. 0xbbbb if is_potentially_non_base10() { @@ -129,7 +129,7 @@ where // If the number is malformed (outside the supported range), // we'll succeed with an appropriate Expr which records that. let expr = match typ { - Int => Expr::Int(&state.input[0..bytes_parsed]), + Num => Expr::Num(&state.input[0..bytes_parsed]), Float => Expr::Float(&state.input[0..bytes_parsed]), // For these we trim off the 0x/0o/0b part Hex => from_base(Base::Hex), @@ -144,7 +144,7 @@ where #[derive(Debug, PartialEq, Eq)] enum LiteralType { - Int, + Num, Float, Hex, Octal, diff --git a/compiler/parse/tests/test_parse.rs b/compiler/parse/tests/test_parse.rs index 6e126c96a8..c66e250cc0 100644 --- a/compiler/parse/tests/test_parse.rs +++ b/compiler/parse/tests/test_parse.rs @@ -147,26 +147,26 @@ mod test_parse { #[test] fn zero_int() { - assert_parses_to("0", Int("0")); + assert_parses_to("0", Num("0")); } #[test] fn positive_int() { - assert_parses_to("1", Int("1")); - assert_parses_to("42", Int("42")); + assert_parses_to("1", Num("1")); + assert_parses_to("42", Num("42")); } #[test] fn negative_int() { - assert_parses_to("-1", Int("-1")); - assert_parses_to("-42", Int("-42")); + assert_parses_to("-1", Num("-1")); + assert_parses_to("-42", Num("-42")); } #[test] fn highest_int() { assert_parses_to( i64::MAX.to_string().as_str(), - Int(i64::MAX.to_string().as_str()), + Num(i64::MAX.to_string().as_str()), ); } @@ -174,24 +174,24 @@ mod test_parse { fn lowest_int() { assert_parses_to( i64::MIN.to_string().as_str(), - Int(i64::MIN.to_string().as_str()), + Num(i64::MIN.to_string().as_str()), ); } #[test] fn int_with_underscore() { - assert_parses_to("1_2_34_567", Int("1_2_34_567")); - assert_parses_to("-1_2_34_567", Int("-1_2_34_567")); + assert_parses_to("1_2_34_567", Num("1_2_34_567")); + assert_parses_to("-1_2_34_567", Num("-1_2_34_567")); // The following cases are silly. They aren't supported on purpose, // but there would be a performance cost to explicitly disallowing them, // which doesn't seem like it would benefit anyone. - assert_parses_to("1_", Int("1_")); - assert_parses_to("1__23", Int("1__23")); + assert_parses_to("1_", Num("1_")); + assert_parses_to("1__23", Num("1__23")); } #[quickcheck] fn all_i64_values_parse(num: i64) { - assert_parses_to(num.to_string().as_str(), Int(num.to_string().as_str())); + assert_parses_to(num.to_string().as_str(), Num(num.to_string().as_str())); } // FLOAT LITERALS @@ -262,12 +262,12 @@ mod test_parse { let label1 = LabeledValue( Located::new(0, 0, 16, 17, "x"), &[], - arena.alloc(Located::new(0, 0, 19, 20, Int("5"))), + arena.alloc(Located::new(0, 0, 19, 20, Num("5"))), ); let label2 = LabeledValue( Located::new(0, 0, 22, 23, "y"), &[], - arena.alloc(Located::new(0, 0, 25, 26, Int("0"))), + arena.alloc(Located::new(0, 0, 25, 26, Num("0"))), ); let fields = bumpalo::vec![in &arena; Located::new(0, 0, 16, 20, label1), @@ -293,9 +293,9 @@ mod test_parse { fn one_plus_two() { let arena = Bump::new(); let tuple = arena.alloc(( - Located::new(0, 0, 0, 1, Int("1")), + Located::new(0, 0, 0, 1, Num("1")), Located::new(0, 0, 1, 2, Plus), - Located::new(0, 0, 2, 3, Int("2")), + Located::new(0, 0, 2, 3, Num("2")), )); let expected = BinOp(tuple); let actual = parse_with(&arena, "1+2"); @@ -307,9 +307,9 @@ mod test_parse { fn one_minus_two() { let arena = Bump::new(); let tuple = arena.alloc(( - Located::new(0, 0, 0, 1, Int("1")), + Located::new(0, 0, 0, 1, Num("1")), Located::new(0, 0, 1, 2, Minus), - Located::new(0, 0, 2, 3, Int("2")), + Located::new(0, 0, 2, 3, Num("2")), )); let expected = BinOp(tuple); let actual = parse_with(&arena, "1-2"); @@ -321,9 +321,9 @@ mod test_parse { fn add_with_spaces() { let arena = Bump::new(); let tuple = arena.alloc(( - Located::new(0, 0, 0, 1, Int("1")), + Located::new(0, 0, 0, 1, Num("1")), Located::new(0, 0, 3, 4, Plus), - Located::new(0, 0, 7, 8, Int("2")), + Located::new(0, 0, 7, 8, Num("2")), )); let expected = BinOp(tuple); let actual = parse_with(&arena, "1 + 2"); @@ -335,9 +335,9 @@ mod test_parse { fn sub_with_spaces() { let arena = Bump::new(); let tuple = arena.alloc(( - Located::new(0, 0, 0, 1, Int("1")), + Located::new(0, 0, 0, 1, Num("1")), Located::new(0, 0, 3, 4, Minus), - Located::new(0, 0, 7, 8, Int("2")), + Located::new(0, 0, 7, 8, Num("2")), )); let expected = BinOp(tuple); let actual = parse_with(&arena, "1 - 2"); @@ -360,7 +360,7 @@ mod test_parse { let tuple = arena.alloc(( Located::new(0, 0, 0, 1, var), Located::new(0, 0, 2, 3, Plus), - Located::new(0, 0, 4, 5, Int("2")), + Located::new(0, 0, 4, 5, Num("2")), )); let expected = BinOp(tuple); let actual = parse_with(&arena, "x + 2"); @@ -382,7 +382,7 @@ mod test_parse { let tuple = arena.alloc(( Located::new(0, 0, 0, 1, var), Located::new(0, 0, 2, 3, Minus), - Located::new(0, 0, 4, 5, Int("2")), + Located::new(0, 0, 4, 5, Num("2")), )); let expected = BinOp(tuple); let actual = parse_with(&arena, "x - 2"); @@ -394,13 +394,13 @@ mod test_parse { fn newline_before_add() { let arena = Bump::new(); let spaced_int = Expr::SpaceAfter( - arena.alloc(Int("3")), + arena.alloc(Num("3")), bumpalo::vec![in &arena; Newline].into_bump_slice(), ); let tuple = arena.alloc(( Located::new(0, 0, 0, 1, spaced_int), Located::new(1, 1, 0, 1, Plus), - Located::new(1, 1, 2, 3, Int("4")), + Located::new(1, 1, 2, 3, Num("4")), )); let expected = BinOp(tuple); let actual = parse_with(&arena, "3 \n+ 4"); @@ -412,13 +412,13 @@ mod test_parse { fn newline_before_sub() { let arena = Bump::new(); let spaced_int = Expr::SpaceAfter( - arena.alloc(Int("3")), + arena.alloc(Num("3")), bumpalo::vec![in &arena; Newline].into_bump_slice(), ); let tuple = arena.alloc(( Located::new(0, 0, 0, 1, spaced_int), Located::new(1, 1, 0, 1, Minus), - Located::new(1, 1, 2, 3, Int("4")), + Located::new(1, 1, 2, 3, Num("4")), )); let expected = BinOp(tuple); let actual = parse_with(&arena, "3 \n- 4"); @@ -430,10 +430,10 @@ mod test_parse { fn newline_after_mul() { let arena = Bump::new(); let spaced_int = arena - .alloc(Int("4")) + .alloc(Num("4")) .before(bumpalo::vec![in &arena; Newline].into_bump_slice()); let tuple = arena.alloc(( - Located::new(0, 0, 0, 1, Int("3")), + Located::new(0, 0, 0, 1, Num("3")), Located::new(0, 0, 3, 4, Star), Located::new(1, 1, 2, 3, spaced_int), )); @@ -447,10 +447,10 @@ mod test_parse { fn newline_after_sub() { let arena = Bump::new(); let spaced_int = arena - .alloc(Int("4")) + .alloc(Num("4")) .before(bumpalo::vec![in &arena; Newline].into_bump_slice()); let tuple = arena.alloc(( - Located::new(0, 0, 0, 1, Int("3")), + Located::new(0, 0, 0, 1, Num("3")), Located::new(0, 0, 3, 4, Minus), Located::new(1, 1, 2, 3, spaced_int), )); @@ -464,12 +464,12 @@ mod test_parse { fn comment_with_unicode() { let arena = Bump::new(); let spaced_int = arena - .alloc(Int("3")) + .alloc(Num("3")) .after(bumpalo::vec![in &arena; LineComment(" 2 × 2")].into_bump_slice()); let tuple = arena.alloc(( Located::new(0, 0, 0, 1, spaced_int), Located::new(1, 1, 0, 1, Plus), - Located::new(1, 1, 2, 3, Int("4")), + Located::new(1, 1, 2, 3, Num("4")), )); let expected = BinOp(tuple); let actual = parse_with(&arena, "3 # 2 × 2\n+ 4"); @@ -481,12 +481,12 @@ mod test_parse { fn comment_before_op() { let arena = Bump::new(); let spaced_int = arena - .alloc(Int("3")) + .alloc(Num("3")) .after(bumpalo::vec![in &arena; LineComment(" test!")].into_bump_slice()); let tuple = arena.alloc(( Located::new(0, 0, 0, 1, spaced_int), Located::new(1, 1, 0, 1, Plus), - Located::new(1, 1, 2, 3, Int("4")), + Located::new(1, 1, 2, 3, Num("4")), )); let expected = BinOp(tuple); let actual = parse_with(&arena, "3 # test!\n+ 4"); @@ -498,10 +498,10 @@ mod test_parse { fn comment_after_op() { let arena = Bump::new(); let spaced_int = arena - .alloc(Int("92")) + .alloc(Num("92")) .before(bumpalo::vec![in &arena; LineComment(" test!")].into_bump_slice()); let tuple = arena.alloc(( - Located::new(0, 0, 0, 2, Int("12")), + Located::new(0, 0, 0, 2, Num("12")), Located::new(0, 0, 4, 5, Star), Located::new(1, 1, 1, 3, spaced_int), )); @@ -515,10 +515,10 @@ mod test_parse { fn ops_with_newlines() { let arena = Bump::new(); let spaced_int1 = arena - .alloc(Int("3")) + .alloc(Num("3")) .after(bumpalo::vec![in &arena; Newline].into_bump_slice()); let spaced_int2 = arena - .alloc(Int("4")) + .alloc(Num("4")) .before(bumpalo::vec![in &arena; Newline, Newline].into_bump_slice()); let tuple = arena.alloc(( Located::new(0, 0, 0, 1, spaced_int1), @@ -559,9 +559,9 @@ mod test_parse { fn minus_twelve_minus_five() { let arena = Bump::new(); let tuple = arena.alloc(( - Located::new(0, 0, 0, 3, Int("-12")), + Located::new(0, 0, 0, 3, Num("-12")), Located::new(0, 0, 3, 4, Minus), - Located::new(0, 0, 4, 5, Int("5")), + Located::new(0, 0, 4, 5, Num("5")), )); let expected = BinOp(tuple); let actual = parse_with(&arena, "-12-5"); @@ -573,9 +573,9 @@ mod test_parse { fn ten_times_eleven() { let arena = Bump::new(); let tuple = arena.alloc(( - Located::new(0, 0, 0, 2, Int("10")), + Located::new(0, 0, 0, 2, Num("10")), Located::new(0, 0, 2, 3, Star), - Located::new(0, 0, 3, 5, Int("11")), + Located::new(0, 0, 3, 5, Num("11")), )); let expected = BinOp(tuple); let actual = parse_with(&arena, "10*11"); @@ -587,12 +587,12 @@ mod test_parse { fn multiple_operators() { let arena = Bump::new(); let inner = arena.alloc(( - Located::new(0, 0, 3, 5, Int("42")), + Located::new(0, 0, 3, 5, Num("42")), Located::new(0, 0, 5, 6, Plus), - Located::new(0, 0, 6, 9, Int("534")), + Located::new(0, 0, 6, 9, Num("534")), )); let outer = arena.alloc(( - Located::new(0, 0, 0, 2, Int("31")), + Located::new(0, 0, 0, 2, Num("31")), Located::new(0, 0, 2, 3, Star), Located::new(0, 0, 3, 9, BinOp(inner)), )); @@ -707,8 +707,8 @@ mod test_parse { #[test] fn apply_private_tag() { let arena = Bump::new(); - let arg1 = arena.alloc(Located::new(0, 0, 6, 8, Int("12"))); - let arg2 = arena.alloc(Located::new(0, 0, 9, 11, Int("34"))); + let arg1 = arena.alloc(Located::new(0, 0, 6, 8, Num("12"))); + let arg2 = arena.alloc(Located::new(0, 0, 9, 11, Num("34"))); let args = bumpalo::vec![in &arena; &*arg1, &*arg2]; let expected = Expr::Apply( arena.alloc(Located::new(0, 0, 0, 5, Expr::PrivateTag("@Whee"))), @@ -723,8 +723,8 @@ mod test_parse { #[test] fn apply_global_tag() { let arena = Bump::new(); - let arg1 = arena.alloc(Located::new(0, 0, 5, 7, Int("12"))); - let arg2 = arena.alloc(Located::new(0, 0, 8, 10, Int("34"))); + let arg1 = arena.alloc(Located::new(0, 0, 5, 7, Num("12"))); + let arg2 = arena.alloc(Located::new(0, 0, 8, 10, Num("34"))); let args = bumpalo::vec![in &arena; &*arg1, &*arg2]; let expected = Expr::Apply( arena.alloc(Located::new(0, 0, 0, 4, Expr::GlobalTag("Whee"))), @@ -739,8 +739,8 @@ mod test_parse { #[test] fn apply_parenthetical_global_tag_args() { let arena = Bump::new(); - let int1 = ParensAround(arena.alloc(Int("12"))); - let int2 = ParensAround(arena.alloc(Int("34"))); + let int1 = ParensAround(arena.alloc(Num("12"))); + let int2 = ParensAround(arena.alloc(Num("34"))); let arg1 = arena.alloc(Located::new(0, 0, 6, 8, int1)); let arg2 = arena.alloc(Located::new(0, 0, 11, 13, int2)); let args = bumpalo::vec![in &arena; &*arg1, &*arg2]; @@ -780,7 +780,7 @@ mod test_parse { let patterns = bumpalo::vec![in &arena; pattern]; let expected = Closure( arena.alloc(patterns), - arena.alloc(Located::new(0, 0, 10, 12, Int("42"))), + arena.alloc(Located::new(0, 0, 10, 12, Num("42"))), ); let actual = parse_with(&arena, "\\Thing -> 42"); @@ -822,7 +822,7 @@ mod test_parse { #[test] fn packed_singleton_list() { let arena = Bump::new(); - let elems = bumpalo::vec![in &arena; &*arena.alloc(Located::new(0, 0, 1, 2, Int("1")))]; + let elems = bumpalo::vec![in &arena; &*arena.alloc(Located::new(0, 0, 1, 2, Num("1")))]; let expected = List(elems); let actual = parse_with(&arena, "[1]"); @@ -832,7 +832,7 @@ mod test_parse { #[test] fn spaced_singleton_list() { let arena = Bump::new(); - let elems = bumpalo::vec![in &arena; &*arena.alloc(Located::new(0, 0, 2, 3, Int("1")))]; + let elems = bumpalo::vec![in &arena; &*arena.alloc(Located::new(0, 0, 2, 3, Num("1")))]; let expected = List(elems); let actual = parse_with(&arena, "[ 1 ]"); @@ -917,7 +917,7 @@ mod test_parse { #[test] fn basic_apply() { let arena = Bump::new(); - let arg = arena.alloc(Located::new(0, 0, 5, 6, Int("1"))); + let arg = arena.alloc(Located::new(0, 0, 5, 6, Num("1"))); let args = bumpalo::vec![in &arena; &*arg]; let expected = Expr::Apply( arena.alloc(Located::new( @@ -941,8 +941,8 @@ mod test_parse { #[test] fn apply_two_args() { let arena = Bump::new(); - let arg1 = arena.alloc(Located::new(0, 0, 6, 8, Int("12"))); - let arg2 = arena.alloc(Located::new(0, 0, 10, 12, Int("34"))); + let arg1 = arena.alloc(Located::new(0, 0, 6, 8, Num("12"))); + let arg2 = arena.alloc(Located::new(0, 0, 10, 12, Num("34"))); let args = bumpalo::vec![in &arena; &*arg1, &*arg2]; let expected = Expr::Apply( arena.alloc(Located::new( @@ -1019,7 +1019,7 @@ mod test_parse { #[test] fn parenthetical_apply() { let arena = Bump::new(); - let arg = arena.alloc(Located::new(0, 0, 7, 8, Int("1"))); + let arg = arena.alloc(Located::new(0, 0, 7, 8, Num("1"))); let args = bumpalo::vec![in &arena; &*arg]; let parens_var = Expr::ParensAround(arena.alloc(Var { module_name: "", @@ -1080,7 +1080,7 @@ mod test_parse { #[test] fn apply_unary_negation() { let arena = Bump::new(); - let arg1 = arena.alloc(Located::new(0, 0, 7, 9, Int("12"))); + let arg1 = arena.alloc(Located::new(0, 0, 7, 9, Num("12"))); let loc_op = Located::new(0, 0, 0, 1, UnaryOp::Negate); let arg2 = arena.alloc(Located::new( 0, @@ -1116,7 +1116,7 @@ mod test_parse { #[test] fn apply_unary_not() { let arena = Bump::new(); - let arg1 = arena.alloc(Located::new(0, 0, 7, 9, Int("12"))); + let arg1 = arena.alloc(Located::new(0, 0, 7, 9, Num("12"))); let loc_op = Located::new(0, 0, 0, 1, UnaryOp::Not); let arg2 = arena.alloc(Located::new( 0, @@ -1152,7 +1152,7 @@ mod test_parse { #[test] fn unary_negation_with_parens() { let arena = Bump::new(); - let arg1 = arena.alloc(Located::new(0, 0, 8, 10, Int("12"))); + let arg1 = arena.alloc(Located::new(0, 0, 8, 10, Num("12"))); let loc_op = Located::new(0, 0, 0, 1, UnaryOp::Negate); let arg2 = arena.alloc(Located::new( 0, @@ -1188,7 +1188,7 @@ mod test_parse { #[test] fn unary_not_with_parens() { let arena = Bump::new(); - let arg1 = arena.alloc(Located::new(0, 0, 8, 10, Int("12"))); + let arg1 = arena.alloc(Located::new(0, 0, 8, 10, Num("12"))); let loc_op = Located::new(0, 0, 0, 1, UnaryOp::Not); let arg2 = arena.alloc(Located::new( 0, @@ -1224,7 +1224,7 @@ mod test_parse { #[test] fn unary_negation_arg() { let arena = Bump::new(); - let arg1 = arena.alloc(Located::new(0, 0, 6, 8, Int("12"))); + let arg1 = arena.alloc(Located::new(0, 0, 6, 8, Num("12"))); let loc_op = Located::new(0, 0, 9, 10, UnaryOp::Negate); let var1 = Var { module_name: "", @@ -1257,7 +1257,7 @@ mod test_parse { let patterns = bumpalo::vec![in &arena; pattern]; let expected = Closure( arena.alloc(patterns), - arena.alloc(Located::new(0, 0, 6, 8, Int("42"))), + arena.alloc(Located::new(0, 0, 6, 8, Num("42"))), ); let actual = parse_with(&arena, "\\a -> 42"); @@ -1271,7 +1271,7 @@ mod test_parse { let patterns = bumpalo::vec![in &arena; pattern]; let expected = Closure( arena.alloc(patterns), - arena.alloc(Located::new(0, 0, 6, 8, Int("42"))), + arena.alloc(Located::new(0, 0, 6, 8, Num("42"))), ); let actual = parse_with(&arena, "\\_ -> 42"); @@ -1297,7 +1297,7 @@ mod test_parse { let patterns = bumpalo::vec![in &arena; arg1, arg2]; let expected = Closure( arena.alloc(patterns), - arena.alloc(Located::new(0, 0, 9, 11, Int("42"))), + arena.alloc(Located::new(0, 0, 9, 11, Num("42"))), ); let actual = parse_with(&arena, "\\a, b -> 42"); @@ -1313,7 +1313,7 @@ mod test_parse { let patterns = bumpalo::vec![in &arena; arg1, arg2, arg3]; let expected = Closure( arena.alloc(patterns), - arena.alloc(Located::new(0, 0, 12, 14, Int("42"))), + arena.alloc(Located::new(0, 0, 12, 14, Num("42"))), ); let actual = parse_with(&arena, "\\a, b, c -> 42"); @@ -1328,7 +1328,7 @@ mod test_parse { let patterns = bumpalo::vec![in &arena; underscore1, underscore2]; let expected = Closure( arena.alloc(patterns), - arena.alloc(Located::new(0, 0, 9, 11, Int("42"))), + arena.alloc(Located::new(0, 0, 9, 11, Num("42"))), ); let actual = parse_with(&arena, "\\_, _ -> 42"); @@ -1343,11 +1343,11 @@ mod test_parse { let newlines = bumpalo::vec![in &arena; Newline, Newline]; let def = Def::Body( arena.alloc(Located::new(1, 1, 0, 1, Identifier("x"))), - arena.alloc(Located::new(1, 1, 2, 3, Int("5"))), + arena.alloc(Located::new(1, 1, 2, 3, Num("5"))), ); let loc_def = &*arena.alloc(Located::new(1, 1, 0, 1, def)); let defs = bumpalo::vec![in &arena; loc_def]; - let ret = Expr::SpaceBefore(arena.alloc(Int("42")), newlines.into_bump_slice()); + let ret = Expr::SpaceBefore(arena.alloc(Num("42")), newlines.into_bump_slice()); let loc_ret = Located::new(3, 3, 0, 2, ret); let reset_indentation = bumpalo::vec![in &arena; LineComment(" leading comment")]; let expected = Expr::SpaceBefore( @@ -1373,11 +1373,11 @@ mod test_parse { let newlines = bumpalo::vec![in &arena; Newline, Newline]; let def = Def::Body( arena.alloc(Located::new(1, 1, 0, 1, Identifier("x"))), - arena.alloc(Located::new(1, 1, 4, 5, Int("5"))), + arena.alloc(Located::new(1, 1, 4, 5, Num("5"))), ); let loc_def = &*arena.alloc(Located::new(1, 1, 0, 1, def)); let defs = bumpalo::vec![in &arena; loc_def]; - let ret = Expr::SpaceBefore(arena.alloc(Int("42")), newlines.into_bump_slice()); + let ret = Expr::SpaceBefore(arena.alloc(Num("42")), newlines.into_bump_slice()); let loc_ret = Located::new(3, 3, 0, 2, ret); let reset_indentation = bumpalo::vec![in &arena; LineComment(" leading comment")]; let expected = Expr::SpaceBefore( @@ -1404,13 +1404,13 @@ mod test_parse { let newline = bumpalo::vec![in &arena; Newline]; let def1 = Def::Body( arena.alloc(Located::new(1, 1, 0, 1, Identifier("x"))), - arena.alloc(Located::new(1, 1, 4, 5, Int("5"))), + arena.alloc(Located::new(1, 1, 4, 5, Num("5"))), ); let loc_def1 = &*arena.alloc(Located::new(1, 1, 0, 1, def1)); let def2 = Def::SpaceBefore( &*arena.alloc(Def::Body( arena.alloc(Located::new(2, 2, 0, 1, Identifier("y"))), - arena.alloc(Located::new(2, 2, 4, 5, Int("6"))), + arena.alloc(Located::new(2, 2, 4, 5, Num("6"))), )), newline.into_bump_slice(), ); @@ -1419,7 +1419,7 @@ mod test_parse { // gets added by .push(), since that's more efficient and since // canonicalization is going to re-sort these all anyway.) let defs = bumpalo::vec![in &arena; loc_def2, loc_def1]; - let ret = Expr::SpaceBefore(arena.alloc(Int("42")), newlines.into_bump_slice()); + let ret = Expr::SpaceBefore(arena.alloc(Num("42")), newlines.into_bump_slice()); let loc_ret = Located::new(4, 4, 0, 2, ret); let reset_indentation = bumpalo::vec![in &arena; LineComment(" leading comment")]; let expected = Expr::SpaceBefore( @@ -1457,13 +1457,13 @@ mod test_parse { 8, RecordDestructure(fields.into_bump_slice()), )), - arena.alloc(Located::new(1, 1, 11, 12, Int("5"))), + arena.alloc(Located::new(1, 1, 11, 12, Num("5"))), ); let loc_def1 = &*arena.alloc(Located::new(1, 1, 1, 8, def1)); let def2 = Def::SpaceBefore( &*arena.alloc(Def::Body( arena.alloc(Located::new(2, 2, 0, 1, Identifier("y"))), - arena.alloc(Located::new(2, 2, 4, 5, Int("6"))), + arena.alloc(Located::new(2, 2, 4, 5, Num("6"))), )), newline.into_bump_slice(), ); @@ -1472,7 +1472,7 @@ mod test_parse { // gets added by .push(), since that's more efficient and since // canonicalization is going to re-sort these all anyway.) let defs = bumpalo::vec![in &arena; loc_def2, loc_def1]; - let ret = Expr::SpaceBefore(arena.alloc(Int("42")), newlines.into_bump_slice()); + let ret = Expr::SpaceBefore(arena.alloc(Num("42")), newlines.into_bump_slice()); let loc_ret = Located::new(4, 4, 0, 2, ret); let reset_indentation = bumpalo::vec![in &arena; LineComment(" leading comment")]; let expected = Expr::SpaceBefore( @@ -1505,14 +1505,14 @@ mod test_parse { ); let def = Def::Body( arena.alloc(Located::new(1, 1, 0, 3, Identifier("foo"))), - arena.alloc(Located::new(1, 1, 6, 7, Int("4"))), + arena.alloc(Located::new(1, 1, 6, 7, Num("4"))), ); let spaced_def = Def::SpaceBefore(arena.alloc(def), newline.into_bump_slice()); let loc_def = &*arena.alloc(Located::new(1, 1, 0, 7, spaced_def)); let loc_ann = &*arena.alloc(Located::new(0, 0, 0, 3, signature)); let defs = bumpalo::vec![in &arena; loc_ann, loc_def]; - let ret = Expr::SpaceBefore(arena.alloc(Int("42")), newlines.into_bump_slice()); + let ret = Expr::SpaceBefore(arena.alloc(Num("42")), newlines.into_bump_slice()); let loc_ret = Located::new(3, 3, 0, 2, ret); let expected = Defs(defs, arena.alloc(loc_ret)); @@ -1552,7 +1552,7 @@ mod test_parse { let loc_ann = &*arena.alloc(Located::new(0, 0, 0, 3, signature)); let defs = bumpalo::vec![in &arena; loc_ann]; - let ret = Expr::SpaceBefore(arena.alloc(Int("42")), newlines.into_bump_slice()); + let ret = Expr::SpaceBefore(arena.alloc(Num("42")), newlines.into_bump_slice()); let loc_ret = Located::new(2, 2, 0, 2, ret); let expected = Defs(defs, arena.alloc(loc_ret)); @@ -1588,7 +1588,7 @@ mod test_parse { let loc_ann = &*arena.alloc(Located::new(0, 0, 0, 4, signature)); let defs = bumpalo::vec![in &arena; loc_ann]; - let ret = Expr::SpaceBefore(arena.alloc(Int("42")), newlines.into_bump_slice()); + let ret = Expr::SpaceBefore(arena.alloc(Num("42")), newlines.into_bump_slice()); let loc_ret = Located::new(2, 2, 0, 2, ret); let expected = Defs(defs, arena.alloc(loc_ret)); @@ -1630,7 +1630,7 @@ mod test_parse { Located::new(1,1,7,8, Identifier("x")), Located::new(1,1,10,11, Underscore) ]; - let body = Located::new(1, 1, 15, 17, Int("42")); + let body = Located::new(1, 1, 15, 17, Num("42")); let closure = Expr::Closure(&args, &body); @@ -1643,7 +1643,7 @@ mod test_parse { let loc_ann = &*arena.alloc(Located::new(0, 0, 0, 3, signature)); let defs = bumpalo::vec![in &arena; loc_ann, loc_def]; - let ret = Expr::SpaceBefore(arena.alloc(Int("42")), newlines.into_bump_slice()); + let ret = Expr::SpaceBefore(arena.alloc(Num("42")), newlines.into_bump_slice()); let loc_ret = Located::new(3, 3, 0, 2, ret); let expected = Defs(defs, arena.alloc(loc_ret)); @@ -1698,7 +1698,7 @@ mod test_parse { let loc_ann = &*arena.alloc(Located::new(0, 0, 0, 3, signature)); let defs = bumpalo::vec![in &arena; loc_ann, loc_def]; - let ret = Expr::SpaceBefore(arena.alloc(Int("42")), newlines.into_bump_slice()); + let ret = Expr::SpaceBefore(arena.alloc(Num("42")), newlines.into_bump_slice()); let loc_ret = Located::new(3, 3, 0, 2, ret); let expected = Defs(defs, arena.alloc(loc_ret)); @@ -1751,7 +1751,7 @@ mod test_parse { let loc_ann = &*arena.alloc(Located::new(0, 0, 0, 3, signature)); let defs = bumpalo::vec![in &arena; loc_ann, loc_def]; - let ret = Expr::SpaceBefore(arena.alloc(Int("42")), newlines.into_bump_slice()); + let ret = Expr::SpaceBefore(arena.alloc(Num("42")), newlines.into_bump_slice()); let loc_ret = Located::new(3, 3, 0, 2, ret); let expected = Defs(defs, arena.alloc(loc_ret)); @@ -1805,7 +1805,7 @@ mod test_parse { let loc_ann = &*arena.alloc(Located::new(0, 0, 0, 3, signature)); let defs = bumpalo::vec![in &arena; loc_ann, loc_def]; - let ret = Expr::SpaceBefore(arena.alloc(Int("42")), newlines.into_bump_slice()); + let ret = Expr::SpaceBefore(arena.alloc(Num("42")), newlines.into_bump_slice()); let loc_ret = Located::new(3, 3, 0, 2, ret); let expected = Defs(defs, arena.alloc(loc_ret)); @@ -1858,7 +1858,7 @@ mod test_parse { let loc_ann = &*arena.alloc(Located::new(0, 0, 0, 3, signature)); let defs = bumpalo::vec![in &arena; loc_ann, loc_def]; - let ret = Expr::SpaceBefore(arena.alloc(Int("42")), newlines.into_bump_slice()); + let ret = Expr::SpaceBefore(arena.alloc(Num("42")), newlines.into_bump_slice()); let loc_ret = Located::new(3, 3, 0, 2, ret); let expected = Defs(defs, arena.alloc(loc_ret)); @@ -1884,7 +1884,7 @@ mod test_parse { let pattern1 = Pattern::SpaceBefore(arena.alloc(StrLiteral("blah")), newlines.into_bump_slice()); let loc_pattern1 = Located::new(1, 1, 1, 7, pattern1); - let expr1 = Int("1"); + let expr1 = Num("1"); let loc_expr1 = Located::new(1, 1, 11, 12, expr1); let branch1 = &*arena.alloc(WhenBranch { patterns: bumpalo::vec![in &arena;loc_pattern1], @@ -1895,7 +1895,7 @@ mod test_parse { let pattern2 = Pattern::SpaceBefore(arena.alloc(StrLiteral("mise")), newlines.into_bump_slice()); let loc_pattern2 = Located::new(2, 2, 1, 7, pattern2); - let expr2 = Int("2"); + let expr2 = Num("2"); let loc_expr2 = Located::new(2, 2, 11, 12, expr2); let branch2 = &*arena.alloc(WhenBranch { patterns: bumpalo::vec![in &arena;loc_pattern2 ], @@ -1928,9 +1928,9 @@ mod test_parse { let arena = Bump::new(); let newlines = bumpalo::vec![in &arena; Newline]; let pattern1 = - Pattern::SpaceBefore(arena.alloc(IntLiteral("1")), newlines.into_bump_slice()); + Pattern::SpaceBefore(arena.alloc(NumLiteral("1")), newlines.into_bump_slice()); let loc_pattern1 = Located::new(1, 1, 1, 2, pattern1); - let expr1 = Int("2"); + let expr1 = Num("2"); let loc_expr1 = Located::new(1, 1, 6, 7, expr1); let branch1 = &*arena.alloc(WhenBranch { patterns: bumpalo::vec![in &arena;loc_pattern1], @@ -1939,9 +1939,9 @@ mod test_parse { }); let newlines = bumpalo::vec![in &arena; Newline]; let pattern2 = - Pattern::SpaceBefore(arena.alloc(IntLiteral("3")), newlines.into_bump_slice()); + Pattern::SpaceBefore(arena.alloc(NumLiteral("3")), newlines.into_bump_slice()); let loc_pattern2 = Located::new(2, 2, 1, 2, pattern2); - let expr2 = Int("4"); + let expr2 = Num("4"); let loc_expr2 = Located::new(2, 2, 6, 7, expr2); let branch2 = &*arena.alloc(WhenBranch { patterns: bumpalo::vec![in &arena;loc_pattern2], @@ -1979,7 +1979,7 @@ mod test_parse { newlines.into_bump_slice(), ); let loc_pattern1 = Located::new(1, 1, 1, 6, pattern1); - let expr1 = Int("2"); + let expr1 = Num("2"); let loc_expr1 = Located::new(1, 1, 10, 11, expr1); let branch1 = &*arena.alloc(WhenBranch { patterns: bumpalo::vec![in &arena;loc_pattern1 ], @@ -1993,7 +1993,7 @@ mod test_parse { newlines.into_bump_slice(), ); let loc_pattern2 = Located::new(2, 2, 1, 9, pattern2); - let expr2 = Int("4"); + let expr2 = Num("4"); let loc_expr2 = Located::new(2, 2, 13, 14, expr2); let branch2 = &*arena.alloc(WhenBranch { patterns: bumpalo::vec![in &arena;loc_pattern2 ], @@ -2030,7 +2030,7 @@ mod test_parse { let pattern1_alt = StrLiteral("blop"); let loc_pattern1 = Located::new(1, 1, 1, 7, pattern1); let loc_pattern1_alt = Located::new(1, 1, 10, 16, pattern1_alt); - let expr1 = Int("1"); + let expr1 = Num("1"); let loc_expr1 = Located::new(1, 1, 20, 21, expr1); let branch1 = &*arena.alloc(WhenBranch { patterns: bumpalo::vec![in &arena;loc_pattern1, loc_pattern1_alt], @@ -2045,7 +2045,7 @@ mod test_parse { Pattern::SpaceBefore(arena.alloc(StrLiteral("bar")), newlines.into_bump_slice()); let loc_pattern2 = Located::new(2, 2, 1, 6, pattern2); let loc_pattern2_alt = Located::new(3, 3, 1, 6, pattern2_alt); - let expr2 = Int("2"); + let expr2 = Num("2"); let loc_expr2 = Located::new(3, 3, 10, 11, expr2); let branch2 = &*arena.alloc(WhenBranch { patterns: bumpalo::vec![in &arena;loc_pattern2, loc_pattern2_alt], @@ -2148,7 +2148,7 @@ mod test_parse { let def1 = SpaceAfter( arena.alloc(Body( arena.alloc(Located::new(0, 0, 0, 3, pattern1)), - arena.alloc(Located::new(0, 0, 6, 7, Int("1"))), + arena.alloc(Located::new(0, 0, 6, 7, Num("1"))), )), newlines1.into_bump_slice(), ); diff --git a/compiler/solve/tests/test_solve.rs b/compiler/solve/tests/test_solve.rs index 1776fb916b..abf87a8849 100644 --- a/compiler/solve/tests/test_solve.rs +++ b/compiler/solve/tests/test_solve.rs @@ -69,7 +69,7 @@ mod test_solve { #[test] fn int_literal() { - infer_eq("5", "Int"); + infer_eq("5", "Num *"); } #[test] @@ -188,7 +188,7 @@ mod test_solve { [42] "# ), - "List Int", + "List (Num *)", ); } @@ -200,7 +200,7 @@ mod test_solve { [[[ 5 ]]] "# ), - "List (List (List Int))", + "List (List (List (Num *)))", ); } @@ -212,7 +212,7 @@ mod test_solve { [ 1, 2, 3 ] "# ), - "List Int", + "List (Num *)", ); } @@ -224,7 +224,7 @@ mod test_solve { [ [ 1 ], [ 2, 3 ] ] "# ), - "List (List Int)", + "List (List (Num *))", ); } @@ -340,7 +340,7 @@ mod test_solve { \_, _ -> 42 "# ), - "*, * -> Int", + "*, * -> Num *", ); } @@ -410,7 +410,7 @@ mod test_solve { func "# ), - "*, * -> Int", + "*, * -> Num *", ); } @@ -474,7 +474,7 @@ mod test_solve { c "# ), - "Int", + "Num *", ); } @@ -509,7 +509,7 @@ mod test_solve { alwaysFive "stuff" "# ), - "Int", + "Num *", ); } @@ -556,7 +556,7 @@ mod test_solve { x "# ), - "Int", + "Num *", ); } @@ -570,7 +570,7 @@ mod test_solve { enlist 5 "# ), - "List Int", + "List (Num *)", ); } @@ -597,7 +597,7 @@ mod test_solve { 1 |> (\a -> a) "# ), - "Int", + "Num *", ); } @@ -611,7 +611,7 @@ mod test_solve { 1 |> always "foo" "# ), - "Int", + "Num *", ); } @@ -676,7 +676,7 @@ mod test_solve { apply identity 5 "# ), - "Int", + "Num *", ); } @@ -705,7 +705,7 @@ mod test_solve { // flip neverendingInt // "# // ), - // "(Int, (a -> a)) -> Int", + // "(Num *, (a -> a)) -> Num *", // ); // } @@ -779,7 +779,7 @@ mod test_solve { // 1 // 2 // "# // ), - // "Int", + // "Num *", // ); // } @@ -791,7 +791,7 @@ mod test_solve { // 1 + 2 // "# // ), - // "Int", + // "Num *", // ); // } @@ -835,12 +835,12 @@ mod test_solve { infer_eq( indoc!( r#" - alwaysFive = \_ -> 5 + alwaysFive = \_ -> 5 - [ alwaysFive "foo", alwaysFive [] ] - "# + [ alwaysFive "foo", alwaysFive [] ] + "# ), - "List Int", + "List (Num *)", ); } @@ -855,7 +855,7 @@ mod test_solve { 24 "# ), - "Int", + "Num *", ); } @@ -869,7 +869,7 @@ mod test_solve { 3 -> 4 "# ), - "Int", + "Num *", ); } @@ -882,17 +882,17 @@ mod test_solve { #[test] fn one_field_record() { - infer_eq("{ x: 5 }", "{ x : Int }"); + infer_eq("{ x: 5 }", "{ x : Num * }"); } #[test] fn two_field_record() { - infer_eq("{ x: 5, y : 3.14 }", "{ x : Int, y : Float }"); + infer_eq("{ x: 5, y : 3.14 }", "{ x : Num *, y : Float }"); } #[test] fn record_literal_accessor() { - infer_eq("{ x: 5, y : 3.14 }.x", "Int"); + infer_eq("{ x: 5, y : 3.14 }.x", "Num *"); } #[test] @@ -951,7 +951,7 @@ mod test_solve { infer_eq( indoc!( r#" - foo : Int -> custom + foo : Num * -> custom foo 2 "# @@ -1017,9 +1017,9 @@ mod test_solve { infer_eq( indoc!( r#" - user = { year: "foo", name: "Sam" } + user = { year: "foo", name: "Sam" } - { user & year: "foo" } + { user & year: "foo" } "# ), "{ name : Str, year : Str }", @@ -1030,7 +1030,8 @@ mod test_solve { fn bare_tag() { infer_eq( indoc!( - r#"Foo + r#" + Foo "# ), "[ Foo ]*", @@ -1041,10 +1042,11 @@ mod test_solve { fn single_tag_pattern() { infer_eq( indoc!( - r#"\Foo -> 42 + r#" + \Foo -> 42 "# ), - "[ Foo ]* -> Int", + "[ Foo ]* -> Num *", ); } @@ -1052,10 +1054,11 @@ mod test_solve { fn single_private_tag_pattern() { infer_eq( indoc!( - r#"\@Foo -> 42 + r#" + \@Foo -> 42 "# ), - "[ @Foo ]* -> Int", + "[ @Foo ]* -> Num *", ); } @@ -1063,13 +1066,14 @@ mod test_solve { fn two_tag_pattern() { infer_eq( indoc!( - r#"\x -> - when x is - True -> 1 - False -> 0 + r#" + \x -> + when x is + True -> 1 + False -> 0 "# ), - "[ False, True ]* -> Int", + "[ False, True ]* -> Num *", ); } @@ -1077,10 +1081,11 @@ mod test_solve { fn tag_application() { infer_eq( indoc!( - r#"Foo "happy" 2020 + r#" + Foo "happy" 2020 "# ), - "[ Foo Str Int ]*", + "[ Foo Str (Num *) ]*", ); } @@ -1088,10 +1093,11 @@ mod test_solve { fn private_tag_application() { infer_eq( indoc!( - r#"@Foo "happy" 2020 + r#" + @Foo "happy" 2020 "# ), - "[ @Foo Str Int ]*", + "[ @Foo Str (Num *) ]*", ); } @@ -1120,7 +1126,7 @@ mod test_solve { { x: 4 } -> x "# ), - "Int", + "Num *", ); } @@ -1129,7 +1135,7 @@ mod test_solve { infer_eq( indoc!( r#" - \Foo x -> Foo x + \Foo x -> Foo x "# ), "[ Foo a ]* -> [ Foo a ]*", @@ -1141,7 +1147,7 @@ mod test_solve { infer_eq( indoc!( r#" - \Foo x _ -> Foo x "y" + \Foo x _ -> Foo x "y" "# ), "[ Foo a * ]* -> [ Foo a Str ]*", @@ -1464,9 +1470,9 @@ mod test_solve { y = numIdentity 3.14 { numIdentity, x : numIdentity 42, y } - "# + "# ), - "{ numIdentity : Num a -> Num a, x : Int, y : Float }", + "{ numIdentity : Num a -> Num a, x : Num a, y : Float }", ); } @@ -1482,7 +1488,7 @@ mod test_solve { _ -> 5 x - "# + "# ), "Int", ); @@ -1494,15 +1500,15 @@ mod test_solve { infer_eq_without_problem( indoc!( r#" - f = \n -> - when n is - 0 -> 0 - _ -> f n + f = \n -> + when n is + 0 -> 0 + _ -> f n - f - "# + f + "# ), - "Int -> Int", + "Num * -> Num *", ); } @@ -1511,12 +1517,12 @@ mod test_solve { infer_eq_without_problem( indoc!( r#" - map : (a -> b), [ Identity a ] -> [ Identity b ] - map = \f, identity -> - when identity is - Identity v -> Identity (f v) - map - "# + map : (a -> b), [ Identity a ] -> [ Identity b ] + map = \f, identity -> + when identity is + Identity v -> Identity (f v) + map + "# ), "(a -> b), [ Identity a ] -> [ Identity b ]", ); @@ -1527,16 +1533,15 @@ mod test_solve { infer_eq_without_problem( indoc!( r#" - # toBit : [ False, True ] -> Num.Num Int.Integer toBit = \bool -> when bool is True -> 1 False -> 0 toBit - "# + "# ), - "[ False, True ]* -> Int", + "[ False, True ]* -> Num *", ); } @@ -1571,9 +1576,9 @@ mod test_solve { _ -> True fromBit - "# + "# ), - "Int -> [ False, True ]*", + "Num * -> [ False, True ]*", ); } @@ -1589,7 +1594,7 @@ mod test_solve { Err e -> Err e map - "# + "# ), "(a -> b), [ Err e, Ok a ] -> [ Err e, Ok b ]", ); @@ -1620,12 +1625,12 @@ mod test_solve { infer_eq_without_problem( indoc!( r#" - foo = \{ x } -> x + foo = \{ x } -> x - foo { x: 5 } + foo { x: 5 } "# ), - "Int", + "Num *", ); } @@ -2288,7 +2293,7 @@ mod test_solve { List.get [ 10, 9, 8, 7 ] 1 "# ), - "Result Int [ IndexOutOfBounds ]*", + "Result (Num *) [ IndexOutOfBounds ]*", ); } @@ -2370,7 +2375,7 @@ mod test_solve { f "# ), - "{ p : *, q : * }* -> Int", + "{ p : *, q : * }* -> Num *", ); } diff --git a/compiler/types/src/types.rs b/compiler/types/src/types.rs index 39ad377d4f..172d0117f0 100644 --- a/compiler/types/src/types.rs +++ b/compiler/types/src/types.rs @@ -621,6 +621,7 @@ pub enum Reason { BinOpRet(BinOp), FloatLiteral, IntLiteral, + NumLiteral, InterpolatedStringVar, WhenBranch { index: usize }, IfCondition, @@ -638,8 +639,9 @@ pub enum PatternCategory { Set, Map, Ctor(TagName), - Int, Str, + Num, + Int, Float, } diff --git a/compiler/uniq/src/sharing.rs b/compiler/uniq/src/sharing.rs index a4eead5afe..88bea5fe5f 100644 --- a/compiler/uniq/src/sharing.rs +++ b/compiler/uniq/src/sharing.rs @@ -515,6 +515,7 @@ pub fn annotate_usage(expr: &Expr, usage: &mut VarUsage) { match expr { RuntimeError(_) + | Num(_, _) | Int(_, _) | Float(_, _) | Str(_) diff --git a/roc-for-elm-programmers.md b/roc-for-elm-programmers.md index 1374aabbf7..ec82efd94a 100644 --- a/roc-for-elm-programmers.md +++ b/roc-for-elm-programmers.md @@ -691,24 +691,6 @@ Any operation which would result in one of these (such as `sqrt` or `/`) will result in a runtime exception. Similarly to overflow, you can opt into handling these a different way, such as `Float.trySqrt` which returns a `Result`. -Also like Elm, number literals with decimal points are `Float`. However, number -literals *without* a decimal point are always `Int`. So `x / 2` will never -compile in Roc; it would have to be `x / 2.0`, like in Python. Also [like Python](https://www.python.org/dev/peps/pep-0515/) -Roc permits underscores in number literals for readability purposes, and supports hexadecimal (`0x01`), octal (`0o01`), and binary (`0b01`) `Int` literals. - -If you put these into a hypothetical Roc REPL, here's what you'd see: - -```elm -> 1_024 + 1_024 -2048 : Int - -> 1.0 + 2.14 -3.14 : Float - -> 1 + 2.14 - -``` - The way `+` works here is also a bit different than in Elm. Imagine if Elm's `(+)` operator had this type: @@ -733,6 +715,32 @@ These don't exist in Roc. * `comparable` is used for comparison operators (like `<` and such), plus `List.sort`, `Dict`, and `Set`. Roc's `List.sort` accepts a `Sorter` argument which specifies how to sort the elements. Roc's comparison operators (like `<`) only accept numbers; `"foo" < "bar"` is valid Elm, but will not compile in Roc. Roc's dictionaries and sets are hashmaps behind the scenes (rather than ordered trees), and their keys have no visible type restrictions. * `number` is replaced by `Num`, as described earlier. +Like in Elm, number literals with decimal points are `Float`. However, number +literals *without* a decimal point are `Num *` instead of `number`. +Also [like Python](https://www.python.org/dev/peps/pep-0515/) +Roc permits underscores in number literals for readability purposes. Roc also supports +hexadecimal (`0x01`), octal (`0o01`), and binary (`0b01`) integer literals; these +literals all have type `Int` instead of `Num *`. + +If you put these into a hypothetical Roc REPL, here's what you'd see: + +```elm +> 1_024 + 1_024 +2048 : Num * + +> 1 + 2.14 +3.14 : Float + +> 1.0 + 1 +2.0 : Float + +> 1.1 + 0x11 + + +> 11 + 0x11 +28 : Int +``` + ## Operators In Elm, operators are functions. In Roc, all operators are syntax sugar. From d4dc0eb683966c8007215dc1f5eee5068b69a018 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Tue, 10 Mar 2020 22:53:53 -0400 Subject: [PATCH 031/428] Implement uniqueness for (Num *) --- compiler/constrain/src/uniq.rs | 36 +++++++++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/compiler/constrain/src/uniq.rs b/compiler/constrain/src/uniq.rs index 9f3ba2871a..4b5977a129 100644 --- a/compiler/constrain/src/uniq.rs +++ b/compiler/constrain/src/uniq.rs @@ -155,7 +155,11 @@ fn constrain_pattern( } NumLiteral(_, _) => { - panic!("TODO uniq constraint for NumLiteral"); + let (num_uvar, val_uvar, num_type, num_var) = unique_unbound_num(var_store); + state.constraints.push(exists( + vec![val_uvar, num_uvar, num_var], + Constraint::Pattern(pattern.region, PatternCategory::Num, num_type, expected), + )); } IntLiteral(_) => { @@ -310,6 +314,20 @@ fn constrain_pattern( } } +fn unique_unbound_num(var_store: &VarStore) -> (Variable, Variable, Type, Variable) { + let num_var = var_store.fresh(); + let num_uvar = var_store.fresh(); + let val_uvar = var_store.fresh(); + + let val_type = Type::Variable(num_var); + let val_utype = attr_type(Bool::variable(val_uvar), val_type); + + let num_utype = Type::Apply(Symbol::NUM_NUM, vec![val_utype]); + let num_type = attr_type(Bool::variable(num_uvar), num_utype); + + (num_uvar, val_uvar, num_type, num_var) +} + fn unique_num(var_store: &VarStore, symbol: Symbol) -> (Variable, Variable, Type) { let num_uvar = var_store.fresh(); let val_uvar = var_store.fresh(); @@ -343,8 +361,20 @@ pub fn constrain_expr( pub use roc_can::expr::Expr::*; match expr { - Num(_var, _) => { - panic!("TODO uniq::constrain_expr for Num literal"); + Num(var, _) => { + let (num_uvar, val_uvar, num_type, num_var) = unique_unbound_num(var_store); + + exists( + vec![*var, val_uvar, num_uvar, num_var], + And(vec![ + Eq( + Type::Variable(*var), + Expected::ForReason(Reason::IntLiteral, num_type, region), + region, + ), + Eq(Type::Variable(*var), expected, region), + ]), + ) } Int(var, _) => { let (num_uvar, int_uvar, num_type) = unique_int(var_store); From 0bac39f9f1af3d3e3bed92e1c5e36ae055af7a9c Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Tue, 10 Mar 2020 22:54:13 -0400 Subject: [PATCH 032/428] Fix from_can for (Num *) --- compiler/mono/src/expr.rs | 75 +++++++++++++++++++++++++++++++-------- 1 file changed, 60 insertions(+), 15 deletions(-) diff --git a/compiler/mono/src/expr.rs b/compiler/mono/src/expr.rs index ef1c2bdedf..38cc5f2de9 100644 --- a/compiler/mono/src/expr.rs +++ b/compiler/mono/src/expr.rs @@ -131,6 +131,65 @@ impl<'a> Expr<'a> { } } +fn from_can_num<'a>(subs: &Subs, var: Variable, num: i64) -> Expr<'a> { + // TODO FIXME Investigate why both INT_INT and INT_INTEGER (and same with + // FLOAT_FLOAT and FLOAT_FLOATINGPOINT) are necessary here. It should + // be one or the other, but not both! The fact that both are necessary + // isn't a problem for this phase, but it suggests that either something + // is wrong with aliases or (more likely) some numeric builtins are being + // assigned the wrong types somewhere. + match subs.get_without_compacting(var).content { + Content::Alias(Symbol::INT_INT, args, _) | Content::Alias(Symbol::INT_INTEGER, args, _) => { + debug_assert!(args.len() == 0); + Expr::Int(num) + } + Content::FlexVar(_) => { + // If this was still a (Num *), assume compiling it to an Int + Expr::Int(num) + } + Content::Alias(Symbol::FLOAT_FLOAT, args, _) + | Content::Alias(Symbol::FLOAT_FLOATINGPOINT, args, _) => { + debug_assert!(args.len() == 0); + Expr::Float(num as f64) + } + Content::Alias(Symbol::NUM_NUM, args, _) => { + debug_assert!(args.len() == 1); + + match subs.get_without_compacting(args[0].1).content { + Content::Alias(Symbol::INT_INTEGER, args, _) => { + debug_assert!(args.len() == 0); + Expr::Int(num) + } + Content::FlexVar(_) => { + // If this was still a (Num *), assume compiling it to an Int + Expr::Int(num) + } + Content::Alias(Symbol::FLOAT_FLOATINGPOINT, args, _) => { + debug_assert!(args.len() == 0); + Expr::Float(num as f64) + } + Content::Structure(FlatType::Apply(Symbol::ATTR_ATTR, attr_args)) => { + debug_assert!(attr_args.len() == 2); + + // Recurse on the second argument + from_can_num(subs, attr_args[1], num) + } + other => panic!( + "Unrecognized Num.Num alias type argument Content: {:?}", + other + ), + } + } + Content::Structure(FlatType::Apply(Symbol::ATTR_ATTR, attr_args)) => { + debug_assert!(attr_args.len() == 2); + + // Recurse on the second argument + from_can_num(subs, attr_args[1], num) + } + other => panic!("Unrecognized Num type argument Content: {:?}", other), + } +} + fn from_can<'a>( env: &mut Env<'a, '_>, can_expr: roc_can::expr::Expr, @@ -141,21 +200,7 @@ fn from_can<'a>( use roc_can::pattern::Pattern::*; match can_expr { - Num(var, num) => match env.subs.get_without_compacting(var).content { - Content::Alias(Symbol::INT_INTEGER, args, _) => { - debug_assert!(args.len() == 0); - Expr::Int(num) - } - Content::FlexVar(_) => { - // If this was still a (Num *), assume compiling it to an Int - Expr::Int(num) - } - Content::Alias(Symbol::FLOAT_FLOATINGPOINT, args, _) => { - debug_assert!(args.len() == 0); - Expr::Float(num as f64) - } - other => panic!("Unrecognized Num type argument Content: {:?}", other), - }, + Num(var, num) => from_can_num(env.subs, var, num), Int(_, num) => Expr::Int(num), Float(_, num) => Expr::Float(num), Str(string) | BlockStr(string) => Expr::Str(env.arena.alloc(string)), From a0d762d1e5eb44d4bc1da7cf8d103b901f86db7d Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Tue, 10 Mar 2020 23:06:11 -0400 Subject: [PATCH 033/428] Fix more mono::expr (Num *) cases --- compiler/mono/src/expr.rs | 52 +++++++++++++++++++++++++++++++-------- 1 file changed, 42 insertions(+), 10 deletions(-) diff --git a/compiler/mono/src/expr.rs b/compiler/mono/src/expr.rs index 38cc5f2de9..f214420019 100644 --- a/compiler/mono/src/expr.rs +++ b/compiler/mono/src/expr.rs @@ -131,7 +131,12 @@ impl<'a> Expr<'a> { } } -fn from_can_num<'a>(subs: &Subs, var: Variable, num: i64) -> Expr<'a> { +enum IntOrFloat { + IntType, + FloatType, +} + +fn to_int_or_float<'a>(subs: &Subs, var: Variable) -> IntOrFloat { // TODO FIXME Investigate why both INT_INT and INT_INTEGER (and same with // FLOAT_FLOAT and FLOAT_FLOATINGPOINT) are necessary here. It should // be one or the other, but not both! The fact that both are necessary @@ -141,16 +146,16 @@ fn from_can_num<'a>(subs: &Subs, var: Variable, num: i64) -> Expr<'a> { match subs.get_without_compacting(var).content { Content::Alias(Symbol::INT_INT, args, _) | Content::Alias(Symbol::INT_INTEGER, args, _) => { debug_assert!(args.len() == 0); - Expr::Int(num) + IntOrFloat::IntType } Content::FlexVar(_) => { // If this was still a (Num *), assume compiling it to an Int - Expr::Int(num) + IntOrFloat::IntType } Content::Alias(Symbol::FLOAT_FLOAT, args, _) | Content::Alias(Symbol::FLOAT_FLOATINGPOINT, args, _) => { debug_assert!(args.len() == 0); - Expr::Float(num as f64) + IntOrFloat::FloatType } Content::Alias(Symbol::NUM_NUM, args, _) => { debug_assert!(args.len() == 1); @@ -158,21 +163,21 @@ fn from_can_num<'a>(subs: &Subs, var: Variable, num: i64) -> Expr<'a> { match subs.get_without_compacting(args[0].1).content { Content::Alias(Symbol::INT_INTEGER, args, _) => { debug_assert!(args.len() == 0); - Expr::Int(num) + IntOrFloat::IntType } Content::FlexVar(_) => { // If this was still a (Num *), assume compiling it to an Int - Expr::Int(num) + IntOrFloat::IntType } Content::Alias(Symbol::FLOAT_FLOATINGPOINT, args, _) => { debug_assert!(args.len() == 0); - Expr::Float(num as f64) + IntOrFloat::FloatType } Content::Structure(FlatType::Apply(Symbol::ATTR_ATTR, attr_args)) => { debug_assert!(attr_args.len() == 2); // Recurse on the second argument - from_can_num(subs, attr_args[1], num) + to_int_or_float(subs, attr_args[1]) } other => panic!( "Unrecognized Num.Num alias type argument Content: {:?}", @@ -184,7 +189,7 @@ fn from_can_num<'a>(subs: &Subs, var: Variable, num: i64) -> Expr<'a> { debug_assert!(attr_args.len() == 2); // Recurse on the second argument - from_can_num(subs, attr_args[1], num) + to_int_or_float(subs, attr_args[1]) } other => panic!("Unrecognized Num type argument Content: {:?}", other), } @@ -200,7 +205,10 @@ fn from_can<'a>( use roc_can::pattern::Pattern::*; match can_expr { - Num(var, num) => from_can_num(env.subs, var, num), + Num(var, num) => match to_int_or_float(env.subs, var) { + IntOrFloat::IntType => Expr::Int(num), + IntOrFloat::FloatType => Expr::Float(num as f64), + }, Int(_, num) => Expr::Int(num), Float(_, num) => Expr::Float(num), Str(string) | BlockStr(string) => Expr::Str(env.arena.alloc(string)), @@ -564,6 +572,30 @@ fn from_can_when<'a>( let (loc_when_pat2, loc_else) = iter.next().unwrap(); match (&loc_when_pat1.value, &loc_when_pat2.value) { + (NumLiteral(var, num), NumLiteral(_, _)) | (NumLiteral(var, num), Underscore) => { + let cond_lhs = arena.alloc(from_can(env, loc_cond.value, procs, None)); + let (builtin, cond_rhs_expr) = match to_int_or_float(env.subs, *var) { + IntOrFloat::IntType => (Builtin::Int64, Expr::Int(*num)), + IntOrFloat::FloatType => (Builtin::Float64, Expr::Float(*num as f64)), + }; + + let cond_rhs = arena.alloc(cond_rhs_expr); + let pass = arena.alloc(from_can(env, loc_then.value, procs, None)); + let fail = arena.alloc(from_can(env, loc_else.value, procs, None)); + let ret_layout = + Layout::from_var(arena, expr_var, env.subs).unwrap_or_else(|err| { + panic!("TODO turn this into a RuntimeError {:?}", err) + }); + + Expr::Cond { + cond_layout: Layout::Builtin(builtin), + cond_lhs, + cond_rhs, + pass, + fail, + ret_layout, + } + } (IntLiteral(int), IntLiteral(_)) | (IntLiteral(int), Underscore) => { let cond_lhs = arena.alloc(from_can(env, loc_cond.value, procs, None)); let cond_rhs = arena.alloc(Expr::Int(*int)); From a033f325f47d31a0b09d8580a758111ad3ba96b9 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Tue, 10 Mar 2020 23:08:26 -0400 Subject: [PATCH 034/428] Support integer (Num *) patterns in jump tables --- compiler/mono/src/expr.rs | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/compiler/mono/src/expr.rs b/compiler/mono/src/expr.rs index f214420019..1dd966aacc 100644 --- a/compiler/mono/src/expr.rs +++ b/compiler/mono/src/expr.rs @@ -672,14 +672,20 @@ fn from_can_when<'a>( match &loc_when_pat.value { NumLiteral(var, num) => { - panic!( - "TODO check if this var is an Int; if so, it's jumpable! var: {:?}, num: {:?}", - var, num - ); - // Switch only compares the condition to the - // alternatives based on their bit patterns, - // so casting from i64 to u64 makes no difference here. - // jumpable_branches.push((*int as u64, mono_expr)); + // This is jumpable iff it's an int + match to_int_or_float(env.subs, *var) { + IntOrFloat::IntType => { + jumpable_branches.push((*num as u64, mono_expr)); + } + IntOrFloat::FloatType => { + // The type checker should have converted these mismatches into RuntimeErrors already! + if cfg!(debug_assertions) { + panic!("A type mismatch in a pattern was not converted to a runtime error: {:?}", loc_when_pat); + } else { + unreachable!(); + } + } + }; } IntLiteral(int) => { // Switch only compares the condition to the From c0837eca1c30b4b1f7a5a584736d85e3e0e2bd8d Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Tue, 10 Mar 2020 23:11:49 -0400 Subject: [PATCH 035/428] Use constantNum over constantInt in load tests --- .../build/interface_with_deps/WithBuiltins.roc | 10 +++++----- compiler/load/tests/test_load.rs | 2 +- compiler/load/tests/test_uniq_load.rs | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/compiler/load/tests/fixtures/build/interface_with_deps/WithBuiltins.roc b/compiler/load/tests/fixtures/build/interface_with_deps/WithBuiltins.roc index fd64d478fa..37300efb8c 100644 --- a/compiler/load/tests/fixtures/build/interface_with_deps/WithBuiltins.roc +++ b/compiler/load/tests/fixtures/build/interface_with_deps/WithBuiltins.roc @@ -1,19 +1,19 @@ interface WithBuiltins - exposes [ floatTest, divisionFn, divisionTest, intTest, constantInt, fromDep2, divDep1ByDep2 ] + exposes [ floatTest, divisionFn, divisionTest, intTest, constantNum, fromDep2, divDep1ByDep2 ] imports [ Dep1, Dep2.{ two } ] floatTest = Float.highest divisionFn = Float.div - + x = 5.0 -divisionTest = Float.highest / x +divisionTest = Float.highest / x intTest = Int.highest -constantInt = 5 - +constantNum = 5 + fromDep2 = Dep2.two divDep1ByDep2 = Dep1.three / fromDep2 diff --git a/compiler/load/tests/test_load.rs b/compiler/load/tests/test_load.rs index 7cc30a048a..b11a5972ea 100644 --- a/compiler/load/tests/test_load.rs +++ b/compiler/load/tests/test_load.rs @@ -213,7 +213,7 @@ mod test_load { "divisionTest" => "Float", "intTest" => "Int", "x" => "Float", - "constantInt" => "Int", + "constantNum" => "Num *", "divDep1ByDep2" => "Float", "fromDep2" => "Float", }, diff --git a/compiler/load/tests/test_uniq_load.rs b/compiler/load/tests/test_uniq_load.rs index a7f8981ad9..d65a87e9d2 100644 --- a/compiler/load/tests/test_uniq_load.rs +++ b/compiler/load/tests/test_uniq_load.rs @@ -208,7 +208,7 @@ mod test_uniq_load { "divisionTest" => "Attr * Float", "intTest" => "Attr * Int", "x" => "Attr * Float", - "constantInt" => "Attr * Int", + "constantNum" => "Attr * (Num (Attr * *))", "divDep1ByDep2" => "Attr * Float", "fromDep2" => "Attr * Float", }, From df85c086abf06ce59f6818ff93b57009fe5d61bf Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Wed, 11 Mar 2020 00:07:19 -0400 Subject: [PATCH 036/428] Fix test_uniq_solve --- compiler/solve/tests/test_uniq_solve.rs | 212 ++++++++++++------------ 1 file changed, 107 insertions(+), 105 deletions(-) diff --git a/compiler/solve/tests/test_uniq_solve.rs b/compiler/solve/tests/test_uniq_solve.rs index 1feccf7e37..615f3c6eef 100644 --- a/compiler/solve/tests/test_uniq_solve.rs +++ b/compiler/solve/tests/test_uniq_solve.rs @@ -57,7 +57,7 @@ mod test_uniq_solve { #[test] fn int_literal() { - infer_eq("5", "Attr * Int"); + infer_eq("5", "Attr * (Num (Attr * *))"); } #[test] @@ -176,7 +176,7 @@ mod test_uniq_solve { [42] "# ), - "Attr * (List (Attr * Int))", + "Attr * (List (Attr * (Num (Attr * *))))", ); } @@ -188,7 +188,7 @@ mod test_uniq_solve { [[[ 5 ]]] "# ), - "Attr * (List (Attr * (List (Attr * (List (Attr * Int))))))", + "Attr * (List (Attr * (List (Attr * (List (Attr * (Num (Attr * *))))))))", ); } @@ -200,7 +200,7 @@ mod test_uniq_solve { [ 1, 2, 3 ] "# ), - "Attr * (List (Attr * Int))", + "Attr * (List (Attr * (Num (Attr * *))))", ); } @@ -212,7 +212,7 @@ mod test_uniq_solve { [ [ 1 ], [ 2, 3 ] ] "# ), - "Attr * (List (Attr * (List (Attr * Int))))", + "Attr * (List (Attr * (List (Attr * (Num (Attr * *))))))", ); } @@ -328,7 +328,7 @@ mod test_uniq_solve { \_, _ -> 42 "# ), - "Attr * (*, * -> Attr * Int)", + "Attr * (*, * -> Attr * (Num (Attr * *)))", ); } @@ -398,7 +398,7 @@ mod test_uniq_solve { func "# ), - "Attr * (*, * -> Attr * Int)", + "Attr * (*, * -> Attr * (Num (Attr * *)))", ); } @@ -462,7 +462,7 @@ mod test_uniq_solve { c "# ), - "Attr * Int", + "Attr * (Num (Attr * *))", ); } @@ -498,7 +498,7 @@ mod test_uniq_solve { alwaysFive "stuff" "# ), - "Attr * Int", + "Attr * (Num (Attr * *))", ); } @@ -546,7 +546,7 @@ mod test_uniq_solve { ), // TODO investigate why is this not shared? // maybe because y is not used it is dropped? - "Attr * Int", + "Attr * (Num (Attr * *))", ); } @@ -560,7 +560,7 @@ mod test_uniq_solve { enlist 5 "# ), - "Attr * (List (Attr * Int))", + "Attr * (List (Attr * (Num (Attr * *))))", ); } @@ -587,7 +587,7 @@ mod test_uniq_solve { 1 |> (\a -> a) "# ), - "Attr * Int", + "Attr * (Num (Attr * *))", ); } @@ -599,7 +599,7 @@ mod test_uniq_solve { (\a -> a) 1 "# ), - "Attr * Int", + "Attr * (Num (Attr * *))", ); } @@ -613,7 +613,7 @@ mod test_uniq_solve { 1 |> always "foo" "# ), - "Attr * Int", + "Attr * (Num (Attr * *))", ); } @@ -679,7 +679,7 @@ mod test_uniq_solve { apply identity 5 "# ), - "Attr * Int", + "Attr * (Num (Attr * *))", ); } @@ -702,13 +702,13 @@ mod test_uniq_solve { // indoc!( // r#" // flip = \f -> (\a b -> f b a) - // neverendingInt = \f int -> f int - // x = neverendingInt (\a -> a) 5 + // neverendingNum = \f int -> f int + // x = neverendingNum (\a -> a) 5 - // flip neverendingInt + // flip neverendingNum // "# // ), - // "(Int, (a -> a)) -> Int", + // "((Num (Attr * *)), (a -> a)) -> (Num (Attr * *))", // ); // } @@ -782,7 +782,7 @@ mod test_uniq_solve { // 1 // 2 // "# // ), - // "Int", + // "(Num (Attr * *))", // ); // } @@ -794,7 +794,7 @@ mod test_uniq_solve { // 1 + 2 // "# // ), - // "Int", + // "(Num (Attr * *))", // ); // } @@ -843,7 +843,7 @@ mod test_uniq_solve { [ alwaysFive "foo", alwaysFive [] ] "# ), - "Attr * (List (Attr * Int))", + "Attr * (List (Attr * (Num (Attr * *))))", ); } @@ -858,7 +858,7 @@ mod test_uniq_solve { 24 "# ), - "Attr * Int", + "Attr * (Num (Attr * *))", ); } @@ -872,18 +872,18 @@ mod test_uniq_solve { 3 -> 4 "# ), - "Attr * Int", + "Attr * (Num (Attr * *))", ); } #[test] fn record() { - infer_eq("{ foo: 42 }", "Attr * { foo : (Attr * Int) }"); + infer_eq("{ foo: 42 }", "Attr * { foo : (Attr * (Num (Attr * *))) }"); } #[test] fn record_access() { - infer_eq("{ foo: 42 }.foo", "Attr * Int"); + infer_eq("{ foo: 42 }.foo", "Attr * (Num (Attr * *))"); } #[test] @@ -937,7 +937,7 @@ mod test_uniq_solve { \Foo -> 42 "# ), - "Attr * (Attr * [ Foo ]* -> Attr * Int)", + "Attr * (Attr * [ Foo ]* -> Attr * (Num (Attr * *)))", ); } @@ -949,7 +949,7 @@ mod test_uniq_solve { \@Foo -> 42 "# ), - "Attr * (Attr * [ @Foo ]* -> Attr * Int)", + "Attr * (Attr * [ @Foo ]* -> Attr * (Num (Attr * *)))", ); } @@ -964,7 +964,7 @@ mod test_uniq_solve { False -> 0 "# ), - "Attr * (Attr * [ False, True ]* -> Attr * Int)", + "Attr * (Attr * [ False, True ]* -> Attr * (Num (Attr * *)))", ); } @@ -976,7 +976,7 @@ mod test_uniq_solve { Foo "happy" 2020 "# ), - "Attr * [ Foo (Attr * Str) (Attr * Int) ]*", + "Attr * [ Foo (Attr * Str) (Attr * (Num (Attr * *))) ]*", ); } @@ -988,7 +988,7 @@ mod test_uniq_solve { @Foo "happy" 2020 "# ), - "Attr * [ @Foo (Attr * Str) (Attr * Int) ]*", + "Attr * [ @Foo (Attr * Str) (Attr * (Num (Attr * *))) ]*", ); } @@ -1037,7 +1037,7 @@ mod test_uniq_solve { { x: 4 } -> x "# ), - "Attr * Int", + "Attr * (Num (Attr * *))", ); } @@ -1079,7 +1079,7 @@ mod test_uniq_solve { Foo x -> x "# ), - "Attr * Int", + "Attr * (Num (Attr * *))", ); } @@ -1092,7 +1092,7 @@ mod test_uniq_solve { @Foo x -> x "# ), - "Attr * Int", + "Attr * (Num (Attr * *))", ); } @@ -1194,7 +1194,7 @@ mod test_uniq_solve { { numIdentity, p, q } "# ), - "Attr * { numIdentity : (Attr Shared (Attr a (Num (Attr b p)) -> Attr a (Num (Attr b p)))), p : (Attr * Int), q : (Attr * Float) }" + "Attr * { numIdentity : (Attr Shared (Attr a (Num (Attr b p)) -> Attr a (Num (Attr b p)))), p : (Attr * (Num (Attr * p))), q : (Attr * Float) }" ); } @@ -1380,7 +1380,7 @@ mod test_uniq_solve { factorial "# ), - "Attr Shared (Attr * Int -> Attr * Int)", + "Attr Shared (Attr * (Num (Attr * *)) -> Attr * (Num (Attr * *)))", ); } @@ -1479,7 +1479,7 @@ mod test_uniq_solve { s.left "# ), - "Attr Shared Int", + "Attr Shared (Num (Attr * *))", ); } @@ -1498,7 +1498,7 @@ mod test_uniq_solve { { y: s.left } "# ), - "Attr * { y : (Attr Shared Int) }", + "Attr * { y : (Attr Shared (Num (Attr * *))) }", ); } @@ -1549,7 +1549,7 @@ mod test_uniq_solve { "# ), // it's fine that the inner fields are not shared: only shared extraction is possible - "Attr * { left : (Attr Shared { left : (Attr * Int), right : (Attr * Int) }), right : (Attr Shared { left : (Attr * Int), right : (Attr * Int) }) }", + "Attr * { left : (Attr Shared { left : (Attr * (Num (Attr * *))), right : (Attr * (Num (Attr * *))) }), right : (Attr Shared { left : (Attr * (Num (Attr * *))), right : (Attr * (Num (Attr * *))) }) }", ); } @@ -1869,7 +1869,7 @@ mod test_uniq_solve { 4 + 4 "# ), - "Attr * Int", + "Attr * (Num (Attr * *))", ); } @@ -1882,7 +1882,7 @@ mod test_uniq_solve { |> List.get 2 "# ), - "Attr * (Result (Attr * Int) (Attr * [ IndexOutOfBounds ]*))", + "Attr * (Result (Attr * (Num (Attr * *))) (Attr * [ IndexOutOfBounds ]*))", ); } @@ -1988,7 +1988,7 @@ mod test_uniq_solve { list "# ), - "Attr * (Attr a (List (Attr Shared Int)) -> Attr a (List (Attr Shared Int)))", + "Attr * (Attr a (List (Attr Shared (Num (Attr b c)))) -> Attr a (List (Attr Shared (Num (Attr b c)))))", ); } @@ -2004,7 +2004,7 @@ mod test_uniq_solve { List.set list 0 42 "# ), - "Attr * (Attr (a | b) (List (Attr b Int)) -> Attr (a | b) (List (Attr b Int)))", + "Attr * (Attr (a | b) (List (Attr b (Num (Attr c d)))) -> Attr (a | b) (List (Attr b (Num (Attr c d)))))", ); } @@ -2047,7 +2047,7 @@ mod test_uniq_solve { sum "# ), - "Attr * (Attr * (List (Attr * Int)) -> Attr * Int)", + "Attr * (Attr * (List (Attr * (Num (Attr a b)))) -> Attr * (Num (Attr a b)))", ); } @@ -2095,7 +2095,7 @@ mod test_uniq_solve { List.getUnsafe (List.set [ 12, 9, 7, 3 ] 1 42) 1 "# ), - "Attr * Int", + "Attr * (Num (Attr * *))", ); } @@ -2113,7 +2113,7 @@ mod test_uniq_solve { f "# ), - "Attr * (Attr (* | a | b) { p : (Attr b *), q : (Attr a *) }* -> Attr * Int)", + "Attr * (Attr (* | a | b) { p : (Attr a *), q : (Attr b *) }* -> Attr * (Num (Attr * *)))", ); } @@ -2234,26 +2234,26 @@ mod test_uniq_solve { infer_eq( indoc!( r#" - Model position : { evaluated : Set position - , openSet : Set position - , costs : Map.Map position Float - , cameFrom : Map.Map position position - } + Model position : { evaluated : Set position + , openSet : Set position + , costs : Map.Map position Float + , cameFrom : Map.Map position position + } - initialModel : position -> Model position - initialModel = \start -> - { evaluated : Set.empty - , openSet : Set.singleton start - , costs : Map.singleton start 0.0 - , cameFrom : Map.empty - } + initialModel : position -> Model position + initialModel = \start -> + { evaluated : Set.empty + , openSet : Set.singleton start + , costs : Map.singleton start 0.0 + , cameFrom : Map.empty + } - cheapestOpen : (position -> Float), Model position -> Result position [ KeyNotFound ]* - cheapestOpen = \costFunction, model -> + cheapestOpen : (position -> Float), Model position -> Result position [ KeyNotFound ]* + cheapestOpen = \costFunction, model -> - folder = \position, resSmallestSoFar -> + folder = \position, resSmallestSoFar -> when Map.get model.costs position is Err e -> Err e @@ -2270,74 +2270,76 @@ mod test_uniq_solve { else Ok smallestSoFar - Set.foldl model.openSet folder (Err KeyNotFound) - |> Result.map (\x -> x.position) + + Set.foldl model.openSet folder (Err KeyNotFound) + |> Result.map (\x -> x.position) + reconstructPath : Map position position, position -> List position + reconstructPath = \cameFrom, goal -> + when Map.get cameFrom goal is + Err KeyNotFound -> + [] - reconstructPath : Map position position, position -> List position - reconstructPath = \cameFrom, goal -> - when Map.get cameFrom goal is - Err KeyNotFound -> - [] + Ok next -> + List.push (reconstructPath cameFrom next) goal - Ok next -> - List.push (reconstructPath cameFrom next) goal - updateCost : position, position, Model position -> Model position - updateCost = \current, neighbour, model -> - newCameFrom = Map.insert model.cameFrom neighbour current + updateCost : position, position, Model position -> Model position + updateCost = \current, neighbour, model -> + newCameFrom = Map.insert model.cameFrom neighbour current - newCosts = Map.insert model.costs neighbour distanceTo + newCosts = Map.insert model.costs neighbour distanceTo - distanceTo = reconstructPath newCameFrom neighbour - |> List.length - |> Num.toFloat + distanceTo = + reconstructPath newCameFrom neighbour + |> List.length + |> Num.toFloat - newModel = { model & costs : newCosts , cameFrom : newCameFrom } + newModel = { model & costs : newCosts , cameFrom : newCameFrom } - when Map.get model.costs neighbour is - Err KeyNotFound -> - newModel - - Ok previousDistance -> - if distanceTo < previousDistance then + when Map.get model.costs neighbour is + Err KeyNotFound -> newModel - else - model + Ok previousDistance -> + if distanceTo < previousDistance then + newModel + + else + model - findPath : { costFunction: (position, position -> Float), moveFunction: (position -> Set position), start : position, end : position } -> Result (List position) [ KeyNotFound ]* - findPath = \{ costFunction, moveFunction, start, end } -> - astar costFunction moveFunction end (initialModel start) + findPath : { costFunction: (position, position -> Float), moveFunction: (position -> Set position), start : position, end : position } -> Result (List position) [ KeyNotFound ]* + findPath = \{ costFunction, moveFunction, start, end } -> + astar costFunction moveFunction end (initialModel start) - astar : (position, position -> Float), (position -> Set position), position, Model position -> [ Err [ KeyNotFound ]*, Ok (List position) ]* - astar = \costFn, moveFn, goal, model -> - when cheapestOpen (\position -> costFn goal position) model is - Err _ -> - Err KeyNotFound + astar : (position, position -> Float), (position -> Set position), position, Model position -> [ Err [ KeyNotFound ]*, Ok (List position) ]* + astar = \costFn, moveFn, goal, model -> + when cheapestOpen (\position -> costFn goal position) model is + Err _ -> + Err KeyNotFound - Ok current -> - if current == goal then - Ok (reconstructPath model.cameFrom goal) + Ok current -> + if current == goal then + Ok (reconstructPath model.cameFrom goal) - else + else - modelPopped = { model & openSet : Set.remove model.openSet current, evaluated : Set.insert model.evaluated current } + modelPopped = { model & openSet : Set.remove model.openSet current, evaluated : Set.insert model.evaluated current } - neighbours = moveFn current + neighbours = moveFn current - newNeighbours = Set.diff neighbours modelPopped.evaluated + newNeighbours = Set.diff neighbours modelPopped.evaluated - modelWithNeighbours = { modelPopped & openSet : Set.union modelPopped.openSet newNeighbours } + modelWithNeighbours = { modelPopped & openSet : Set.union modelPopped.openSet newNeighbours } - modelWithCosts = Set.foldl newNeighbours (\nb, md -> updateCost current nb md) modelWithNeighbours + modelWithCosts = Set.foldl newNeighbours (\nb, md -> updateCost current nb md) modelWithNeighbours - astar costFn moveFn goal modelWithCosts + astar costFn moveFn goal modelWithCosts - findPath + findPath "# ), "Attr * (Attr * { costFunction : (Attr Shared (Attr Shared position, Attr Shared position -> Attr Shared Float)), end : (Attr Shared position), moveFunction : (Attr Shared (Attr Shared position -> Attr * (Set (Attr Shared position)))), start : (Attr Shared position) } -> Attr * (Result (Attr * (List (Attr Shared position))) (Attr * [ KeyNotFound ]*)))" From 55623605f34bad02f9df1dca4e0504e0d503c1d3 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Wed, 11 Mar 2020 00:11:23 -0400 Subject: [PATCH 037/428] clippy mcclip --- compiler/mono/src/expr.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/compiler/mono/src/expr.rs b/compiler/mono/src/expr.rs index 1dd966aacc..f62017a47f 100644 --- a/compiler/mono/src/expr.rs +++ b/compiler/mono/src/expr.rs @@ -136,7 +136,7 @@ enum IntOrFloat { FloatType, } -fn to_int_or_float<'a>(subs: &Subs, var: Variable) -> IntOrFloat { +fn to_int_or_float(subs: &Subs, var: Variable) -> IntOrFloat { // TODO FIXME Investigate why both INT_INT and INT_INTEGER (and same with // FLOAT_FLOAT and FLOAT_FLOATINGPOINT) are necessary here. It should // be one or the other, but not both! The fact that both are necessary @@ -145,7 +145,7 @@ fn to_int_or_float<'a>(subs: &Subs, var: Variable) -> IntOrFloat { // assigned the wrong types somewhere. match subs.get_without_compacting(var).content { Content::Alias(Symbol::INT_INT, args, _) | Content::Alias(Symbol::INT_INTEGER, args, _) => { - debug_assert!(args.len() == 0); + debug_assert!(args.is_empty()); IntOrFloat::IntType } Content::FlexVar(_) => { @@ -154,7 +154,7 @@ fn to_int_or_float<'a>(subs: &Subs, var: Variable) -> IntOrFloat { } Content::Alias(Symbol::FLOAT_FLOAT, args, _) | Content::Alias(Symbol::FLOAT_FLOATINGPOINT, args, _) => { - debug_assert!(args.len() == 0); + debug_assert!(args.is_empty()); IntOrFloat::FloatType } Content::Alias(Symbol::NUM_NUM, args, _) => { @@ -162,7 +162,7 @@ fn to_int_or_float<'a>(subs: &Subs, var: Variable) -> IntOrFloat { match subs.get_without_compacting(args[0].1).content { Content::Alias(Symbol::INT_INTEGER, args, _) => { - debug_assert!(args.len() == 0); + debug_assert!(args.is_empty()); IntOrFloat::IntType } Content::FlexVar(_) => { @@ -170,7 +170,7 @@ fn to_int_or_float<'a>(subs: &Subs, var: Variable) -> IntOrFloat { IntOrFloat::IntType } Content::Alias(Symbol::FLOAT_FLOATINGPOINT, args, _) => { - debug_assert!(args.len() == 0); + debug_assert!(args.is_empty()); IntOrFloat::FloatType } Content::Structure(FlatType::Apply(Symbol::ATTR_ATTR, attr_args)) => { From 9ba09c7d8251070df0d720bbfd59d17f0d2fc24f Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 11 Mar 2020 14:07:49 +0100 Subject: [PATCH 038/428] use Reason::NumLiteral in uniqueness inference we can 'just' do that here because there is a var_store available. That isn't true for normal inference, where we would have to put the variable in during solving --- compiler/constrain/src/uniq.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/constrain/src/uniq.rs b/compiler/constrain/src/uniq.rs index 4b5977a129..b3c4f192dc 100644 --- a/compiler/constrain/src/uniq.rs +++ b/compiler/constrain/src/uniq.rs @@ -369,7 +369,7 @@ pub fn constrain_expr( And(vec![ Eq( Type::Variable(*var), - Expected::ForReason(Reason::IntLiteral, num_type, region), + Expected::ForReason(Reason::NumLiteral, num_type, region), region, ), Eq(Type::Variable(*var), expected, region), From 17348231fde0208576542b8c8c653efc9e71e296 Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 11 Mar 2020 14:18:26 +0100 Subject: [PATCH 039/428] fix uniqueness Num inference The variable in Num is the `a` in `Num a`. For Int/Float literals, it's a helper variable to generate better error messages --- compiler/constrain/src/uniq.rs | 24 ++++++++++++++---------- compiler/solve/tests/test_uniq_solve.rs | 3 ++- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/compiler/constrain/src/uniq.rs b/compiler/constrain/src/uniq.rs index b3c4f192dc..26976acf96 100644 --- a/compiler/constrain/src/uniq.rs +++ b/compiler/constrain/src/uniq.rs @@ -154,10 +154,10 @@ fn constrain_pattern( ); } - NumLiteral(_, _) => { - let (num_uvar, val_uvar, num_type, num_var) = unique_unbound_num(var_store); + NumLiteral(inner_var, _) => { + let (num_uvar, val_uvar, num_type, num_var) = unique_unbound_num(*inner_var, var_store); state.constraints.push(exists( - vec![val_uvar, num_uvar, num_var], + vec![val_uvar, num_uvar, num_var, *inner_var], Constraint::Pattern(pattern.region, PatternCategory::Num, num_type, expected), )); } @@ -314,12 +314,15 @@ fn constrain_pattern( } } -fn unique_unbound_num(var_store: &VarStore) -> (Variable, Variable, Type, Variable) { +fn unique_unbound_num( + inner_var: Variable, + var_store: &VarStore, +) -> (Variable, Variable, Type, Variable) { let num_var = var_store.fresh(); let num_uvar = var_store.fresh(); let val_uvar = var_store.fresh(); - let val_type = Type::Variable(num_var); + let val_type = Type::Variable(inner_var); let val_utype = attr_type(Bool::variable(val_uvar), val_type); let num_utype = Type::Apply(Symbol::NUM_NUM, vec![val_utype]); @@ -361,18 +364,19 @@ pub fn constrain_expr( pub use roc_can::expr::Expr::*; match expr { - Num(var, _) => { - let (num_uvar, val_uvar, num_type, num_var) = unique_unbound_num(var_store); + Num(inner_var, _) => { + let var = var_store.fresh(); + let (num_uvar, val_uvar, num_type, num_var) = unique_unbound_num(*inner_var, var_store); exists( - vec![*var, val_uvar, num_uvar, num_var], + vec![var, *inner_var, val_uvar, num_uvar, num_var], And(vec![ Eq( - Type::Variable(*var), + Type::Variable(var), Expected::ForReason(Reason::NumLiteral, num_type, region), region, ), - Eq(Type::Variable(*var), expected, region), + Eq(Type::Variable(var), expected, region), ]), ) } diff --git a/compiler/solve/tests/test_uniq_solve.rs b/compiler/solve/tests/test_uniq_solve.rs index 615f3c6eef..ed95d89621 100644 --- a/compiler/solve/tests/test_uniq_solve.rs +++ b/compiler/solve/tests/test_uniq_solve.rs @@ -2113,7 +2113,8 @@ mod test_uniq_solve { f "# ), - "Attr * (Attr (* | a | b) { p : (Attr a *), q : (Attr b *) }* -> Attr * (Num (Attr * *)))", + "Attr * (Attr (* | a | b) { p : (Attr b *), q : (Attr a *) }* -> Attr * (Num (Attr * *)))" + //"Attr * (Attr (* | a | b) { p : (Attr a *), q : (Attr b *) }* -> Attr * (Num (Attr * *)))", ); } From d47d409a92b8421e755a01a2734502bbe2593631 Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 11 Mar 2020 14:25:16 +0100 Subject: [PATCH 040/428] clean up to_int_or_float with the changes from the previous commit, this now works as it should --- compiler/mono/src/expr.rs | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/compiler/mono/src/expr.rs b/compiler/mono/src/expr.rs index f62017a47f..2febe13710 100644 --- a/compiler/mono/src/expr.rs +++ b/compiler/mono/src/expr.rs @@ -137,14 +137,8 @@ enum IntOrFloat { } fn to_int_or_float(subs: &Subs, var: Variable) -> IntOrFloat { - // TODO FIXME Investigate why both INT_INT and INT_INTEGER (and same with - // FLOAT_FLOAT and FLOAT_FLOATINGPOINT) are necessary here. It should - // be one or the other, but not both! The fact that both are necessary - // isn't a problem for this phase, but it suggests that either something - // is wrong with aliases or (more likely) some numeric builtins are being - // assigned the wrong types somewhere. match subs.get_without_compacting(var).content { - Content::Alias(Symbol::INT_INT, args, _) | Content::Alias(Symbol::INT_INTEGER, args, _) => { + Content::Alias(Symbol::INT_INTEGER, args, _) => { debug_assert!(args.is_empty()); IntOrFloat::IntType } @@ -152,8 +146,7 @@ fn to_int_or_float(subs: &Subs, var: Variable) -> IntOrFloat { // If this was still a (Num *), assume compiling it to an Int IntOrFloat::IntType } - Content::Alias(Symbol::FLOAT_FLOAT, args, _) - | Content::Alias(Symbol::FLOAT_FLOATINGPOINT, args, _) => { + Content::Alias(Symbol::FLOAT_FLOATINGPOINT, args, _) => { debug_assert!(args.is_empty()); IntOrFloat::FloatType } From 4c9f2c1b6e5c1cea0163c7898e48e090b3aa3729 Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 11 Mar 2020 15:24:44 +0100 Subject: [PATCH 041/428] monomorphize addition --- compiler/module/src/symbol.rs | 2 ++ compiler/mono/src/expr.rs | 18 ++++++++++--- compiler/mono/tests/test_mono.rs | 43 ++++++++++++++++++++++++++++++++ 3 files changed, 60 insertions(+), 3 deletions(-) diff --git a/compiler/module/src/symbol.rs b/compiler/module/src/symbol.rs index dea7b585d6..4ab921501f 100644 --- a/compiler/module/src/symbol.rs +++ b/compiler/module/src/symbol.rs @@ -575,6 +575,7 @@ define_builtins! { 4 INT_MOD: "mod" 5 INT_HIGHEST: "highest" 6 INT_LOWEST: "lowest" + 7 INT_ADD: "add" } 3 FLOAT: "Float" => { 0 FLOAT_FLOAT: "Float" imported // the Float.Float type alias @@ -585,6 +586,7 @@ define_builtins! { 5 FLOAT_SQRT: "sqrt" 6 FLOAT_HIGHEST: "highest" 7 FLOAT_LOWEST: "lowest" + 8 FLOAT_ADD: "add" } 4 BOOL: "Bool" => { 0 BOOL_BOOL: "Bool" imported // the Bool.Bool type alias diff --git a/compiler/mono/src/expr.rs b/compiler/mono/src/expr.rs index d9b6c9a928..a45af92586 100644 --- a/compiler/mono/src/expr.rs +++ b/compiler/mono/src/expr.rs @@ -264,13 +264,23 @@ fn from_can<'a>( } Call(boxed, loc_args, _) => { - let (fn_var, loc_expr, _) = *boxed; + let (fn_var, loc_expr, ret_var) = *boxed; + + let specialize_builtin_functions = { + |symbol, subs: &Subs| match dbg!(symbol) { + Symbol::NUM_ADD => match to_int_or_float(subs, ret_var) { + IntOrFloat::FloatType => Symbol::FLOAT_ADD, + IntOrFloat::IntType => Symbol::INT_ADD, + }, + _ => symbol, + } + }; match from_can(env, loc_expr.value, procs, None) { Expr::Load(proc_name) => { // Some functions can potentially mutate in-place. // If we have one of those, switch to the in-place version if appropriate. - match proc_name { + match specialize_builtin_functions(proc_name, &env.subs) { Symbol::LIST_SET => { let subs = &env.subs; // The first arg is the one with the List in it. @@ -302,7 +312,9 @@ fn from_can<'a>( _ => call_by_name(env, procs, proc_name, loc_args), } } - _ => call_by_name(env, procs, proc_name, loc_args), + specialized_proc_symbol => { + call_by_name(env, procs, specialized_proc_symbol, loc_args) + } } } ptr => { diff --git a/compiler/mono/tests/test_mono.rs b/compiler/mono/tests/test_mono.rs index 04e8156933..47a7efedce 100644 --- a/compiler/mono/tests/test_mono.rs +++ b/compiler/mono/tests/test_mono.rs @@ -78,6 +78,49 @@ mod test_mono { compiles_to("0.5", Float(0.5)); } + #[test] + fn float_addition() { + compiles_to( + "3.0 + 4", + CallByName( + Symbol::FLOAT_ADD, + &[ + (Float(3.0), Layout::Builtin(Builtin::Float64)), + (Float(4.0), Layout::Builtin(Builtin::Float64)), + ], + ), + ); + } + + #[test] + fn int_addition() { + compiles_to( + "0xDEADBEEF + 4", + CallByName( + Symbol::INT_ADD, + &[ + (Int(3735928559), Layout::Builtin(Builtin::Int64)), + (Int(4), Layout::Builtin(Builtin::Int64)), + ], + ), + ); + } + + #[test] + fn num_addition() { + // Default to Int for `Num *` + compiles_to( + "3 + 5", + CallByName( + Symbol::INT_ADD, + &[ + (Int(3), Layout::Builtin(Builtin::Int64)), + (Int(5), Layout::Builtin(Builtin::Int64)), + ], + ), + ); + } + #[test] fn bool_literal() { let arena = Bump::new(); From 5c9cf0ef3773ef4186316e11dadd501cbb082104 Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 11 Mar 2020 22:01:56 +0100 Subject: [PATCH 042/428] add type hash function --- compiler/types/src/pretty_print.rs | 6 +-- compiler/types/src/subs.rs | 85 +++++++++++++++++++++++++++++- 2 files changed, 87 insertions(+), 4 deletions(-) diff --git a/compiler/types/src/pretty_print.rs b/compiler/types/src/pretty_print.rs index ad89737b40..cf5e8a3588 100644 --- a/compiler/types/src/pretty_print.rs +++ b/compiler/types/src/pretty_print.rs @@ -504,13 +504,13 @@ fn write_flat_type( } } -fn chase_ext_tag_union( - subs: &mut Subs, +pub fn chase_ext_tag_union( + subs: &Subs, var: Variable, fields: &mut Vec<(TagName, Vec)>, ) -> Option { use FlatType::*; - match subs.get(var).content { + match subs.get_without_compacting(var).content { Content::Structure(EmptyTagUnion) => None, Content::Structure(TagUnion(tags, ext_var)) | Content::Structure(RecursiveTagUnion(_, tags, ext_var)) => { diff --git a/compiler/types/src/subs.rs b/compiler/types/src/subs.rs index 9ac431e55c..44cfa0ea84 100644 --- a/compiler/types/src/subs.rs +++ b/compiler/types/src/subs.rs @@ -42,7 +42,7 @@ struct NameState { normals: u32, } -#[derive(Default)] +#[derive(Default, Clone)] pub struct Subs { utable: UnificationTable>, } @@ -542,6 +542,89 @@ pub enum FlatType { Boolean(boolean_algebra::Bool), } +#[derive(Clone, Debug, Hash, PartialEq, Eq, Copy)] +pub struct ContentHash(u64); + +impl ContentHash { + pub fn from_var(var: Variable, subs: &Subs) -> Self { + use std::hash::Hasher; + + let mut hasher = std::collections::hash_map::DefaultHasher::new(); + Self::from_var_help(var, subs, &mut hasher); + + ContentHash(hasher.finish()) + } + + pub fn from_var_help(var: Variable, subs: &Subs, hasher: &mut T) + where + T: std::hash::Hasher, + { + Self::from_content_help(&subs.get_without_compacting(var).content, subs, hasher) + } + + pub fn from_content_help(content: &Content, subs: &Subs, hasher: &mut T) + where + T: std::hash::Hasher, + { + match content { + Content::Alias(_, _, actual) => Self::from_var_help(*actual, subs, hasher), + Content::Structure(flat_type) => { + hasher.write_u8(0x10); + Self::from_flat_type_help(flat_type, subs, hasher) + } + Content::FlexVar(_) | Content::RigidVar(_) => { + hasher.write_u8(0x11); + } + Content::Error => { + hasher.write_u8(0x12); + } + } + } + + pub fn from_flat_type_help(flat_type: &FlatType, subs: &Subs, hasher: &mut T) + where + T: std::hash::Hasher, + { + use std::hash::Hash; + + match flat_type { + FlatType::Func(arguments, ret) => { + hasher.write_u8(0); + + for var in arguments { + Self::from_var_help(*var, subs, hasher); + } + + Self::from_var_help(*ret, subs, hasher); + } + + FlatType::TagUnion(tags, ext) => { + hasher.write_u8(1); + + // We have to sort by the key, so this clone seems to be required + let mut tag_vec = Vec::with_capacity(tags.len()); + tag_vec.extend(tags.clone().into_iter()); + + match crate::pretty_print::chase_ext_tag_union(subs, *ext, &mut tag_vec) { + Some(_) => panic!("Tag union with non-empty ext var"), + None => { + tag_vec.sort(); + for (name, arguments) in tag_vec { + name.hash(hasher); + + for var in arguments { + Self::from_var_help(var, subs, hasher); + } + } + } + } + } + + _ => todo!(), + } + } +} + #[derive(PartialEq, Eq, Debug, Clone, Copy)] pub enum Builtin { Str, From f7a2be113ebf568053fc5a71011b9c097fdb8a67 Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 11 Mar 2020 22:03:01 +0100 Subject: [PATCH 043/428] monomorphize closures --- compiler/mono/src/expr.rs | 169 ++++++++++++++++++++++++++++--- compiler/mono/tests/test_mono.rs | 38 +++++++ 2 files changed, 192 insertions(+), 15 deletions(-) diff --git a/compiler/mono/src/expr.rs b/compiler/mono/src/expr.rs index a45af92586..0554575e0e 100644 --- a/compiler/mono/src/expr.rs +++ b/compiler/mono/src/expr.rs @@ -7,12 +7,20 @@ use roc_collections::all::MutMap; use roc_module::ident::{Lowercase, TagName}; use roc_module::symbol::{IdentIds, ModuleId, Symbol}; use roc_region::all::Located; -use roc_types::subs::{Content, FlatType, Subs, Variable}; +use roc_types::subs::{Content, ContentHash, FlatType, Subs, Variable}; -pub type Procs<'a> = MutMap>>; +pub type Procs<'a> = MutMap>; + +#[derive(Clone, Debug, PartialEq)] +pub struct PartialProc<'a> { + pub annotation: Variable, + pub body: roc_can::expr::Expr, + pub specializations: MutMap>)>, +} #[derive(Clone, Debug, PartialEq)] pub struct Proc<'a> { + pub name: Symbol, pub args: &'a [(Layout<'a>, Symbol)], pub body: Expr<'a>, pub closes_over: Layout<'a>, @@ -255,19 +263,29 @@ fn from_can<'a>( Expr::Store(stored.into_bump_slice(), arena.alloc(ret)) } - Closure(_, _, _, loc_args, boxed_body) => { - let (loc_body, ret_var) = *boxed_body; + Closure(annotation, _, _, _loc_args, boxed_body) => { + let (loc_body, _ret_var) = *boxed_body; let symbol = name.unwrap_or_else(|| gen_closure_name(procs, &mut env.ident_ids, env.home)); - add_closure(env, symbol, loc_body.value, ret_var, &loc_args, procs) + procs.insert( + symbol, + PartialProc { + annotation, + body: loc_body.value, + specializations: MutMap::default(), + }, + ); + + // add_closure(env, symbol, loc_body.value, ret_var, &loc_args, procs) + Expr::FunctionPointer(symbol) } Call(boxed, loc_args, _) => { let (fn_var, loc_expr, ret_var) = *boxed; let specialize_builtin_functions = { - |symbol, subs: &Subs| match dbg!(symbol) { + |symbol, subs: &Subs| match symbol { Symbol::NUM_ADD => match to_int_or_float(subs, ret_var) { IntOrFloat::FloatType => Symbol::FLOAT_ADD, IntOrFloat::IntType => Symbol::INT_ADD, @@ -307,14 +325,19 @@ fn from_can<'a>( Symbol::LIST_SET }; - call_by_name(env, procs, new_name, loc_args) + call_by_name(env, procs, fn_var, ret_var, new_name, loc_args) } - _ => call_by_name(env, procs, proc_name, loc_args), + _ => call_by_name(env, procs, fn_var, ret_var, proc_name, loc_args), } } - specialized_proc_symbol => { - call_by_name(env, procs, specialized_proc_symbol, loc_args) - } + specialized_proc_symbol => call_by_name( + env, + procs, + fn_var, + ret_var, + specialized_proc_symbol, + loc_args, + ), } } ptr => { @@ -466,6 +489,7 @@ fn from_can<'a>( } } +/* fn add_closure<'a>( env: &mut Env<'a, '_>, symbol: Symbol, @@ -513,6 +537,7 @@ fn add_closure<'a>( Expr::FunctionPointer(symbol) } +*/ fn store_pattern<'a>( env: &mut Env<'a, '_>, @@ -819,19 +844,133 @@ fn from_can_when<'a>( fn call_by_name<'a>( env: &mut Env<'a, '_>, procs: &mut Procs<'a>, + fn_var: Variable, + ret_var: Variable, proc_name: Symbol, loc_args: std::vec::Vec<(Variable, Located)>, ) -> Expr<'a> { + // create specialized procedure to call + + // TODO this seems very wrong! + // something with mutable and immutable borrow of procs + let procs_clone = procs.clone(); + let op_partial_proc = procs_clone.get(&proc_name); + + let specialized_proc_name = if let Some(partial_proc) = op_partial_proc { + let content_hash = ContentHash::from_var(fn_var, env.subs); + + if let Some(specialization) = partial_proc.specializations.get(&content_hash) { + // a specialization with this type hash already exists, use its symbol + specialization.0 + } else { + // generate a symbol for this specialization + let spec_proc_name = gen_closure_name(procs, &mut env.ident_ids, env.home); + + // register proc, so specialization doesn't loop infinitely + // for recursive definitions + let mut temp = partial_proc.clone(); + temp.specializations + .insert(content_hash, (spec_proc_name, None)); + procs.insert(proc_name, temp); + + let proc = specialize_proc_body( + env, + procs, + fn_var, + ret_var, + spec_proc_name, + &loc_args, + partial_proc, + ); + + // we must be careful here, the specialization could have added different + // specializations of proc_name, so we must get the partial_proc again! + procs.get_mut(&proc_name).map(|partial_proc| { + partial_proc + .specializations + .insert(content_hash, (spec_proc_name, proc)) + }); + + spec_proc_name + } + } else { + // This happens for built-in symbols (they are never defined as a Closure) + proc_name + }; + + // generate actual call let mut args = Vec::with_capacity_in(loc_args.len(), env.arena); - let subs = env.subs; - let arena = env.arena; for (var, loc_arg) in loc_args { - let layout = Layout::from_var(arena, var, subs, env.pointer_size) + let layout = Layout::from_var(&env.arena, var, &env.subs, env.pointer_size) .unwrap_or_else(|err| panic!("TODO gracefully handle bad layout: {:?}", err)); args.push((from_can(env, loc_arg.value, procs, None), layout)); } - Expr::CallByName(proc_name, args.into_bump_slice()) + Expr::CallByName(specialized_proc_name, args.into_bump_slice()) +} + +fn specialize_proc_body<'a>( + env: &mut Env<'a, '_>, + procs: &mut Procs<'a>, + fn_var: Variable, + ret_var: Variable, + proc_name: Symbol, + loc_args: &[(Variable, Located)], + partial_proc: &PartialProc<'a>, +) -> Option> { + let mut subs: Subs = env.subs.clone(); + roc_unify::unify::unify(&mut subs, partial_proc.annotation, fn_var); + + // Swap in copied subs, specialize the body, put old subs back + let specialized_body = { + let mut env = Env { + subs: env.arena.alloc(subs), + arena: env.arena, + home: env.home, + ident_ids: &mut env.ident_ids, + pointer_size: env.pointer_size, + }; + + from_can(&mut env, partial_proc.body.clone(), procs, None) + }; + + let mut proc_args = Vec::with_capacity_in(loc_args.len(), &env.arena); + + for (arg_var, _loc_arg) in loc_args.iter() { + let layout = match Layout::from_var(&env.arena, *arg_var, env.subs, env.pointer_size) { + Ok(layout) => layout, + Err(()) => { + // Invalid closure! + return None; + } + }; + + // TODO FIXME what is the idea here? arguments don't map to identifiers one-to-one + // e.g. underscore and record patterns + let arg_name = proc_name; + + // let arg_name: Symbol = match &loc_arg.value { + // Pattern::Identifier(symbol) => *symbol, + // _ => { + // panic!("TODO determine arg_name for pattern {:?}", loc_arg.value); + // } + // }; + + proc_args.push((layout, arg_name)); + } + + let ret_layout = Layout::from_var(&env.arena, ret_var, env.subs, env.pointer_size) + .unwrap_or_else(|err| panic!("TODO handle invalid function {:?}", err)); + + let proc = Proc { + name: proc_name, + args: proc_args.into_bump_slice(), + body: specialized_body, + closes_over: Layout::Struct(&[]), + ret_layout, + }; + + Some(proc) } diff --git a/compiler/mono/tests/test_mono.rs b/compiler/mono/tests/test_mono.rs index 47a7efedce..b3f4f1fd0e 100644 --- a/compiler/mono/tests/test_mono.rs +++ b/compiler/mono/tests/test_mono.rs @@ -62,6 +62,8 @@ mod test_mono { pointer_size, ); + dbg!(&procs); + // Put this module's ident_ids back in the interns interns.all_ident_ids.insert(home, ident_ids); @@ -121,6 +123,42 @@ mod test_mono { ); } + #[test] + fn specialize_closure() { + compiles_to( + r#" + f = \x -> x + 5 + + { x: f 0x4, y: f 3.14 } + "#, + { + use self::Builtin::*; + use Layout::Builtin; + let home = test_home(); + + let gen_symbol_3 = Interns::from_index(home, 3); + let gen_symbol_4 = Interns::from_index(home, 4); + + Struct { + fields: &[ + ( + "x".into(), + CallByName(gen_symbol_3, &[(Int(4), Builtin(Int64))]), + ), + ( + "y".into(), + CallByName(gen_symbol_4, &[(Float(3.14), Builtin(Float64))]), + ), + ], + layout: Layout::Struct(&[ + ("x".into(), Builtin(Int64)), + ("y".into(), Builtin(Float64)), + ]), + } + }, + ) + } + #[test] fn bool_literal() { let arena = Bump::new(); From a037173cdbad9911d3cb3689cb57eb3488d39842 Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 11 Mar 2020 23:00:44 +0100 Subject: [PATCH 044/428] complete the Content hashing --- compiler/types/src/pretty_print.rs | 23 ++++++++ compiler/types/src/subs.rs | 85 ++++++++++++++++++++++++++++-- compiler/types/src/types.rs | 4 +- 3 files changed, 107 insertions(+), 5 deletions(-) diff --git a/compiler/types/src/pretty_print.rs b/compiler/types/src/pretty_print.rs index cf5e8a3588..f576a5ca50 100644 --- a/compiler/types/src/pretty_print.rs +++ b/compiler/types/src/pretty_print.rs @@ -525,6 +525,29 @@ pub fn chase_ext_tag_union( } } +pub fn chase_ext_record( + subs: &Subs, + var: Variable, + fields: &mut MutMap, +) -> Option { + use crate::subs::Content::*; + use crate::subs::FlatType::*; + + match subs.get_without_compacting(var).content { + Structure(Record(sub_fields, sub_ext)) => { + fields.extend(sub_fields.into_iter()); + + chase_ext_record(subs, sub_ext, fields) + } + + Structure(EmptyRecord) => None, + + Alias(_, _, var) => chase_ext_record(subs, var, fields), + + content => Some(content), + } +} + fn write_boolean(env: &Env, boolean: Bool, subs: &mut Subs, buf: &mut String, parens: Parens) { match boolean.simplify(subs) { Err(atom) => write_boolean_atom(env, atom, subs, buf, parens), diff --git a/compiler/types/src/subs.rs b/compiler/types/src/subs.rs index 44cfa0ea84..bad0b24414 100644 --- a/compiler/types/src/subs.rs +++ b/compiler/types/src/subs.rs @@ -567,7 +567,10 @@ impl ContentHash { T: std::hash::Hasher, { match content { - Content::Alias(_, _, actual) => Self::from_var_help(*actual, subs, hasher), + Content::Alias(_, _, actual) => { + // ensure an alias has the same hash as just the body of the alias + Self::from_var_help(*actual, subs, hasher) + } Content::Structure(flat_type) => { hasher.write_u8(0x10); Self::from_flat_type_help(flat_type, subs, hasher) @@ -598,9 +601,49 @@ impl ContentHash { Self::from_var_help(*ret, subs, hasher); } - FlatType::TagUnion(tags, ext) => { + FlatType::Apply(symbol, arguments) => { hasher.write_u8(1); + symbol.hash(hasher); + + for var in arguments { + Self::from_var_help(*var, subs, hasher); + } + } + + FlatType::EmptyRecord => { + hasher.write_u8(2); + } + + FlatType::Record(fields, ext) => { + hasher.write_u8(3); + + // We have to sort by the key, so this clone seems to be required + let mut fields = fields.clone(); + + match crate::pretty_print::chase_ext_record(subs, *ext, &mut fields) { + Some(_) => panic!("Record with non-empty ext var"), + None => { + let mut fields_vec = Vec::with_capacity(fields.len()); + fields_vec.extend(fields.into_iter()); + + fields_vec.sort(); + + for (name, argument) in fields_vec { + name.hash(hasher); + Self::from_var_help(argument, subs, hasher); + } + } + } + } + + FlatType::EmptyTagUnion => { + hasher.write_u8(4); + } + + FlatType::TagUnion(tags, ext) => { + hasher.write_u8(5); + // We have to sort by the key, so this clone seems to be required let mut tag_vec = Vec::with_capacity(tags.len()); tag_vec.extend(tags.clone().into_iter()); @@ -620,7 +663,43 @@ impl ContentHash { } } - _ => todo!(), + FlatType::RecursiveTagUnion(_rec, tags, ext) => { + // TODO should the rec_var be hashed in? + hasher.write_u8(6); + + // We have to sort by the key, so this clone seems to be required + let mut tag_vec = Vec::with_capacity(tags.len()); + tag_vec.extend(tags.clone().into_iter()); + + match crate::pretty_print::chase_ext_tag_union(subs, *ext, &mut tag_vec) { + Some(_) => panic!("Tag union with non-empty ext var"), + None => { + tag_vec.sort(); + for (name, arguments) in tag_vec { + name.hash(hasher); + + for var in arguments { + Self::from_var_help(var, subs, hasher); + } + } + } + } + } + + FlatType::Boolean(boolean) => { + hasher.write_u8(7); + match boolean.simplify(subs) { + Ok(_variables) => hasher.write_u8(1), + Err(crate::boolean_algebra::Atom::One) => hasher.write_u8(1), + Err(crate::boolean_algebra::Atom::Zero) => hasher.write_u8(0), + Err(crate::boolean_algebra::Atom::Variable(_)) => unreachable!(), + } + } + + FlatType::Erroneous(_problem) => { + hasher.write_u8(8); + //TODO hash the problem? + } } } } diff --git a/compiler/types/src/types.rs b/compiler/types/src/types.rs index 172d0117f0..2ff8f257ca 100644 --- a/compiler/types/src/types.rs +++ b/compiler/types/src/types.rs @@ -728,14 +728,14 @@ pub fn name_type_var(letters_used: u32, taken: &mut MutSet) -> (Lower } pub fn gather_fields( - subs: &mut Subs, + subs: &Subs, fields: MutMap, var: Variable, ) -> RecordStructure { use crate::subs::Content::*; use crate::subs::FlatType::*; - match subs.get(var).content { + match subs.get_without_compacting(var).content { Structure(Record(sub_fields, sub_ext)) => { gather_fields(subs, union(fields, &sub_fields), sub_ext) } From 2d0649fa665c4f763d25cb22628cfb6096011299 Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 11 Mar 2020 23:13:32 +0100 Subject: [PATCH 045/428] attempt fix for gen tests --- compiler/gen/tests/test_gen.rs | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/compiler/gen/tests/test_gen.rs b/compiler/gen/tests/test_gen.rs index 464f680ddd..fb09a4243f 100644 --- a/compiler/gen/tests/test_gen.rs +++ b/compiler/gen/tests/test_gen.rs @@ -85,13 +85,15 @@ mod test_gen { // Declare all the Procs, then insert them into scope so their bodies // can look up their Funcs in scope later when calling each other by value. - for (name, opt_proc) in procs.iter() { - if let Some(proc) = opt_proc { - let (func_id, sig) = declare_proc(&env, &mut module, name.clone(), proc); + for (_name, partial_proc) in procs.iter() { + for ( _content_hash, opt_proc ) in partial_proc.specializations.iter() { + if let (name, Some(proc)) = opt_proc { + let (func_id, sig) = declare_proc(&env, &mut module, name.clone(), &proc); - declared.push((proc.clone(), sig.clone(), func_id)); + declared.push((proc.clone(), sig.clone(), func_id)); - scope.insert(name.clone(), ScopeEntry::Func { func_id, sig }); + scope.insert(name.clone(), ScopeEntry::Func { func_id, sig }); + } } } @@ -234,12 +236,14 @@ mod test_gen { // 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. - for (symbol, opt_proc) in procs.clone().into_iter() { - if let Some(proc) = opt_proc { + for (_symbol, partial_proc) in procs.clone().into_iter() { + for ( _content_hash, opt_proc ) in partial_proc.specializations { + if let (symbol, Some(proc)) = opt_proc { let (fn_val, arg_basic_types) = build_proc_header(&env, symbol, &proc); headers.push((proc, fn_val, arg_basic_types)); } + } } // Build each proc using its header info. @@ -369,12 +373,15 @@ mod test_gen { // 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. - for (symbol, opt_proc) in procs.clone().into_iter() { - if let Some(proc) = opt_proc { + for (_symbol, partial_proc) in procs.clone().into_iter() { + for ( _content_hash, opt_proc ) in partial_proc.specializations { + if let (symbol, Some(proc)) = opt_proc { let (fn_val, arg_basic_types) = build_proc_header(&env, symbol, &proc); headers.push((proc, fn_val, arg_basic_types)); } + } + } // Build each proc using its header info. From bb9c9d423a31f41570e039753cafad70e7b1dbab Mon Sep 17 00:00:00 2001 From: Folkert Date: Thu, 12 Mar 2020 00:40:10 +0100 Subject: [PATCH 046/428] make Procs a struct --- compiler/gen/tests/test_gen.rs | 34 ++++++------- compiler/mono/src/expr.rs | 83 ++++++++++++++++++++++++++------ compiler/mono/tests/test_mono.rs | 3 +- 3 files changed, 84 insertions(+), 36 deletions(-) diff --git a/compiler/gen/tests/test_gen.rs b/compiler/gen/tests/test_gen.rs index fb09a4243f..92a52f6c0a 100644 --- a/compiler/gen/tests/test_gen.rs +++ b/compiler/gen/tests/test_gen.rs @@ -24,13 +24,13 @@ mod test_gen { use inkwell::passes::PassManager; use inkwell::types::BasicType; use inkwell::OptimizationLevel; - use roc_collections::all::{ImMap, MutMap}; + use roc_collections::all::ImMap; use roc_gen::crane::build::{declare_proc, define_proc_body, ScopeEntry}; use roc_gen::crane::convert::type_from_layout; use roc_gen::crane::imports::define_malloc; use roc_gen::llvm::build::{build_proc, build_proc_header}; use roc_gen::llvm::convert::basic_type_from_layout; - use roc_mono::expr::Expr; + use roc_mono::expr::{Expr, Procs}; use roc_mono::layout::Layout; use roc_types::subs::Subs; use std::ffi::{CStr, CString}; @@ -65,7 +65,7 @@ mod test_gen { let main_ret_type = type_from_layout(cfg, &layout); // Compile and add all the Procs before adding main - let mut procs = MutMap::default(); + let mut procs = Procs::default(); let mut env = roc_gen::crane::build::Env { arena: &arena, interns, @@ -85,16 +85,14 @@ mod test_gen { // Declare all the Procs, then insert them into scope so their bodies // can look up their Funcs in scope later when calling each other by value. - for (_name, partial_proc) in procs.iter() { - for ( _content_hash, opt_proc ) in partial_proc.specializations.iter() { - if let (name, Some(proc)) = opt_proc { - let (func_id, sig) = declare_proc(&env, &mut module, name.clone(), &proc); + for (name, opt_proc) in procs.as_map().into_iter() { + if let Some(proc) = opt_proc { + let (func_id, sig) = declare_proc(&env, &mut module, name, &proc); declared.push((proc.clone(), sig.clone(), func_id)); scope.insert(name.clone(), ScopeEntry::Func { func_id, sig }); } - } } for (proc, sig, fn_id) in declared { @@ -222,7 +220,7 @@ mod test_gen { module: arena.alloc(module), pointer_bytes }; - let mut procs = MutMap::default(); + let mut procs = Procs::default(); let mut ident_ids = env.interns.all_ident_ids.remove(&home).unwrap(); // Populate Procs and get the low-level Expr from the canonical Expr @@ -236,14 +234,12 @@ mod test_gen { // 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. - for (_symbol, partial_proc) in procs.clone().into_iter() { - for ( _content_hash, opt_proc ) in partial_proc.specializations { - if let (symbol, Some(proc)) = opt_proc { + for (symbol, opt_proc) in procs.as_map().into_iter() { + if let Some(proc) = opt_proc { let (fn_val, arg_basic_types) = build_proc_header(&env, symbol, &proc); headers.push((proc, fn_val, arg_basic_types)); } - } } // Build each proc using its header info. @@ -275,7 +271,7 @@ mod test_gen { &ImMap::default(), main_fn, &main_body, - &mut MutMap::default(), + &mut Procs::default(), ); builder.build_return(Some(&ret)); @@ -359,7 +355,7 @@ mod test_gen { module: arena.alloc(module), pointer_bytes }; - let mut procs = MutMap::default(); + let mut procs = Procs::default(); let mut ident_ids = env.interns.all_ident_ids.remove(&home).unwrap(); // Populate Procs and get the low-level Expr from the canonical Expr @@ -373,14 +369,12 @@ mod test_gen { // 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. - for (_symbol, partial_proc) in procs.clone().into_iter() { - for ( _content_hash, opt_proc ) in partial_proc.specializations { - if let (symbol, Some(proc)) = opt_proc { + for (symbol, opt_proc) in procs.as_map().into_iter() { + if let Some(proc) = opt_proc { let (fn_val, arg_basic_types) = build_proc_header(&env, symbol, &proc); headers.push((proc, fn_val, arg_basic_types)); } - } } @@ -413,7 +407,7 @@ mod test_gen { &ImMap::default(), main_fn, &main_body, - &mut MutMap::default(), + &mut Procs::default(), ); builder.build_return(Some(&ret)); diff --git a/compiler/mono/src/expr.rs b/compiler/mono/src/expr.rs index 0554575e0e..93e687a823 100644 --- a/compiler/mono/src/expr.rs +++ b/compiler/mono/src/expr.rs @@ -3,13 +3,66 @@ use bumpalo::collections::Vec; use bumpalo::Bump; use roc_can; use roc_can::pattern::Pattern; -use roc_collections::all::MutMap; +use roc_collections::all::{MutMap, MutSet}; use roc_module::ident::{Lowercase, TagName}; use roc_module::symbol::{IdentIds, ModuleId, Symbol}; use roc_region::all::Located; use roc_types::subs::{Content, ContentHash, FlatType, Subs, Variable}; -pub type Procs<'a> = MutMap>; +#[derive(Clone, Debug, PartialEq, Default)] +pub struct Procs<'a> { + user_defined: MutMap>, + builtin: MutSet, +} + +impl<'a> Procs<'a> { + fn insert_user_defined(&mut self, symbol: Symbol, partial_proc: PartialProc<'a>) { + self.user_defined.insert(symbol, partial_proc); + } + + fn insert_specialization( + &mut self, + symbol: Symbol, + hash: ContentHash, + spec_name: Symbol, + proc: Option>, + ) { + self.user_defined + .get_mut(&symbol) + .map(|partial_proc| partial_proc.specializations.insert(hash, (spec_name, proc))); + } + + fn get_user_defined(&self, symbol: Symbol) -> Option<&PartialProc<'a>> { + self.user_defined.get(&symbol) + } + + pub fn len(&self) -> usize { + self.user_defined + .values() + .map(|v| v.specializations.len()) + .sum() + } + + fn insert_builtin(&mut self, symbol: Symbol) { + self.builtin.insert(symbol); + } + + pub fn as_map(&self) -> MutMap>> { + let mut result = MutMap::default(); + + for partial_proc in self.user_defined.values() { + for (_, (symbol, opt_proc)) in partial_proc.specializations.clone().into_iter() { + result.insert(symbol, opt_proc); + } + } + + for symbol in self.builtin.iter() { + result.insert(*symbol, None); + } + + result + } +} #[derive(Clone, Debug, PartialEq)] pub struct PartialProc<'a> { @@ -268,7 +321,7 @@ fn from_can<'a>( let symbol = name.unwrap_or_else(|| gen_closure_name(procs, &mut env.ident_ids, env.home)); - procs.insert( + procs.insert_user_defined( symbol, PartialProc { annotation, @@ -854,7 +907,7 @@ fn call_by_name<'a>( // TODO this seems very wrong! // something with mutable and immutable borrow of procs let procs_clone = procs.clone(); - let op_partial_proc = procs_clone.get(&proc_name); + let op_partial_proc = procs_clone.get_user_defined(proc_name); let specialized_proc_name = if let Some(partial_proc) = op_partial_proc { let content_hash = ContentHash::from_var(fn_var, env.subs); @@ -868,10 +921,12 @@ fn call_by_name<'a>( // register proc, so specialization doesn't loop infinitely // for recursive definitions - let mut temp = partial_proc.clone(); - temp.specializations - .insert(content_hash, (spec_proc_name, None)); - procs.insert(proc_name, temp); + // let mut temp = partial_proc.clone(); + // temp.specializations + // .insert(content_hash, (spec_proc_name, None)); + // procs.insert_user_defined(proc_name, temp); + + procs.insert_specialization(proc_name, content_hash, spec_proc_name, None); let proc = specialize_proc_body( env, @@ -883,21 +938,19 @@ fn call_by_name<'a>( partial_proc, ); - // we must be careful here, the specialization could have added different - // specializations of proc_name, so we must get the partial_proc again! - procs.get_mut(&proc_name).map(|partial_proc| { - partial_proc - .specializations - .insert(content_hash, (spec_proc_name, proc)) - }); + procs.insert_specialization(proc_name, content_hash, spec_proc_name, proc); spec_proc_name } } else { // This happens for built-in symbols (they are never defined as a Closure) + procs.insert_builtin(proc_name); proc_name }; + dbg!(proc_name); + dbg!(specialized_proc_name); + // generate actual call let mut args = Vec::with_capacity_in(loc_args.len(), env.arena); diff --git a/compiler/mono/tests/test_mono.rs b/compiler/mono/tests/test_mono.rs index b3f4f1fd0e..10cf290372 100644 --- a/compiler/mono/tests/test_mono.rs +++ b/compiler/mono/tests/test_mono.rs @@ -17,6 +17,7 @@ mod test_mono { use roc_module::ident::TagName::*; use roc_module::symbol::{Interns, Symbol}; use roc_mono::expr::Expr::{self, *}; + use roc_mono::expr::Procs; use roc_mono::layout::{Builtin, Layout}; use roc_types::subs::Subs; @@ -45,7 +46,7 @@ mod test_mono { let (_content, subs) = infer_expr(subs, &mut unify_problems, &constraint, var); // Compile and add all the Procs before adding main - let mut procs = MutMap::default(); + let mut procs = Procs::default(); let mut ident_ids = interns.all_ident_ids.remove(&home).unwrap(); // assume 64-bit pointers From 74b58db47786b419368897bd9af1d3aa167777a6 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Wed, 11 Mar 2020 19:45:12 -0400 Subject: [PATCH 047/428] Use Procs::default() --- compiler/mono/tests/test_opt.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/mono/tests/test_opt.rs b/compiler/mono/tests/test_opt.rs index a7c6cdebc1..483c022eb2 100644 --- a/compiler/mono/tests/test_opt.rs +++ b/compiler/mono/tests/test_opt.rs @@ -13,9 +13,9 @@ mod helpers; mod test_opt { use crate::helpers::{infer_expr, uniq_expr}; use bumpalo::Bump; - use roc_collections::all::MutMap; use roc_module::symbol::Symbol; use roc_mono::expr::Expr::{self, *}; + use roc_mono::expr::Procs; use roc_mono::layout::{Builtin, Layout}; // HELPERS @@ -28,7 +28,7 @@ mod test_opt { let (_content, subs) = infer_expr(subs, &mut unify_problems, &constraint, var); // Compile and add all the Procs before adding main - let mut procs = MutMap::default(); + let mut procs = Procs::default(); let mut ident_ids = interns.all_ident_ids.remove(&home).unwrap(); // assume 64-bit pointers From 649575fab8f48f7e77d71be8e566216c219380b9 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Wed, 11 Mar 2020 19:48:19 -0400 Subject: [PATCH 048/428] Improve some error messages --- compiler/gen/src/crane/build.rs | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/compiler/gen/src/crane/build.rs b/compiler/gen/src/crane/build.rs index cb45836e22..126b5193d1 100644 --- a/compiler/gen/src/crane/build.rs +++ b/compiler/gen/src/crane/build.rs @@ -120,9 +120,7 @@ pub fn build_expr<'a, B: Backend>( let fn_id = match scope.get(name) { Some(ScopeEntry::Func{ func_id, .. }) => *func_id, other => panic!( - "FunctionPointer could not find function named {:?} in scope; instead, found {:?} in scope {:?}", - name, other, scope - ), + "FunctionPointer could not find function named {:?} declared in scope (and it was not special-cased in crane::build as a builtin); instead, found {:?} in scope {:?}", name, other, scope), }; let func_ref = module.declare_func_in_func(fn_id, &mut builder.func); @@ -665,12 +663,9 @@ fn call_by_name<'a, B: Backend>( } _ => { let fn_id = match scope.get(&symbol) { - Some(ScopeEntry::Func{ func_id, .. }) => *func_id, - other => panic!( - "CallByName could not find function named {:?} in scope; instead, found {:?} in scope {:?}", - symbol, other, scope - ), - }; + Some(ScopeEntry::Func { func_id, .. }) => *func_id, + other => panic!("CallByName could not find function named {:?} declared in scope (and it was not special-cased in crane::build as a builtin); instead, found {:?} in scope {:?}", symbol, other, scope), + }; let local_func = module.declare_func_in_func(fn_id, &mut builder.func); let mut arg_vals = Vec::with_capacity_in(args.len(), env.arena); From 523282e7bc2fd87ddd45476a133b69c9fde334fb Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Wed, 11 Mar 2020 20:50:32 -0400 Subject: [PATCH 049/428] Implement Int.#add --- compiler/gen/src/crane/build.rs | 2 +- compiler/gen/src/llvm/build.rs | 4 ++-- compiler/module/src/symbol.rs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/compiler/gen/src/crane/build.rs b/compiler/gen/src/crane/build.rs index 126b5193d1..f7ccfab05a 100644 --- a/compiler/gen/src/crane/build.rs +++ b/compiler/gen/src/crane/build.rs @@ -545,7 +545,7 @@ fn call_by_name<'a, B: Backend>( procs: &Procs<'a>, ) -> Value { match symbol { - Symbol::NUM_ADD => { + Symbol::INT_ADD | Symbol::NUM_ADD => { debug_assert!(args.len() == 2); let a = build_arg(&args[0], env, scope, module, builder, procs); let b = build_arg(&args[1], env, scope, module, builder, procs); diff --git a/compiler/gen/src/llvm/build.rs b/compiler/gen/src/llvm/build.rs index 6dbfc17981..c2e294abf9 100644 --- a/compiler/gen/src/llvm/build.rs +++ b/compiler/gen/src/llvm/build.rs @@ -536,13 +536,13 @@ fn call_with_args<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, ) -> BasicValueEnum<'ctx> { match symbol { - Symbol::NUM_ADD => { + Symbol::INT_ADD | Symbol::NUM_ADD => { debug_assert!(args.len() == 2); let int_val = env.builder.build_int_add( args[0].into_int_value(), args[1].into_int_value(), - "ADD_I64", + "add_i64", ); BasicValueEnum::IntValue(int_val) diff --git a/compiler/module/src/symbol.rs b/compiler/module/src/symbol.rs index 4ab921501f..9c39b8f745 100644 --- a/compiler/module/src/symbol.rs +++ b/compiler/module/src/symbol.rs @@ -575,7 +575,7 @@ define_builtins! { 4 INT_MOD: "mod" 5 INT_HIGHEST: "highest" 6 INT_LOWEST: "lowest" - 7 INT_ADD: "add" + 7 INT_ADD: "#add" } 3 FLOAT: "Float" => { 0 FLOAT_FLOAT: "Float" imported // the Float.Float type alias From 21e4eb505a49bff84e67044acce6f38b84ec33f6 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Wed, 11 Mar 2020 20:50:52 -0400 Subject: [PATCH 050/428] Implement and test Float.#add --- compiler/gen/src/crane/build.rs | 7 +++++++ compiler/gen/src/llvm/build.rs | 11 +++++++++++ compiler/gen/tests/test_gen.rs | 13 +++++++++++++ compiler/module/src/symbol.rs | 2 +- 4 files changed, 32 insertions(+), 1 deletion(-) diff --git a/compiler/gen/src/crane/build.rs b/compiler/gen/src/crane/build.rs index f7ccfab05a..56ad0110e0 100644 --- a/compiler/gen/src/crane/build.rs +++ b/compiler/gen/src/crane/build.rs @@ -552,6 +552,13 @@ fn call_by_name<'a, B: Backend>( builder.ins().iadd(a, b) } + Symbol::FLOAT_ADD => { + debug_assert!(args.len() == 2); + let a = build_arg(&args[0], env, scope, module, builder, procs); + let b = build_arg(&args[1], env, scope, module, builder, procs); + + builder.ins().fadd(a, b) + } Symbol::NUM_SUB => { debug_assert!(args.len() == 2); let a = build_arg(&args[0], env, scope, module, builder, procs); diff --git a/compiler/gen/src/llvm/build.rs b/compiler/gen/src/llvm/build.rs index c2e294abf9..df44bdf4f2 100644 --- a/compiler/gen/src/llvm/build.rs +++ b/compiler/gen/src/llvm/build.rs @@ -547,6 +547,17 @@ fn call_with_args<'a, 'ctx, 'env>( BasicValueEnum::IntValue(int_val) } + Symbol::FLOAT_ADD => { + debug_assert!(args.len() == 2); + + let float_val = env.builder.build_float_add( + args[0].into_float_value(), + args[1].into_float_value(), + "add_f64", + ); + + BasicValueEnum::FloatValue(float_val) + } Symbol::NUM_SUB => { debug_assert!(args.len() == 2); diff --git a/compiler/gen/tests/test_gen.rs b/compiler/gen/tests/test_gen.rs index 92a52f6c0a..f51bf606c4 100644 --- a/compiler/gen/tests/test_gen.rs +++ b/compiler/gen/tests/test_gen.rs @@ -800,6 +800,19 @@ mod test_gen { ); } + #[test] + fn gen_add_f64() { + assert_evals_to!( + indoc!( + r#" + 1.1 + 2.4 + 3 + "# + ), + 6.5, + f64 + ); + } + #[test] fn gen_add_i64() { assert_evals_to!( diff --git a/compiler/module/src/symbol.rs b/compiler/module/src/symbol.rs index 9c39b8f745..15650d2320 100644 --- a/compiler/module/src/symbol.rs +++ b/compiler/module/src/symbol.rs @@ -586,7 +586,7 @@ define_builtins! { 5 FLOAT_SQRT: "sqrt" 6 FLOAT_HIGHEST: "highest" 7 FLOAT_LOWEST: "lowest" - 8 FLOAT_ADD: "add" + 8 FLOAT_ADD: "#add" } 4 BOOL: "Bool" => { 0 BOOL_BOOL: "Bool" imported // the Bool.Bool type alias From a0c4e9179214d0f5886b17757bd52b61549d1a4d Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Wed, 11 Mar 2020 20:53:06 -0400 Subject: [PATCH 051/428] Rename List.set_in_place to List.#setInPlace --- compiler/module/src/symbol.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/module/src/symbol.rs b/compiler/module/src/symbol.rs index 15650d2320..ddd243e9b1 100644 --- a/compiler/module/src/symbol.rs +++ b/compiler/module/src/symbol.rs @@ -608,7 +608,7 @@ define_builtins! { 2 LIST_ISEMPTY: "isEmpty" 3 LIST_GET: "get" 4 LIST_SET: "set" - 5 LIST_SET_IN_PLACE: "set_in_place" + 5 LIST_SET_IN_PLACE: "#setInPlace" 6 LIST_PUSH: "push" 7 LIST_MAP: "map" 8 LIST_LENGTH: "length" From 762b2c7b10dd4af11a1bb3eb084fe14b643be215 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Wed, 11 Mar 2020 21:03:58 -0400 Subject: [PATCH 052/428] use IntOrFloat::* --- compiler/mono/src/expr.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/compiler/mono/src/expr.rs b/compiler/mono/src/expr.rs index 93e687a823..0aa83a4f90 100644 --- a/compiler/mono/src/expr.rs +++ b/compiler/mono/src/expr.rs @@ -335,13 +335,15 @@ fn from_can<'a>( } Call(boxed, loc_args, _) => { + use IntOrFloat::*; + let (fn_var, loc_expr, ret_var) = *boxed; let specialize_builtin_functions = { |symbol, subs: &Subs| match symbol { Symbol::NUM_ADD => match to_int_or_float(subs, ret_var) { - IntOrFloat::FloatType => Symbol::FLOAT_ADD, - IntOrFloat::IntType => Symbol::INT_ADD, + FloatType => Symbol::FLOAT_ADD, + IntType => Symbol::INT_ADD, }, _ => symbol, } From 9fcfa90bff7fd4fd2d2a814338c3ce27ff23d47f Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Wed, 11 Mar 2020 21:05:41 -0400 Subject: [PATCH 053/428] Change capitalization --- compiler/gen/src/llvm/build.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/compiler/gen/src/llvm/build.rs b/compiler/gen/src/llvm/build.rs index df44bdf4f2..c9fedd2fa8 100644 --- a/compiler/gen/src/llvm/build.rs +++ b/compiler/gen/src/llvm/build.rs @@ -564,7 +564,7 @@ fn call_with_args<'a, 'ctx, 'env>( let int_val = env.builder.build_int_sub( args[0].into_int_value(), args[1].into_int_value(), - "SUB_I64", + "sub_I64", ); BasicValueEnum::IntValue(int_val) @@ -575,7 +575,7 @@ fn call_with_args<'a, 'ctx, 'env>( let int_val = env.builder.build_int_mul( args[0].into_int_value(), args[1].into_int_value(), - "MUL_I64", + "mul_i64", ); BasicValueEnum::IntValue(int_val) @@ -585,7 +585,7 @@ fn call_with_args<'a, 'ctx, 'env>( let int_val = env .builder - .build_int_neg(args[0].into_int_value(), "NEGATE_I64"); + .build_int_neg(args[0].into_int_value(), "negate_i64"); BasicValueEnum::IntValue(int_val) } @@ -598,7 +598,7 @@ fn call_with_args<'a, 'ctx, 'env>( let builder = env.builder; let elem_bytes = 8; // TODO Look this up instead of hardcoding it! let elem_size = env.context.i64_type().const_int(elem_bytes, false); - let offset = builder.build_int_mul(elem_index, elem_size, "MUL_OFFSET"); + let offset = builder.build_int_mul(elem_index, elem_size, "mul_offset"); let elem_ptr = unsafe { builder.build_gep(list_ptr, &[offset], "elem") }; From df78068e81724460f5ea8ff6b0090e74a4662b4c Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Wed, 11 Mar 2020 21:10:41 -0400 Subject: [PATCH 054/428] Implement int and float subtraction --- compiler/gen/src/crane/build.rs | 9 ++++++++- compiler/gen/src/llvm/build.rs | 13 ++++++++++++- compiler/gen/tests/test_gen.rs | 13 +++++++++++++ compiler/module/src/symbol.rs | 2 ++ compiler/mono/src/expr.rs | 4 ++++ 5 files changed, 39 insertions(+), 2 deletions(-) diff --git a/compiler/gen/src/crane/build.rs b/compiler/gen/src/crane/build.rs index 56ad0110e0..b46c0d80b4 100644 --- a/compiler/gen/src/crane/build.rs +++ b/compiler/gen/src/crane/build.rs @@ -559,13 +559,20 @@ fn call_by_name<'a, B: Backend>( builder.ins().fadd(a, b) } - Symbol::NUM_SUB => { + Symbol::INT_SUB | Symbol::NUM_SUB => { debug_assert!(args.len() == 2); let a = build_arg(&args[0], env, scope, module, builder, procs); let b = build_arg(&args[1], env, scope, module, builder, procs); builder.ins().isub(a, b) } + Symbol::FLOAT_SUB => { + debug_assert!(args.len() == 2); + let a = build_arg(&args[0], env, scope, module, builder, procs); + let b = build_arg(&args[1], env, scope, module, builder, procs); + + builder.ins().fsub(a, b) + } Symbol::NUM_MUL => { debug_assert!(args.len() == 2); let a = build_arg(&args[0], env, scope, module, builder, procs); diff --git a/compiler/gen/src/llvm/build.rs b/compiler/gen/src/llvm/build.rs index c9fedd2fa8..e530f4baef 100644 --- a/compiler/gen/src/llvm/build.rs +++ b/compiler/gen/src/llvm/build.rs @@ -558,7 +558,7 @@ fn call_with_args<'a, 'ctx, 'env>( BasicValueEnum::FloatValue(float_val) } - Symbol::NUM_SUB => { + Symbol::INT_SUB | Symbol::NUM_SUB => { debug_assert!(args.len() == 2); let int_val = env.builder.build_int_sub( @@ -569,6 +569,17 @@ fn call_with_args<'a, 'ctx, 'env>( BasicValueEnum::IntValue(int_val) } + Symbol::FLOAT_SUB => { + debug_assert!(args.len() == 2); + + let float_val = env.builder.build_float_sub( + args[0].into_float_value(), + args[1].into_float_value(), + "sub_f64", + ); + + BasicValueEnum::FloatValue(float_val) + } Symbol::NUM_MUL => { debug_assert!(args.len() == 2); diff --git a/compiler/gen/tests/test_gen.rs b/compiler/gen/tests/test_gen.rs index f51bf606c4..fe67f1eb42 100644 --- a/compiler/gen/tests/test_gen.rs +++ b/compiler/gen/tests/test_gen.rs @@ -826,6 +826,19 @@ mod test_gen { ); } + #[test] + fn gen_sub_f64() { + assert_evals_to!( + indoc!( + r#" + 1.5 - 2.4 - 3 + "# + ), + -3.9, + f64 + ); + } + #[test] fn gen_sub_i64() { assert_evals_to!( diff --git a/compiler/module/src/symbol.rs b/compiler/module/src/symbol.rs index ddd243e9b1..bee661cef2 100644 --- a/compiler/module/src/symbol.rs +++ b/compiler/module/src/symbol.rs @@ -576,6 +576,7 @@ define_builtins! { 5 INT_HIGHEST: "highest" 6 INT_LOWEST: "lowest" 7 INT_ADD: "#add" + 8 INT_SUB: "#sub" } 3 FLOAT: "Float" => { 0 FLOAT_FLOAT: "Float" imported // the Float.Float type alias @@ -587,6 +588,7 @@ define_builtins! { 6 FLOAT_HIGHEST: "highest" 7 FLOAT_LOWEST: "lowest" 8 FLOAT_ADD: "#add" + 9 FLOAT_SUB: "#sub" } 4 BOOL: "Bool" => { 0 BOOL_BOOL: "Bool" imported // the Bool.Bool type alias diff --git a/compiler/mono/src/expr.rs b/compiler/mono/src/expr.rs index 0aa83a4f90..3d7fd87568 100644 --- a/compiler/mono/src/expr.rs +++ b/compiler/mono/src/expr.rs @@ -345,6 +345,10 @@ fn from_can<'a>( FloatType => Symbol::FLOAT_ADD, IntType => Symbol::INT_ADD, }, + Symbol::NUM_SUB => match to_int_or_float(subs, ret_var) { + FloatType => Symbol::FLOAT_SUB, + IntType => Symbol::INT_SUB, + }, _ => symbol, } }; From 2ad70d44a235e50f07e03c1d26525ec4037910ad Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Wed, 11 Mar 2020 21:15:30 -0400 Subject: [PATCH 055/428] Rename Attr module to #Attr, drop #Attr.@Attr --- compiler/module/src/symbol.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/compiler/module/src/symbol.rs b/compiler/module/src/symbol.rs index bee661cef2..5b3683a188 100644 --- a/compiler/module/src/symbol.rs +++ b/compiler/module/src/symbol.rs @@ -547,11 +547,13 @@ macro_rules! define_builtins { }; } +// NOTE: Some of these builtins have a # at the beginning of their names. +// This is because they are for compiler use only, and should not cause +// namespace conflicts with userspace! define_builtins! { - 0 ATTR: "Attr" => { + 0 ATTR: "#Attr" => { 0 UNDERSCORE: "_" // the _ used in pattern matches. This is Symbol 0. - 1 ATTR_ATTR: "Attr" // the Attr.Attr type alias, used in uniqueness types - 2 ATTR_AT_ATTR: "@Attr" // the Attr.@Attr private tag + 1 ATTR_ATTR: "Attr" // the #Attr.Attr type alias, used in uniqueness types. } 1 NUM: "Num" => { 0 NUM_NUM: "Num" imported // the Num.Num type alias From cf5e3f92a53486132f1f97d4431bf6211254a8dd Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Wed, 11 Mar 2020 21:36:27 -0400 Subject: [PATCH 056/428] Have mono::Env store &mut Subs --- compiler/gen/tests/test_gen.rs | 12 ++++++------ compiler/mono/src/expr.rs | 16 +++++++--------- compiler/mono/tests/test_mono.rs | 8 ++++---- compiler/mono/tests/test_opt.rs | 4 ++-- 4 files changed, 19 insertions(+), 21 deletions(-) diff --git a/compiler/gen/tests/test_gen.rs b/compiler/gen/tests/test_gen.rs index fe67f1eb42..1809593a57 100644 --- a/compiler/gen/tests/test_gen.rs +++ b/compiler/gen/tests/test_gen.rs @@ -46,7 +46,7 @@ mod test_gen { let CanExprOut { loc_expr, var_store, var, constraint, home, interns, .. } = can_expr($src); let subs = Subs::new(var_store.into()); let mut unify_problems = Vec::new(); - let (content, subs) = infer_expr(subs, &mut unify_problems, &constraint, var); + let (content, mut subs) = infer_expr(subs, &mut unify_problems, &constraint, var); let shared_builder = settings::builder(); let shared_flags = settings::Flags::new(shared_builder); let mut module: Module = @@ -75,7 +75,7 @@ mod test_gen { let mut ident_ids = env.interns.all_ident_ids.remove(&home).unwrap(); // Populate Procs and Subs, and get the low-level Expr from the canonical Expr - let mono_expr = Expr::new(&arena, &subs, loc_expr.value, &mut procs, home, &mut ident_ids, POINTER_SIZE); + let mono_expr = Expr::new(&arena, &mut subs, loc_expr.value, &mut procs, home, &mut ident_ids, POINTER_SIZE); // Put this module's ident_ids back in the interns env.interns.all_ident_ids.insert(home, ident_ids); @@ -174,7 +174,7 @@ mod test_gen { let CanExprOut { loc_expr, var_store, var, constraint, home, interns, .. } = can_expr($src); let subs = Subs::new(var_store.into()); let mut unify_problems = Vec::new(); - let (content, subs) = infer_expr(subs, &mut unify_problems, &constraint, var); + let (content, mut subs) = infer_expr(subs, &mut unify_problems, &constraint, var); let context = Context::create(); let module = context.create_module("app"); @@ -224,7 +224,7 @@ mod test_gen { let mut ident_ids = env.interns.all_ident_ids.remove(&home).unwrap(); // Populate Procs and get the low-level Expr from the canonical Expr - let main_body = Expr::new(&arena, &subs, loc_expr.value, &mut procs, home, &mut ident_ids, POINTER_SIZE); + let main_body = Expr::new(&arena, &mut subs, loc_expr.value, &mut procs, home, &mut ident_ids, POINTER_SIZE); // Put this module's ident_ids back in the interns, so we can use them in Env. env.interns.all_ident_ids.insert(home, ident_ids); @@ -309,7 +309,7 @@ mod test_gen { let (loc_expr, _output, _problems, subs, var, constraint, home, interns) = uniq_expr($src); let mut unify_problems = Vec::new(); - let (content, subs) = infer_expr(subs, &mut unify_problems, &constraint, var); + let (content, mut subs) = infer_expr(subs, &mut unify_problems, &constraint, var); let context = Context::create(); let module = context.create_module("app"); @@ -359,7 +359,7 @@ mod test_gen { let mut ident_ids = env.interns.all_ident_ids.remove(&home).unwrap(); // Populate Procs and get the low-level Expr from the canonical Expr - let main_body = Expr::new(&arena, &subs, loc_expr.value, &mut procs, home, &mut ident_ids, POINTER_SIZE); + let main_body = Expr::new(&arena, &mut subs, loc_expr.value, &mut procs, home, &mut ident_ids, POINTER_SIZE); // Put this module's ident_ids back in the interns, so we can use them in Env. env.interns.all_ident_ids.insert(home, ident_ids); diff --git a/compiler/mono/src/expr.rs b/compiler/mono/src/expr.rs index 3d7fd87568..d3ec6eee68 100644 --- a/compiler/mono/src/expr.rs +++ b/compiler/mono/src/expr.rs @@ -82,7 +82,7 @@ pub struct Proc<'a> { struct Env<'a, 'i> { pub arena: &'a Bump, - pub subs: &'a Subs, + pub subs: &'a mut Subs, pub home: ModuleId, pub ident_ids: &'i mut IdentIds, pub pointer_size: u32, @@ -175,7 +175,7 @@ pub enum Expr<'a> { impl<'a> Expr<'a> { pub fn new( arena: &'a Bump, - subs: &'a Subs, + subs: &'a mut Subs, can_expr: roc_can::expr::Expr, procs: &mut Procs<'a>, home: ModuleId, @@ -430,7 +430,6 @@ fn from_can<'a>( } => from_can_when(env, cond_var, expr_var, *loc_cond, branches, procs), Record(ext_var, fields) => { - let subs = env.subs; let arena = env.arena; let mut field_bodies = Vec::with_capacity_in(fields.len(), arena); @@ -440,7 +439,7 @@ fn from_can<'a>( field_bodies.push((label, expr)); } - let struct_layout = match Layout::from_var(arena, ext_var, subs, env.pointer_size) { + let struct_layout = match Layout::from_var(arena, ext_var, env.subs, env.pointer_size) { Ok(layout) => layout, Err(()) => { // Invalid field! @@ -494,10 +493,9 @@ fn from_can<'a>( field, .. } => { - let subs = env.subs; let arena = env.arena; - let struct_layout = match Layout::from_var(arena, ext_var, subs, env.pointer_size) { + let struct_layout = match Layout::from_var(arena, ext_var, env.subs, env.pointer_size) { Ok(layout) => layout, Err(()) => { // Invalid field! @@ -505,7 +503,8 @@ fn from_can<'a>( } }; - let field_layout = match Layout::from_var(arena, field_var, subs, env.pointer_size) { + let field_layout = match Layout::from_var(arena, field_var, env.subs, env.pointer_size) + { Ok(layout) => layout, Err(()) => { // Invalid field! @@ -524,9 +523,8 @@ fn from_can<'a>( elem_var, loc_elems, } => { - let subs = env.subs; let arena = env.arena; - let elem_layout = match Layout::from_var(arena, elem_var, subs, env.pointer_size) { + let elem_layout = match Layout::from_var(arena, elem_var, env.subs, env.pointer_size) { Ok(layout) => layout, Err(()) => { panic!("TODO gracefully handle List with invalid element layout"); diff --git a/compiler/mono/tests/test_mono.rs b/compiler/mono/tests/test_mono.rs index 10cf290372..65ae5ea18e 100644 --- a/compiler/mono/tests/test_mono.rs +++ b/compiler/mono/tests/test_mono.rs @@ -43,7 +43,7 @@ mod test_mono { } = can_expr(src); let subs = Subs::new(var_store.into()); let mut unify_problems = Vec::new(); - let (_content, subs) = infer_expr(subs, &mut unify_problems, &constraint, var); + let (_content, mut subs) = infer_expr(subs, &mut unify_problems, &constraint, var); // Compile and add all the Procs before adding main let mut procs = Procs::default(); @@ -55,7 +55,7 @@ mod test_mono { // Populate Procs and Subs, and get the low-level Expr from the canonical Expr let mono_expr = Expr::new( &arena, - &subs, + &mut subs, loc_expr.value, &mut procs, home, @@ -128,8 +128,8 @@ mod test_mono { fn specialize_closure() { compiles_to( r#" - f = \x -> x + 5 - + f = \x -> x + 5 + { x: f 0x4, y: f 3.14 } "#, { diff --git a/compiler/mono/tests/test_opt.rs b/compiler/mono/tests/test_opt.rs index 483c022eb2..7acdd776cb 100644 --- a/compiler/mono/tests/test_opt.rs +++ b/compiler/mono/tests/test_opt.rs @@ -25,7 +25,7 @@ mod test_opt { let (loc_expr, _, _problems, subs, var, constraint, home, mut interns) = uniq_expr(src); let mut unify_problems = Vec::new(); - let (_content, subs) = infer_expr(subs, &mut unify_problems, &constraint, var); + let (_content, mut subs) = infer_expr(subs, &mut unify_problems, &constraint, var); // Compile and add all the Procs before adding main let mut procs = Procs::default(); @@ -37,7 +37,7 @@ mod test_opt { // Populate Procs and Subs, and get the low-level Expr from the canonical Expr let mono_expr = Expr::new( &arena, - &subs, + &mut subs, loc_expr.value, &mut procs, home, From 3dbaac210a253c1751c7437dec1a4a59a39ea572 Mon Sep 17 00:00:00 2001 From: Folkert Date: Thu, 12 Mar 2020 02:38:33 +0100 Subject: [PATCH 057/428] add snapshot functions to Subs --- compiler/types/src/subs.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/compiler/types/src/subs.rs b/compiler/types/src/subs.rs index bad0b24414..56b8b3a09a 100644 --- a/compiler/types/src/subs.rs +++ b/compiler/types/src/subs.rs @@ -6,7 +6,7 @@ use roc_module::symbol::Symbol; use std::fmt; use std::iter::{once, Iterator}; use std::sync::atomic::{AtomicU32, Ordering}; -use ven_ena::unify::{InPlace, UnificationTable, UnifyKey}; +use ven_ena::unify::{InPlace, Snapshot, UnificationTable, UnifyKey}; #[derive(Clone, Copy, Hash, PartialEq, Eq)] pub struct Mark(i32); @@ -402,6 +402,14 @@ impl Subs { pub fn is_empty(&self) -> bool { self.utable.is_empty() } + + pub fn snapshot(&mut self) -> Snapshot> { + self.utable.snapshot() + } + + pub fn rollback_to(&mut self, snapshot: Snapshot>) { + self.utable.rollback_to(snapshot) + } } #[inline(always)] From c9644e4ee7d35d002b422cc1a28e5aeff6546050 Mon Sep 17 00:00:00 2001 From: Folkert Date: Thu, 12 Mar 2020 02:42:30 +0100 Subject: [PATCH 058/428] use snapshots to remove clone on Subs --- compiler/mono/src/expr.rs | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/compiler/mono/src/expr.rs b/compiler/mono/src/expr.rs index d3ec6eee68..e5eb771568 100644 --- a/compiler/mono/src/expr.rs +++ b/compiler/mono/src/expr.rs @@ -977,21 +977,12 @@ fn specialize_proc_body<'a>( loc_args: &[(Variable, Located)], partial_proc: &PartialProc<'a>, ) -> Option> { - let mut subs: Subs = env.subs.clone(); - roc_unify::unify::unify(&mut subs, partial_proc.annotation, fn_var); - - // Swap in copied subs, specialize the body, put old subs back - let specialized_body = { - let mut env = Env { - subs: env.arena.alloc(subs), - arena: env.arena, - home: env.home, - ident_ids: &mut env.ident_ids, - pointer_size: env.pointer_size, - }; - - from_can(&mut env, partial_proc.body.clone(), procs, None) - }; + // unify the called function with the specialized signature, then specialize the function body + let snapshot = env.subs.snapshot(); + roc_unify::unify::unify(env.subs, partial_proc.annotation, fn_var); + let specialized_body = from_can(env, partial_proc.body.clone(), procs, None); + // reset subs, so we don't get type errors when specializing for a different signature + env.subs.rollback_to(snapshot); let mut proc_args = Vec::with_capacity_in(loc_args.len(), &env.arena); From 9db7d2229a16f59be63b5a0124b6cac61959bc66 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Wed, 11 Mar 2020 22:18:44 -0400 Subject: [PATCH 059/428] Remove a .clone() on procs --- compiler/mono/src/expr.rs | 78 +++++++++++++++++++++++---------------- 1 file changed, 46 insertions(+), 32 deletions(-) diff --git a/compiler/mono/src/expr.rs b/compiler/mono/src/expr.rs index e5eb771568..d664bbe63d 100644 --- a/compiler/mono/src/expr.rs +++ b/compiler/mono/src/expr.rs @@ -908,50 +908,63 @@ fn call_by_name<'a>( ) -> Expr<'a> { // create specialized procedure to call - // TODO this seems very wrong! - // something with mutable and immutable borrow of procs - let procs_clone = procs.clone(); - let op_partial_proc = procs_clone.get_user_defined(proc_name); + // If we need to specialize the body, this will get populated with the info + // we need to do that. This is defined outside the procs.get_user_defined(...) call + // because if we tried to specialize the body inside that match, we would + // get a borrow checker error about trying to borrow `procs` as mutable + // while there is still an active immutable borrow. + let opt_specialize_body: Option<(ContentHash, Variable, roc_can::expr::Expr)>; - let specialized_proc_name = if let Some(partial_proc) = op_partial_proc { + let specialized_proc_name = if let Some(partial_proc) = procs.get_user_defined(proc_name) { let content_hash = ContentHash::from_var(fn_var, env.subs); if let Some(specialization) = partial_proc.specializations.get(&content_hash) { + opt_specialize_body = None; + // a specialization with this type hash already exists, use its symbol specialization.0 } else { + opt_specialize_body = Some(( + content_hash, + partial_proc.annotation, + partial_proc.body.clone(), + )); + // generate a symbol for this specialization - let spec_proc_name = gen_closure_name(procs, &mut env.ident_ids, env.home); - - // register proc, so specialization doesn't loop infinitely - // for recursive definitions - // let mut temp = partial_proc.clone(); - // temp.specializations - // .insert(content_hash, (spec_proc_name, None)); - // procs.insert_user_defined(proc_name, temp); - - procs.insert_specialization(proc_name, content_hash, spec_proc_name, None); - - let proc = specialize_proc_body( - env, - procs, - fn_var, - ret_var, - spec_proc_name, - &loc_args, - partial_proc, - ); - - procs.insert_specialization(proc_name, content_hash, spec_proc_name, proc); - - spec_proc_name + gen_closure_name(procs, &mut env.ident_ids, env.home) } } else { + opt_specialize_body = None; + // This happens for built-in symbols (they are never defined as a Closure) procs.insert_builtin(proc_name); proc_name }; + if let Some((content_hash, annotation, body)) = opt_specialize_body { + // register proc, so specialization doesn't loop infinitely + // for recursive definitions + // let mut temp = partial_proc.clone(); + // temp.specializations + // .insert(content_hash, (spec_proc_name, None)); + // procs.insert_user_defined(proc_name, temp); + + procs.insert_specialization(proc_name, content_hash, specialized_proc_name, None); + + let proc = specialize_proc_body( + env, + procs, + fn_var, + ret_var, + specialized_proc_name, + &loc_args, + annotation, + body, + ); + + procs.insert_specialization(proc_name, content_hash, specialized_proc_name, proc); + } + dbg!(proc_name); dbg!(specialized_proc_name); @@ -975,12 +988,13 @@ fn specialize_proc_body<'a>( ret_var: Variable, proc_name: Symbol, loc_args: &[(Variable, Located)], - partial_proc: &PartialProc<'a>, + annotation: Variable, + body: roc_can::expr::Expr, ) -> Option> { // unify the called function with the specialized signature, then specialize the function body let snapshot = env.subs.snapshot(); - roc_unify::unify::unify(env.subs, partial_proc.annotation, fn_var); - let specialized_body = from_can(env, partial_proc.body.clone(), procs, None); + roc_unify::unify::unify(env.subs, annotation, fn_var); + let specialized_body = from_can(env, body, procs, None); // reset subs, so we don't get type errors when specializing for a different signature env.subs.rollback_to(snapshot); From db362f6df096195dbb84b6504ee3837489f02b8f Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Thu, 12 Mar 2020 00:31:53 -0400 Subject: [PATCH 060/428] Reproduce named identity function bug --- compiler/gen/tests/test_gen.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/compiler/gen/tests/test_gen.rs b/compiler/gen/tests/test_gen.rs index 1809593a57..2bfb722823 100644 --- a/compiler/gen/tests/test_gen.rs +++ b/compiler/gen/tests/test_gen.rs @@ -787,6 +787,21 @@ mod test_gen { ); } + #[test] + fn apply_identity_() { + assert_evals_to!( + indoc!( + r#" + identity = \a -> a + + identity 5 + "# + ), + 5, + i64 + ); + } + #[test] fn apply_unnamed_fn() { assert_evals_to!( From 173ba925ff192610733cf7188358715341e4b352 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Thu, 12 Mar 2020 00:39:28 -0400 Subject: [PATCH 061/428] Formatting --- compiler/gen/tests/test_gen.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/compiler/gen/tests/test_gen.rs b/compiler/gen/tests/test_gen.rs index 2bfb722823..06103dd529 100644 --- a/compiler/gen/tests/test_gen.rs +++ b/compiler/gen/tests/test_gen.rs @@ -86,13 +86,13 @@ mod test_gen { // Declare all the Procs, then insert them into scope so their bodies // can look up their Funcs in scope later when calling each other by value. for (name, opt_proc) in procs.as_map().into_iter() { - if let Some(proc) = opt_proc { - let (func_id, sig) = declare_proc(&env, &mut module, name, &proc); + if let Some(proc) = opt_proc { + let (func_id, sig) = declare_proc(&env, &mut module, name, &proc); - declared.push((proc.clone(), sig.clone(), func_id)); + declared.push((proc.clone(), sig.clone(), func_id)); - scope.insert(name.clone(), ScopeEntry::Func { func_id, sig }); - } + scope.insert(name.clone(), ScopeEntry::Func { func_id, sig }); + } } for (proc, sig, fn_id) in declared { From 9b68fbe3c974a25ebd87458b71b9ba9569f04098 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Thu, 12 Mar 2020 00:39:53 -0400 Subject: [PATCH 062/428] Add PRETTY_PRINT_DEBUG_SYMBOLS --- compiler/module/src/symbol.rs | 42 ++++++++++++++++++++++------------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/compiler/module/src/symbol.rs b/compiler/module/src/symbol.rs index 5b3683a188..cce62d3652 100644 --- a/compiler/module/src/symbol.rs +++ b/compiler/module/src/symbol.rs @@ -9,6 +9,14 @@ use std::{fmt, u32}; #[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct Symbol(u64); +// When this is `true` (which it normally should be), Symbol's Debug::fmt implementation +// attempts to pretty print debug symbols using interns recorded using +// register_debug_idents calls (which should be made in debug mode). +// Set it to false if you want to see the raw ModuleId and IdentId ints, +// but please set it back to true before checking in the result! +#[cfg(debug_assertions)] +const PRETTY_PRINT_DEBUG_SYMBOLS: bool = true; + /// In Debug builds only, Symbol has a name() method that lets /// you look up its name in a global intern table. This table is /// behind a mutex, so it is neither populated nor available in release builds. @@ -101,26 +109,30 @@ impl Symbol { impl fmt::Debug for Symbol { #[cfg(debug_assertions)] fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let module_id = self.module_id(); - let ident_id = self.ident_id(); + if PRETTY_PRINT_DEBUG_SYMBOLS { + let module_id = self.module_id(); + let ident_id = self.ident_id(); - match DEBUG_IDENT_IDS_BY_MODULE_ID.lock() { - Ok(names) => match &names.get(&module_id.0) { - Some(ident_ids) => match ident_ids.get_name(ident_id) { - Some(ident_str) => write!(f, "`{:?}.{}`", module_id, ident_str), + match DEBUG_IDENT_IDS_BY_MODULE_ID.lock() { + Ok(names) => match &names.get(&module_id.0) { + Some(ident_ids) => match ident_ids.get_name(ident_id) { + Some(ident_str) => write!(f, "`{:?}.{}`", module_id, ident_str), + None => fallback_debug_fmt(*self, f), + }, None => fallback_debug_fmt(*self, f), }, - None => fallback_debug_fmt(*self, f), - }, - Err(err) => { - // Print and return Err rather than panicking, because this - // might be used in a panic error message, and if we panick - // while we're already panicking it'll kill the process - // without printing any of the errors! - println!("DEBUG INFO: Failed to acquire lock for Debug reading from DEBUG_IDENT_IDS_BY_MODULE_ID, presumably because a thread panicked: {:?}", err); + Err(err) => { + // Print and return Err rather than panicking, because this + // might be used in a panic error message, and if we panick + // while we're already panicking it'll kill the process + // without printing any of the errors! + println!("DEBUG INFO: Failed to acquire lock for Debug reading from DEBUG_IDENT_IDS_BY_MODULE_ID, presumably because a thread panicked: {:?}", err); - fallback_debug_fmt(*self, f) + fallback_debug_fmt(*self, f) + } } + } else { + fallback_debug_fmt(*self, f) } } From 494a8574bfc49c19a7d5d4019d5a0bbe4bcf0970 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Thu, 12 Mar 2020 00:40:07 -0400 Subject: [PATCH 063/428] Drop obsolete add_closure function --- compiler/mono/src/expr.rs | 51 --------------------------------------- 1 file changed, 51 deletions(-) diff --git a/compiler/mono/src/expr.rs b/compiler/mono/src/expr.rs index d664bbe63d..7dd13f6955 100644 --- a/compiler/mono/src/expr.rs +++ b/compiler/mono/src/expr.rs @@ -330,7 +330,6 @@ fn from_can<'a>( }, ); - // add_closure(env, symbol, loc_body.value, ret_var, &loc_args, procs) Expr::FunctionPointer(symbol) } @@ -546,56 +545,6 @@ fn from_can<'a>( } } -/* -fn add_closure<'a>( - env: &mut Env<'a, '_>, - symbol: Symbol, - can_body: roc_can::expr::Expr, - ret_var: Variable, - loc_args: &[(Variable, Located)], - procs: &mut Procs<'a>, -) -> Expr<'a> { - let subs = &env.subs; - let arena = env.arena; - let mut proc_args = Vec::with_capacity_in(loc_args.len(), arena); - - for (arg_var, loc_arg) in loc_args.iter() { - let layout = match Layout::from_var(arena, *arg_var, subs, env.pointer_size) { - Ok(layout) => layout, - Err(()) => { - // Invalid closure! - procs.insert(symbol, None); - - return Expr::FunctionPointer(symbol); - } - }; - - let arg_name: Symbol = match &loc_arg.value { - Pattern::Identifier(symbol) => *symbol, - _ => { - panic!("TODO determine arg_name for pattern {:?}", loc_arg.value); - } - }; - - proc_args.push((layout, arg_name)); - } - - let ret_layout = Layout::from_var(arena, ret_var, subs, env.pointer_size) - .unwrap_or_else(|err| panic!("TODO handle invalid function {:?}", err)); - - let proc = Proc { - args: proc_args.into_bump_slice(), - body: from_can(env, can_body, procs, None), - closes_over: Layout::Struct(&[]), - ret_layout, - }; - - procs.insert(symbol, Some(proc)); - - Expr::FunctionPointer(symbol) -} -*/ - fn store_pattern<'a>( env: &mut Env<'a, '_>, can_pat: Pattern, From 723ef8e6d087cb432973d0ecdc814d38b4494093 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Thu, 12 Mar 2020 00:40:16 -0400 Subject: [PATCH 064/428] Add a missing register_debug_idents call --- compiler/mono/src/expr.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/compiler/mono/src/expr.rs b/compiler/mono/src/expr.rs index 7dd13f6955..284310d153 100644 --- a/compiler/mono/src/expr.rs +++ b/compiler/mono/src/expr.rs @@ -594,6 +594,8 @@ fn store_pattern<'a>( fn gen_closure_name(procs: &Procs<'_>, ident_ids: &mut IdentIds, home: ModuleId) -> Symbol { let ident_id = ident_ids.add(format!("_{}", procs.len()).into()); + home.register_debug_idents(&ident_ids); + Symbol::new(home, ident_id) } From f74471012ca9719266b4180667975816cfcae9ca Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Thu, 12 Mar 2020 00:53:24 -0400 Subject: [PATCH 065/428] Improve an error message --- compiler/gen/src/crane/build.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/compiler/gen/src/crane/build.rs b/compiler/gen/src/crane/build.rs index b46c0d80b4..fcec1ba984 100644 --- a/compiler/gen/src/crane/build.rs +++ b/compiler/gen/src/crane/build.rs @@ -159,7 +159,10 @@ pub fn build_expr<'a, B: Backend>( Some(ScopeEntry::Func { .. }) => { panic!("TODO I don't yet know how to return fn pointers") } - None => panic!("Could not find a var for {:?} in scope {:?}", name, scope), + None => panic!( + "Could not resolve lookup for {:?} because no ScopeEntry was found for {:?} in scope {:?}", + name, name, scope + ), }, Struct { layout, fields } => { let cfg = env.cfg; From 9761aabe65173f070220375d83475cc29dd5c8b1 Mon Sep 17 00:00:00 2001 From: Folkert Date: Thu, 12 Mar 2020 12:58:03 +0100 Subject: [PATCH 066/428] add is_empty to Procs --- compiler/mono/src/expr.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/compiler/mono/src/expr.rs b/compiler/mono/src/expr.rs index 284310d153..9c1cb5aea1 100644 --- a/compiler/mono/src/expr.rs +++ b/compiler/mono/src/expr.rs @@ -43,6 +43,10 @@ impl<'a> Procs<'a> { .sum() } + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + fn insert_builtin(&mut self, symbol: Symbol) { self.builtin.insert(symbol); } From cfb3952fbfe26abc11afd161dace0e1780a207fc Mon Sep 17 00:00:00 2001 From: Folkert Date: Thu, 12 Mar 2020 13:20:26 +0100 Subject: [PATCH 067/428] put patterns into PartialProc So Load(symbol) finds a defined value --- compiler/mono/src/expr.rs | 40 ++++++++++++++++++++++++--------------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/compiler/mono/src/expr.rs b/compiler/mono/src/expr.rs index 9c1cb5aea1..60cd959372 100644 --- a/compiler/mono/src/expr.rs +++ b/compiler/mono/src/expr.rs @@ -71,6 +71,7 @@ impl<'a> Procs<'a> { #[derive(Clone, Debug, PartialEq)] pub struct PartialProc<'a> { pub annotation: Variable, + pub patterns: std::vec::Vec<(Variable, Located)>, pub body: roc_can::expr::Expr, pub specializations: MutMap>)>, } @@ -320,7 +321,7 @@ fn from_can<'a>( Expr::Store(stored.into_bump_slice(), arena.alloc(ret)) } - Closure(annotation, _, _, _loc_args, boxed_body) => { + Closure(annotation, _, _, loc_args, boxed_body) => { let (loc_body, _ret_var) = *boxed_body; let symbol = name.unwrap_or_else(|| gen_closure_name(procs, &mut env.ident_ids, env.home)); @@ -329,6 +330,7 @@ fn from_can<'a>( symbol, PartialProc { annotation, + patterns: loc_args, body: loc_body.value, specializations: MutMap::default(), }, @@ -868,7 +870,13 @@ fn call_by_name<'a>( // because if we tried to specialize the body inside that match, we would // get a borrow checker error about trying to borrow `procs` as mutable // while there is still an active immutable borrow. - let opt_specialize_body: Option<(ContentHash, Variable, roc_can::expr::Expr)>; + #[allow(clippy::type_complexity)] + let opt_specialize_body: Option<( + ContentHash, + Variable, + roc_can::expr::Expr, + std::vec::Vec<(Variable, Located)>, + )>; let specialized_proc_name = if let Some(partial_proc) = procs.get_user_defined(proc_name) { let content_hash = ContentHash::from_var(fn_var, env.subs); @@ -883,6 +891,7 @@ fn call_by_name<'a>( content_hash, partial_proc.annotation, partial_proc.body.clone(), + partial_proc.patterns.clone(), )); // generate a symbol for this specialization @@ -896,7 +905,7 @@ fn call_by_name<'a>( proc_name }; - if let Some((content_hash, annotation, body)) = opt_specialize_body { + if let Some((content_hash, annotation, body, loc_patterns)) = opt_specialize_body { // register proc, so specialization doesn't loop infinitely // for recursive definitions // let mut temp = partial_proc.clone(); @@ -913,6 +922,7 @@ fn call_by_name<'a>( ret_var, specialized_proc_name, &loc_args, + &loc_patterns, annotation, body, ); @@ -920,9 +930,6 @@ fn call_by_name<'a>( procs.insert_specialization(proc_name, content_hash, specialized_proc_name, proc); } - dbg!(proc_name); - dbg!(specialized_proc_name); - // generate actual call let mut args = Vec::with_capacity_in(loc_args.len(), env.arena); @@ -936,6 +943,7 @@ fn call_by_name<'a>( Expr::CallByName(specialized_proc_name, args.into_bump_slice()) } +#[allow(clippy::too_many_arguments)] fn specialize_proc_body<'a>( env: &mut Env<'a, '_>, procs: &mut Procs<'a>, @@ -943,6 +951,7 @@ fn specialize_proc_body<'a>( ret_var: Variable, proc_name: Symbol, loc_args: &[(Variable, Located)], + loc_patterns: &[(Variable, Located)], annotation: Variable, body: roc_can::expr::Expr, ) -> Option> { @@ -955,7 +964,7 @@ fn specialize_proc_body<'a>( let mut proc_args = Vec::with_capacity_in(loc_args.len(), &env.arena); - for (arg_var, _loc_arg) in loc_args.iter() { + for ((arg_var, _), (_, loc_pattern)) in loc_args.iter().zip(loc_patterns.iter()) { let layout = match Layout::from_var(&env.arena, *arg_var, env.subs, env.pointer_size) { Ok(layout) => layout, Err(()) => { @@ -966,14 +975,15 @@ fn specialize_proc_body<'a>( // TODO FIXME what is the idea here? arguments don't map to identifiers one-to-one // e.g. underscore and record patterns - let arg_name = proc_name; - - // let arg_name: Symbol = match &loc_arg.value { - // Pattern::Identifier(symbol) => *symbol, - // _ => { - // panic!("TODO determine arg_name for pattern {:?}", loc_arg.value); - // } - // }; + let arg_name: Symbol = match &loc_pattern.value { + Pattern::Identifier(symbol) => *symbol, + _ => { + panic!( + "TODO determine arg_name for pattern {:?}", + loc_pattern.value + ); + } + }; proc_args.push((layout, arg_name)); } From de40cf62f3ac203450e7e0138d9311e7baa6246f Mon Sep 17 00:00:00 2001 From: Folkert Date: Thu, 12 Mar 2020 14:03:26 +0100 Subject: [PATCH 068/428] special-case anonymous functions --- compiler/mono/src/expr.rs | 91 +++++++++++++++++++++++++++------------ 1 file changed, 64 insertions(+), 27 deletions(-) diff --git a/compiler/mono/src/expr.rs b/compiler/mono/src/expr.rs index 60cd959372..558c60b893 100644 --- a/compiler/mono/src/expr.rs +++ b/compiler/mono/src/expr.rs @@ -12,6 +12,7 @@ use roc_types::subs::{Content, ContentHash, FlatType, Subs, Variable}; #[derive(Clone, Debug, PartialEq, Default)] pub struct Procs<'a> { user_defined: MutMap>, + anonymous: MutMap>>, builtin: MutSet, } @@ -20,6 +21,10 @@ impl<'a> Procs<'a> { self.user_defined.insert(symbol, partial_proc); } + fn insert_anonymous(&mut self, symbol: Symbol, proc: Option>) { + self.anonymous.insert(symbol, proc); + } + fn insert_specialization( &mut self, symbol: Symbol, @@ -37,10 +42,14 @@ impl<'a> Procs<'a> { } pub fn len(&self) -> usize { - self.user_defined + let anonymous: usize = self.anonymous.len(); + let user_defined: usize = self + .user_defined .values() .map(|v| v.specializations.len()) - .sum() + .sum(); + + anonymous + user_defined } pub fn is_empty(&self) -> bool { @@ -60,6 +69,10 @@ impl<'a> Procs<'a> { } } + for (symbol, proc) in self.anonymous.clone().into_iter() { + result.insert(symbol, proc); + } + for symbol in self.builtin.iter() { result.insert(*symbol, None); } @@ -71,7 +84,7 @@ impl<'a> Procs<'a> { #[derive(Clone, Debug, PartialEq)] pub struct PartialProc<'a> { pub annotation: Variable, - pub patterns: std::vec::Vec<(Variable, Located)>, + pub patterns: std::vec::Vec>, pub body: roc_can::expr::Expr, pub specializations: MutMap>)>, } @@ -322,19 +335,46 @@ fn from_can<'a>( } Closure(annotation, _, _, loc_args, boxed_body) => { - let (loc_body, _ret_var) = *boxed_body; - let symbol = - name.unwrap_or_else(|| gen_closure_name(procs, &mut env.ident_ids, env.home)); + let (loc_body, ret_var) = *boxed_body; - procs.insert_user_defined( - symbol, - PartialProc { - annotation, - patterns: loc_args, - body: loc_body.value, - specializations: MutMap::default(), - }, - ); + let (arg_vars, patterns): (std::vec::Vec<_>, std::vec::Vec<_>) = + loc_args.into_iter().unzip(); + + let symbol = match name { + Some(symbol) => { + // a named closure + procs.insert_user_defined( + symbol, + PartialProc { + annotation, + patterns, + body: loc_body.value, + specializations: MutMap::default(), + }, + ); + symbol + } + None => { + // an anonymous closure. These will always be specialized already + // by the surrounding context + let symbol = gen_closure_name(procs, &mut env.ident_ids, env.home); + let opt_proc = specialize_proc_body( + env, + procs, + annotation, + ret_var, + symbol, + &arg_vars, + &patterns, + annotation, + loc_body.value, + ); + + procs.insert_anonymous(symbol, opt_proc); + + symbol + } + }; Expr::FunctionPointer(symbol) } @@ -875,7 +915,7 @@ fn call_by_name<'a>( ContentHash, Variable, roc_can::expr::Expr, - std::vec::Vec<(Variable, Located)>, + std::vec::Vec>, )>; let specialized_proc_name = if let Some(partial_proc) = procs.get_user_defined(proc_name) { @@ -907,21 +947,17 @@ fn call_by_name<'a>( if let Some((content_hash, annotation, body, loc_patterns)) = opt_specialize_body { // register proc, so specialization doesn't loop infinitely - // for recursive definitions - // let mut temp = partial_proc.clone(); - // temp.specializations - // .insert(content_hash, (spec_proc_name, None)); - // procs.insert_user_defined(proc_name, temp); - procs.insert_specialization(proc_name, content_hash, specialized_proc_name, None); + let arg_vars = loc_args.iter().map(|v| v.0).collect::>(); + let proc = specialize_proc_body( env, procs, fn_var, ret_var, specialized_proc_name, - &loc_args, + &arg_vars, &loc_patterns, annotation, body, @@ -950,21 +986,22 @@ fn specialize_proc_body<'a>( fn_var: Variable, ret_var: Variable, proc_name: Symbol, - loc_args: &[(Variable, Located)], - loc_patterns: &[(Variable, Located)], + loc_args: &[Variable], + loc_patterns: &[Located], annotation: Variable, body: roc_can::expr::Expr, ) -> Option> { // unify the called function with the specialized signature, then specialize the function body let snapshot = env.subs.snapshot(); - roc_unify::unify::unify(env.subs, annotation, fn_var); + let unified = roc_unify::unify::unify(env.subs, annotation, fn_var); + debug_assert!(unified.mismatches.is_empty()); let specialized_body = from_can(env, body, procs, None); // reset subs, so we don't get type errors when specializing for a different signature env.subs.rollback_to(snapshot); let mut proc_args = Vec::with_capacity_in(loc_args.len(), &env.arena); - for ((arg_var, _), (_, loc_pattern)) in loc_args.iter().zip(loc_patterns.iter()) { + for (arg_var, loc_pattern) in loc_args.iter().zip(loc_patterns.iter()) { let layout = match Layout::from_var(&env.arena, *arg_var, env.subs, env.pointer_size) { Ok(layout) => layout, Err(()) => { From bfd9150af4fde9433478e5fed5ca506e835f5be9 Mon Sep 17 00:00:00 2001 From: Dan Bruder Date: Thu, 12 Mar 2020 10:37:07 -0400 Subject: [PATCH 069/428] access record fields progress --- compiler/gen/src/crane/build.rs | 61 ++++++++++++++++++++++++++----- compiler/gen/src/crane/convert.rs | 15 +------- compiler/gen/tests/test_gen.rs | 13 ++----- compiler/mono/src/expr.rs | 6 ++- 4 files changed, 62 insertions(+), 33 deletions(-) diff --git a/compiler/gen/src/crane/build.rs b/compiler/gen/src/crane/build.rs index cb45836e22..493642cd56 100644 --- a/compiler/gen/src/crane/build.rs +++ b/compiler/gen/src/crane/build.rs @@ -194,16 +194,59 @@ pub fn build_expr<'a, B: Backend>( builder.ins().stack_store(val, slot, Offset32::new(offset)); } - let ir_type = type_from_layout(cfg, layout); - builder.ins().stack_addr(ir_type, slot, Offset32::new(0)) + builder + .ins() + .stack_addr(cfg.pointer_type(), slot, Offset32::new(0)) + } + Access { + label, + field_layout, + struct_layout: Layout::Struct(fields), + record, + } => { + let cfg = env.cfg; + + // Reconstruct the struct to determine the combined layout + // TODO get rid of clones + let mut reconstructed_struct_layout = + Vec::with_capacity_in(fields.len() + 1, env.arena); + for field in fields.iter() { + reconstructed_struct_layout.push(field.clone()); + } + reconstructed_struct_layout.push((label.clone(), field_layout.clone())); + reconstructed_struct_layout.sort_by(|a, b| { + a.0.partial_cmp(&b.0) + .expect("TODO: failed to sort struct fields in crane access") + }); + + // Find the offset we are trying to access + let mut offset = 0; + for (local_label, layout) in reconstructed_struct_layout.iter() { + if local_label == label { + break; + } + + let field_size = match layout { + Layout::Builtin(Builtin::Int64) => std::mem::size_of::(), + _ => panic!( + "Missing struct field size in offset calculation for struct access for {:?}", + layout + ), + }; + + offset += field_size; + } + + let offset = i32::try_from(offset) + .expect("TODO gracefully handle usize -> i32 conversion in struct access"); + + let mem_flags = MemFlags::new(); + let record = build_expr(env, scope, module, builder, record, procs); + + builder + .ins() + .load(cfg.pointer_type(), mem_flags, record, Offset32::new(offset)) } - // Access { - // label, - // field_layout, - // struct_layout, - // } => { - // panic!("I don't yet know how to crane build {:?}", expr); - // } Str(str_literal) => { if str_literal.is_empty() { panic!("TODO build an empty string in Crane"); diff --git a/compiler/gen/src/crane/convert.rs b/compiler/gen/src/crane/convert.rs index 4039fabb68..6a0c9ad2ef 100644 --- a/compiler/gen/src/crane/convert.rs +++ b/compiler/gen/src/crane/convert.rs @@ -10,20 +10,7 @@ pub fn type_from_layout(cfg: TargetFrontendConfig, layout: &Layout<'_>) -> Type use roc_mono::layout::Layout::*; match layout { - Pointer(_) | FunctionPointer(_, _) => cfg.pointer_type(), - Struct(fields) => { - // This will change as we add more fields and field types to the tests - let naive_all_ints = fields.iter().all(|ref field| match field.1 { - Builtin(Int64) => true, - _ => false, - }); - - if naive_all_ints && fields.len() == 3 { - types::I64.by(4).unwrap() - } else { - panic!("TODO layout_to_crane_type for Struct"); - } - } + Pointer(_) | FunctionPointer(_, _) | Struct(_) => cfg.pointer_type(), Builtin(builtin) => match builtin { Int64 => types::I64, Float64 => types::F64, diff --git a/compiler/gen/tests/test_gen.rs b/compiler/gen/tests/test_gen.rs index 464f680ddd..e82f274709 100644 --- a/compiler/gen/tests/test_gen.rs +++ b/compiler/gen/tests/test_gen.rs @@ -873,9 +873,7 @@ mod test_gen { // assert_evals_to!( // indoc!( // r#" - // point = { x: 15, y: 17, z: 19 } - - // point.x + // { y: 17, x: 15, z: 19 }.x // "# // ), // 15, @@ -885,20 +883,17 @@ mod test_gen { // assert_evals_to!( // indoc!( // r#" - // point = { x: 15, y: 17, z: 19 } - - // point.y + // { x: 15, y: 17, z: 19 }.y // "# // ), // 17, // i64 // ); + // assert_evals_to!( // indoc!( // r#" - // point = { x: 15, y: 17, z: 19 } - - // point.z + // { x: 15, y: 17, z: 19 }.z // "# // ), // 19, diff --git a/compiler/mono/src/expr.rs b/compiler/mono/src/expr.rs index d9b6c9a928..63762aa15b 100644 --- a/compiler/mono/src/expr.rs +++ b/compiler/mono/src/expr.rs @@ -101,6 +101,7 @@ pub enum Expr<'a> { label: Lowercase, field_layout: Layout<'a>, struct_layout: Layout<'a>, + record: &'a Expr<'a>, }, Array { @@ -398,7 +399,7 @@ fn from_can<'a>( ext_var, field_var, field, - .. + loc_expr, } => { let subs = env.subs; let arena = env.arena; @@ -419,10 +420,13 @@ fn from_can<'a>( } }; + let record = arena.alloc(from_can(env, loc_expr.value, procs, None)); + Expr::Access { label: field, field_layout, struct_layout, + record, } } From f372e4d108c9efc12ec01c9dd4f4329425b09777 Mon Sep 17 00:00:00 2001 From: Folkert Date: Thu, 12 Mar 2020 16:55:45 +0100 Subject: [PATCH 070/428] move fresh symbol generation into Env --- compiler/mono/src/expr.rs | 27 ++++++---- compiler/mono/tests/test_mono.rs | 86 ++++++++++++++++++++++++++++++++ 2 files changed, 103 insertions(+), 10 deletions(-) diff --git a/compiler/mono/src/expr.rs b/compiler/mono/src/expr.rs index 558c60b893..1197e9344d 100644 --- a/compiler/mono/src/expr.rs +++ b/compiler/mono/src/expr.rs @@ -104,6 +104,20 @@ struct Env<'a, 'i> { pub home: ModuleId, pub ident_ids: &'i mut IdentIds, pub pointer_size: u32, + symbol_counter: usize, +} + +impl<'a, 'i> Env<'a, 'i> { + pub fn fresh_symbol(&mut self) -> Symbol { + let ident_id = self + .ident_ids + .add(format!("_{}", self.symbol_counter).into()); + self.symbol_counter += 1; + + self.home.register_debug_idents(&self.ident_ids); + + Symbol::new(self.home, ident_id) + } } #[derive(Clone, Debug, PartialEq)] @@ -206,6 +220,7 @@ impl<'a> Expr<'a> { home, ident_ids, pointer_size, + symbol_counter: 0, }; from_can(&mut env, can_expr, procs, None) @@ -357,7 +372,7 @@ fn from_can<'a>( None => { // an anonymous closure. These will always be specialized already // by the surrounding context - let symbol = gen_closure_name(procs, &mut env.ident_ids, env.home); + let symbol = env.fresh_symbol(); let opt_proc = specialize_proc_body( env, procs, @@ -637,14 +652,6 @@ fn store_pattern<'a>( } } -fn gen_closure_name(procs: &Procs<'_>, ident_ids: &mut IdentIds, home: ModuleId) -> Symbol { - let ident_id = ident_ids.add(format!("_{}", procs.len()).into()); - - home.register_debug_idents(&ident_ids); - - Symbol::new(home, ident_id) -} - fn from_can_when<'a>( env: &mut Env<'a, '_>, cond_var: Variable, @@ -935,7 +942,7 @@ fn call_by_name<'a>( )); // generate a symbol for this specialization - gen_closure_name(procs, &mut env.ident_ids, env.home) + env.fresh_symbol() } } else { opt_specialize_body = None; diff --git a/compiler/mono/tests/test_mono.rs b/compiler/mono/tests/test_mono.rs index 65ae5ea18e..4e29588d34 100644 --- a/compiler/mono/tests/test_mono.rs +++ b/compiler/mono/tests/test_mono.rs @@ -160,6 +160,92 @@ mod test_mono { ) } + #[test] + fn polymorphic_identity() { + compiles_to( + r#" + id = \x -> x + + id { x: id 0x4 } + "#, + { + use self::Builtin::*; + use Layout::Builtin; + let home = test_home(); + + let gen_symbol_3 = Interns::from_index(home, 3); + let gen_symbol_4 = Interns::from_index(home, 4); + + CallByName( + gen_symbol_3, + &[( + Struct { + fields: &[( + "x".into(), + CallByName(gen_symbol_4, &[(Int(4), Builtin(Int64))]), + )], + layout: Layout::Struct(&[("x".into(), Builtin(Int64))]), + }, + Layout::Struct(&[("x".into(), Builtin(Int64))]), + )], + ) + }, + ) + } + + // needs LetRec to be converted to mono + // #[test] + // fn polymorphic_recursive() { + // compiles_to( + // r#" + // f = \x -> + // when x < 10 is + // True -> f (x + 1) + // False -> x + // + // { x: f 0x4, y: f 3.14 } + // "#, + // { + // use self::Builtin::*; + // use Layout::Builtin; + // let home = test_home(); + // + // let gen_symbol_3 = Interns::from_index(home, 3); + // let gen_symbol_4 = Interns::from_index(home, 4); + // + // Float(3.4) + // + // }, + // ) + // } + + // needs layout for non-empty tag union + // #[test] + // fn is_nil() { + // let arena = Bump::new(); + // + // compiles_to_with_interns( + // r#" + // LinkedList a : [ Cons a (LinkedList a), Nil ] + // + // isNil : LinkedList a -> Bool + // isNil = \list -> + // when list is + // Nil -> True + // Cons _ _ -> False + // + // listInt : LinkedList Int + // listInt = Nil + // + // isNil listInt + // "#, + // |interns| { + // let home = test_home(); + // let var_is_nil = interns.symbol(home, "isNil".into()); + // }, + // ); + // } + #[test] fn bool_literal() { let arena = Bump::new(); From 4da01c720e92b5f6097140824e16ec6fd00d5440 Mon Sep 17 00:00:00 2001 From: Folkert Date: Thu, 12 Mar 2020 21:32:58 +0100 Subject: [PATCH 071/428] monomorphize the ext_var of records/tag unions --- compiler/mono/src/expr.rs | 120 ++++++++++++++++---- compiler/mono/tests/test_mono.rs | 20 ++++ compiler/region/src/all.rs | 5 + compiler/types/src/pretty_print.rs | 16 +-- compiler/types/src/subs.rs | 174 +++++++++++++++++++++-------- 5 files changed, 258 insertions(+), 77 deletions(-) diff --git a/compiler/mono/src/expr.rs b/compiler/mono/src/expr.rs index 1197e9344d..0fd6237211 100644 --- a/compiler/mono/src/expr.rs +++ b/compiler/mono/src/expr.rs @@ -84,7 +84,7 @@ impl<'a> Procs<'a> { #[derive(Clone, Debug, PartialEq)] pub struct PartialProc<'a> { pub annotation: Variable, - pub patterns: std::vec::Vec>, + pub patterns: Vec<'a, Symbol>, pub body: roc_can::expr::Expr, pub specializations: MutMap>)>, } @@ -284,6 +284,79 @@ fn to_int_or_float(subs: &Subs, var: Variable) -> IntOrFloat { } } +fn patterns_to_when<'a>( + env: &mut Env<'a, '_>, + patterns: std::vec::Vec<(Variable, Located)>, + body_var: Variable, + mut body: Located, +) -> ( + Vec<'a, Variable>, + Vec<'a, Symbol>, + Located, +) { + let mut arg_vars = Vec::with_capacity_in(patterns.len(), env.arena); + let mut symbols = Vec::with_capacity_in(patterns.len(), env.arena); + + for (pattern_var, pattern) in patterns.into_iter().rev() { + let (new_symbol, new_body) = pattern_to_when(env, pattern_var, pattern, body_var, body); + body = new_body; + symbols.push(new_symbol); + arg_vars.push(pattern_var); + } + + (arg_vars, symbols, body) +} + +/// turn irrefutable patterns into when. For example +/// +/// foo = \{ x } -> body +/// +/// Assuming the above program typechecks, the pattern match cannot fail +/// (it is irrefutable). It becomes +/// +/// foo = \r -> +/// when r is +/// { x } -> body +/// +/// conversion of one-pattern when expressions will do the most optimal thing +fn pattern_to_when<'a>( + env: &mut Env<'a, '_>, + pattern_var: Variable, + pattern: Located, + body_var: Variable, + body: Located, +) -> (Symbol, Located) { + use roc_can::expr::Expr::*; + use roc_can::pattern::Pattern::*; + + match &pattern.value { + Identifier(symbol) => (*symbol, body), + Underscore => { + // for underscore we generate a dummy Symbol + (env.fresh_symbol(), body) + } + + AppliedTag(_, _, _) | RecordDestructure(_, _) | Shadowed(_, _) | UnsupportedPattern(_) => { + let symbol = env.fresh_symbol(); + + let wrapped_body = When { + cond_var: pattern_var, + expr_var: body_var, + loc_cond: Box::new(Located::at_zero(Var(symbol))), + branches: vec![(pattern, body)], + }; + + (symbol, Located::at_zero(wrapped_body)) + } + + // These patters are refutable, and thus should never occur outside a `when` expression + IntLiteral(_) | NumLiteral(_,_) | FloatLiteral(_) | StrLiteral(_) => { + unreachable!("refutable pattern {:?} where irrefutable pattern is expected. This should never happen!", pattern.value) + } + + } +} + fn from_can<'a>( env: &mut Env<'a, '_>, can_expr: roc_can::expr::Expr, @@ -352,8 +425,16 @@ fn from_can<'a>( Closure(annotation, _, _, loc_args, boxed_body) => { let (loc_body, ret_var) = *boxed_body; - let (arg_vars, patterns): (std::vec::Vec<_>, std::vec::Vec<_>) = - loc_args.into_iter().unzip(); + // turn record/tag patterns into a when expression, e.g. + // + // foo = \{ x } -> body + // + // becomes + // + // foo = \r -> when r is { x } -> body + // + // conversion of one-pattern when expressions will do the most optimal thing + let (arg_vars, arg_symbols, body) = patterns_to_when(env, loc_args, ret_var, loc_body); let symbol = match name { Some(symbol) => { @@ -362,8 +443,8 @@ fn from_can<'a>( symbol, PartialProc { annotation, - patterns, - body: loc_body.value, + patterns: arg_symbols, + body: body.value, specializations: MutMap::default(), }, ); @@ -373,6 +454,11 @@ fn from_can<'a>( // an anonymous closure. These will always be specialized already // by the surrounding context let symbol = env.fresh_symbol(); + + // Has the side-effect of monomorphizing record types + // turning the ext_var into EmptyRecord or EmptyTagUnion + let _ = ContentHash::from_var(annotation, env.subs); + let opt_proc = specialize_proc_body( env, procs, @@ -380,9 +466,9 @@ fn from_can<'a>( ret_var, symbol, &arg_vars, - &patterns, + &arg_symbols, annotation, - loc_body.value, + body.value, ); procs.insert_anonymous(symbol, opt_proc); @@ -922,7 +1008,7 @@ fn call_by_name<'a>( ContentHash, Variable, roc_can::expr::Expr, - std::vec::Vec>, + Vec<'a, Symbol>, )>; let specialized_proc_name = if let Some(partial_proc) = procs.get_user_defined(proc_name) { @@ -994,7 +1080,7 @@ fn specialize_proc_body<'a>( ret_var: Variable, proc_name: Symbol, loc_args: &[Variable], - loc_patterns: &[Located], + pattern_symbols: &[Symbol], annotation: Variable, body: roc_can::expr::Expr, ) -> Option> { @@ -1008,7 +1094,7 @@ fn specialize_proc_body<'a>( let mut proc_args = Vec::with_capacity_in(loc_args.len(), &env.arena); - for (arg_var, loc_pattern) in loc_args.iter().zip(loc_patterns.iter()) { + for (arg_var, arg_name) in loc_args.iter().zip(pattern_symbols.iter()) { let layout = match Layout::from_var(&env.arena, *arg_var, env.subs, env.pointer_size) { Ok(layout) => layout, Err(()) => { @@ -1017,19 +1103,7 @@ fn specialize_proc_body<'a>( } }; - // TODO FIXME what is the idea here? arguments don't map to identifiers one-to-one - // e.g. underscore and record patterns - let arg_name: Symbol = match &loc_pattern.value { - Pattern::Identifier(symbol) => *symbol, - _ => { - panic!( - "TODO determine arg_name for pattern {:?}", - loc_pattern.value - ); - } - }; - - proc_args.push((layout, arg_name)); + proc_args.push((layout, *arg_name)); } let ret_layout = Layout::from_var(&env.arena, ret_var, env.subs, env.pointer_size) diff --git a/compiler/mono/tests/test_mono.rs b/compiler/mono/tests/test_mono.rs index 4e29588d34..1d7103dd3e 100644 --- a/compiler/mono/tests/test_mono.rs +++ b/compiler/mono/tests/test_mono.rs @@ -160,6 +160,26 @@ mod test_mono { ) } + // #[test] + // fn record_pattern() { + // compiles_to( + // r#" + // \{ x } -> x + 0x5 + // "#, + // { Float(3.45) }, + // ) + // } + // + // #[test] + // fn tag_pattern() { + // compiles_to( + // r#" + // \Foo x -> x + 0x5 + // "#, + // { Float(3.45) }, + // ) + // } + #[test] fn polymorphic_identity() { compiles_to( diff --git a/compiler/region/src/all.rs b/compiler/region/src/all.rs index 772d0e0afc..5c9410bc0c 100644 --- a/compiler/region/src/all.rs +++ b/compiler/region/src/all.rs @@ -89,6 +89,11 @@ impl Located { pub fn at(region: Region, value: T) -> Located { Located { value, region } } + + pub fn at_zero(value: T) -> Located { + let region = Region::zero(); + Located { value, region } + } } impl Located { diff --git a/compiler/types/src/pretty_print.rs b/compiler/types/src/pretty_print.rs index f576a5ca50..0108b35618 100644 --- a/compiler/types/src/pretty_print.rs +++ b/compiler/types/src/pretty_print.rs @@ -436,7 +436,7 @@ fn write_flat_type( buf.push_str(" ]"); - if let Some(content) = ext_content { + if let Err(content) = ext_content { // This is an open tag union, so print the variable // right after the ']' // @@ -483,7 +483,7 @@ fn write_flat_type( buf.push_str(" ]"); - if let Some(content) = ext_content { + if let Err(content) = ext_content { // This is an open tag union, so print the variable // right after the ']' // @@ -508,10 +508,10 @@ pub fn chase_ext_tag_union( subs: &Subs, var: Variable, fields: &mut Vec<(TagName, Vec)>, -) -> Option { +) -> Result<(), Content> { use FlatType::*; match subs.get_without_compacting(var).content { - Content::Structure(EmptyTagUnion) => None, + Content::Structure(EmptyTagUnion) => Ok(()), Content::Structure(TagUnion(tags, ext_var)) | Content::Structure(RecursiveTagUnion(_, tags, ext_var)) => { for (label, vars) in tags { @@ -521,7 +521,7 @@ pub fn chase_ext_tag_union( chase_ext_tag_union(subs, ext_var, fields) } - content => Some(content), + content => Err(content), } } @@ -529,7 +529,7 @@ pub fn chase_ext_record( subs: &Subs, var: Variable, fields: &mut MutMap, -) -> Option { +) -> Result<(), Content> { use crate::subs::Content::*; use crate::subs::FlatType::*; @@ -540,11 +540,11 @@ pub fn chase_ext_record( chase_ext_record(subs, sub_ext, fields) } - Structure(EmptyRecord) => None, + Structure(EmptyRecord) => Ok(()), Alias(_, _, var) => chase_ext_record(subs, var, fields), - content => Some(content), + content => Err(content), } } diff --git a/compiler/types/src/subs.rs b/compiler/types/src/subs.rs index 56b8b3a09a..3e8b42e3dc 100644 --- a/compiler/types/src/subs.rs +++ b/compiler/types/src/subs.rs @@ -554,7 +554,7 @@ pub enum FlatType { pub struct ContentHash(u64); impl ContentHash { - pub fn from_var(var: Variable, subs: &Subs) -> Self { + pub fn from_var(var: Variable, subs: &mut Subs) -> Self { use std::hash::Hasher; let mut hasher = std::collections::hash_map::DefaultHasher::new(); @@ -563,14 +563,14 @@ impl ContentHash { ContentHash(hasher.finish()) } - pub fn from_var_help(var: Variable, subs: &Subs, hasher: &mut T) + pub fn from_var_help(var: Variable, subs: &mut Subs, hasher: &mut T) where T: std::hash::Hasher, { - Self::from_content_help(&subs.get_without_compacting(var).content, subs, hasher) + Self::from_content_help(var, &subs.get_without_compacting(var).content, subs, hasher) } - pub fn from_content_help(content: &Content, subs: &Subs, hasher: &mut T) + pub fn from_content_help(var: Variable, content: &Content, subs: &mut Subs, hasher: &mut T) where T: std::hash::Hasher, { @@ -581,7 +581,7 @@ impl ContentHash { } Content::Structure(flat_type) => { hasher.write_u8(0x10); - Self::from_flat_type_help(flat_type, subs, hasher) + Self::from_flat_type_help(var, flat_type, subs, hasher) } Content::FlexVar(_) | Content::RigidVar(_) => { hasher.write_u8(0x11); @@ -592,8 +592,12 @@ impl ContentHash { } } - pub fn from_flat_type_help(flat_type: &FlatType, subs: &Subs, hasher: &mut T) - where + pub fn from_flat_type_help( + flat_type_var: Variable, + flat_type: &FlatType, + subs: &mut Subs, + hasher: &mut T, + ) where T: std::hash::Hasher, { use std::hash::Hash; @@ -623,26 +627,50 @@ impl ContentHash { hasher.write_u8(2); } - FlatType::Record(fields, ext) => { + FlatType::Record(record_fields, ext) => { hasher.write_u8(3); - // We have to sort by the key, so this clone seems to be required - let mut fields = fields.clone(); + // NOTE: This function will modify the subs, putting all fields from the ext_var + // into the record itself, then setting the ext_var to EMPTY_RECORD - match crate::pretty_print::chase_ext_record(subs, *ext, &mut fields) { - Some(_) => panic!("Record with non-empty ext var"), - None => { - let mut fields_vec = Vec::with_capacity(fields.len()); - fields_vec.extend(fields.into_iter()); + let mut fields = Vec::with_capacity(record_fields.len()); - fields_vec.sort(); - - for (name, argument) in fields_vec { - name.hash(hasher); - Self::from_var_help(argument, subs, hasher); + let mut extracted_fields_from_ext = false; + if *ext != Variable::EMPTY_RECORD { + let mut fields_map = MutMap::default(); + match crate::pretty_print::chase_ext_record(subs, *ext, &mut fields_map) { + Err(Content::FlexVar(_)) | Ok(()) => { + if !fields_map.is_empty() { + extracted_fields_from_ext = true; + fields.extend(fields_map.into_iter()); + } } + Err(content) => panic!("Record with unexpected ext_var: {:?}", content), } } + + fields.extend(record_fields.clone().into_iter()); + fields.sort(); + + for (name, argument) in &fields { + name.hash(hasher); + Self::from_var_help(*argument, subs, hasher); + } + + if *ext != Variable::EMPTY_RECORD { + // unify ext with empty record + let desc = subs.get(Variable::EMPTY_RECORD); + subs.union(Variable::EMPTY_RECORD, *ext, desc); + } + + if extracted_fields_from_ext { + let fields_map = fields.into_iter().collect(); + + subs.set_content( + flat_type_var, + Content::Structure(FlatType::Record(fields_map, Variable::EMPTY_RECORD)), + ); + } } FlatType::EmptyTagUnion => { @@ -652,46 +680,100 @@ impl ContentHash { FlatType::TagUnion(tags, ext) => { hasher.write_u8(5); - // We have to sort by the key, so this clone seems to be required + // NOTE: This function will modify the subs, putting all tags from the ext_var + // into the tag union itself, then setting the ext_var to EMPTY_TAG_UNION + let mut tag_vec = Vec::with_capacity(tags.len()); - tag_vec.extend(tags.clone().into_iter()); - match crate::pretty_print::chase_ext_tag_union(subs, *ext, &mut tag_vec) { - Some(_) => panic!("Tag union with non-empty ext var"), - None => { - tag_vec.sort(); - for (name, arguments) in tag_vec { - name.hash(hasher); - - for var in arguments { - Self::from_var_help(var, subs, hasher); - } + let mut extracted_fields_from_ext = false; + if *ext != Variable::EMPTY_TAG_UNION { + match crate::pretty_print::chase_ext_tag_union(subs, *ext, &mut tag_vec) { + Err(Content::FlexVar(_)) | Ok(()) => { + extracted_fields_from_ext = !tag_vec.is_empty(); } + Err(content) => panic!("TagUnion with unexpected ext_var: {:?}", content), } } + + tag_vec.extend(tags.clone().into_iter()); + tag_vec.sort(); + for (name, arguments) in &tag_vec { + name.hash(hasher); + + for var in arguments { + Self::from_var_help(*var, subs, hasher); + } + } + + if *ext != Variable::EMPTY_TAG_UNION { + // unify ext with empty record + let desc = subs.get(Variable::EMPTY_TAG_UNION); + subs.union(Variable::EMPTY_TAG_UNION, *ext, desc); + } + + if extracted_fields_from_ext { + let fields_map = tag_vec.into_iter().collect(); + + subs.set_content( + flat_type_var, + Content::Structure(FlatType::TagUnion( + fields_map, + Variable::EMPTY_TAG_UNION, + )), + ); + } } - FlatType::RecursiveTagUnion(_rec, tags, ext) => { - // TODO should the rec_var be hashed in? + FlatType::RecursiveTagUnion(rec, tags, ext) => { + // NOTE: rec is not hashed in. If all the tags and their arguments are the same, + // then the recursive tag unions are the same hasher.write_u8(6); - // We have to sort by the key, so this clone seems to be required + // NOTE: This function will modify the subs, putting all tags from the ext_var + // into the tag union itself, then setting the ext_var to EMPTY_TAG_UNION + let mut tag_vec = Vec::with_capacity(tags.len()); - tag_vec.extend(tags.clone().into_iter()); - match crate::pretty_print::chase_ext_tag_union(subs, *ext, &mut tag_vec) { - Some(_) => panic!("Tag union with non-empty ext var"), - None => { - tag_vec.sort(); - for (name, arguments) in tag_vec { - name.hash(hasher); - - for var in arguments { - Self::from_var_help(var, subs, hasher); - } + let mut extracted_fields_from_ext = false; + if *ext != Variable::EMPTY_TAG_UNION { + match crate::pretty_print::chase_ext_tag_union(subs, *ext, &mut tag_vec) { + Err(Content::FlexVar(_)) | Ok(()) => { + extracted_fields_from_ext = !tag_vec.is_empty(); + } + Err(content) => { + panic!("RecursiveTagUnion with unexpected ext_var: {:?}", content) } } } + + tag_vec.extend(tags.clone().into_iter()); + tag_vec.sort(); + for (name, arguments) in &tag_vec { + name.hash(hasher); + + for var in arguments { + Self::from_var_help(*var, subs, hasher); + } + } + + if *ext != Variable::EMPTY_TAG_UNION { + // unify ext with empty record + let desc = subs.get(Variable::EMPTY_TAG_UNION); + subs.union(Variable::EMPTY_TAG_UNION, *ext, desc); + } + + if extracted_fields_from_ext { + let fields_map = tag_vec.into_iter().collect(); + + subs.set_content( + flat_type_var, + Content::Structure(FlatType::RecursiveTagUnion( + *rec, + fields_map, + Variable::EMPTY_TAG_UNION, + )), + ); + } } FlatType::Boolean(boolean) => { From f02193b962fa1b0f5785d9e22c87c69bb0f75548 Mon Sep 17 00:00:00 2001 From: Folkert Date: Thu, 12 Mar 2020 22:56:58 +0100 Subject: [PATCH 072/428] fix new clippy warnings --- compiler/can/src/def.rs | 2 +- compiler/load/src/file.rs | 7 +++++-- compiler/parse/src/expr.rs | 7 +++++-- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/compiler/can/src/def.rs b/compiler/can/src/def.rs index 13a0431cfc..ae462339cc 100644 --- a/compiler/can/src/def.rs +++ b/compiler/can/src/def.rs @@ -1326,7 +1326,7 @@ fn to_pending_def<'a>( } } - Err(_err) => panic!("TODO gracefully handle shadowing of type alias"), + Err(err) => panic!("TODO gracefully handle shadowing of type alias {:?}", err), } } diff --git a/compiler/load/src/file.rs b/compiler/load/src/file.rs index 15e1051029..33aa4097dc 100644 --- a/compiler/load/src/file.rs +++ b/compiler/load/src/file.rs @@ -1048,8 +1048,11 @@ fn parse_and_constrain( (module, ident_ids, constraint, problems) } - Err(_runtime_error) => { - panic!("TODO gracefully handle module canonicalization error"); + Err(runtime_error) => { + panic!( + "TODO gracefully handle module canonicalization error {:?}", + runtime_error + ); } }; diff --git a/compiler/parse/src/expr.rs b/compiler/parse/src/expr.rs index dfaf6b1434..289a8e71d0 100644 --- a/compiler/parse/src/expr.rs +++ b/compiler/parse/src/expr.rs @@ -1310,8 +1310,11 @@ pub fn ident_etc<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>> { region: loc_arg.region, }); } - Err(_malformed) => { - panic!("TODO early return malformed pattern"); + Err(malformed) => { + panic!( + "TODO early return malformed pattern {:?}", + malformed + ); } } } From aeeaf99c176f2e567bffd9457833d51409dd6dad Mon Sep 17 00:00:00 2001 From: Folkert Date: Fri, 13 Mar 2020 00:16:40 +0100 Subject: [PATCH 073/428] fix bug in constraint gen for If the condition expression wasn't actually constrained --- compiler/constrain/src/expr.rs | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/compiler/constrain/src/expr.rs b/compiler/constrain/src/expr.rs index bb4295c622..bf22f7fa30 100644 --- a/compiler/constrain/src/expr.rs +++ b/compiler/constrain/src/expr.rs @@ -328,16 +328,27 @@ pub fn constrain_expr( } => { let bool_type = Type::Variable(Variable::BOOL); let expect_bool = Expected::ForReason(Reason::IfCondition, bool_type, region); - let mut branch_cons = Vec::with_capacity(2 * branches.len() + 2); + let mut branch_cons = Vec::with_capacity(2 * branches.len() + 3); + + // TODO why does this cond var exist? is it for error messages? + let cond_var_is_bool_con = Eq( + Type::Variable(*cond_var), + expect_bool.clone(), + Region::zero(), + ); + + branch_cons.push(cond_var_is_bool_con); match expected { FromAnnotation(name, arity, _, tipe) => { for (index, (loc_cond, loc_body)) in branches.iter().enumerate() { - let cond_con = Eq( - Type::Variable(*cond_var), - expect_bool.clone(), + let cond_con = constrain_expr( + env, loc_cond.region, + &loc_cond.value, + expect_bool.clone(), ); + let then_con = constrain_expr( env, loc_body.region, @@ -374,11 +385,13 @@ pub fn constrain_expr( } _ => { for (index, (loc_cond, loc_body)) in branches.iter().enumerate() { - let cond_con = Eq( - Type::Variable(*cond_var), - expect_bool.clone(), + let cond_con = constrain_expr( + env, loc_cond.region, + &loc_cond.value, + expect_bool.clone(), ); + let then_con = constrain_expr( env, loc_body.region, From e3e84ecb0c32476d4292497e1210ac74b9759304 Mon Sep 17 00:00:00 2001 From: Folkert Date: Fri, 13 Mar 2020 00:20:59 +0100 Subject: [PATCH 074/428] fix If bug in uniq constraint gen --- compiler/constrain/src/uniq.rs | 37 ++++++++++++++++++++++++++++------ 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/compiler/constrain/src/uniq.rs b/compiler/constrain/src/uniq.rs index cfbec8d69b..b27216713c 100644 --- a/compiler/constrain/src/uniq.rs +++ b/compiler/constrain/src/uniq.rs @@ -806,6 +806,21 @@ pub fn constrain_expr( let mut branch_cons = Vec::with_capacity(2 * branches.len() + 2); let mut cond_uniq_vars = Vec::with_capacity(branches.len() + 2); + // TODO why does this cond var exist? is it for error messages? + let cond_uniq_var = var_store.fresh(); + cond_uniq_vars.push(cond_uniq_var); + let cond_var_is_bool_con = Eq( + Type::Variable(*cond_var), + Expected::ForReason( + Reason::IfCondition, + attr_type(Bool::variable(cond_uniq_var), bool_type.clone()), + region, + ), + Region::zero(), + ); + + branch_cons.push(cond_var_is_bool_con); + match expected { Expected::FromAnnotation(name, arity, _, tipe) => { for (index, (loc_cond, loc_body)) in branches.iter().enumerate() { @@ -817,11 +832,16 @@ pub fn constrain_expr( ); cond_uniq_vars.push(cond_uniq_var); - let cond_con = Eq( - Type::Variable(*cond_var), - expect_bool.clone(), + let cond_con = constrain_expr( + env, + var_store, + var_usage, + applied_usage_constraint, loc_cond.region, + &loc_cond.value, + expect_bool, ); + let then_con = constrain_expr( env, var_store, @@ -879,11 +899,16 @@ pub fn constrain_expr( ); cond_uniq_vars.push(cond_uniq_var); - let cond_con = Eq( - Type::Variable(*cond_var), - expect_bool.clone(), + let cond_con = constrain_expr( + env, + var_store, + var_usage, + applied_usage_constraint, loc_cond.region, + &loc_cond.value, + expect_bool, ); + let then_con = constrain_expr( env, var_store, From 263e4b4b6c1153d1a4e544cdeda2c12dd6bb266f Mon Sep 17 00:00:00 2001 From: Folkert Date: Fri, 13 Mar 2020 00:40:37 +0100 Subject: [PATCH 075/428] add tests for mono If --- compiler/mono/tests/test_mono.rs | 91 ++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) diff --git a/compiler/mono/tests/test_mono.rs b/compiler/mono/tests/test_mono.rs index 1d7103dd3e..a4c7237b3c 100644 --- a/compiler/mono/tests/test_mono.rs +++ b/compiler/mono/tests/test_mono.rs @@ -160,6 +160,97 @@ mod test_mono { ) } + #[test] + fn if_expression() { + compiles_to( + r#" + if True then "bar" else "foo" + "#, + { + use self::Builtin::*; + use Layout::Builtin; + + Cond { + cond: &Expr::Bool(true), + cond_layout: Builtin(Bool(Global("False".into()), Global("True".into()))), + pass: &Expr::Str("bar"), + fail: &Expr::Str("foo"), + ret_layout: Builtin(Str), + } + }, + ) + } + + #[test] + fn multiway_if_expression() { + compiles_to( + r#" + if True then + "bar" + else if False then + "foo" + else + "baz" + "#, + { + use self::Builtin::*; + use Layout::Builtin; + + Cond { + cond: &Expr::Bool(true), + cond_layout: Builtin(Bool(Global("False".into()), Global("True".into()))), + pass: &Expr::Str("bar"), + fail: &Cond { + cond: &Expr::Bool(false), + cond_layout: Builtin(Bool(Global("False".into()), Global("True".into()))), + pass: &Expr::Str("foo"), + fail: &Expr::Str("baz"), + ret_layout: Builtin(Str), + }, + ret_layout: Builtin(Str), + } + }, + ) + } + + #[test] + fn annotated_if_expression() { + // an if with an annotation gets constrained differently. Make sure the result is still correct. + compiles_to( + r#" + x : Str + x = if True then "bar" else "foo" + + x + "#, + { + use self::Builtin::*; + use Layout::Builtin; + + let home = test_home(); + let symbol_x = Interns::from_index(home, 0); + + Store( + &[( + symbol_x, + Builtin(Str), + Cond { + cond: &Expr::Bool(true), + cond_layout: Builtin(Bool( + Global("False".into()), + Global("True".into()), + )), + pass: &Expr::Str("bar"), + fail: &Expr::Str("foo"), + ret_layout: Builtin(Str), + }, + )], + &Load(symbol_x), + ) + }, + ) + } + // #[test] // fn record_pattern() { // compiles_to( From a7af366c3a333a26380db95bc3e63e89ec1dbe96 Mon Sep 17 00:00:00 2001 From: Folkert Date: Fri, 13 Mar 2020 01:54:17 +0100 Subject: [PATCH 076/428] new code gen for Cond --- compiler/gen/src/crane/build.rs | 40 ++++++------- compiler/gen/src/llvm/build.rs | 52 ++++++++++++---- compiler/module/src/symbol.rs | 2 + compiler/mono/src/expr.rs | 101 +++++++++++++++++++++++++++++++- 4 files changed, 163 insertions(+), 32 deletions(-) diff --git a/compiler/gen/src/crane/build.rs b/compiler/gen/src/crane/build.rs index fcec1ba984..c2a03249aa 100644 --- a/compiler/gen/src/crane/build.rs +++ b/compiler/gen/src/crane/build.rs @@ -52,16 +52,14 @@ pub fn build_expr<'a, B: Backend>( Bool(val) => builder.ins().bconst(types::B1, *val), Byte(val) => builder.ins().iconst(types::I8, *val as i64), Cond { - cond_lhs, - cond_rhs, + cond, pass, fail, cond_layout, ret_layout, } => { let branch = Branch2 { - cond_lhs, - cond_rhs, + cond, pass, fail, cond_layout, @@ -269,8 +267,7 @@ pub fn build_expr<'a, B: Backend>( } struct Branch2<'a> { - cond_lhs: &'a Expr<'a>, - cond_rhs: &'a Expr<'a>, + cond: &'a Expr<'a>, cond_layout: &'a Layout<'a>, pass: &'a Expr<'a>, fail: &'a Expr<'a>, @@ -296,24 +293,13 @@ fn build_branch2<'a, B: Backend>( builder.declare_var(ret, ret_type); - let lhs = build_expr(env, scope, module, builder, branch.cond_lhs, procs); - let rhs = build_expr(env, scope, module, builder, branch.cond_rhs, procs); + let cond = build_expr(env, scope, module, builder, branch.cond, procs); let pass_block = builder.create_block(); let fail_block = builder.create_block(); match branch.cond_layout { - Layout::Builtin(Builtin::Float64) => { - // For floats, first do a `fcmp` comparison to get a bool answer about equality, - // then use `brnz` to branch if that bool equality answer was nonzero (aka true). - let is_eq = builder.ins().fcmp(FloatCC::Equal, lhs, rhs); - - builder.ins().brnz(is_eq, pass_block, &[]); - } - Layout::Builtin(Builtin::Int64) => { - // For ints, we can compare and branch in the same instruction: `icmp` - builder - .ins() - .br_icmp(IntCC::Equal, lhs, rhs, pass_block, &[]); + Layout::Builtin(Builtin::Bool(_, _)) => { + builder.ins().brnz(cond, pass_block, &[]); } other => panic!("I don't know how to build a conditional for {:?}", other), } @@ -589,6 +575,20 @@ fn call_by_name<'a, B: Backend>( builder.ins().ineg(num) } + Symbol::INT_EQ => { + debug_assert!(args.len() == 2); + let a = build_arg(&args[0], env, scope, module, builder, procs); + let b = build_arg(&args[1], env, scope, module, builder, procs); + + builder.ins().icmp(IntCC::Equal, a, b) + } + Symbol::FLOAT_EQ => { + debug_assert!(args.len() == 2); + let a = build_arg(&args[0], env, scope, module, builder, procs); + let b = build_arg(&args[1], env, scope, module, builder, procs); + + builder.ins().fcmp(FloatCC::Equal, a, b) + } Symbol::LIST_GET_UNSAFE => { debug_assert!(args.len() == 2); diff --git a/compiler/gen/src/llvm/build.rs b/compiler/gen/src/llvm/build.rs index e530f4baef..5f6e791945 100644 --- a/compiler/gen/src/llvm/build.rs +++ b/compiler/gen/src/llvm/build.rs @@ -46,22 +46,20 @@ pub fn build_expr<'a, 'ctx, 'env>( Int(num) => env.context.i64_type().const_int(*num as u64, true).into(), Float(num) => env.context.f64_type().const_float(*num).into(), Cond { - cond_lhs, - cond_rhs, + cond, pass, fail, ret_layout, .. } => { - let cond = Branch2 { - cond_lhs, - cond_rhs, + let conditional = Branch2 { + cond, pass, fail, ret_layout: ret_layout.clone(), }; - build_branch2(env, scope, parent, cond, procs) + build_branch2(env, scope, parent, conditional, procs) } Branches { .. } => { panic!("TODO build_branches(env, scope, parent, cond_lhs, branches, procs)"); @@ -251,8 +249,7 @@ pub fn build_expr<'a, 'ctx, 'env>( } struct Branch2<'a> { - cond_lhs: &'a Expr<'a>, - cond_rhs: &'a Expr<'a>, + cond: &'a Expr<'a>, pass: &'a Expr<'a>, fail: &'a Expr<'a>, ret_layout: Layout<'a>, @@ -269,9 +266,9 @@ fn build_branch2<'a, 'ctx, 'env>( let ret_layout = cond.ret_layout; let ret_type = basic_type_from_layout(env.context, &ret_layout); - let lhs = build_expr(env, scope, parent, cond.cond_lhs, procs); - let rhs = build_expr(env, scope, parent, cond.cond_rhs, procs); + let cond_expr = build_expr(env, scope, parent, cond.cond, procs); + /* match (lhs, rhs) { (FloatValue(lhs_float), FloatValue(rhs_float)) => { let comparison = @@ -294,6 +291,17 @@ fn build_branch2<'a, 'ctx, 'env>( cond.cond_lhs, cond.cond_rhs ), } + */ + + match cond_expr { + IntValue(value) => build_phi2( + env, scope, parent, value, cond.pass, cond.fail, ret_type, procs, + ), + _ => panic!( + "Tried to make a branch out of an invalid condition: cond_expr = {:?}", + cond_expr, + ), + } } struct SwitchArgs<'a, 'ctx> { @@ -600,6 +608,30 @@ fn call_with_args<'a, 'ctx, 'env>( BasicValueEnum::IntValue(int_val) } + Symbol::INT_EQ => { + debug_assert!(args.len() == 2); + + let int_val = env.builder.build_int_compare( + IntPredicate::EQ, + args[0].into_int_value(), + args[1].into_int_value(), + "cmp_i64", + ); + + BasicValueEnum::IntValue(int_val) + } + Symbol::FLOAT_EQ => { + debug_assert!(args.len() == 2); + + let int_val = env.builder.build_float_compare( + FloatPredicate::OEQ, + args[0].into_float_value(), + args[1].into_float_value(), + "cmp_f64", + ); + + BasicValueEnum::IntValue(int_val) + } Symbol::LIST_GET_UNSAFE => { debug_assert!(args.len() == 2); diff --git a/compiler/module/src/symbol.rs b/compiler/module/src/symbol.rs index cce62d3652..6ba11959e0 100644 --- a/compiler/module/src/symbol.rs +++ b/compiler/module/src/symbol.rs @@ -591,6 +591,7 @@ define_builtins! { 6 INT_LOWEST: "lowest" 7 INT_ADD: "#add" 8 INT_SUB: "#sub" + 9 INT_EQ: "#eq" } 3 FLOAT: "Float" => { 0 FLOAT_FLOAT: "Float" imported // the Float.Float type alias @@ -603,6 +604,7 @@ define_builtins! { 7 FLOAT_LOWEST: "lowest" 8 FLOAT_ADD: "#add" 9 FLOAT_SUB: "#sub" + 10 FLOAT_EQ: "#eq" } 4 BOOL: "Bool" => { 0 BOOL_BOOL: "Bool" imported // the Bool.Bool type alias diff --git a/compiler/mono/src/expr.rs b/compiler/mono/src/expr.rs index 0fd6237211..3a4d3739e2 100644 --- a/compiler/mono/src/expr.rs +++ b/compiler/mono/src/expr.rs @@ -150,8 +150,7 @@ pub enum Expr<'a> { // The left-hand side of the conditional comparison and the right-hand side. // These are stored separately because there are different machine instructions // for e.g. "compare float and jump" vs. "compare integer and jump" - cond_lhs: &'a Expr<'a>, - cond_rhs: &'a Expr<'a>, + cond: &'a Expr<'a>, cond_layout: Layout<'a>, // What to do if the condition either passes or fails pass: &'a Expr<'a>, @@ -575,6 +574,34 @@ fn from_can<'a>( branches, } => from_can_when(env, cond_var, expr_var, *loc_cond, branches, procs), + If { + cond_var, + branch_var, + branches, + final_else, + } => { + let mut expr = from_can(env, final_else.value, procs, None); + + let ret_layout = Layout::from_var(env.arena, branch_var, env.subs, env.pointer_size) + .expect("invalid ret_layout"); + let cond_layout = Layout::from_var(env.arena, cond_var, env.subs, env.pointer_size) + .expect("invalid cond_layout"); + + for (loc_cond, loc_then) in branches.into_iter().rev() { + let cond = from_can(env, loc_cond.value, procs, None); + let then = from_can(env, loc_then.value, procs, None); + expr = Expr::Cond { + cond: env.arena.alloc(cond), + cond_layout: cond_layout.clone(), + pass: env.arena.alloc(then), + fail: env.arena.alloc(expr), + ret_layout: ret_layout.clone(), + }; + } + + expr + } + Record(ext_var, fields) => { let arena = env.arena; let mut field_bodies = Vec::with_capacity_in(fields.len(), arena); @@ -785,6 +812,75 @@ fn from_can_when<'a>( let (loc_when_pat2, loc_else) = iter.next().unwrap(); match (&loc_when_pat1.value, &loc_when_pat2.value) { + (NumLiteral(var, num), Underscore) => { + let cond_lhs = from_can(env, loc_cond.value, procs, None); + + let (fn_symbol, builtin, cond_rhs_expr) = match to_int_or_float(env.subs, *var) + { + IntOrFloat::IntType => (Symbol::INT_EQ, Builtin::Int64, Expr::Int(*num)), + IntOrFloat::FloatType => { + (Symbol::FLOAT_EQ, Builtin::Float64, Expr::Float(*num as f64)) + } + }; + let cond_rhs = cond_rhs_expr; + + let cond = arena.alloc(Expr::CallByName( + fn_symbol, + arena.alloc([ + (cond_lhs, Layout::Builtin(builtin.clone())), + (cond_rhs, Layout::Builtin(builtin)), + ]), + )); + + let pass = arena.alloc(from_can(env, loc_then.value, procs, None)); + let fail = arena.alloc(from_can(env, loc_else.value, procs, None)); + let ret_layout = Layout::from_var(arena, expr_var, env.subs, env.pointer_size) + .unwrap_or_else(|err| { + panic!("TODO turn this into a RuntimeError {:?}", err) + }); + + Expr::Cond { + cond_layout: Layout::Builtin(Builtin::Bool( + TagName::Global("False".into()), + TagName::Global("True".into()), + )), + cond, + pass, + fail, + ret_layout, + } + } + (FloatLiteral(float), Underscore) => { + let cond_lhs = from_can(env, loc_cond.value, procs, None); + let cond_rhs = Expr::Float(*float); + + let cond = arena.alloc(Expr::CallByName( + Symbol::FLOAT_EQ, + arena.alloc([ + (cond_lhs, Layout::Builtin(Builtin::Float64)), + (cond_rhs, Layout::Builtin(Builtin::Float64)), + ]), + )); + + let pass = arena.alloc(from_can(env, loc_then.value, procs, None)); + let fail = arena.alloc(from_can(env, loc_else.value, procs, None)); + let ret_layout = Layout::from_var(arena, expr_var, env.subs, env.pointer_size) + .unwrap_or_else(|err| { + panic!("TODO turn this into a RuntimeError {:?}", err) + }); + + Expr::Cond { + cond_layout: Layout::Builtin(Builtin::Bool( + TagName::Global("False".into()), + TagName::Global("True".into()), + )), + cond, + pass, + fail, + ret_layout, + } + } + /* (NumLiteral(var, num), NumLiteral(_, _)) | (NumLiteral(var, num), Underscore) => { let cond_lhs = arena.alloc(from_can(env, loc_cond.value, procs, None)); let (builtin, cond_rhs_expr) = match to_int_or_float(env.subs, *var) { @@ -847,6 +943,7 @@ fn from_can_when<'a>( ret_layout, } } + */ _ => { panic!("TODO handle more conds"); } From e01a6bab9bcd323eafa6b4bfa13da69279650b9b Mon Sep 17 00:00:00 2001 From: Folkert Date: Fri, 13 Mar 2020 13:05:47 +0100 Subject: [PATCH 077/428] improve error reporting by mismatch! macro --- compiler/unify/src/unify.rs | 53 ++++++++++++++++++++++++++++++++----- 1 file changed, 46 insertions(+), 7 deletions(-) diff --git a/compiler/unify/src/unify.rs b/compiler/unify/src/unify.rs index 884365b400..35962750ae 100644 --- a/compiler/unify/src/unify.rs +++ b/compiler/unify/src/unify.rs @@ -19,6 +19,45 @@ macro_rules! mismatch { } vec![Mismatch::TypeMismatch] }}; + ($msg:expr) => {{ + if cfg!(debug_assertions) { + println!( + "Mismatch in {} Line {} Column {}", + file!(), + line!(), + column!() + ); + } + println!($msg); + println!(""); + vec![Mismatch::TypeMismatch] + }}; + ($msg:expr,) => {{ + if cfg!(debug_assertions) { + println!( + "Mismatch in {} Line {} Column {}", + file!(), + line!(), + column!() + ); + } + println!($msg); + println!(""); + vec![Mismatch::TypeMismatch] + }}; + ($msg:expr, $($arg:tt)*) => {{ + if cfg!(debug_assertions) { + println!( + "Mismatch in {} Line {} Column {}", + file!(), + line!(), + column!() + ); + } + println!($msg, $($arg)*); + println!(""); + vec![Mismatch::TypeMismatch] + }}; } type Pool = Vec; @@ -154,9 +193,9 @@ fn unify_structure( } } } - RigidVar(_) => { + RigidVar(name) => { // Type mismatch! Rigid can only unify with flex. - mismatch!() + mismatch!("trying to unify {:?} with rigid var {:?}", &flat_type, name) } Structure(ref other_flat_type) => { @@ -552,11 +591,11 @@ fn unify_flat_type( problems } } - (_other1, _other2) => { - // Can't unify other1 and other2 - // dbg!(&_other1, &_other2); - mismatch!() - } + (other1, other2) => mismatch!( + "Trying to two flat types that are incompatible: {:?} ~ {:?}", + other1, + other2 + ), } } From 9ef4a6d891955c04176a0ec60ff9192ff3d74a74 Mon Sep 17 00:00:00 2001 From: Folkert Date: Fri, 13 Mar 2020 13:06:37 +0100 Subject: [PATCH 078/428] fix bug in uniq isEq type it always wanted Boolean arguments ... --- compiler/builtins/src/unique.rs | 4 ++-- compiler/solve/tests/test_uniq_solve.rs | 32 ++++++++++++++++++------- 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/compiler/builtins/src/unique.rs b/compiler/builtins/src/unique.rs index 199bd14eda..224de39d2d 100644 --- a/compiler/builtins/src/unique.rs +++ b/compiler/builtins/src/unique.rs @@ -397,13 +397,13 @@ pub fn types() -> MutMap { // isEq or (==) : Attr u1 Bool, Attr u2 Bool -> Attr u3 Bool add_type( Symbol::BOOL_EQ, - unique_function(vec![bool_type(UVAR1), bool_type(UVAR2)], bool_type(UVAR3)), + unique_function(vec![flex(TVAR1), flex(TVAR2)], bool_type(UVAR3)), ); // isNeq or (!=) : Attr u1 Bool, Attr u2 Bool -> Attr u3 Bool add_type( Symbol::BOOL_NEQ, - unique_function(vec![bool_type(UVAR1), bool_type(UVAR2)], bool_type(UVAR3)), + unique_function(vec![flex(TVAR1), flex(TVAR2)], bool_type(UVAR3)), ); // and or (&&) : Attr u1 Bool, Attr u2 Bool -> Attr u3 Bool diff --git a/compiler/solve/tests/test_uniq_solve.rs b/compiler/solve/tests/test_uniq_solve.rs index dbc0343889..2f26288dae 100644 --- a/compiler/solve/tests/test_uniq_solve.rs +++ b/compiler/solve/tests/test_uniq_solve.rs @@ -1408,7 +1408,8 @@ mod test_uniq_solve { #[test] fn quicksort() { - infer_eq( + with_larger_debug_stack(|| { + infer_eq( indoc!( r#" quicksort : List (Num a), Int, Int -> List (Num a) @@ -1462,6 +1463,7 @@ mod test_uniq_solve { ), "Attr Shared (Attr b (List (Attr Shared (Num (Attr c a)))), Attr Shared Int, Attr Shared Int -> Attr b (List (Attr Shared (Num (Attr c a)))))" ); + }) } #[test] @@ -2083,7 +2085,8 @@ mod test_uniq_solve { reverse "# ), - "Attr * (Attr * (List (Attr (a | b) c)) -> Attr (* | a | b) (List (Attr a c)))", + "Attr * (Attr * (List (Attr (a | b) c)) -> Attr (* | a | b) (List (Attr b c)))", + //"Attr * (Attr * (List (Attr (a | b) c)) -> Attr (* | a | b) (List (Attr a c)))", ); } @@ -2327,18 +2330,17 @@ mod test_uniq_solve { Ok (reconstructPath model.cameFrom goal) else + modelPopped = { model & openSet : Set.remove model.openSet current, evaluated : Set.insert model.evaluated current } - modelPopped = { model & openSet : Set.remove model.openSet current, evaluated : Set.insert model.evaluated current } + neighbours = moveFn current - neighbours = moveFn current + newNeighbours = Set.diff neighbours modelPopped.evaluated - newNeighbours = Set.diff neighbours modelPopped.evaluated + modelWithNeighbours = { modelPopped & openSet : Set.union modelPopped.openSet newNeighbours } - modelWithNeighbours = { modelPopped & openSet : Set.union modelPopped.openSet newNeighbours } + modelWithCosts = Set.foldl newNeighbours (\nb, md -> updateCost current nb md) modelWithNeighbours - modelWithCosts = Set.foldl newNeighbours (\nb, md -> updateCost current nb md) modelWithNeighbours - - astar costFn moveFn goal modelWithCosts + astar costFn moveFn goal modelWithCosts findPath "# @@ -2348,6 +2350,18 @@ mod test_uniq_solve { }); } + #[test] + fn equals() { + infer_eq( + indoc!( + r#" + \a, b -> a == b + "# + ), + "Attr * (*, * -> Attr * Bool)", + ); + } + #[test] fn instantiated_alias() { infer_eq( From eea25429b630bc32272228330f2c1e46ce1b8d92 Mon Sep 17 00:00:00 2001 From: Dan Bruder Date: Fri, 13 Mar 2020 09:16:04 -0400 Subject: [PATCH 079/428] uncomment llvm tests for basic_record --- compiler/gen/tests/test_gen.rs | 60 +++++++++++++++++----------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/compiler/gen/tests/test_gen.rs b/compiler/gen/tests/test_gen.rs index 5488825aee..ebe01fc0c5 100644 --- a/compiler/gen/tests/test_gen.rs +++ b/compiler/gen/tests/test_gen.rs @@ -910,36 +910,36 @@ mod test_gen { ); } - // #[test] - // fn basic_record() { - // assert_evals_to!( - // indoc!( - // r#" - // { y: 17, x: 15, z: 19 }.x - // "# - // ), - // 15, - // i64 - // ); + #[test] + fn basic_record() { + assert_evals_to!( + indoc!( + r#" + { y: 17, x: 15, z: 19 }.x + "# + ), + 15, + i64 + ); - // assert_evals_to!( - // indoc!( - // r#" - // { x: 15, y: 17, z: 19 }.y - // "# - // ), - // 17, - // i64 - // ); + assert_evals_to!( + indoc!( + r#" + { x: 15, y: 17, z: 19 }.y + "# + ), + 17, + i64 + ); - // assert_evals_to!( - // indoc!( - // r#" - // { x: 15, y: 17, z: 19 }.z - // "# - // ), - // 19, - // i64 - // ); - // } + assert_evals_to!( + indoc!( + r#" + { x: 15, y: 17, z: 19 }.z + "# + ), + 19, + i64 + ); + } } From a3b1ec3be0a4b6793060469f612d820bf675c5b8 Mon Sep 17 00:00:00 2001 From: Folkert Date: Fri, 13 Mar 2020 15:09:39 +0100 Subject: [PATCH 080/428] pull tags/fields from the ext_var during unification --- compiler/mono/src/layout.rs | 27 +++++++++++++++++++++------ compiler/types/src/pretty_print.rs | 12 ++++++------ compiler/types/src/subs.rs | 6 +++--- compiler/unify/src/unify.rs | 25 ++++++++++++++++++++++--- 4 files changed, 52 insertions(+), 18 deletions(-) diff --git a/compiler/mono/src/layout.rs b/compiler/mono/src/layout.rs index 4a7d9166ad..7ccb2fb507 100644 --- a/compiler/mono/src/layout.rs +++ b/compiler/mono/src/layout.rs @@ -194,8 +194,15 @@ fn layout_from_flat_type<'a>( arena.alloc(ret), )) } - Record(mut fields, ext_var) => { - flatten_record(&mut fields, ext_var, subs); + Record(fields, ext_var) => { + debug_assert!({ + // the ext_var is empty + let mut ext_fields = MutMap::default(); + match roc_types::pretty_print::chase_ext_record(subs, ext_var, &mut ext_fields) { + Ok(()) | Err((_, Content::FlexVar(_))) => ext_fields.is_empty(), + Err((_, content)) => panic!("invalid content in ext_var: {:?}", content), + } + }); let ext_content = subs.get_without_compacting(ext_var).content; let ext_layout = match Layout::from_content(arena, ext_content, subs, pointer_size) { Ok(layout) => layout, @@ -239,10 +246,18 @@ fn layout_from_flat_type<'a>( Ok(Layout::Struct(field_layouts.into_bump_slice())) } - TagUnion(mut tags, ext_var) => { - // Recursively inject the contents of ext_var into tags - // until we have all the tags in one map. - flatten_union(&mut tags, ext_var, subs); + TagUnion(tags, ext_var) => { + debug_assert!({ + // the ext_var is empty + let mut ext_fields = std::vec::Vec::new(); + match roc_types::pretty_print::chase_ext_tag_union(subs, ext_var, &mut ext_fields) { + Ok(()) | Err((_, Content::FlexVar(_))) => { + println!("Tags: {:?}, ext_tags: {:?}", &tags, ext_fields); + ext_fields.is_empty() + } + Err(content) => panic!("invalid content in ext_var: {:?}", content), + } + }); match tags.len() { 0 => { diff --git a/compiler/types/src/pretty_print.rs b/compiler/types/src/pretty_print.rs index 0108b35618..2fa473ed08 100644 --- a/compiler/types/src/pretty_print.rs +++ b/compiler/types/src/pretty_print.rs @@ -436,7 +436,7 @@ fn write_flat_type( buf.push_str(" ]"); - if let Err(content) = ext_content { + if let Err((_, content)) = ext_content { // This is an open tag union, so print the variable // right after the ']' // @@ -483,7 +483,7 @@ fn write_flat_type( buf.push_str(" ]"); - if let Err(content) = ext_content { + if let Err((_, content)) = ext_content { // This is an open tag union, so print the variable // right after the ']' // @@ -508,7 +508,7 @@ pub fn chase_ext_tag_union( subs: &Subs, var: Variable, fields: &mut Vec<(TagName, Vec)>, -) -> Result<(), Content> { +) -> Result<(), (Variable, Content)> { use FlatType::*; match subs.get_without_compacting(var).content { Content::Structure(EmptyTagUnion) => Ok(()), @@ -521,7 +521,7 @@ pub fn chase_ext_tag_union( chase_ext_tag_union(subs, ext_var, fields) } - content => Err(content), + content => Err((var, content)), } } @@ -529,7 +529,7 @@ pub fn chase_ext_record( subs: &Subs, var: Variable, fields: &mut MutMap, -) -> Result<(), Content> { +) -> Result<(), (Variable, Content)> { use crate::subs::Content::*; use crate::subs::FlatType::*; @@ -544,7 +544,7 @@ pub fn chase_ext_record( Alias(_, _, var) => chase_ext_record(subs, var, fields), - content => Err(content), + content => Err((var, content)), } } diff --git a/compiler/types/src/subs.rs b/compiler/types/src/subs.rs index 3e8b42e3dc..7f8251c4fe 100644 --- a/compiler/types/src/subs.rs +++ b/compiler/types/src/subs.rs @@ -639,7 +639,7 @@ impl ContentHash { if *ext != Variable::EMPTY_RECORD { let mut fields_map = MutMap::default(); match crate::pretty_print::chase_ext_record(subs, *ext, &mut fields_map) { - Err(Content::FlexVar(_)) | Ok(()) => { + Err((_, Content::FlexVar(_))) | Ok(()) => { if !fields_map.is_empty() { extracted_fields_from_ext = true; fields.extend(fields_map.into_iter()); @@ -688,7 +688,7 @@ impl ContentHash { let mut extracted_fields_from_ext = false; if *ext != Variable::EMPTY_TAG_UNION { match crate::pretty_print::chase_ext_tag_union(subs, *ext, &mut tag_vec) { - Err(Content::FlexVar(_)) | Ok(()) => { + Err((_, Content::FlexVar(_))) | Ok(()) => { extracted_fields_from_ext = !tag_vec.is_empty(); } Err(content) => panic!("TagUnion with unexpected ext_var: {:?}", content), @@ -737,7 +737,7 @@ impl ContentHash { let mut extracted_fields_from_ext = false; if *ext != Variable::EMPTY_TAG_UNION { match crate::pretty_print::chase_ext_tag_union(subs, *ext, &mut tag_vec) { - Err(Content::FlexVar(_)) | Ok(()) => { + Err((_, Content::FlexVar(_))) | Ok(()) => { extracted_fields_from_ext = !tag_vec.is_empty(); } Err(content) => { diff --git a/compiler/unify/src/unify.rs b/compiler/unify/src/unify.rs index 35962750ae..28d4e6fe5b 100644 --- a/compiler/unify/src/unify.rs +++ b/compiler/unify/src/unify.rs @@ -320,7 +320,15 @@ fn unify_shared_fields( } if num_shared_fields == matching_fields.len() { - let flat_type = FlatType::Record(union(matching_fields, &other_fields), ext); + // 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 flat_type = FlatType::Record(fields, new_ext_var); merge(subs, ctx, Structure(flat_type)) } else { @@ -460,10 +468,21 @@ fn unify_shared_tags( } if num_shared_tags == matching_tags.len() { + // 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) + { + 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 { - FlatType::RecursiveTagUnion(rec, union(matching_tags, &other_tags), ext) + FlatType::RecursiveTagUnion(rec, new_tags, new_ext_var) } else { - FlatType::TagUnion(union(matching_tags, &other_tags), ext) + FlatType::TagUnion(new_tags, new_ext_var) }; merge(subs, ctx, Structure(flat_type)) From c16d5cb3f19b42fc2fda6722f295250eecaad29d Mon Sep 17 00:00:00 2001 From: Dan Bruder Date: Fri, 13 Mar 2020 10:13:32 -0400 Subject: [PATCH 081/428] struct creation and access in llvm --- compiler/gen/src/llvm/build.rs | 74 ++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/compiler/gen/src/llvm/build.rs b/compiler/gen/src/llvm/build.rs index e530f4baef..619900e207 100644 --- a/compiler/gen/src/llvm/build.rs +++ b/compiler/gen/src/llvm/build.rs @@ -244,6 +244,80 @@ pub fn build_expr<'a, 'ctx, 'env>( BasicValueEnum::PointerValue(ptr) } } + + Struct { fields, .. } => { + let ctx = env.context; + let builder = env.builder; + + // Sort the fields + let mut sorted_fields = Vec::with_capacity_in(fields.len(), env.arena); + for field in fields.iter() { + sorted_fields.push(field); + } + sorted_fields.sort_by_key(|k| &k.0); + + // Determine types + let mut field_types = Vec::with_capacity_in(fields.len(), env.arena); + let mut field_vals = Vec::with_capacity_in(fields.len(), env.arena); + + for (_, ref inner_expr) in sorted_fields.iter() { + let val = build_expr(env, &scope, parent, inner_expr, procs); + + let field_type = match inner_expr { + Int(_) => BasicTypeEnum::IntType(ctx.i64_type()), + _ => panic!("I don't yet know how to get Inkwell type for {:?}", val), + }; + + field_types.push(field_type); + field_vals.push(val); + } + + // Create the struct_type + let struct_type = ctx.struct_type(field_types.into_bump_slice(), false); + let mut struct_val = struct_type.const_zero().into(); + + // Insert field exprs into struct_val + for (index, field_val) in field_vals.into_iter().enumerate() { + struct_val = builder + .build_insert_value(struct_val, field_val, index as u32, "insert_field") + .unwrap(); + } + + BasicValueEnum::StructValue(struct_val.into_struct_value()) + } + Access { + label, + field_layout, + struct_layout: Layout::Struct(fields), + record, + } => { + let builder = env.builder; + + // Reconstruct struct layout + let mut reconstructed_struct_layout = + Vec::with_capacity_in(fields.len() + 1, env.arena); + for field in fields.iter() { + reconstructed_struct_layout.push(field.clone()); + } + reconstructed_struct_layout.push((label.clone(), field_layout.clone())); + reconstructed_struct_layout.sort_by(|a, b| { + a.0.partial_cmp(&b.0) + .expect("TODO: failed to sort struct fields in crane access") + }); + + // Get index + let index = reconstructed_struct_layout + .iter() + .position(|(local_label, _)| local_label == label) + .unwrap() as u32; // TODO + + // Get Struct val + let struct_val = build_expr(env, &scope, parent, record, procs).into_struct_value(); + + builder + .build_extract_value(struct_val, index, "field_access") + .unwrap() + } _ => { panic!("I don't yet know how to LLVM build {:?}", expr); } From 6651180283414bdd0111e8d5e62cfe23a820216c Mon Sep 17 00:00:00 2001 From: Folkert Date: Fri, 13 Mar 2020 15:13:43 +0100 Subject: [PATCH 082/428] implement Bool in llvm --- compiler/gen/src/llvm/build.rs | 27 +-------------------------- 1 file changed, 1 insertion(+), 26 deletions(-) diff --git a/compiler/gen/src/llvm/build.rs b/compiler/gen/src/llvm/build.rs index 5f6e791945..b75379c804 100644 --- a/compiler/gen/src/llvm/build.rs +++ b/compiler/gen/src/llvm/build.rs @@ -45,6 +45,7 @@ pub fn build_expr<'a, 'ctx, 'env>( match expr { Int(num) => env.context.i64_type().const_int(*num as u64, true).into(), Float(num) => env.context.f64_type().const_float(*num).into(), + Bool(b) => env.context.bool_type().const_int(*b as u64, false).into(), Cond { cond, pass, @@ -262,37 +263,11 @@ fn build_branch2<'a, 'ctx, 'env>( cond: Branch2<'a>, procs: &Procs<'a>, ) -> BasicValueEnum<'ctx> { - let builder = env.builder; let ret_layout = cond.ret_layout; let ret_type = basic_type_from_layout(env.context, &ret_layout); let cond_expr = build_expr(env, scope, parent, cond.cond, procs); - /* - match (lhs, rhs) { - (FloatValue(lhs_float), FloatValue(rhs_float)) => { - let comparison = - builder.build_float_compare(FloatPredicate::OEQ, lhs_float, rhs_float, "cond"); - - build_phi2( - env, scope, parent, comparison, cond.pass, cond.fail, ret_type, procs, - ) - } - - (IntValue(lhs_int), IntValue(rhs_int)) => { - let comparison = builder.build_int_compare(IntPredicate::EQ, lhs_int, rhs_int, "cond"); - - build_phi2( - env, scope, parent, comparison, cond.pass, cond.fail, ret_type, procs, - ) - } - _ => panic!( - "Tried to make a branch out of incompatible conditions: lhs = {:?} and rhs = {:?}", - cond.cond_lhs, cond.cond_rhs - ), - } - */ - match cond_expr { IntValue(value) => build_phi2( env, scope, parent, value, cond.pass, cond.fail, ret_type, procs, From 735358c5774cfc6a7ee208d710b10f6c179815a0 Mon Sep 17 00:00:00 2001 From: Folkert Date: Fri, 13 Mar 2020 15:15:10 +0100 Subject: [PATCH 083/428] implement isEq for Float/Int --- compiler/mono/src/expr.rs | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/compiler/mono/src/expr.rs b/compiler/mono/src/expr.rs index 3a4d3739e2..aec5c87247 100644 --- a/compiler/mono/src/expr.rs +++ b/compiler/mono/src/expr.rs @@ -454,10 +454,6 @@ fn from_can<'a>( // by the surrounding context let symbol = env.fresh_symbol(); - // Has the side-effect of monomorphizing record types - // turning the ext_var into EmptyRecord or EmptyTagUnion - let _ = ContentHash::from_var(annotation, env.subs); - let opt_proc = specialize_proc_body( env, procs, @@ -484,16 +480,34 @@ fn from_can<'a>( let (fn_var, loc_expr, ret_var) = *boxed; + // Optimization: have a cheap "is_builtin" check, that looks at the + // module ID to see if it's possibly a builting symbol let specialize_builtin_functions = { - |symbol, subs: &Subs| match symbol { - Symbol::NUM_ADD => match to_int_or_float(subs, ret_var) { + |env: &mut Env<'a, '_>, symbol| match symbol { + Symbol::NUM_ADD => match to_int_or_float(env.subs, ret_var) { FloatType => Symbol::FLOAT_ADD, IntType => Symbol::INT_ADD, }, - Symbol::NUM_SUB => match to_int_or_float(subs, ret_var) { + Symbol::NUM_SUB => match to_int_or_float(env.subs, ret_var) { FloatType => Symbol::FLOAT_SUB, IntType => Symbol::INT_SUB, }, + // TODO make this work for more than just int/float + Symbol::BOOL_EQ => { + match Layout::from_var(env.arena, loc_args[0].0, env.subs, env.pointer_size) + { + Ok(Layout::Builtin(builtin)) => match builtin { + Builtin::Int64 => Symbol::INT_EQ, + Builtin::Float64 => Symbol::FLOAT_EQ, + _ => panic!("Equality not unimplemented for {:?}", builtin), + }, + Ok(complex) => panic!( + "TODO support equality on complex layouts like {:?}", + complex + ), + Err(()) => panic!("Invalid layout"), + } + } _ => symbol, } }; @@ -502,7 +516,7 @@ fn from_can<'a>( Expr::Load(proc_name) => { // Some functions can potentially mutate in-place. // If we have one of those, switch to the in-place version if appropriate. - match specialize_builtin_functions(proc_name, &env.subs) { + match specialize_builtin_functions(env, proc_name) { Symbol::LIST_SET => { let subs = &env.subs; // The first arg is the one with the List in it. From 97f1548a24b566df931a1baa5757481b3ccbaebb Mon Sep 17 00:00:00 2001 From: Folkert Date: Fri, 13 Mar 2020 15:16:27 +0100 Subject: [PATCH 084/428] add tests --- compiler/gen/tests/test_gen.rs | 60 ++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/compiler/gen/tests/test_gen.rs b/compiler/gen/tests/test_gen.rs index 06103dd529..918927d3bc 100644 --- a/compiler/gen/tests/test_gen.rs +++ b/compiler/gen/tests/test_gen.rs @@ -787,6 +787,66 @@ mod test_gen { ); } + #[test] + fn gen_if_fn() { + assert_evals_to!( + indoc!( + r#" + limitedNegate = \num -> + if num == 1 then + -1 + else if num == -1 then + 1 + else + num + + limitedNegate 1 + "# + ), + -1, + i64 + ); + } + + #[test] + fn gen_float_eq() { + assert_evals_to!( + indoc!( + r#" + 1.0 == 1.0 + "# + ), + true, + bool + ); + } + + #[test] + fn gen_literal_true() { + assert_evals_to!( + indoc!( + r#" + if True then -1 else 1 + "# + ), + -1, + i64 + ); + } + + #[test] + fn gen_if_float_fn() { + assert_evals_to!( + indoc!( + r#" + if True then -1.0 else 1.0 + "# + ), + -1.0, + f64 + ); + } + #[test] fn apply_identity_() { assert_evals_to!( From bfde5c596db7baa4316e6b78aa1ce38561ccb582 Mon Sep 17 00:00:00 2001 From: Folkert Date: Fri, 13 Mar 2020 15:17:37 +0100 Subject: [PATCH 085/428] remove dead code --- compiler/mono/src/layout.rs | 49 ------------------------------------- 1 file changed, 49 deletions(-) diff --git a/compiler/mono/src/layout.rs b/compiler/mono/src/layout.rs index 7ccb2fb507..a177d04999 100644 --- a/compiler/mono/src/layout.rs +++ b/compiler/mono/src/layout.rs @@ -373,55 +373,6 @@ fn layout_from_num_content<'a>(content: Content) -> Result, ()> { } } -/// Recursively inline the contents ext_var into this union until we have -/// a flat union containing all the tags. -fn flatten_union( - tags: &mut MutMap>, - ext_var: Variable, - subs: &Subs, -) { - use roc_types::subs::Content::*; - use roc_types::subs::FlatType::*; - - match subs.get_without_compacting(ext_var).content { - Structure(EmptyTagUnion) => (), - Structure(TagUnion(new_tags, new_ext_var)) - | Structure(RecursiveTagUnion(_, new_tags, new_ext_var)) => { - for (tag_name, vars) in new_tags { - tags.insert(tag_name, vars); - } - - flatten_union(tags, new_ext_var, subs) - } - Alias(_, _, actual) => flatten_union(tags, actual, subs), - invalid => { - panic!("Compiler error: flatten_union got an ext_var in a tag union that wasn't itself a tag union; instead, it was: {:?}", invalid); - } - }; -} - -/// Recursively inline the contents ext_var into this record until we have -/// a flat record containing all the fields. -fn flatten_record(fields: &mut MutMap, ext_var: Variable, subs: &Subs) { - use roc_types::subs::Content::*; - use roc_types::subs::FlatType::*; - - match subs.get_without_compacting(ext_var).content { - Structure(EmptyRecord) => (), - Structure(Record(new_tags, new_ext_var)) => { - for (label, var) in new_tags { - fields.insert(label, var); - } - - flatten_record(fields, new_ext_var, subs) - } - Alias(_, _, actual) => flatten_record(fields, actual, subs), - invalid => { - panic!("Compiler error: flatten_record encountered an ext_var in a record that wasn't itself a record; instead, it was: {:?}", invalid); - } - }; -} - fn unwrap_num_tag<'a>(subs: &Subs, var: Variable) -> Result, ()> { match subs.get_without_compacting(var).content { Content::Structure(flat_type) => match flat_type { From 242679b841b8bd21fe483d46bb1d34d51f85d996 Mon Sep 17 00:00:00 2001 From: Folkert Date: Fri, 13 Mar 2020 16:14:37 +0100 Subject: [PATCH 086/428] fix clippy complaints --- compiler/gen/src/crane/build.rs | 1 + compiler/mono/src/layout.rs | 51 +++++++++++++++++++++------------ 2 files changed, 33 insertions(+), 19 deletions(-) diff --git a/compiler/gen/src/crane/build.rs b/compiler/gen/src/crane/build.rs index c2a03249aa..090f46b780 100644 --- a/compiler/gen/src/crane/build.rs +++ b/compiler/gen/src/crane/build.rs @@ -524,6 +524,7 @@ fn build_arg<'a, B: Backend>( } #[inline(always)] +#[allow(clippy::cognitive_complexity)] fn call_by_name<'a, B: Backend>( env: &Env<'a>, symbol: Symbol, diff --git a/compiler/mono/src/layout.rs b/compiler/mono/src/layout.rs index a177d04999..3a12261bbc 100644 --- a/compiler/mono/src/layout.rs +++ b/compiler/mono/src/layout.rs @@ -121,6 +121,7 @@ impl<'a> Builtin<'a> { } } +#[allow(clippy::cognitive_complexity)] fn layout_from_flat_type<'a>( arena: &'a Bump, flat_type: FlatType, @@ -195,14 +196,7 @@ fn layout_from_flat_type<'a>( )) } Record(fields, ext_var) => { - debug_assert!({ - // the ext_var is empty - let mut ext_fields = MutMap::default(); - match roc_types::pretty_print::chase_ext_record(subs, ext_var, &mut ext_fields) { - Ok(()) | Err((_, Content::FlexVar(_))) => ext_fields.is_empty(), - Err((_, content)) => panic!("invalid content in ext_var: {:?}", content), - } - }); + debug_assert!(ext_var_is_empty_record(subs, ext_var)); let ext_content = subs.get_without_compacting(ext_var).content; let ext_layout = match Layout::from_content(arena, ext_content, subs, pointer_size) { Ok(layout) => layout, @@ -247,17 +241,7 @@ fn layout_from_flat_type<'a>( Ok(Layout::Struct(field_layouts.into_bump_slice())) } TagUnion(tags, ext_var) => { - debug_assert!({ - // the ext_var is empty - let mut ext_fields = std::vec::Vec::new(); - match roc_types::pretty_print::chase_ext_tag_union(subs, ext_var, &mut ext_fields) { - Ok(()) | Err((_, Content::FlexVar(_))) => { - println!("Tags: {:?}, ext_tags: {:?}", &tags, ext_fields); - ext_fields.is_empty() - } - Err(content) => panic!("invalid content in ext_var: {:?}", content), - } - }); + debug_assert!(ext_var_is_empty_tag_union(subs, ext_var)); match tags.len() { 0 => { @@ -345,6 +329,35 @@ fn layout_from_flat_type<'a>( } } +fn ext_var_is_empty_tag_union(subs: &Subs, ext_var: Variable) -> bool { + // the ext_var is empty + let mut ext_fields = std::vec::Vec::new(); + match roc_types::pretty_print::chase_ext_tag_union(subs, ext_var, &mut ext_fields) { + Ok(()) | Err((_, Content::FlexVar(_))) => { + if !ext_fields.is_empty() { + println!("ext_tags: {:?}", ext_fields); + } + ext_fields.is_empty() + } + Err(content) => panic!("invalid content in ext_var: {:?}", content), + } +} + +fn ext_var_is_empty_record(subs: &Subs, ext_var: Variable) -> bool { + // the ext_var is empty + let mut ext_fields = MutMap::default(); + match roc_types::pretty_print::chase_ext_record(subs, ext_var, &mut ext_fields) { + Ok(()) | Err((_, Content::FlexVar(_))) => { + if !ext_fields.is_empty() { + println!("ext_fields: {:?}", ext_fields); + } + + ext_fields.is_empty() + } + Err((_, content)) => panic!("invalid content in ext_var: {:?}", content), + } +} + fn layout_from_num_content<'a>(content: Content) -> Result, ()> { use roc_types::subs::Content::*; use roc_types::subs::FlatType::*; From f89445915984b433066b6634f7060d6882386eb5 Mon Sep 17 00:00:00 2001 From: Folkert Date: Fri, 13 Mar 2020 16:38:33 +0100 Subject: [PATCH 087/428] fix comment --- compiler/builtins/src/unique.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/builtins/src/unique.rs b/compiler/builtins/src/unique.rs index 224de39d2d..510ceaf633 100644 --- a/compiler/builtins/src/unique.rs +++ b/compiler/builtins/src/unique.rs @@ -394,13 +394,13 @@ pub fn types() -> MutMap { // Bool module - // isEq or (==) : Attr u1 Bool, Attr u2 Bool -> Attr u3 Bool + // isEq or (==) : *, * -> Attr u Bool add_type( Symbol::BOOL_EQ, unique_function(vec![flex(TVAR1), flex(TVAR2)], bool_type(UVAR3)), ); - // isNeq or (!=) : Attr u1 Bool, Attr u2 Bool -> Attr u3 Bool + // isNeq or (!=) : *, * -> Attr u Bool add_type( Symbol::BOOL_NEQ, unique_function(vec![flex(TVAR1), flex(TVAR2)], bool_type(UVAR3)), From 05a3e8c3d84e9de499a3ceaa3089d3115921f8bb Mon Sep 17 00:00:00 2001 From: Folkert Date: Fri, 13 Mar 2020 17:01:18 +0100 Subject: [PATCH 088/428] equality for Byte and Bool --- compiler/gen/src/crane/build.rs | 2 +- compiler/gen/src/llvm/build.rs | 28 +++++++++++++++++++++++++++- compiler/gen/tests/test_gen.rs | 21 +++++++++++++++++++++ compiler/module/src/symbol.rs | 4 +++- compiler/mono/src/expr.rs | 10 +++++++--- 5 files changed, 59 insertions(+), 6 deletions(-) diff --git a/compiler/gen/src/crane/build.rs b/compiler/gen/src/crane/build.rs index 090f46b780..b1112b1d4e 100644 --- a/compiler/gen/src/crane/build.rs +++ b/compiler/gen/src/crane/build.rs @@ -576,7 +576,7 @@ fn call_by_name<'a, B: Backend>( builder.ins().ineg(num) } - Symbol::INT_EQ => { + Symbol::INT_EQ_I64 | Symbol::INT_EQ_I8 | Symbol::INT_EQ_I1 => { debug_assert!(args.len() == 2); let a = build_arg(&args[0], env, scope, module, builder, procs); let b = build_arg(&args[1], env, scope, module, builder, procs); diff --git a/compiler/gen/src/llvm/build.rs b/compiler/gen/src/llvm/build.rs index b75379c804..600672945a 100644 --- a/compiler/gen/src/llvm/build.rs +++ b/compiler/gen/src/llvm/build.rs @@ -46,6 +46,7 @@ pub fn build_expr<'a, 'ctx, 'env>( Int(num) => env.context.i64_type().const_int(*num as u64, true).into(), Float(num) => env.context.f64_type().const_float(*num).into(), Bool(b) => env.context.bool_type().const_int(*b as u64, false).into(), + Byte(b) => env.context.i8_type().const_int(*b as u64, false).into(), Cond { cond, pass, @@ -513,6 +514,7 @@ pub fn verify_fn(fn_val: FunctionValue<'_>) { } #[inline(always)] +#[allow(clippy::cognitive_complexity)] fn call_with_args<'a, 'ctx, 'env>( symbol: Symbol, args: &[BasicValueEnum<'ctx>], @@ -583,7 +585,7 @@ fn call_with_args<'a, 'ctx, 'env>( BasicValueEnum::IntValue(int_val) } - Symbol::INT_EQ => { + Symbol::INT_EQ_I64 => { debug_assert!(args.len() == 2); let int_val = env.builder.build_int_compare( @@ -595,6 +597,30 @@ fn call_with_args<'a, 'ctx, 'env>( BasicValueEnum::IntValue(int_val) } + Symbol::INT_EQ_I1 => { + debug_assert!(args.len() == 2); + + let int_val = env.builder.build_int_compare( + IntPredicate::EQ, + args[0].into_int_value(), + args[1].into_int_value(), + "cmp_i1", + ); + + BasicValueEnum::IntValue(int_val) + } + Symbol::INT_EQ_I8 => { + debug_assert!(args.len() == 2); + + let int_val = env.builder.build_int_compare( + IntPredicate::EQ, + args[0].into_int_value(), + args[1].into_int_value(), + "cmp_i8", + ); + + BasicValueEnum::IntValue(int_val) + } Symbol::FLOAT_EQ => { debug_assert!(args.len() == 2); diff --git a/compiler/gen/tests/test_gen.rs b/compiler/gen/tests/test_gen.rs index 918927d3bc..f1c4a69c13 100644 --- a/compiler/gen/tests/test_gen.rs +++ b/compiler/gen/tests/test_gen.rs @@ -970,6 +970,27 @@ mod test_gen { ); } + #[test] + fn basic_enum() { + assert_evals_to!( + indoc!( + r#" + Fruit : [ Apple, Orange, Banana ] + + apple : Fruit + apple = Apple + + orange : Fruit + orange = Orange + + apple == orange + "# + ), + false, + bool + ); + } + // #[test] // fn basic_record() { // assert_evals_to!( diff --git a/compiler/module/src/symbol.rs b/compiler/module/src/symbol.rs index 6ba11959e0..fe26b534fd 100644 --- a/compiler/module/src/symbol.rs +++ b/compiler/module/src/symbol.rs @@ -591,7 +591,9 @@ define_builtins! { 6 INT_LOWEST: "lowest" 7 INT_ADD: "#add" 8 INT_SUB: "#sub" - 9 INT_EQ: "#eq" + 9 INT_EQ_I64: "#eqi64" // Equality on 64-bit integers, the standard in Roc + 10 INT_EQ_I1: "#eqi1" // Equality on boolean (theoretically i1) values + 11 INT_EQ_I8: "#eqi8" // Equality on byte (theoretically i8) values } 3 FLOAT: "Float" => { 0 FLOAT_FLOAT: "Float" imported // the Float.Float type alias diff --git a/compiler/mono/src/expr.rs b/compiler/mono/src/expr.rs index aec5c87247..6764b6f988 100644 --- a/compiler/mono/src/expr.rs +++ b/compiler/mono/src/expr.rs @@ -497,9 +497,11 @@ fn from_can<'a>( match Layout::from_var(env.arena, loc_args[0].0, env.subs, env.pointer_size) { Ok(Layout::Builtin(builtin)) => match builtin { - Builtin::Int64 => Symbol::INT_EQ, + Builtin::Int64 => Symbol::INT_EQ_I64, Builtin::Float64 => Symbol::FLOAT_EQ, - _ => panic!("Equality not unimplemented for {:?}", builtin), + Builtin::Bool(_, _) => Symbol::INT_EQ_I1, + Builtin::Byte(_) => Symbol::INT_EQ_I8, + _ => panic!("Equality not implemented for {:?}", builtin), }, Ok(complex) => panic!( "TODO support equality on complex layouts like {:?}", @@ -831,7 +833,9 @@ fn from_can_when<'a>( let (fn_symbol, builtin, cond_rhs_expr) = match to_int_or_float(env.subs, *var) { - IntOrFloat::IntType => (Symbol::INT_EQ, Builtin::Int64, Expr::Int(*num)), + IntOrFloat::IntType => { + (Symbol::INT_EQ_I64, Builtin::Int64, Expr::Int(*num)) + } IntOrFloat::FloatType => { (Symbol::FLOAT_EQ, Builtin::Float64, Expr::Float(*num as f64)) } From df54bb5aeffef6644ff95ca9e2ccd82c3b1324db Mon Sep 17 00:00:00 2001 From: Folkert Date: Fri, 13 Mar 2020 20:49:16 +0100 Subject: [PATCH 089/428] fix incorrect signature for isEq in uniq builtins --- compiler/builtins/src/unique.rs | 8 ++++---- compiler/solve/tests/test_uniq_solve.rs | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/compiler/builtins/src/unique.rs b/compiler/builtins/src/unique.rs index 510ceaf633..0df4f849fd 100644 --- a/compiler/builtins/src/unique.rs +++ b/compiler/builtins/src/unique.rs @@ -394,16 +394,16 @@ pub fn types() -> MutMap { // Bool module - // isEq or (==) : *, * -> Attr u Bool + // isEq or (==) : a, a -> Attr u Bool add_type( Symbol::BOOL_EQ, - unique_function(vec![flex(TVAR1), flex(TVAR2)], bool_type(UVAR3)), + unique_function(vec![flex(TVAR1), flex(TVAR1)], bool_type(UVAR3)), ); - // isNeq or (!=) : *, * -> Attr u Bool + // isNeq or (!=) : a, a -> Attr u Bool add_type( Symbol::BOOL_NEQ, - unique_function(vec![flex(TVAR1), flex(TVAR2)], bool_type(UVAR3)), + unique_function(vec![flex(TVAR1), flex(TVAR1)], bool_type(UVAR3)), ); // and or (&&) : Attr u1 Bool, Attr u2 Bool -> Attr u3 Bool diff --git a/compiler/solve/tests/test_uniq_solve.rs b/compiler/solve/tests/test_uniq_solve.rs index 2f26288dae..69e0e553a3 100644 --- a/compiler/solve/tests/test_uniq_solve.rs +++ b/compiler/solve/tests/test_uniq_solve.rs @@ -2358,7 +2358,7 @@ mod test_uniq_solve { \a, b -> a == b "# ), - "Attr * (*, * -> Attr * Bool)", + "Attr * (a, a -> Attr * Bool)", ); } From 129be8623359b144fe2bf7397c24628bc35c2f0b Mon Sep 17 00:00:00 2001 From: Folkert Date: Fri, 13 Mar 2020 21:24:42 +0100 Subject: [PATCH 090/428] clean up when to Cond conversion --- compiler/mono/src/expr.rs | 106 ++++++++++++-------------------------- 1 file changed, 34 insertions(+), 72 deletions(-) diff --git a/compiler/mono/src/expr.rs b/compiler/mono/src/expr.rs index 6764b6f988..28655d4a07 100644 --- a/compiler/mono/src/expr.rs +++ b/compiler/mono/src/expr.rs @@ -827,6 +827,11 @@ fn from_can_when<'a>( let (loc_when_pat1, loc_then) = iter.next().unwrap(); let (loc_when_pat2, loc_else) = iter.next().unwrap(); + let cond_layout = Layout::Builtin(Builtin::Bool( + TagName::Global("False".into()), + TagName::Global("True".into()), + )); + match (&loc_when_pat1.value, &loc_when_pat2.value) { (NumLiteral(var, num), Underscore) => { let cond_lhs = from_can(env, loc_cond.value, procs, None); @@ -858,10 +863,34 @@ fn from_can_when<'a>( }); Expr::Cond { - cond_layout: Layout::Builtin(Builtin::Bool( - TagName::Global("False".into()), - TagName::Global("True".into()), - )), + cond_layout, + cond, + pass, + fail, + ret_layout, + } + } + (IntLiteral(int), Underscore) => { + let cond_lhs = from_can(env, loc_cond.value, procs, None); + let cond_rhs = Expr::Int(*int); + + let cond = arena.alloc(Expr::CallByName( + Symbol::INT_EQ_I64, + arena.alloc([ + (cond_lhs, Layout::Builtin(Builtin::Int64)), + (cond_rhs, Layout::Builtin(Builtin::Int64)), + ]), + )); + + let pass = arena.alloc(from_can(env, loc_then.value, procs, None)); + let fail = arena.alloc(from_can(env, loc_else.value, procs, None)); + let ret_layout = Layout::from_var(arena, expr_var, env.subs, env.pointer_size) + .unwrap_or_else(|err| { + panic!("TODO turn this into a RuntimeError {:?}", err) + }); + + Expr::Cond { + cond_layout, cond, pass, fail, @@ -888,80 +917,13 @@ fn from_can_when<'a>( }); Expr::Cond { - cond_layout: Layout::Builtin(Builtin::Bool( - TagName::Global("False".into()), - TagName::Global("True".into()), - )), + cond_layout, cond, pass, fail, ret_layout, } } - /* - (NumLiteral(var, num), NumLiteral(_, _)) | (NumLiteral(var, num), Underscore) => { - let cond_lhs = arena.alloc(from_can(env, loc_cond.value, procs, None)); - let (builtin, cond_rhs_expr) = match to_int_or_float(env.subs, *var) { - IntOrFloat::IntType => (Builtin::Int64, Expr::Int(*num)), - IntOrFloat::FloatType => (Builtin::Float64, Expr::Float(*num as f64)), - }; - - let cond_rhs = arena.alloc(cond_rhs_expr); - let pass = arena.alloc(from_can(env, loc_then.value, procs, None)); - let fail = arena.alloc(from_can(env, loc_else.value, procs, None)); - let ret_layout = Layout::from_var(arena, expr_var, env.subs, env.pointer_size) - .unwrap_or_else(|err| { - panic!("TODO turn this into a RuntimeError {:?}", err) - }); - - Expr::Cond { - cond_layout: Layout::Builtin(builtin), - cond_lhs, - cond_rhs, - pass, - fail, - ret_layout, - } - } - (IntLiteral(int), IntLiteral(_)) | (IntLiteral(int), Underscore) => { - let cond_lhs = arena.alloc(from_can(env, loc_cond.value, procs, None)); - let cond_rhs = arena.alloc(Expr::Int(*int)); - let pass = arena.alloc(from_can(env, loc_then.value, procs, None)); - let fail = arena.alloc(from_can(env, loc_else.value, procs, None)); - let ret_layout = Layout::from_var(arena, expr_var, env.subs, env.pointer_size) - .unwrap_or_else(|err| { - panic!("TODO turn this into a RuntimeError {:?}", err) - }); - - Expr::Cond { - cond_layout: Layout::Builtin(Builtin::Int64), - cond_lhs, - cond_rhs, - pass, - fail, - ret_layout, - } - } - (FloatLiteral(float), FloatLiteral(_)) | (FloatLiteral(float), Underscore) => { - let cond_lhs = arena.alloc(from_can(env, loc_cond.value, procs, None)); - let cond_rhs = arena.alloc(Expr::Float(*float)); - let pass = arena.alloc(from_can(env, loc_then.value, procs, None)); - let fail = arena.alloc(from_can(env, loc_else.value, procs, None)); - let ret_layout = Layout::from_var(arena, expr_var, env.subs, env.pointer_size) - .unwrap_or_else(|err| { - panic!("TODO turn this into a RuntimeError {:?}", err) - }); - - Expr::Cond { - cond_layout: Layout::Builtin(Builtin::Float64), - cond_lhs, - cond_rhs, - pass, - fail, - ret_layout, - } - } - */ _ => { panic!("TODO handle more conds"); } From 2e697ee8f9536ed157699aaac2143a72c89128f5 Mon Sep 17 00:00:00 2001 From: Folkert Date: Fri, 13 Mar 2020 23:28:36 +0100 Subject: [PATCH 091/428] add whole_var to tag union/record the existing var is for the extension (ext_var). during mono we need the whole type to look up the layout, so store that as well --- compiler/can/src/def.rs | 4 +-- compiler/can/src/pattern.rs | 62 +++++++++++++++++++++++++++---------- 2 files changed, 47 insertions(+), 19 deletions(-) diff --git a/compiler/can/src/def.rs b/compiler/can/src/def.rs index ae462339cc..374d882f18 100644 --- a/compiler/can/src/def.rs +++ b/compiler/can/src/def.rs @@ -662,13 +662,13 @@ fn pattern_to_vars_by_symbol( vars_by_symbol.insert(symbol.clone(), expr_var); } - AppliedTag(_, _, arguments) => { + AppliedTag { arguments, .. } => { for (var, nested) in arguments { pattern_to_vars_by_symbol(vars_by_symbol, &nested.value, *var); } } - RecordDestructure(_, destructs) => { + RecordDestructure { destructs, .. } => { for destruct in destructs { vars_by_symbol.insert(destruct.value.symbol.clone(), destruct.value.var); } diff --git a/compiler/can/src/pattern.rs b/compiler/can/src/pattern.rs index 34e3af98fe..5c456ff32c 100644 --- a/compiler/can/src/pattern.rs +++ b/compiler/can/src/pattern.rs @@ -14,12 +14,21 @@ use roc_types::subs::{VarStore, Variable}; #[derive(Clone, Debug, PartialEq)] pub enum Pattern { Identifier(Symbol), - AppliedTag(Variable, TagName, Vec<(Variable, Located)>), + AppliedTag { + whole_var: Variable, + ext_var: Variable, + tag_name: TagName, + arguments: Vec<(Variable, Located)>, + }, + RecordDestructure { + whole_var: Variable, + ext_var: Variable, + destructs: Vec>, + }, IntLiteral(i64), NumLiteral(Variable, i64), FloatLiteral(f64), StrLiteral(Box), - RecordDestructure(Variable, Vec>), Underscore, // Runtime Exceptions @@ -51,12 +60,12 @@ pub fn symbols_from_pattern_help(pattern: &Pattern, symbols: &mut Vec) { symbols.push(symbol.clone()); } - AppliedTag(_, _, arguments) => { + AppliedTag { arguments, .. } => { for (_, nested) in arguments { symbols_from_pattern_help(&nested.value, symbols); } } - RecordDestructure(_, destructs) => { + RecordDestructure { destructs, .. } => { for destruct in destructs { symbols.push(destruct.value.symbol.clone()); } @@ -103,17 +112,23 @@ pub fn canonicalize_pattern<'a>( }, GlobalTag(name) => { // Canonicalize the tag's name. - Pattern::AppliedTag(var_store.fresh(), TagName::Global((*name).into()), vec![]) + Pattern::AppliedTag { + whole_var: var_store.fresh(), + ext_var: var_store.fresh(), + tag_name: TagName::Global((*name).into()), + arguments: vec![], + } } PrivateTag(name) => { let ident_id = env.ident_ids.get_or_insert(&(*name).into()); // Canonicalize the tag's name. - Pattern::AppliedTag( - var_store.fresh(), - TagName::Private(Symbol::new(env.home, ident_id)), - vec![], - ) + Pattern::AppliedTag { + whole_var: var_store.fresh(), + ext_var: var_store.fresh(), + tag_name: TagName::Private(Symbol::new(env.home, ident_id)), + arguments: vec![], + } } Apply(tag, patterns) => { let tag_name = match tag.value { @@ -141,7 +156,12 @@ pub fn canonicalize_pattern<'a>( )); } - Pattern::AppliedTag(var_store.fresh(), tag_name, can_patterns) + Pattern::AppliedTag { + whole_var: var_store.fresh(), + ext_var: var_store.fresh(), + tag_name, + arguments: can_patterns, + } } FloatLiteral(ref string) => match pattern_type { @@ -208,7 +228,8 @@ pub fn canonicalize_pattern<'a>( } RecordDestructure(patterns) => { let ext_var = var_store.fresh(); - let mut fields = Vec::with_capacity(patterns.len()); + let whole_var = var_store.fresh(); + let mut destructs = Vec::with_capacity(patterns.len()); let mut opt_erroneous = None; for loc_pattern in *patterns { @@ -221,7 +242,7 @@ pub fn canonicalize_pattern<'a>( region, ) { Ok(symbol) => { - fields.push(Located { + destructs.push(Located { region: loc_pattern.region, value: RecordDestruct { var: var_store.fresh(), @@ -262,7 +283,7 @@ pub fn canonicalize_pattern<'a>( loc_guard.region, ); - fields.push(Located { + destructs.push(Located { region: loc_pattern.region, value: RecordDestruct { var: var_store.fresh(), @@ -292,7 +313,11 @@ pub fn canonicalize_pattern<'a>( // If we encountered an erroneous pattern (e.g. one with shadowing), // use the resulting RuntimeError. Otherwise, return a successful record destructure. - opt_erroneous.unwrap_or_else(|| Pattern::RecordDestructure(ext_var, fields)) + opt_erroneous.unwrap_or_else(|| Pattern::RecordDestructure { + whole_var, + ext_var, + destructs, + }) } RecordField(_name, _loc_pattern) => { unreachable!("should have been handled in RecordDestructure"); @@ -345,12 +370,15 @@ fn add_bindings_from_patterns( Identifier(symbol) => { answer.push((*symbol, *region)); } - AppliedTag(_, _, loc_args) => { + AppliedTag { + arguments: loc_args, + .. + } => { for (_, loc_arg) in loc_args { add_bindings_from_patterns(&loc_arg.region, &loc_arg.value, scope, answer); } } - RecordDestructure(_, destructs) => { + RecordDestructure { destructs, .. } => { for Located { region, value: RecordDestruct { symbol, .. }, From 38c93c001f1a946c72df7bedf7e7b95617f3c4d8 Mon Sep 17 00:00:00 2001 From: Folkert Date: Fri, 13 Mar 2020 23:34:29 +0100 Subject: [PATCH 092/428] constrain whole_var --- compiler/constrain/src/pattern.rs | 60 ++++++++++++++++++++++++------- compiler/constrain/src/uniq.rs | 55 +++++++++++++++++++++------- 2 files changed, 89 insertions(+), 26 deletions(-) diff --git a/compiler/constrain/src/pattern.rs b/compiler/constrain/src/pattern.rs index 5702a22d81..cf27ad9f09 100644 --- a/compiler/constrain/src/pattern.rs +++ b/compiler/constrain/src/pattern.rs @@ -58,7 +58,7 @@ fn headers_from_annotation_help( | FloatLiteral(_) | StrLiteral(_) => true, - RecordDestructure(_, destructs) => match annotation.value.shallow_dealias() { + RecordDestructure { destructs, .. } => match annotation.value.shallow_dealias() { Type::Record(fields, _) => { for destruct in destructs { // NOTE ignores the .guard field. @@ -77,7 +77,11 @@ fn headers_from_annotation_help( _ => false, }, - AppliedTag(_, tag_name, arguments) => match annotation.value.shallow_dealias() { + AppliedTag { + tag_name, + arguments, + .. + } => match annotation.value.shallow_dealias() { Type::TagUnion(tags, _) => { if let Some((_, arg_types)) = tags.iter().find(|(name, _)| name == tag_name) { if !arguments.len() == arg_types.len() { @@ -164,7 +168,12 @@ pub fn constrain_pattern( )); } - RecordDestructure(ext_var, patterns) => { + RecordDestructure { + whole_var, + ext_var, + destructs, + } => { + state.vars.push(*whole_var); state.vars.push(*ext_var); let ext_type = Type::Variable(*ext_var); @@ -179,7 +188,7 @@ pub fn constrain_pattern( guard, }, .. - } in patterns + } in destructs { let pat_type = Type::Variable(*var); let expected = PExpected::NoExpectation(pat_type.clone()); @@ -207,14 +216,31 @@ pub fn constrain_pattern( } let record_type = Type::Record(field_types, Box::new(ext_type)); - let record_con = - Constraint::Pattern(region, PatternCategory::Record, record_type, expected); + let whole_con = Constraint::Eq( + Type::Variable(*whole_var), + Expected::NoExpectation(record_type), + region, + ); + + let record_con = Constraint::Pattern( + region, + PatternCategory::Record, + Type::Variable(*whole_var), + expected, + ); + + state.constraints.push(whole_con); state.constraints.push(record_con); } - AppliedTag(ext_var, tag_name, patterns) => { - let mut argument_types = Vec::with_capacity(patterns.len()); - for (pattern_var, loc_pattern) in patterns { + AppliedTag { + whole_var, + ext_var, + tag_name, + arguments, + } => { + let mut argument_types = Vec::with_capacity(arguments.len()); + for (pattern_var, loc_pattern) in arguments { state.vars.push(*pattern_var); let pattern_type = Type::Variable(*pattern_var); @@ -224,17 +250,25 @@ pub fn constrain_pattern( constrain_pattern(&loc_pattern.value, loc_pattern.region, expected, state); } + let whole_con = Constraint::Eq( + Type::Variable(*whole_var), + Expected::NoExpectation(Type::TagUnion( + vec![(tag_name.clone(), argument_types)], + Box::new(Type::Variable(*ext_var)), + )), + region, + ); + let tag_con = Constraint::Pattern( region, PatternCategory::Ctor(tag_name.clone()), - Type::TagUnion( - vec![(tag_name.clone(), argument_types)], - Box::new(Type::Variable(*ext_var)), - ), + Type::Variable(*whole_var), expected, ); + state.vars.push(*whole_var); state.vars.push(*ext_var); + state.constraints.push(whole_con); state.constraints.push(tag_con); } Shadowed(_, _) => { diff --git a/compiler/constrain/src/uniq.rs b/compiler/constrain/src/uniq.rs index b27216713c..4bc20142d3 100644 --- a/compiler/constrain/src/uniq.rs +++ b/compiler/constrain/src/uniq.rs @@ -143,6 +143,8 @@ fn constrain_pattern( use roc_can::pattern::Pattern::*; use roc_types::types::PatternCategory; + let region = pattern.region; + match &pattern.value { Identifier(symbol) => { state.headers.insert( @@ -190,10 +192,15 @@ fn constrain_pattern( )); } - RecordDestructure(ext_var, patterns) => { + RecordDestructure { + whole_var, + ext_var, + destructs, + } => { // TODO if a subpattern doesn't bind any identifiers, it doesn't count for uniqueness - let mut pattern_uniq_vars = Vec::with_capacity(patterns.len()); + let mut pattern_uniq_vars = Vec::with_capacity(destructs.len()); + state.vars.push(*whole_var); state.vars.push(*ext_var); let ext_type = Type::Variable(*ext_var); @@ -207,7 +214,7 @@ fn constrain_pattern( guard, }, .. - } in patterns + } in destructs { let pat_uniq_var = var_store.fresh(); pattern_uniq_vars.push(pat_uniq_var); @@ -251,22 +258,35 @@ fn constrain_pattern( record_uniq_type, Type::Record(field_types, Box::new(ext_type)), ); + + let whole_con = Constraint::Eq( + Type::Variable(*whole_var), + Expected::NoExpectation(record_type), + region, + ); + let record_con = Constraint::Pattern( - pattern.region, + region, PatternCategory::Record, - record_type, + Type::Variable(*whole_var), expected, ); + state.constraints.push(whole_con); state.constraints.push(record_con); } - AppliedTag(ext_var, symbol, patterns) => { + AppliedTag { + whole_var, + ext_var, + tag_name, + arguments, + } => { // TODO if a subpattern doesn't bind any identifiers, it doesn't count for uniqueness - let mut argument_types = Vec::with_capacity(patterns.len()); - let mut pattern_uniq_vars = Vec::with_capacity(patterns.len()); + let mut argument_types = Vec::with_capacity(arguments.len()); + let mut pattern_uniq_vars = Vec::with_capacity(arguments.len()); - for (pattern_var, loc_pattern) in patterns { + for (pattern_var, loc_pattern) in arguments { state.vars.push(*pattern_var); let pat_uniq_var = var_store.fresh(); @@ -292,19 +312,28 @@ fn constrain_pattern( let union_type = attr_type( tag_union_uniq_type, Type::TagUnion( - vec![(symbol.clone(), argument_types)], + vec![(tag_name.clone(), argument_types)], Box::new(Type::Variable(*ext_var)), ), ); + let whole_con = Constraint::Eq( + Type::Variable(*whole_var), + Expected::NoExpectation(union_type), + region, + ); + let tag_con = Constraint::Pattern( - pattern.region, - PatternCategory::Ctor(symbol.clone()), - union_type, + region, + PatternCategory::Ctor(tag_name.clone()), + Type::Variable(*whole_var), expected, ); + state.vars.push(*whole_var); state.vars.push(*ext_var); + + state.constraints.push(whole_con); state.constraints.push(tag_con); } From c4c40ec87862c3beef2180bd012fe333048e0eb5 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sat, 14 Mar 2020 00:07:32 +0100 Subject: [PATCH 093/428] fix cond generation With Enum patterns (u8 in size), the size of the condition and the branches could disagree. (the branches would be i64). we now cast the branches to the smaller integer type based on the layout --- compiler/gen/src/llvm/build.rs | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/compiler/gen/src/llvm/build.rs b/compiler/gen/src/llvm/build.rs index 600672945a..d674c36b23 100644 --- a/compiler/gen/src/llvm/build.rs +++ b/compiler/gen/src/llvm/build.rs @@ -295,12 +295,15 @@ fn build_switch<'a, 'ctx, 'env>( switch_args: SwitchArgs<'a, 'ctx>, procs: &Procs<'a>, ) -> BasicValueEnum<'ctx> { + use roc_mono::layout::Builtin; + let arena = env.arena; let builder = env.builder; let context = env.context; let SwitchArgs { branches, cond_expr, + cond_layout, default_branch, ret_type, .. @@ -316,7 +319,24 @@ fn build_switch<'a, 'ctx, 'env>( let mut cases = Vec::with_capacity_in(branches.len(), arena); for (int, _) in branches.iter() { - let int_val = context.i64_type().const_int(*int as u64, false); + // Switch constants must all be same type as switch value! + // e.g. this is incorrect, and will trigger a LLVM warning: + // + // switch i8 %apple1, label %default [ + // i64 2, label %branch2 + // i64 0, label %branch0 + // i64 1, label %branch1 + // ] + // + // they either need to all be i8, or i64 + let int_val = match cond_layout { + Layout::Builtin(Builtin::Int64) => context.i64_type().const_int(*int as u64, false), + Layout::Builtin(Builtin::Bool(_, _)) => { + context.bool_type().const_int(*int as u64, false) + } + Layout::Builtin(Builtin::Byte(_)) => context.i8_type().const_int(*int as u64, false), + _ => panic!("Can't cast to cond_layout = {:?}", cond_layout), + }; let block = context.append_basic_block(parent, format!("branch{}", int).as_str()); cases.push((int_val, block)); From 1570e9b96e2be73d84b7ac09dce8d98b07efe366 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sat, 14 Mar 2020 00:10:11 +0100 Subject: [PATCH 094/428] make mono patterns nicer --- compiler/gen/tests/test_gen.rs | 22 ++++ compiler/mono/src/expr.rs | 206 +++++++++++++++++++++------------ 2 files changed, 155 insertions(+), 73 deletions(-) diff --git a/compiler/gen/tests/test_gen.rs b/compiler/gen/tests/test_gen.rs index f1c4a69c13..744bfdd189 100644 --- a/compiler/gen/tests/test_gen.rs +++ b/compiler/gen/tests/test_gen.rs @@ -991,6 +991,28 @@ mod test_gen { ); } + #[test] + fn when_on_enum() { + assert_evals_to!( + indoc!( + r#" + Fruit : [ Apple, Orange, Banana ] + + apple : Fruit + apple = Apple + + when apple is + Apple -> 1 + Banana -> 2 + Orange -> 3 + _ -> 4 + "# + ), + 1, + i64 + ); + } + // #[test] // fn basic_record() { // assert_evals_to!( diff --git a/compiler/mono/src/expr.rs b/compiler/mono/src/expr.rs index 28655d4a07..348e2896b1 100644 --- a/compiler/mono/src/expr.rs +++ b/compiler/mono/src/expr.rs @@ -2,11 +2,10 @@ use crate::layout::{Builtin, Layout}; use bumpalo::collections::Vec; use bumpalo::Bump; use roc_can; -use roc_can::pattern::Pattern; use roc_collections::all::{MutMap, MutSet}; -use roc_module::ident::{Lowercase, TagName}; +use roc_module::ident::{Ident, Lowercase, TagName}; use roc_module::symbol::{IdentIds, ModuleId, Symbol}; -use roc_region::all::Located; +use roc_region::all::{Located, Region}; use roc_types::subs::{Content, ContentHash, FlatType, Subs, Variable}; #[derive(Clone, Debug, PartialEq, Default)] @@ -335,7 +334,7 @@ fn pattern_to_when<'a>( (env.fresh_symbol(), body) } - AppliedTag(_, _, _) | RecordDestructure(_, _) | Shadowed(_, _) | UnsupportedPattern(_) => { + AppliedTag {..} | RecordDestructure {..} | Shadowed(_, _) | UnsupportedPattern(_) => { let symbol = env.fresh_symbol(); let wrapped_body = When { @@ -405,9 +404,10 @@ fn from_can<'a>( } // If it wasn't specifically an Identifier & Closure, proceed as normal. + let mono_pattern = from_can_pattern(env, loc_pattern.value); store_pattern( env, - loc_pattern.value, + mono_pattern, loc_expr.value, def.expr_var, procs, @@ -743,7 +743,7 @@ fn store_pattern<'a>( procs: &mut Procs<'a>, stored: &mut Vec<'a, (Symbol, Layout<'a>, Expr<'a>)>, ) { - use roc_can::pattern::Pattern::*; + use Pattern::*; let layout = match Layout::from_var(env.arena, var, env.subs, env.pointer_size) { Ok(layout) => layout, @@ -792,7 +792,7 @@ fn from_can_when<'a>( )>, procs: &mut Procs<'a>, ) -> Expr<'a> { - use roc_can::pattern::Pattern::*; + use Pattern::*; match branches.len() { 0 => { @@ -807,9 +807,10 @@ fn from_can_when<'a>( let mut stored = Vec::with_capacity_in(1, arena); let (loc_when_pattern, loc_branch) = branches.into_iter().next().unwrap(); + let mono_pattern = from_can_pattern(env, loc_when_pattern.value); store_pattern( env, - loc_when_pattern.value, + mono_pattern, loc_cond.value, cond_var, procs, @@ -824,52 +825,18 @@ fn from_can_when<'a>( // A when-expression with exactly 2 branches compiles to a Cond. let arena = env.arena; let mut iter = branches.into_iter(); - let (loc_when_pat1, loc_then) = iter.next().unwrap(); - let (loc_when_pat2, loc_else) = iter.next().unwrap(); + let (can_loc_when_pat1, loc_then) = iter.next().unwrap(); + let (can_loc_when_pat2, loc_else) = iter.next().unwrap(); + + let when_pat1 = from_can_pattern(env, can_loc_when_pat1.value); + let when_pat2 = from_can_pattern(env, can_loc_when_pat2.value); let cond_layout = Layout::Builtin(Builtin::Bool( TagName::Global("False".into()), TagName::Global("True".into()), )); - match (&loc_when_pat1.value, &loc_when_pat2.value) { - (NumLiteral(var, num), Underscore) => { - let cond_lhs = from_can(env, loc_cond.value, procs, None); - - let (fn_symbol, builtin, cond_rhs_expr) = match to_int_or_float(env.subs, *var) - { - IntOrFloat::IntType => { - (Symbol::INT_EQ_I64, Builtin::Int64, Expr::Int(*num)) - } - IntOrFloat::FloatType => { - (Symbol::FLOAT_EQ, Builtin::Float64, Expr::Float(*num as f64)) - } - }; - let cond_rhs = cond_rhs_expr; - - let cond = arena.alloc(Expr::CallByName( - fn_symbol, - arena.alloc([ - (cond_lhs, Layout::Builtin(builtin.clone())), - (cond_rhs, Layout::Builtin(builtin)), - ]), - )); - - let pass = arena.alloc(from_can(env, loc_then.value, procs, None)); - let fail = arena.alloc(from_can(env, loc_else.value, procs, None)); - let ret_layout = Layout::from_var(arena, expr_var, env.subs, env.pointer_size) - .unwrap_or_else(|err| { - panic!("TODO turn this into a RuntimeError {:?}", err) - }); - - Expr::Cond { - cond_layout, - cond, - pass, - fail, - ret_layout, - } - } + match (&when_pat1, &when_pat2) { (IntLiteral(int), Underscore) => { let cond_lhs = from_can(env, loc_cond.value, procs, None); let cond_rhs = Expr::Int(*int); @@ -946,6 +913,8 @@ fn from_can_when<'a>( // TODO we can also convert floats to integer representations. let is_switchable = match layout { Layout::Builtin(Builtin::Int64) => true, + Layout::Builtin(Builtin::Bool(_, _)) => true, + Layout::Builtin(Builtin::Byte(_)) => true, _ => false, }; @@ -959,43 +928,31 @@ fn from_can_when<'a>( for (loc_when_pat, loc_expr) in branches { let mono_expr = from_can(env, loc_expr.value, procs, None); + let when_pat = from_can_pattern(env, loc_when_pat.value); - match &loc_when_pat.value { - NumLiteral(var, num) => { - // This is jumpable iff it's an int - match to_int_or_float(env.subs, *var) { - IntOrFloat::IntType => { - jumpable_branches.push((*num as u64, mono_expr)); - } - IntOrFloat::FloatType => { - // The type checker should have converted these mismatches into RuntimeErrors already! - if cfg!(debug_assertions) { - panic!("A type mismatch in a pattern was not converted to a runtime error: {:?}", loc_when_pat); - } else { - unreachable!(); - } - } - }; - } + match &when_pat { IntLiteral(int) => { // Switch only compares the condition to the // alternatives based on their bit patterns, // so casting from i64 to u64 makes no difference here. jumpable_branches.push((*int as u64, mono_expr)); } - Identifier(_symbol) => { + BitLiteral(v) => jumpable_branches.push((*v as u64, mono_expr)), + EnumLiteral(v) => jumpable_branches.push((*v as u64, mono_expr)), + Identifier(symbol) => { // Since this is an ident, it must be // the last pattern in the `when`. // We can safely treat this like an `_` // except that we need to wrap this branch // in a `Store` so the identifier is in scope! - opt_default_branch = Some(arena.alloc(if true { - // Using `if true` for this TODO panic to avoid a warning - panic!("TODO wrap this expr in an Expr::Store: {:?}", mono_expr) - } else { - mono_expr - })); + // TODO does this evaluate `cond` twice? + let mono_with_store = Expr::Store( + arena.alloc([(*symbol, layout.clone(), cond.clone())]), + arena.alloc(mono_expr), + ); + + opt_default_branch = Some(arena.alloc(mono_with_store)); } Underscore => { // We should always have exactly one default branch! @@ -1016,7 +973,7 @@ fn from_can_when<'a>( | FloatLiteral(_) => { // The type checker should have converted these mismatches into RuntimeErrors already! if cfg!(debug_assertions) { - panic!("A type mismatch in a pattern was not converted to a runtime error: {:?}", loc_when_pat); + panic!("A type mismatch in a pattern was not converted to a runtime error: {:?}", when_pat); } else { unreachable!(); } @@ -1035,6 +992,7 @@ fn from_can_when<'a>( .unwrap_or_else(|err| { panic!("TODO turn cond_layout into a RuntimeError {:?}", err) }); + let ret_layout = Layout::from_var(arena, expr_var, env.subs, env.pointer_size) .unwrap_or_else(|err| { panic!("TODO turn ret_layout into a RuntimeError {:?}", err) @@ -1196,3 +1154,105 @@ fn specialize_proc_body<'a>( Some(proc) } + +/// A pattern, including possible problems (e.g. shadowing) so that +/// codegen can generate a runtime error if this pattern is reached. +#[derive(Clone, Debug, PartialEq)] +pub enum Pattern<'a> { + Identifier(Symbol), + AppliedTag(TagName, Vec<'a, Pattern<'a>>, Layout<'a>), + BitLiteral(bool), + EnumLiteral(u8), + IntLiteral(i64), + FloatLiteral(f64), + StrLiteral(Box), + RecordDestructure(Vec<'a, RecordDestruct<'a>>, Layout<'a>), + Underscore, + + // Runtime Exceptions + Shadowed(Region, Located), + // Example: (5 = 1 + 2) is an unsupported pattern in an assignment; Int patterns aren't allowed in assignments! + UnsupportedPattern(Region), +} + +#[derive(Clone, Debug, PartialEq)] +pub struct RecordDestruct<'a> { + pub label: Lowercase, + pub symbol: Symbol, + pub guard: Option>, +} + +fn from_can_pattern<'a>( + env: &mut Env<'a, '_>, + can_pattern: roc_can::pattern::Pattern, +) -> Pattern<'a> { + use roc_can::pattern::Pattern::*; + match can_pattern { + Underscore => Pattern::Underscore, + Identifier(symbol) => Pattern::Identifier(symbol), + IntLiteral(v) => Pattern::IntLiteral(v), + FloatLiteral(v) => Pattern::FloatLiteral(v), + StrLiteral(v) => Pattern::StrLiteral(v), + Shadowed(region, ident) => Pattern::Shadowed(region, ident), + UnsupportedPattern(region) => Pattern::UnsupportedPattern(region), + + NumLiteral(var, num) => match to_int_or_float(env.subs, var) { + IntOrFloat::IntType => Pattern::IntLiteral(num), + IntOrFloat::FloatType => Pattern::FloatLiteral(num as f64), + }, + + AppliedTag { + whole_var, + tag_name, + arguments, + .. + } => match Layout::from_var(env.arena, whole_var, env.subs, env.pointer_size) { + Ok(Layout::Builtin(Builtin::Bool(_bottom, top))) => { + Pattern::BitLiteral(tag_name == top) + } + Ok(Layout::Builtin(Builtin::Byte(conversion))) => match conversion.get(&tag_name) { + Some(index) => Pattern::EnumLiteral(*index), + None => unreachable!("Tag must be in its own type"), + }, + Ok(layout) => { + let mut mono_args = Vec::with_capacity_in(arguments.len(), env.arena); + for (_, loc_pat) in arguments { + mono_args.push(from_can_pattern(env, loc_pat.value)); + } + + Pattern::AppliedTag(tag_name, mono_args, layout) + } + Err(()) => panic!("Invalid layout"), + }, + + RecordDestructure { + whole_var, + destructs, + .. + } => match Layout::from_var(env.arena, whole_var, env.subs, env.pointer_size) { + Ok(layout) => { + let mut mono_destructs = Vec::with_capacity_in(destructs.len(), env.arena); + for loc_rec_des in destructs { + mono_destructs.push(from_can_record_destruct(env, loc_rec_des.value)); + } + + Pattern::RecordDestructure(mono_destructs, layout) + } + Err(()) => panic!("Invalid layout"), + }, + } +} + +fn from_can_record_destruct<'a>( + env: &mut Env<'a, '_>, + can_rd: roc_can::pattern::RecordDestruct, +) -> RecordDestruct<'a> { + RecordDestruct { + label: can_rd.label, + symbol: can_rd.symbol, + guard: match can_rd.guard { + None => None, + Some((_, loc_pattern)) => Some(from_can_pattern(env, loc_pattern.value)), + }, + } +} From e62ddc9ef530135ee8f80fb73a25786e3c661816 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sat, 14 Mar 2020 01:01:14 +0100 Subject: [PATCH 095/428] make record variable names clearer --- compiler/can/src/expr.rs | 13 +++++++++++-- compiler/constrain/src/expr.rs | 6 +++--- compiler/constrain/src/uniq.rs | 6 +++--- compiler/mono/src/expr.rs | 19 +++++++++++-------- compiler/uniq/src/sharing.rs | 2 +- 5 files changed, 29 insertions(+), 17 deletions(-) diff --git a/compiler/can/src/expr.rs b/compiler/can/src/expr.rs index 0eadebc970..853bf75186 100644 --- a/compiler/can/src/expr.rs +++ b/compiler/can/src/expr.rs @@ -85,7 +85,10 @@ pub enum Expr { ), // Product Types - Record(Variable, SendMap), + Record { + record_var: Variable, + fields: SendMap, + }, /// Empty record constant EmptyRecord, @@ -195,7 +198,13 @@ pub fn canonicalize_expr<'a>( } else { let (can_fields, output) = canonicalize_fields(env, var_store, scope, fields); - (Record(var_store.fresh(), can_fields), output) + ( + Record { + record_var: var_store.fresh(), + fields: can_fields, + }, + output, + ) } } ast::Expr::Str(string) => (Str((*string).into()), Output::default()), diff --git a/compiler/constrain/src/expr.rs b/compiler/constrain/src/expr.rs index bf22f7fa30..c5f8f4c071 100644 --- a/compiler/constrain/src/expr.rs +++ b/compiler/constrain/src/expr.rs @@ -90,7 +90,7 @@ pub fn constrain_expr( ), Float(var, _) => float_literal(*var, expected, region), EmptyRecord => constrain_empty_record(region, expected), - Expr::Record(stored_var, fields) => { + Expr::Record { record_var, fields } => { if fields.is_empty() { constrain_empty_record(region, expected) } else { @@ -125,9 +125,9 @@ pub fn constrain_expr( constraints.push(record_con); // variable to store in the AST - let stored_con = Eq(Type::Variable(*stored_var), expected, region); + let stored_con = Eq(Type::Variable(*record_var), expected, region); - field_vars.push(*stored_var); + field_vars.push(*record_var); constraints.push(stored_con); exists(field_vars, And(constraints)) diff --git a/compiler/constrain/src/uniq.rs b/compiler/constrain/src/uniq.rs index 4bc20142d3..f9f82f6ea4 100644 --- a/compiler/constrain/src/uniq.rs +++ b/compiler/constrain/src/uniq.rs @@ -457,12 +457,12 @@ pub fn constrain_expr( ), ) } - Record(variable, fields) => { + Record { record_var, fields } => { // NOTE: canonicalization guarantees at least one field // zero fields generates an EmptyRecord let mut field_types = SendMap::default(); let mut field_vars = Vec::with_capacity(fields.len()); - field_vars.push(*variable); + field_vars.push(*record_var); // Constraints need capacity for each field + 1 for the record itself + 1 for ext let mut constraints = Vec::with_capacity(2 + fields.len()); @@ -501,7 +501,7 @@ pub fn constrain_expr( ), ); let record_con = Eq(record_type, expected.clone(), region); - let ext_con = Eq(Type::Variable(*variable), expected, region); + let ext_con = Eq(Type::Variable(*record_var), expected, region); constraints.push(record_con); constraints.push(ext_con); diff --git a/compiler/mono/src/expr.rs b/compiler/mono/src/expr.rs index 5d6022f9b2..8ae9fa4f79 100644 --- a/compiler/mono/src/expr.rs +++ b/compiler/mono/src/expr.rs @@ -619,7 +619,9 @@ fn from_can<'a>( expr } - Record(ext_var, fields) => { + Record { + record_var, fields, .. + } => { let arena = env.arena; let mut field_bodies = Vec::with_capacity_in(fields.len(), arena); @@ -629,13 +631,14 @@ fn from_can<'a>( field_bodies.push((label, expr)); } - let struct_layout = match Layout::from_var(arena, ext_var, env.subs, env.pointer_size) { - Ok(layout) => layout, - Err(()) => { - // Invalid field! - panic!("TODO gracefully handle Record with invalid struct_layout"); - } - }; + let struct_layout = + match Layout::from_var(arena, record_var, env.subs, env.pointer_size) { + Ok(layout) => layout, + Err(()) => { + // Invalid field! + panic!("TODO gracefully handle Record with invalid struct_layout"); + } + }; Expr::Struct { fields: field_bodies.into_bump_slice(), diff --git a/compiler/uniq/src/sharing.rs b/compiler/uniq/src/sharing.rs index 88bea5fe5f..e39a69c6d4 100644 --- a/compiler/uniq/src/sharing.rs +++ b/compiler/uniq/src/sharing.rs @@ -632,7 +632,7 @@ pub fn annotate_usage(expr: &Expr, usage: &mut VarUsage) { annotate_usage(&loc_expr.value, usage); } } - Record(_, fields) => { + Record { fields, .. } => { for (_, field) in fields { annotate_usage(&field.loc_expr.value, usage); } From b43be95b1979def28eba4feb9dda461997278a44 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sat, 14 Mar 2020 01:13:44 +0100 Subject: [PATCH 096/428] add record_var to Access and Accessor --- compiler/can/src/expr.rs | 26 ++++++++++----------- compiler/constrain/src/expr.rs | 30 ++++++++++++++++++------- compiler/constrain/src/uniq.rs | 24 +++++++++++++++++--- compiler/mono/src/expr.rs | 18 ++++++++------- compiler/solve/tests/test_uniq_solve.rs | 4 ++-- 5 files changed, 67 insertions(+), 35 deletions(-) diff --git a/compiler/can/src/expr.rs b/compiler/can/src/expr.rs index 853bf75186..a5387360d6 100644 --- a/compiler/can/src/expr.rs +++ b/compiler/can/src/expr.rs @@ -95,6 +95,7 @@ pub enum Expr { /// Look up exactly one field on a record, e.g. (expr).foo. Access { + record_var: Variable, ext_var: Variable, field_var: Variable, loc_expr: Box>, @@ -102,6 +103,7 @@ pub enum Expr { }, /// field accessor as a function, e.g. (.foo) expr Accessor { + record_var: Variable, ext_var: Variable, field_var: Variable, field: Lowercase, @@ -488,6 +490,7 @@ pub fn canonicalize_expr<'a>( ( Access { + record_var: var_store.fresh(), field_var: var_store.fresh(), ext_var: var_store.fresh(), loc_expr: Box::new(loc_expr), @@ -496,20 +499,15 @@ pub fn canonicalize_expr<'a>( output, ) } - ast::Expr::AccessorFunction(field) => { - let ext_var = var_store.fresh(); - let field_var = var_store.fresh(); - let field_name: Lowercase = (*field).into(); - - ( - Accessor { - field: field_name, - ext_var, - field_var, - }, - Output::default(), - ) - } + ast::Expr::AccessorFunction(field) => ( + Accessor { + record_var: var_store.fresh(), + ext_var: var_store.fresh(), + field_var: var_store.fresh(), + field: (*field).into(), + }, + Output::default(), + ), ast::Expr::GlobalTag(tag) => { let variant_var = var_store.fresh(); let ext_var = var_store.fresh(); diff --git a/compiler/constrain/src/expr.rs b/compiler/constrain/src/expr.rs index c5f8f4c071..77a80dc7d3 100644 --- a/compiler/constrain/src/expr.rs +++ b/compiler/constrain/src/expr.rs @@ -515,6 +515,7 @@ pub fn constrain_expr( exists(vec![cond_var, *expr_var], And(constraints)) } Access { + record_var, ext_var, field_var, loc_expr, @@ -533,6 +534,8 @@ pub fn constrain_expr( let record_type = Type::Record(rec_field_types, Box::new(ext_type)); let record_expected = Expected::NoExpectation(record_type); + let record_con = Eq(Type::Variable(*record_var), record_expected.clone(), region); + let constraint = constrain_expr( &Env { home: env.home, @@ -544,12 +547,17 @@ pub fn constrain_expr( ); exists( - vec![field_var, ext_var], - And(vec![constraint, Eq(field_type, expected, region)]), + vec![*record_var, field_var, ext_var], + And(vec![ + constraint, + Eq(field_type, expected, region), + record_con, + ]), ) } Accessor { field, + record_var, ext_var, field_var, } => { @@ -563,13 +571,19 @@ pub fn constrain_expr( field_types.insert(label, field_type.clone()); let record_type = Type::Record(field_types, Box::new(ext_type)); + let record_expected = Expected::NoExpectation(record_type.clone()); + let record_con = Eq(Type::Variable(*record_var), record_expected, region); + exists( - vec![field_var, ext_var], - Eq( - Type::Function(vec![record_type], Box::new(field_type)), - expected, - region, - ), + vec![*record_var, field_var, ext_var], + And(vec![ + Eq( + Type::Function(vec![record_type], Box::new(field_type)), + expected, + region, + ), + record_con, + ]), ) } LetRec(defs, loc_ret, var, aliases) => { diff --git a/compiler/constrain/src/uniq.rs b/compiler/constrain/src/uniq.rs index f9f82f6ea4..6a0f547217 100644 --- a/compiler/constrain/src/uniq.rs +++ b/compiler/constrain/src/uniq.rs @@ -1141,6 +1141,7 @@ pub fn constrain_expr( } Access { + record_var, ext_var, field_var, loc_expr, @@ -1163,6 +1164,8 @@ pub fn constrain_expr( ); let record_expected = Expected::NoExpectation(record_type); + let record_con = Eq(Type::Variable(*record_var), record_expected.clone(), region); + let inner_constraint = constrain_expr( env, var_store, @@ -1174,13 +1177,24 @@ pub fn constrain_expr( ); exists( - vec![*field_var, *ext_var, field_uniq_var, record_uniq_var], - And(vec![Eq(field_type, expected, region), inner_constraint]), + vec![ + *record_var, + *field_var, + *ext_var, + field_uniq_var, + record_uniq_var, + ], + And(vec![ + Eq(field_type, expected, region), + inner_constraint, + record_con, + ]), ) } Accessor { field, + record_var, field_var, ext_var, } => { @@ -1200,6 +1214,9 @@ pub fn constrain_expr( Type::Record(field_types, Box::new(Type::Variable(*ext_var))), ); + let record_expected = Expected::NoExpectation(record_type.clone()); + let record_con = Eq(Type::Variable(*record_var), record_expected, region); + let fn_uniq_var = var_store.fresh(); let fn_type = attr_type( Bool::variable(fn_uniq_var), @@ -1208,13 +1225,14 @@ pub fn constrain_expr( exists( vec![ + *record_var, *field_var, *ext_var, fn_uniq_var, field_uniq_var, record_uniq_var, ], - And(vec![Eq(fn_type, expected, region)]), + And(vec![Eq(fn_type, expected, region), record_con]), ) } RuntimeError(_) => True, diff --git a/compiler/mono/src/expr.rs b/compiler/mono/src/expr.rs index 8ae9fa4f79..e324f1f23b 100644 --- a/compiler/mono/src/expr.rs +++ b/compiler/mono/src/expr.rs @@ -681,20 +681,22 @@ fn from_can<'a>( } Access { - ext_var, + record_var, field_var, field, loc_expr, + .. } => { let arena = env.arena; - let struct_layout = match Layout::from_var(arena, ext_var, env.subs, env.pointer_size) { - Ok(layout) => layout, - Err(()) => { - // Invalid field! - panic!("TODO gracefully handle Access with invalid struct_layout"); - } - }; + let struct_layout = + match Layout::from_var(arena, record_var, env.subs, env.pointer_size) { + Ok(layout) => layout, + Err(()) => { + // Invalid field! + panic!("TODO gracefully handle Access with invalid struct_layout"); + } + }; let field_layout = match Layout::from_var(arena, field_var, env.subs, env.pointer_size) { diff --git a/compiler/solve/tests/test_uniq_solve.rs b/compiler/solve/tests/test_uniq_solve.rs index 69e0e553a3..eb7e386a69 100644 --- a/compiler/solve/tests/test_uniq_solve.rs +++ b/compiler/solve/tests/test_uniq_solve.rs @@ -2116,8 +2116,8 @@ mod test_uniq_solve { f "# ), - "Attr * (Attr (* | a | b) { p : (Attr a *), q : (Attr b *) }* -> Attr * (Num (Attr * *)))", - //"Attr * (Attr (* | a | b) { p : (Attr b *), q : (Attr a *) }* -> Attr * (Num (Attr * *)))" + //"Attr * (Attr (* | a | b) { p : (Attr a *), q : (Attr b *) }* -> Attr * (Num (Attr * *)))", + "Attr * (Attr (* | a | b) { p : (Attr b *), q : (Attr a *) }* -> Attr * (Num (Attr * *)))" ); } From e2a079f0ccafe26ffbd3528e9105a24de2fd14f6 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sat, 14 Mar 2020 01:43:14 +0100 Subject: [PATCH 097/428] empty ext vars in type_to_var --- compiler/solve/src/solve.rs | 43 +++++++++++++++++++++++++++++++------ 1 file changed, 37 insertions(+), 6 deletions(-) diff --git a/compiler/solve/src/solve.rs b/compiler/solve/src/solve.rs index 791c03ba43..46e60e7220 100644 --- a/compiler/solve/src/solve.rs +++ b/compiler/solve/src/solve.rs @@ -504,8 +504,17 @@ fn type_to_variable( ); } - let ext_var = type_to_variable(subs, rank, pools, cached, ext); - let content = Content::Structure(FlatType::Record(field_vars, ext_var)); + let temp_ext_var = type_to_variable(subs, rank, pools, cached, ext); + let new_ext_var = match roc_types::pretty_print::chase_ext_record( + subs, + temp_ext_var, + &mut field_vars, + ) { + Ok(()) => Variable::EMPTY_RECORD, + Err((new, _)) => new, + }; + + let content = Content::Structure(FlatType::Record(field_vars, new_ext_var)); register(subs, rank, pools, content) } @@ -522,8 +531,19 @@ fn type_to_variable( tag_vars.insert(tag.clone(), tag_argument_vars); } - let ext_var = type_to_variable(subs, rank, pools, cached, ext); - let content = Content::Structure(FlatType::TagUnion(tag_vars, ext_var)); + let temp_ext_var = type_to_variable(subs, rank, pools, cached, ext); + let mut ext_tag_vec = Vec::new(); + let new_ext_var = match roc_types::pretty_print::chase_ext_tag_union( + subs, + temp_ext_var, + &mut ext_tag_vec, + ) { + Ok(()) => Variable::EMPTY_TAG_UNION, + Err((new, _)) => new, + }; + tag_vars.extend(ext_tag_vec.into_iter()); + + let content = Content::Structure(FlatType::TagUnion(tag_vars, new_ext_var)); register(subs, rank, pools, content) } @@ -540,9 +560,20 @@ fn type_to_variable( tag_vars.insert(tag.clone(), tag_argument_vars); } - let ext_var = type_to_variable(subs, rank, pools, cached, ext); + let temp_ext_var = type_to_variable(subs, rank, pools, cached, ext); + let mut ext_tag_vec = Vec::new(); + let new_ext_var = match roc_types::pretty_print::chase_ext_tag_union( + subs, + temp_ext_var, + &mut ext_tag_vec, + ) { + Ok(()) => Variable::EMPTY_TAG_UNION, + Err((new, _)) => new, + }; + tag_vars.extend(ext_tag_vec.into_iter()); + let content = - Content::Structure(FlatType::RecursiveTagUnion(*rec_var, tag_vars, ext_var)); + Content::Structure(FlatType::RecursiveTagUnion(*rec_var, tag_vars, new_ext_var)); register(subs, rank, pools, content) } From 8c43c66aa36d1654972746cbdb271a4166e6bba7 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Thu, 12 Mar 2020 22:50:45 -0400 Subject: [PATCH 098/428] Remove Sort from roc-for-elm-programmers.md --- roc-for-elm-programmers.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/roc-for-elm-programmers.md b/roc-for-elm-programmers.md index 1374aabbf7..f8522cffc7 100644 --- a/roc-for-elm-programmers.md +++ b/roc-for-elm-programmers.md @@ -47,6 +47,9 @@ Rather than a `type alias` keyword, in Roc you define type aliases with `:` like Username : Str ``` +You can also define type aliases anywhere, not just at the top level. +Their scoping rules work as normal. + Separately, Roc also allows standalone type annotations with no corresponding implementation. So I can write this as an annotation with no implementation: @@ -911,7 +914,6 @@ Roc's standard library has these modules: * `Bytes` * `Str` * `Result` -* `Sort` Some differences to note: @@ -924,7 +926,6 @@ Some differences to note: * No `Process`, `Platform`, `Cmd`, or `Sub` - similarly to `Task`, these are things platform authors would include, or not. * No `Maybe`. This is by design. If a function returns a potential error, use `Result` with an error type that uses a zero-arg tag to describe what went wrong. (For example, `List.first : List a -> Result a [ ListWasEmpty ]*` instead of `List.first : List a -> Maybe a`.) If you want to have a record field be optional, use an Optional Record Field directly (see earlier). If you want to describe something that's neither an operation that can fail nor an optional field, use a more descriptive tag - e.g. for a nullable JSON decoder, instead of `nullable : Decoder a -> Decoder (Maybe a)`, make a self-documenting API like `nullable : Decoder a -> Decoder [ Null, NonNull a ]*`. * `List` refers to something more like Elm's `Array`, as noted earlier. -* `Sort` works [like this](https://package.elm-lang.org/packages/rtfeldman/elm-sorter-experiment/2.1.1/Sort). It's only for `List`, but it's important enough to be one of the standard modules - not only so that lists can be sortable without needing to install a separate package, but also because it demonstrates the "decoder pattern" of API design using opaque types. ## Operator Desugaring Table From 6f64bb2f9b31fca4c05a224d7c0ba41bfddcbe5f Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Tue, 10 Mar 2020 02:51:54 -0400 Subject: [PATCH 099/428] Update List.getUnsafe to use wrapper --- compiler/gen/src/llvm/build.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/compiler/gen/src/llvm/build.rs b/compiler/gen/src/llvm/build.rs index a4e23ebffd..11fafabe79 100644 --- a/compiler/gen/src/llvm/build.rs +++ b/compiler/gen/src/llvm/build.rs @@ -646,8 +646,7 @@ fn call_with_args<'a, 'ctx, 'env>( // TODO here, check to see if the requested index exceeds the length of the array. // Slot 0 in the tuple struct is the pointer to the array data - let array_ptr_field = builder.build_extract_value(tuple_struct, 1, "unwrapped_list_len").unwrap().into_pointer_value(); - let array_data_ptr = builder.build_load(array_ptr_field, "get_array_data").into_pointer_value(); + let array_data_ptr = builder.build_extract_value(tuple_struct, 0, "unwrapped_list_ptr").unwrap().into_pointer_value(); let elem_bytes = 8; // TODO Look this size up instead of hardcoding it! let elem_size = env.context.i64_type().const_int(elem_bytes, false); From 80722b872a5077e7dbda72f5edb22c20304aff98 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Tue, 10 Mar 2020 02:56:44 -0400 Subject: [PATCH 100/428] Update List.set to use wrapper --- compiler/gen/src/llvm/build.rs | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/compiler/gen/src/llvm/build.rs b/compiler/gen/src/llvm/build.rs index 11fafabe79..63ef659aa3 100644 --- a/compiler/gen/src/llvm/build.rs +++ b/compiler/gen/src/llvm/build.rs @@ -611,11 +611,11 @@ fn call_with_args<'a, 'ctx, 'env>( Symbol::LIST_LEN => { debug_assert!(args.len() == 1); - let tuple_struct = args[0].into_struct_value(); + let wrapper_struct = args[0].into_struct_value(); let builder = env.builder; // Get the 32-bit int length - let i32_val = builder.build_extract_value(tuple_struct, 1, "unwrapped_list_len").unwrap().into_int_value(); + let i32_val = builder.build_extract_value(wrapper_struct, 1, "unwrapped_list_len").unwrap().into_int_value(); // cast the 32-bit length to a 64-bit int BasicValueEnum::IntValue(builder.build_int_cast(i32_val, env.context.i64_type(), "i32_to_i64")) @@ -637,16 +637,16 @@ fn call_with_args<'a, 'ctx, 'env>( // List.get : List elem, Int -> Result elem [ OutOfBounds ]* debug_assert!(args.len() == 2); - let tuple_struct = args[0].into_struct_value(); + let wrapper_struct = args[0].into_struct_value(); let elem_index = args[1].into_int_value(); - // Slot 1 in the array is the length - let _list_len = builder.build_extract_value(tuple_struct, 1, "unwrapped_list_len").unwrap().into_int_value(); + // Slot 1 in the wrapper struct is the length + let _list_len = builder.build_extract_value(wrapper_struct, 1, "unwrapped_list_len").unwrap().into_int_value(); // TODO here, check to see if the requested index exceeds the length of the array. - // Slot 0 in the tuple struct is the pointer to the array data - let array_data_ptr = builder.build_extract_value(tuple_struct, 0, "unwrapped_list_ptr").unwrap().into_pointer_value(); + // Slot 0 in the wrapper struct is the pointer to the array data + let array_data_ptr = builder.build_extract_value(wrapper_struct, 0, "unwrapped_list_ptr").unwrap().into_pointer_value(); let elem_bytes = 8; // TODO Look this size up instead of hardcoding it! let elem_size = env.context.i64_type().const_int(elem_bytes, false); @@ -664,19 +664,18 @@ fn call_with_args<'a, 'ctx, 'env>( debug_assert!(args.len() == 3); - let tuple_ptr = args[0].into_pointer_value(); + let wrapper_struct = args[0].into_struct_value(); let elem_index = args[1].into_int_value(); let elem = args[2]; - // Slot 1 in the array is the length - let _list_len = unsafe { builder.build_struct_gep(tuple_ptr, 1, "list_tuple_len") }; + // Slot 1 in the wrapper struct is the length + let _list_len = builder.build_extract_value(wrapper_struct, 1, "unwrapped_list_len").unwrap().into_int_value(); // TODO here, check to see if the requested index exceeds the length of the array. // If so, bail out and return the list unaltered. - // Slot 0 in the tuple struct is the pointer to the array data - let array_ptr_field = unsafe { builder.build_struct_gep(tuple_ptr, 0, "list_tuple_ptr") }; - let array_data_ptr = builder.build_load(array_ptr_field, "get_array_data").into_pointer_value(); + // Slot 0 in the wrapper struct is the pointer to the array data + let array_data_ptr = builder.build_extract_value(wrapper_struct, 0, "unwrapped_list_ptr").unwrap().into_pointer_value(); let elem_bytes = 8; // TODO Look this size up instead of hardcoding it! let elem_size = env.context.i64_type().const_int(elem_bytes, false); @@ -690,8 +689,8 @@ fn call_with_args<'a, 'ctx, 'env>( // Mutate the array in-place. builder.build_store(elem_ptr, elem); - // Return a pointer to the wrapper tuple. - tuple_ptr.into() + // Return the wrapper unchanged, since pointer, length and capacity are all unchanged + wrapper_struct.into() } _ => { let fn_val = env From 4c19dd86ff5a88e6084ccf02fb07d69c15d32c63 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Tue, 10 Mar 2020 03:01:01 -0400 Subject: [PATCH 101/428] First pass at some List implementation stuff --- compiler/gen/src/crane/build.rs | 45 +++++++++++++++++++++++++++++++-- compiler/gen/src/llvm/build.rs | 30 ++++++++++++++-------- compiler/gen/tests/test_gen.rs | 17 +++++-------- compiler/mono/src/layout.rs | 5 ++++ 4 files changed, 75 insertions(+), 22 deletions(-) diff --git a/compiler/gen/src/crane/build.rs b/compiler/gen/src/crane/build.rs index cb45836e22..4b68804407 100644 --- a/compiler/gen/src/crane/build.rs +++ b/compiler/gen/src/crane/build.rs @@ -233,9 +233,34 @@ pub fn build_expr<'a, B: Backend>( } } Array { elem_layout, elems } => { + let cfg = env.cfg; + let ptr_bytes = cfg.pointer_bytes() as u32; + if elems.is_empty() { - panic!("TODO build an empty Array in Crane"); + let slot = builder.create_stack_slot(StackSlotData::new( + StackSlotKind::ExplicitSlot, + ptr_bytes + 64, + )); + let ptr_type = cfg.pointer_type(); + let null_ptr = builder.ins().null(ptr_type); + let zero = builder.ins().iconst(Type::int(32).unwrap(), 0); + + // Initialize a null pointer for the pointer + builder.ins().stack_store(null_ptr, slot, Offset32::new(0)); + + // Set both length and capacity to 0 + let ptr_bytes = ptr_bytes as i32; + + builder + .ins() + .stack_store(zero, slot, Offset32::new(ptr_bytes)); + builder + .ins() + .stack_store(zero, slot, Offset32::new(ptr_bytes + 32)); + + builder.ins().stack_addr(ptr_type, slot, Offset32::new(0)) } else { + panic!("TODO make this work like the empty List, then verify that they both actually work"); let elem_bytes = elem_layout.stack_size(env.cfg.pointer_bytes() as u32) as usize; let bytes_len = (elem_bytes * elems.len()) + 1/* TODO drop the +1 when we have structs and this is no longer NUL-terminated. */; let ptr = call_malloc(env, module, builder, bytes_len); @@ -574,6 +599,22 @@ fn call_by_name<'a, B: Backend>( builder.ins().ineg(num) } + Symbol::LIST_LEN => { + debug_assert!(args.len() == 1); + + let list = build_arg(&args[0], env, scope, module, builder, procs); + + // Get the 32-bit int length + let i32_val = builder.ins().load_complex( + Type::int(32).unwrap(), + MemFlags::new(), + &[list], + Offset32::new(0), + ); + + // Cast the 32-bit int length to a 64-bit integer + builder.ins().bitcast(Type::int(64).unwrap(), i32_val) + } Symbol::LIST_GET_UNSAFE => { debug_assert!(args.len() == 2); @@ -667,7 +708,7 @@ fn call_by_name<'a, B: Backend>( let fn_id = match scope.get(&symbol) { Some(ScopeEntry::Func{ func_id, .. }) => *func_id, other => panic!( - "CallByName could not find function named {:?} in scope; instead, found {:?} in scope {:?}", + "CallByName could not find function named {:?} declared in scope (and it was not special-cased in crane::build as a builtin); instead, found {:?} in scope {:?}", symbol, other, scope ), }; diff --git a/compiler/gen/src/llvm/build.rs b/compiler/gen/src/llvm/build.rs index 63ef659aa3..6ae2a84169 100644 --- a/compiler/gen/src/llvm/build.rs +++ b/compiler/gen/src/llvm/build.rs @@ -14,7 +14,7 @@ use crate::llvm::convert::{ use roc_collections::all::ImMap; use roc_module::symbol::{Interns, Symbol}; use roc_mono::expr::{Expr, Proc, Procs}; -use roc_mono::layout::Layout; +use roc_mono::layout::{Builtin, Layout}; /// This is for Inkwell's FunctionValue::verify - we want to know the verification /// output in debug builds, but we don't want it to print to stdout in release builds! @@ -222,7 +222,7 @@ pub fn build_expr<'a, 'ctx, 'env>( .build_insert_value( struct_type.const_zero(), BasicValueEnum::PointerValue(ptr_type.const_null()), - 0, + Builtin::WRAPPER_PTR, "insert_ptr", ) .unwrap(); @@ -258,17 +258,27 @@ pub fn build_expr<'a, 'ctx, 'env>( // Field 0: pointer struct_val = builder - .build_insert_value(struct_type.const_zero(), ptr_val, 0, "insert_ptr") + .build_insert_value( + struct_type.const_zero(), + ptr_val, + Builtin::WRAPPER_PTR, + "insert_ptr", + ) .unwrap(); // Field 1: length struct_val = builder - .build_insert_value(struct_val, len, 1, "insert_len") + .build_insert_value(struct_val, len, Builtin::WRAPPER_LEN, "insert_len") .unwrap(); // Field 2: capacity (initially set to length) struct_val = builder - .build_insert_value(struct_val, len, 2, "insert_capacity") + .build_insert_value( + struct_val, + len, + Builtin::WRAPPER_CAPACITY, + "insert_capacity", + ) .unwrap(); BasicValueEnum::StructValue(struct_val.into_struct_value()) @@ -615,7 +625,7 @@ fn call_with_args<'a, 'ctx, 'env>( let builder = env.builder; // Get the 32-bit int length - let i32_val = builder.build_extract_value(wrapper_struct, 1, "unwrapped_list_len").unwrap().into_int_value(); + let i32_val = builder.build_extract_value(wrapper_struct, Builtin::WRAPPER_LEN, "unwrapped_list_len").unwrap().into_int_value(); // cast the 32-bit length to a 64-bit int BasicValueEnum::IntValue(builder.build_int_cast(i32_val, env.context.i64_type(), "i32_to_i64")) @@ -641,12 +651,12 @@ fn call_with_args<'a, 'ctx, 'env>( let elem_index = args[1].into_int_value(); // Slot 1 in the wrapper struct is the length - let _list_len = builder.build_extract_value(wrapper_struct, 1, "unwrapped_list_len").unwrap().into_int_value(); + let _list_len = builder.build_extract_value(wrapper_struct, Builtin::WRAPPER_LEN, "unwrapped_list_len").unwrap().into_int_value(); // TODO here, check to see if the requested index exceeds the length of the array. // Slot 0 in the wrapper struct is the pointer to the array data - let array_data_ptr = builder.build_extract_value(wrapper_struct, 0, "unwrapped_list_ptr").unwrap().into_pointer_value(); + let array_data_ptr = builder.build_extract_value(wrapper_struct, Builtin::WRAPPER_PTR, "unwrapped_list_ptr").unwrap().into_pointer_value(); let elem_bytes = 8; // TODO Look this size up instead of hardcoding it! let elem_size = env.context.i64_type().const_int(elem_bytes, false); @@ -669,13 +679,13 @@ fn call_with_args<'a, 'ctx, 'env>( let elem = args[2]; // Slot 1 in the wrapper struct is the length - let _list_len = builder.build_extract_value(wrapper_struct, 1, "unwrapped_list_len").unwrap().into_int_value(); + let _list_len = builder.build_extract_value(wrapper_struct, Builtin::WRAPPER_LEN, "unwrapped_list_len").unwrap().into_int_value(); // TODO here, check to see if the requested index exceeds the length of the array. // If so, bail out and return the list unaltered. // Slot 0 in the wrapper struct is the pointer to the array data - let array_data_ptr = builder.build_extract_value(wrapper_struct, 0, "unwrapped_list_ptr").unwrap().into_pointer_value(); + let array_data_ptr = builder.build_extract_value(wrapper_struct, Builtin::WRAPPER_PTR, "unwrapped_list_ptr").unwrap().into_pointer_value(); let elem_bytes = 8; // TODO Look this size up instead of hardcoding it! let elem_size = env.context.i64_type().const_int(elem_bytes, false); diff --git a/compiler/gen/tests/test_gen.rs b/compiler/gen/tests/test_gen.rs index 90d0b05ab3..bc3c07ba24 100644 --- a/compiler/gen/tests/test_gen.rs +++ b/compiler/gen/tests/test_gen.rs @@ -488,14 +488,13 @@ mod test_gen { // } #[test] - fn int_list_len() { - // assert_evals_to!("List.len [ 12, 9, 6, 3 ]", 4, i64); - assert_llvm_evals_to!("List.len [ 12, 9, 6, 3 ]", 4, i64, |x| x); + fn basic_int_list_len() { + assert_evals_to!("List.len [ 12, 9, 6, 3 ]", 4, i64); } #[test] fn loaded_int_list_len() { - assert_llvm_evals_to!( + assert_evals_to!( indoc!( r#" nums = [ 2, 4, 6 ] @@ -504,14 +503,13 @@ mod test_gen { "# ), 3, - i64, - |x| x + i64 ); } #[test] fn fn_int_list_len() { - assert_llvm_evals_to!( + assert_evals_to!( indoc!( r#" # TODO remove this annotation once monomorphization works! @@ -524,14 +522,13 @@ mod test_gen { "# ), 3, - i64, - |x| x + i64 ); } // #[test] // fn int_list_is_empty() { - // assert_evals_to!("List.is_empty [ 12, 9, 6, 3 ]", 0, i32, |x| x); + // assert_evals_to!("List.is_empty [ 12, 9, 6, 3 ]", 0, u8, |x| x); // } #[test] diff --git a/compiler/mono/src/layout.rs b/compiler/mono/src/layout.rs index b56a5a6b4c..4646aadbcd 100644 --- a/compiler/mono/src/layout.rs +++ b/compiler/mono/src/layout.rs @@ -92,6 +92,11 @@ impl<'a> Builtin<'a> { const SET_WORDS: u32 = Builtin::MAP_WORDS; // Set is an alias for Map with {} for value const LIST_WORDS: u32 = 3; + /// Layout of collection wrapper - a struct of (pointer, length, capacity) + pub const WRAPPER_PTR: u32 = 0; + pub const WRAPPER_LEN: u32 = 1; + pub const WRAPPER_CAPACITY: u32 = 2; + pub fn stack_size(&self, pointer_size: u32) -> u32 { use Builtin::*; From 458b28de17986e1981a42974c014d055a46784a5 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Fri, 13 Mar 2020 22:41:35 -0400 Subject: [PATCH 102/428] Drop dbg! statement --- compiler/mono/tests/test_mono.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/compiler/mono/tests/test_mono.rs b/compiler/mono/tests/test_mono.rs index 1d7103dd3e..4b486817d7 100644 --- a/compiler/mono/tests/test_mono.rs +++ b/compiler/mono/tests/test_mono.rs @@ -63,8 +63,6 @@ mod test_mono { pointer_size, ); - dbg!(&procs); - // Put this module's ident_ids back in the interns interns.all_ident_ids.insert(home, ident_ids); From de25dc048471d3e3ea902e8654fcfd68f441635e Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Fri, 13 Mar 2020 22:41:46 -0400 Subject: [PATCH 103/428] Re-enable empty list test --- compiler/gen/tests/test_gen.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/compiler/gen/tests/test_gen.rs b/compiler/gen/tests/test_gen.rs index 2780bede4b..bd62a9004d 100644 --- a/compiler/gen/tests/test_gen.rs +++ b/compiler/gen/tests/test_gen.rs @@ -486,10 +486,10 @@ mod test_gen { assert_evals_to!("1234.0", 1234.0, f64); } - // #[test] - // fn empty_list_len() { - // assert_evals_to!("List.len []", 0, i64); - // } + #[test] + fn empty_list_len() { + assert_evals_to!("List.len []", 0, i64); + } #[test] fn basic_int_list_len() { From e9a0c8b3ef00e307442b704910a3938c1341f258 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Fri, 13 Mar 2020 22:41:55 -0400 Subject: [PATCH 104/428] Fix mono for empty list --- compiler/mono/src/expr.rs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/compiler/mono/src/expr.rs b/compiler/mono/src/expr.rs index 089bd2f67e..29e6ccb967 100644 --- a/compiler/mono/src/expr.rs +++ b/compiler/mono/src/expr.rs @@ -674,11 +674,18 @@ fn from_can<'a>( loc_elems, } => { let arena = env.arena; - let elem_layout = match Layout::from_var(arena, elem_var, env.subs, env.pointer_size) { - Ok(layout) => layout, - Err(()) => { - panic!("TODO gracefully handle List with invalid element layout"); - } + let subs = &env.subs; + let elem_content = subs.get_without_compacting(elem_var).content; + let elem_layout = match elem_content { + // We have to special-case the empty list, because trying to + // compute a layout for an unbound var won't work. + Content::FlexVar(_) => Layout::Builtin(Builtin::EmptyList), + content => match Layout::from_content(arena, content, env.subs, env.pointer_size) { + Ok(layout) => layout, + Err(()) => { + panic!("TODO gracefully handle List with invalid element layout"); + } + }, }; let mut elems = Vec::with_capacity_in(loc_elems.len(), arena); From e01db9b2847b3a5c604af2e6aa343b8828ac17bf Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Fri, 13 Mar 2020 23:33:32 -0400 Subject: [PATCH 105/428] wip empty list --- compiler/gen/src/crane/build.rs | 48 ++++++++++++++++++--------------- compiler/gen/tests/test_gen.rs | 4 +-- 2 files changed, 28 insertions(+), 24 deletions(-) diff --git a/compiler/gen/src/crane/build.rs b/compiler/gen/src/crane/build.rs index bbca43f648..58dc46c82e 100644 --- a/compiler/gen/src/crane/build.rs +++ b/compiler/gen/src/crane/build.rs @@ -283,26 +283,30 @@ pub fn build_expr<'a, B: Backend>( if elems.is_empty() { let slot = builder.create_stack_slot(StackSlotData::new( StackSlotKind::ExplicitSlot, - ptr_bytes + 64, + // 1 pointer-sized slot for the array pointer, and + // 1 pointer-sized slot for the length + ptr_bytes * 2, )); - let ptr_type = cfg.pointer_type(); - let null_ptr = builder.ins().null(ptr_type); - let zero = builder.ins().iconst(Type::int(32).unwrap(), 0); - // Initialize a null pointer for the pointer - builder.ins().stack_store(null_ptr, slot, Offset32::new(0)); + // Set list pointer to null + { + // let null_ptr = builder.ins().null(ptr_type); + let zero = builder.ins().iconst(cfg.pointer_type(), 0); - // Set both length and capacity to 0 - let ptr_bytes = ptr_bytes as i32; + builder.ins().stack_store(zero, slot, Offset32::new(0)); + } - builder - .ins() - .stack_store(zero, slot, Offset32::new(ptr_bytes)); - builder - .ins() - .stack_store(zero, slot, Offset32::new(ptr_bytes + 32)); + // Set length to 0 + { + let zero = builder.ins().iconst(cfg.pointer_type(), 0); - builder.ins().stack_addr(ptr_type, slot, Offset32::new(0)) + builder + .ins() + .stack_store(zero, slot, Offset32::new(ptr_bytes as i32)); + } + + // Return the pointer + builder.ins().stack_addr(cfg.pointer_type(), slot, Offset32::new(0)) } else { panic!("TODO make this work like the empty List, then verify that they both actually work"); let elem_bytes = elem_layout.stack_size(env.cfg.pointer_bytes() as u32) as usize; @@ -660,18 +664,18 @@ fn call_by_name<'a, B: Backend>( Symbol::LIST_LEN => { debug_assert!(args.len() == 1); - let list = build_arg(&args[0], env, scope, module, builder, procs); + let list_ptr = build_arg(&args[0], env, scope, module, builder, procs); - // Get the 32-bit int length - let i32_val = builder.ins().load_complex( - Type::int(32).unwrap(), + // Get the usize int length + let len_usize = builder.ins().load_complex( + env.cfg.pointer_type(), MemFlags::new(), - &[list], + &[list_ptr], Offset32::new(0), ); - // Cast the 32-bit int length to a 64-bit integer - builder.ins().bitcast(Type::int(64).unwrap(), i32_val) + // Cast the usize length to 64-bit integer + builder.ins().bitcast(Type::int(64).unwrap(), len_usize) } Symbol::LIST_GET_UNSAFE => { debug_assert!(args.len() == 2); diff --git a/compiler/gen/tests/test_gen.rs b/compiler/gen/tests/test_gen.rs index bd62a9004d..9ca71b26ae 100644 --- a/compiler/gen/tests/test_gen.rs +++ b/compiler/gen/tests/test_gen.rs @@ -488,12 +488,12 @@ mod test_gen { #[test] fn empty_list_len() { - assert_evals_to!("List.len []", 0, i64); + assert_evals_to!("List.len []", 0, usize); } #[test] fn basic_int_list_len() { - assert_evals_to!("List.len [ 12, 9, 6, 3 ]", 4, i64); + assert_evals_to!("List.len [ 12, 9, 6, 3 ]", 4, usize); } #[test] From 51ad7ea0f1119b78adbd3bd8bc5fc36131761f7f Mon Sep 17 00:00:00 2001 From: Chad Stearns Date: Sat, 14 Mar 2020 12:28:36 -0400 Subject: [PATCH 106/428] unused def report and batch report text --- compiler/reporting/src/report.rs | 64 +++++++++++++----- compiler/reporting/tests/test_reporting.rs | 78 +++++++++++++++++++--- 2 files changed, 118 insertions(+), 24 deletions(-) diff --git a/compiler/reporting/src/report.rs b/compiler/reporting/src/report.rs index 47743afae5..e264ed9aff 100644 --- a/compiler/reporting/src/report.rs +++ b/compiler/reporting/src/report.rs @@ -1,6 +1,6 @@ +use crate::report::ReportText::{Batch, Region, Value}; use roc_module::symbol::{Interns, ModuleId, Symbol}; use roc_problem::can::Problem; -use roc_region::all::Region; use roc_types::pretty_print::content_to_string; use roc_types::subs::{Content, Subs}; use std::path::PathBuf; @@ -11,19 +11,32 @@ pub struct Report { pub text: ReportText, } -impl Report { - pub fn can_problem(_filename: PathBuf, _problem: Problem) -> Self { - // let text = match problem { - // Problem::UnusedDef(symbol, region) => { - // panic!("TODO implelment me!"); - // } - // _ => { - // panic!("TODO implement others"); - // } - // }; +pub fn can_problem(filename: PathBuf, problem: Problem) -> Report { + let mut texts = Vec::new(); - // Report { filename, text } - panic!("TODO implement me!"); + match problem { + Problem::UnusedDef(symbol, region) => { + texts.push(Value(symbol)); + texts.push(plain_text(" is not used anywhere in your code.")); + texts.push(newline()); + texts.push(newline()); + texts.push(Region(region)); + texts.push(newline()); + texts.push(newline()); + texts.push(plain_text("If you didn't intend on using ")); + texts.push(Value(symbol)); + texts.push(plain_text( + " then remove it so future readers of your code don't wonder why it is there.", + )); + } + _ => { + panic!("TODO implement others"); + } + }; + + Report { + filename, + text: Batch(texts), } } @@ -43,13 +56,25 @@ pub enum ReportText { EmText(Box), /// A region in the original source - Region(Region), + Region(roc_region::all::Region), /// A URL, which should be rendered as a hyperlink. Url(Box), /// The documentation for this symbol. Docs(Symbol), + + Batch(Vec), +} + +pub fn plain_text(str: &str) -> ReportText { + use ReportText::*; + + Plain(Box::from(str)) +} + +fn newline() -> ReportText { + plain_text("\n") } impl ReportText { @@ -89,7 +114,7 @@ impl ReportText { } Type(content) => buf.push_str(content_to_string(content, subs, home, interns).as_str()), Region(region) => { - for i in region.start_line..region.end_line { + for i in region.start_line..=region.end_line { buf.push_str(i.to_string().as_str()); buf.push_str(" |"); @@ -100,12 +125,19 @@ impl ReportText { buf.push_str(src_lines[i as usize]); } - buf.push('\n'); + if i != region.end_line { + buf.push('\n'); + } } } Docs(_) => { panic!("TODO implment docs"); } + Batch(report_texts) => { + for report_text in report_texts { + report_text.render_ci(buf, subs, home, src_lines, interns); + } + } } } diff --git a/compiler/reporting/tests/test_reporting.rs b/compiler/reporting/tests/test_reporting.rs index 6d0ef66cf8..8b9708ddc9 100644 --- a/compiler/reporting/tests/test_reporting.rs +++ b/compiler/reporting/tests/test_reporting.rs @@ -11,24 +11,30 @@ mod helpers; mod test_report { use crate::helpers::test_home; use roc_module::symbol::{Interns, ModuleId}; - use roc_reporting::report::{Report, ReportText}; + use roc_reporting::report::{can_problem, plain_text, Report, ReportText}; use roc_types::pretty_print::name_all_type_vars; use roc_types::subs::Subs; use roc_types::types; use std::path::PathBuf; // use roc_region::all; use crate::helpers::{assert_correct_variable_usage, can_expr, infer_expr, CanExprOut}; - use roc_reporting::report::ReportText::{EmText, Plain, Region, Type, Url, Value}; + use roc_problem::can::Problem; + use roc_reporting::report::ReportText::{Batch, EmText, Plain, Region, Type, Url, Value}; use roc_types::subs::Content::{FlexVar, RigidVar, Structure}; use roc_types::subs::FlatType::EmptyRecord; + fn filename_from_string(str: &str) -> PathBuf { + let mut filename = PathBuf::new(); + filename.push(str); + + return filename; + } + // use roc_problem::can; fn to_simple_report(text: ReportText) -> Report { - let mut filename = PathBuf::new(); - filename.push(r"\code\proj\Main.roc"); Report { text: text, - filename: filename, + filename: filename_from_string(r"\code\proj\Main.roc"), } } @@ -69,7 +75,7 @@ mod test_report { fn report_renders_as_from_src(src: &str, report: Report, expected_rendering: &str) { let (_type_problems, _can_problems, mut subs, home, interns) = infer_expr_help(src); - let mut buf = String::new(); + let mut buf: String = String::new(); let src_lines: Vec<&str> = src.split('\n').collect(); report @@ -96,7 +102,7 @@ mod test_report { #[test] fn report_plain() { - report_renders_as(to_simple_report(Plain(Box::from("y"))), "y"); + report_renders_as(to_simple_report(plain_text("y")), "y"); } #[test] @@ -152,6 +158,62 @@ mod test_report { report_renders_as(to_simple_report(Type(Structure(EmptyRecord))), "{}"); } + #[test] + fn report_batch_of_plain_text() { + let mut report_texts = Vec::new(); + + report_texts.push(plain_text("Wait a second. ")); + report_texts.push(plain_text("There is a problem here. -> ")); + report_texts.push(EmText(Box::from("y"))); + + report_renders_as( + to_simple_report(Batch(report_texts)), + "Wait a second. There is a problem here. -> *y*", + ); + } + + #[test] + fn report_unused_def() { + let src: &str = indoc!( + r#" + x = 1 + y = 2 + + x + "# + ); + + let (_type_problems, can_problems, mut subs, home, interns) = infer_expr_help(src); + + let mut buf: String = String::new(); + let src_lines: Vec<&str> = src.split('\n').collect(); + + match can_problems.first() { + None => {} + Some(problem) => { + let report = can_problem( + filename_from_string(r"\code\proj\Main.roc"), + problem.clone(), + ); + report + .text + .render_ci(&mut buf, &mut subs, home, &src_lines, &interns) + } + } + + assert_eq!( + buf, + indoc!( + r#" + y is not used anywhere in your code. + + 1 | y = 2 + + If you didn't intend on using y then remove it so future readers of your code don't wonder why it is there."# + ) + ); + } + #[test] fn report_region() { report_renders_as_from_src( @@ -166,7 +228,7 @@ mod test_report { ), to_simple_report(Region(roc_region::all::Region { start_line: 1, - end_line: 4, + end_line: 3, start_col: 0, end_col: 0, })), From 5beb65880c0decac1b933fb84271de667c4ab4be Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sat, 14 Mar 2020 20:12:27 -0400 Subject: [PATCH 107/428] Fix List.len --- compiler/gen/src/crane/build.rs | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/compiler/gen/src/crane/build.rs b/compiler/gen/src/crane/build.rs index 58dc46c82e..75892f0ece 100644 --- a/compiler/gen/src/crane/build.rs +++ b/compiler/gen/src/crane/build.rs @@ -36,6 +36,15 @@ pub struct Env<'a> { pub malloc: FuncId, } +impl<'a> Env<'a> { + /// This is necessary when you want usize or isize, + /// because cfg.pointer_type() returns a pointer type, + /// not an integer type, which casues verification to fail. + pub fn ptr_sized_int(&self) -> Type { + Type::int(self.cfg.pointer_bits() as u16).unwrap() + } +} + pub fn build_expr<'a, B: Backend>( env: &Env<'a>, scope: &Scope, @@ -667,15 +676,12 @@ fn call_by_name<'a, B: Backend>( let list_ptr = build_arg(&args[0], env, scope, module, builder, procs); // Get the usize int length - let len_usize = builder.ins().load_complex( - env.cfg.pointer_type(), + builder.ins().load( + env.ptr_sized_int(), MemFlags::new(), - &[list_ptr], + list_ptr, Offset32::new(0), - ); - - // Cast the usize length to 64-bit integer - builder.ins().bitcast(Type::int(64).unwrap(), len_usize) + ) } Symbol::LIST_GET_UNSAFE => { debug_assert!(args.len() == 2); From 5bf82fa42ca421c314f20ae2eca518303dc0ae2a Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sat, 14 Mar 2020 20:44:39 -0400 Subject: [PATCH 108/428] Fix Cranelift gen of nonempty lists --- compiler/gen/src/crane/build.rs | 69 +++++++++++++-------------------- compiler/gen/tests/test_gen.rs | 7 ++-- compiler/mono/src/layout.rs | 8 ++-- 3 files changed, 35 insertions(+), 49 deletions(-) diff --git a/compiler/gen/src/crane/build.rs b/compiler/gen/src/crane/build.rs index 75892f0ece..a6401653e0 100644 --- a/compiler/gen/src/crane/build.rs +++ b/compiler/gen/src/crane/build.rs @@ -288,39 +288,18 @@ pub fn build_expr<'a, B: Backend>( Array { elem_layout, elems } => { let cfg = env.cfg; let ptr_bytes = cfg.pointer_bytes() as u32; + let slot = builder.create_stack_slot(StackSlotData::new( + StackSlotKind::ExplicitSlot, + ptr_bytes * Builtin::LIST_WORDS, + )); - if elems.is_empty() { - let slot = builder.create_stack_slot(StackSlotData::new( - StackSlotKind::ExplicitSlot, - // 1 pointer-sized slot for the array pointer, and - // 1 pointer-sized slot for the length - ptr_bytes * 2, - )); - - // Set list pointer to null - { - // let null_ptr = builder.ins().null(ptr_type); - let zero = builder.ins().iconst(cfg.pointer_type(), 0); - - builder.ins().stack_store(zero, slot, Offset32::new(0)); - } - - // Set length to 0 - { - let zero = builder.ins().iconst(cfg.pointer_type(), 0); - - builder - .ins() - .stack_store(zero, slot, Offset32::new(ptr_bytes as i32)); - } - - // Return the pointer - builder.ins().stack_addr(cfg.pointer_type(), slot, Offset32::new(0)) + let elems_ptr = if elems.is_empty() { + // Empty lists get a null pointer so they don't allocate on the heap + builder.ins().iconst(cfg.pointer_type(), 0) } else { - panic!("TODO make this work like the empty List, then verify that they both actually work"); - let elem_bytes = elem_layout.stack_size(env.cfg.pointer_bytes() as u32) as usize; - let bytes_len = (elem_bytes * elems.len()) + 1/* TODO drop the +1 when we have structs and this is no longer NUL-terminated. */; - let ptr = call_malloc(env, module, builder, bytes_len); + let elem_bytes = elem_layout.stack_size(ptr_bytes as u32); + let bytes_len = elem_bytes as usize * elems.len(); + let elems_ptr = call_malloc(env, module, builder, bytes_len); let mem_flags = MemFlags::new(); // Copy the elements from the literal into the array @@ -328,20 +307,28 @@ pub fn build_expr<'a, B: Backend>( let offset = Offset32::new(elem_bytes as i32 * index as i32); let val = build_expr(env, scope, module, builder, elem, procs); - builder.ins().store(mem_flags, val, ptr, offset); + builder.ins().store(mem_flags, val, elems_ptr, offset); } - // Add a NUL terminator at the end. - // TODO: Instead of NUL-terminating, return a struct - // with the pointer and also the length and capacity. - let nul_terminator = builder.ins().iconst(types::I8, 0); - let index = bytes_len as i32 - 1; - let offset = Offset32::new(index); + elems_ptr + }; - builder.ins().store(mem_flags, nul_terminator, ptr, offset); + // Store the pointer in slot 0 + builder + .ins() + .stack_store(elems_ptr, slot, Offset32::new(0)); - ptr + // Store the length in slot 1 + { + let length = builder.ins().iconst(env.ptr_sized_int(), elems.len() as i64); + + builder + .ins() + .stack_store(length, slot, Offset32::new(ptr_bytes as i32)); } + + // Return the pointer to the wrapper + builder.ins().stack_addr(cfg.pointer_type(), slot, Offset32::new(0)) } _ => { panic!("I don't yet know how to crane build {:?}", expr); @@ -680,7 +667,7 @@ fn call_by_name<'a, B: Backend>( env.ptr_sized_int(), MemFlags::new(), list_ptr, - Offset32::new(0), + Offset32::new(env.cfg.pointer_bytes() as i32), ) } Symbol::LIST_GET_UNSAFE => { diff --git a/compiler/gen/tests/test_gen.rs b/compiler/gen/tests/test_gen.rs index 9ca71b26ae..8e477043a7 100644 --- a/compiler/gen/tests/test_gen.rs +++ b/compiler/gen/tests/test_gen.rs @@ -507,7 +507,7 @@ mod test_gen { "# ), 3, - i64 + usize ); } @@ -517,7 +517,6 @@ mod test_gen { indoc!( r#" # TODO remove this annotation once monomorphization works! - getLen : List Int -> Int getLen = \list -> List.len list nums = [ 2, 4, 6 ] @@ -526,13 +525,13 @@ mod test_gen { "# ), 3, - i64 + usize ); } // #[test] // fn int_list_is_empty() { - // assert_evals_to!("List.is_empty [ 12, 9, 6, 3 ]", 0, u8, |x| x); + // assert_evals_to!("List.isEmpty [ 12, 9, 6, 3 ]", 0, u8, |x| x); // } #[test] diff --git a/compiler/mono/src/layout.rs b/compiler/mono/src/layout.rs index 1d0eb4c554..f7e35107e8 100644 --- a/compiler/mono/src/layout.rs +++ b/compiler/mono/src/layout.rs @@ -104,10 +104,10 @@ impl<'a> Builtin<'a> { const BYTE_SIZE: u32 = std::mem::size_of::() as u32; /// Number of machine words in an empty one of these - const STR_WORDS: u32 = 3; - const MAP_WORDS: u32 = 6; - const SET_WORDS: u32 = Builtin::MAP_WORDS; // Set is an alias for Map with {} for value - const LIST_WORDS: u32 = 3; + pub const STR_WORDS: u32 = 2; + pub const MAP_WORDS: u32 = 6; + pub const SET_WORDS: u32 = Builtin::MAP_WORDS; // Set is an alias for Map with {} for value + pub const LIST_WORDS: u32 = 2; /// Layout of collection wrapper - a struct of (pointer, length, capacity) pub const WRAPPER_PTR: u32 = 0; From 5c3c30ed094c439107873a3a609acb5e217b8bba Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sat, 14 Mar 2020 21:17:09 -0400 Subject: [PATCH 109/428] Fix List.get and List.set --- compiler/gen/src/crane/build.rs | 46 ++++++++++++++++++------ compiler/gen/tests/test_gen.rs | 63 ++++++++++++++++++--------------- 2 files changed, 70 insertions(+), 39 deletions(-) diff --git a/compiler/gen/src/crane/build.rs b/compiler/gen/src/crane/build.rs index fd3868962e..2f5cdfa971 100644 --- a/compiler/gen/src/crane/build.rs +++ b/compiler/gen/src/crane/build.rs @@ -674,20 +674,29 @@ fn call_by_name<'a, B: Backend>( Symbol::LIST_GET_UNSAFE => { debug_assert!(args.len() == 2); - let list_ptr = build_arg(&args[0], env, scope, module, builder, procs); + let wrapper_ptr = build_arg(&args[0], env, scope, module, builder, procs); let elem_index = build_arg(&args[1], env, scope, module, builder, procs); let elem_type = Type::int(64).unwrap(); // TODO Look this up instead of hardcoding it! let elem_bytes = 8; // TODO Look this up instead of hardcoding it! let elem_size = builder.ins().iconst(types::I64, elem_bytes); + // Load the pointer we got to the wrapper struct + let elems_ptr = builder.ins().load( + env.cfg.pointer_type(), + MemFlags::new(), + wrapper_ptr, + Offset32::new(0), + ); + // Multiply the requested index by the size of each element. let offset = builder.ins().imul(elem_index, elem_size); + // Follow the pointer in the wrapper struct to the actual elements builder.ins().load_complex( elem_type, MemFlags::new(), - &[list_ptr, offset], + &[elems_ptr, offset], Offset32::new(0), ) } @@ -703,7 +712,7 @@ fn call_by_name<'a, B: Backend>( let elem_bytes = elem_layout.stack_size(env.cfg.pointer_bytes() as u32) as usize; let bytes_len = (elem_bytes * num_elems) + 1/* TODO drop the +1 when we have structs and this is no longer NUL-terminated. */; - let ptr = call_malloc(env, module, builder, bytes_len); + let wrapper_ptr = call_malloc(env, module, builder, bytes_len); // let mem_flags = MemFlags::new(); // Copy the elements from the literal into the array @@ -722,17 +731,24 @@ fn call_by_name<'a, B: Backend>( // let offset = Offset32::new(index); // builder.ins().store(mem_flags, nul_terminator, ptr, offset); + // Load the pointer we got to the wrapper struct + let _elems_ptr = builder.ins().load( + env.cfg.pointer_type(), + MemFlags::new(), + wrapper_ptr, + Offset32::new(0), + ); list_set_in_place( env, - ptr, + wrapper_ptr, build_arg(&args[1], env, scope, module, builder, procs), build_arg(&args[2], env, scope, module, builder, procs), elem_layout, builder, ); - ptr + wrapper_ptr } _ => { unreachable!("Invalid List layout for List.set: {:?}", list_layout); @@ -809,21 +825,31 @@ fn call_malloc( fn list_set_in_place<'a>( env: &Env<'a>, - list_ptr: Value, + wrapper_ptr: Value, elem_index: Value, elem: Value, elem_layout: &Layout<'a>, builder: &mut FunctionBuilder, ) -> Value { + let elems_ptr = builder.ins().load( + env.cfg.pointer_type(), + MemFlags::new(), + wrapper_ptr, + Offset32::new(0), + ); + let elem_bytes = elem_layout.stack_size(env.cfg.pointer_bytes() as u32); let elem_size = builder.ins().iconst(types::I64, elem_bytes as i64); // Multiply the requested index by the size of each element. let offset = builder.ins().imul(elem_index, elem_size); - builder - .ins() - .store_complex(MemFlags::new(), elem, &[list_ptr, offset], Offset32::new(0)); + builder.ins().store_complex( + MemFlags::new(), + elem, + &[elems_ptr, offset], + Offset32::new(0), + ); - list_ptr + wrapper_ptr } diff --git a/compiler/gen/tests/test_gen.rs b/compiler/gen/tests/test_gen.rs index 8f0775cf99..4e0102d91e 100644 --- a/compiler/gen/tests/test_gen.rs +++ b/compiler/gen/tests/test_gen.rs @@ -496,44 +496,49 @@ mod test_gen { assert_evals_to!("List.len [ 12, 9, 6, 3 ]", 4, usize); } - #[test] - fn loaded_int_list_len() { - assert_evals_to!( - indoc!( - r#" - nums = [ 2, 4, 6 ] + // #[test] + // fn loaded_int_list_len() { + // assert_evals_to!( + // indoc!( + // r#" + // nums = [ 2, 4, 6 ] - List.len nums - "# - ), - 3, - usize - ); - } + // List.len nums + // "# + // ), + // 3, + // usize + // ); + // } - #[test] - fn fn_int_list_len() { - assert_evals_to!( - indoc!( - r#" - # TODO remove this annotation once monomorphization works! - getLen = \list -> List.len list + // #[test] + // fn fn_int_list_len() { + // assert_evals_to!( + // indoc!( + // r#" + // # TODO remove this annotation once monomorphization works! + // getLen = \list -> List.len list - nums = [ 2, 4, 6 ] + // nums = [ 2, 4, 6 ] - getLen nums - "# - ), - 3, - usize - ); - } + // getLen nums + // "# + // ), + // 3, + // usize + // ); + // } // #[test] // fn int_list_is_empty() { // assert_evals_to!("List.isEmpty [ 12, 9, 6, 3 ]", 0, u8, |x| x); // } + #[test] + fn head_int_list() { + assert_evals_to!("List.getUnsafe [ 12, 9, 6, 3 ] 0", 12, i64); + } + #[test] fn get_int_list() { assert_evals_to!("List.getUnsafe [ 12, 9, 6, 3 ] 1", 9, i64); @@ -1023,7 +1028,7 @@ mod test_gen { assert_evals_to!( indoc!( r#" - Fruit : [ Apple, Orange, Banana ] + Fruit : [ Apple, Orange, Banana ] apple : Fruit apple = Apple From f3c9ac2b40886545e63eb225104dd5fc3aeb679e Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Mon, 20 Jan 2020 23:04:18 -0500 Subject: [PATCH 110/428] Try out some docs --- compiler/builtins/docs/Float.roc | 2 +- compiler/builtins/docs/Int.roc | 64 +++++++++++++++++++++++++++----- compiler/builtins/docs/List.roc | 13 +++++++ compiler/builtins/docs/Num.roc | 35 +++++++++++------ compiler/builtins/docs/Str.roc | 8 +++- 5 files changed, 100 insertions(+), 22 deletions(-) diff --git a/compiler/builtins/docs/Float.roc b/compiler/builtins/docs/Float.roc index 6aabf81c5b..98b21dd8b3 100644 --- a/compiler/builtins/docs/Float.roc +++ b/compiler/builtins/docs/Float.roc @@ -48,7 +48,7 @@ interface Float ## See #Float.highest and #Float.lowest for the highest and ## lowest values that can be held in a #Float. ## -## Like #Int, it's possible for #Float operations to overflow and underflow +## Like #Int, it's possible for #Float operations to overflow. ## if they exceed the bounds of #Float.highest and #Float.lowest. When this happens: ## ## * In a development build, you'll get an assertion failure. diff --git a/compiler/builtins/docs/Int.roc b/compiler/builtins/docs/Int.roc index 58ce030c39..77fc945860 100644 --- a/compiler/builtins/docs/Int.roc +++ b/compiler/builtins/docs/Int.roc @@ -4,9 +4,7 @@ interface Int ## Types -# Integer := Integer - -## A 64-bit signed integer. All number literals without decimal points are #Int values. +## An integer value. All number literals without decimal points are #Int values. ## ## >>> 1 ## @@ -17,22 +15,70 @@ interface Int ## ## >>> 1_000_000 ## -## See #Int.highest and #Int.lowest for the highest and -## lowest values that can be held in an #Int. +## Integers come in two flavors: *signed* and *unsigned*. +## +## * *Unsigned* integers can never be negative. The lowest value they can hold is zero. +## * *Signed* integers can be negative. +## +## Integers also come in different sizes. Choosing a size depends on your performance +## needs and the range of numbers you need to represent. At a high level, the +## general trade-offs are: +## +## * Larger integer sizes can represent a wider range of numbers. If you absolutely need to represent numbers in a certain range, make sure to pick an integer size that can hold them! +## * Smaller integer sizes take up less memory. This savings rarely matters in variables and function arguments, but the sizes of integers that you use in data structures can add up. This can also affect whether those data structures fit in [cache lines](https://en.wikipedia.org/wiki/CPU_cache#Cache_performance), which can be a performance bottleneck. +## * CPUs typically work fastest on their native [word size](https://en.wikipedia.org/wiki/Word_(computer_architecture)). For example, 64-bit CPUs tend to work fastest on 64-bit integers. Especially if your performance profiling shows that you are CPU bound rather than memory bound, consider #Iword or #Uword. +## +## Here are the different fixed size integer types: +## +## | Range | Type | Size | +## |--------------------------------------------------------|-------|----------| +## | ` -128` | #I8 | 1 Byte | +## | ` 127` | | | +## |--------------------------------------------------------|-------|----------| +## | ` 0` | #U8 | 1 Byte | +## | ` 255` | | | +## |--------------------------------------------------------|-------|----------| +## | ` -32_768` | #I16 | 2 Bytes | +## | ` 32_767` | | | +## |--------------------------------------------------------|-------|----------| +## | ` 0` | #U16 | 2 Bytes | +## | ` 65_535` | | | +## |--------------------------------------------------------|-------|----------| +## | ` -2_147_483_648` | #I32 | 4 Bytes | +## | ` 2_147_483_647` | | | +## |--------------------------------------------------------|-------|----------| +## | ` 0` | #U32 | 4 Bytes | +## | ` (over 4 billion) 4_294_967_295` | | | +## |--------------------------------------------------------|-------|----------| +## | ` -9_223_372_036_854_775_808` | #I64 | 8 Bytes | +## | ` 9_223_372_036_854_775_807` | | | +## |--------------------------------------------------------|-------|----------| +## | ` 0` | #U64 | 8 Bytes | +## | ` (over 18 quintillion) 18_446_744_073_709_551_615` | | | +## |--------------------------------------------------------|-------|----------| +## | `-170_141_183_460_469_231_731_687_303_715_884_105_728` | #I128 | 16 Bytes | +## | ` 170_141_183_460_469_231_731_687_303_715_884_105_727` | | | +## |--------------------------------------------------------|-------|----------| +## | ` (over 340 undecillion) 0` | #U128 | 16 Bytes | +## | ` 340_282_366_920_938_463_463_374_607_431_768_211_455` | | | +## +## There are also two variable-size integer types: #Iword and #Uword. +## Their sizes are determined by the machine word size for the system you're +## compiling for. For example, on a 64-bit system, #Iword is the same as #I64, +## and #Uword is the same as #U64. ## ## If any operation would result in an #Int that is either too big ## or too small to fit in that range (e.g. running `Int.highest + 1`), -## then the operation will *overflow* or *underflow*, respectively. -## When this happens: +## then the operation will overflow. When this happens: ## ## * In a development build, you'll get an assertion failure. -## * In a release build, you'll get [wrapping overflow](https://en.wikipedia.org/wiki/Integer_overflow#Saturated_arithmetic), which is almost always a mathematically incorrect outcome for the requested operation. +## * In a release build, you'll get [wrapping overflow](https://en.wikipedia.org/wiki/Integer_overflow), which is almost always a mathematically incorrect outcome for the requested operation. (If you actually want wrapping, because you're writing something like a hash function, use functions like #Int.addWrapping.) ## ## As such, it's very important to design your code not to exceed these bounds! ## If you need to do math outside these bounds, consider using ## a different representation other than #Int. The reason #Int has these ## bounds is for performance reasons. -#Int : Num Integer +# Int size : Num [ @Int size ] ## Arithmetic diff --git a/compiler/builtins/docs/List.roc b/compiler/builtins/docs/List.roc index bdba0779bc..dc870a2acb 100644 --- a/compiler/builtins/docs/List.roc +++ b/compiler/builtins/docs/List.roc @@ -27,6 +27,11 @@ interface List ## > applies to lists that take up 8 machine words in memory or fewer, so ## > for example on a 64-bit system, a list of 8 #Int values will be ## > stored as a flat array instead of as an RRBT. +## +## One #List can store up to 2,147,483,648 elements (just over 2 billion). If you need to store more +## elements than that, you can split them into smaller lists and operate +## on those instead of on one large #List. This often runs faster in practice, +## even for strings much smaller than 2 gigabytes. List elem : @List elem ## Initialize @@ -191,6 +196,13 @@ walkBackwards : List elem, { start : state, step : (state, elem -> state) } -> s ## Check +## Returns the length of the list - the number of elements it contains. +## +## One #List can store up to 2,147,483,648 elements (just over 2 billion), which +## is exactly equal to the highest valid #I32 value. This means the #U32 this function +## returns can always be safely converted to an #I32 without losing any data. +len : List * -> U32 + isEmpty : List * -> Bool contains : List elem, elem -> Bool @@ -198,3 +210,4 @@ contains : List elem, elem -> Bool all : List elem, (elem -> Bool) -> Bool any : List elem, (elem -> Bool) -> Bool + diff --git a/compiler/builtins/docs/Num.roc b/compiler/builtins/docs/Num.roc index 58121d80ce..7eb5ba24cb 100644 --- a/compiler/builtins/docs/Num.roc +++ b/compiler/builtins/docs/Num.roc @@ -1,4 +1,4 @@ -api Num provides Num, DivByZero..., negate, abs, add, sub, mul, isOdd, isEven, isPositive, isNegative, isZero +api Num provides Num, DivByZero..., neg, abs, add, sub, mul, isOdd, isEven, isPositive, isNegative, isZero ## Types @@ -25,17 +25,21 @@ Num range : @Num range ## Return a negative number when given a positive one, and vice versa. ## -## Some languages have a unary `-` operator (for example, `-(a + b)`), but Roc does not. If you want to negate a number, calling this function is the way to do it! +## >>> Num.neg 5 ## -## > Num.neg 5 +## >>> Num.neg -2.5 ## -## > Num.neg -2.5 +## >>> Num.neg 0 ## -## > Num.neg 0 +## >>> Num.neg 0.0 ## -## > Num.neg 0.0 +## This is safe to use with any #Float, but it can cause overflow when used with certain #Int values. ## -## This will crash when given #Int.lowestValue, because doing so will result in a number higher than #Int.highestValue. +## For example, calling #Num.neg on the lowest value of a signed integer (such as #Int.lowestI64 or #Int.lowestI32) will cause overflow. +## This is because, for any given size of signed integer (32-bit, 64-bit, etc.) its negated lowest value turns out to be 1 higher than +## the highest value it can represent. (For this reason, calling #Num.abs on the lowest signed value will also cause overflow.) +## +## Additionally, calling #Num.neg on any unsigned integer (such as any #U64 or #U32 value) other than 0 will cause overflow. ## ## (It will never crash when given a #Float, however, because of how floating point numbers represent positive and negative numbers.) neg : Num range -> Num range @@ -44,14 +48,23 @@ neg : Num range -> Num range ## ## * For a positive number, returns the same number. ## * For a negative number, returns the same number except positive. +## * For zero, returns zero. ## -## > Num.abs 4 +## >>> Num.abs 4 ## -## > Num.abs -2.5 +## >>> Num.abs -2.5 ## -## > Num.abs 0 +## >>> Num.abs 0 ## -## > Num.abs 0.0 +## >>> Num.abs 0.0 +## +## This is safe to use with any #Float, but it can cause overflow when used with certain #Int values. +## +## For example, calling #Num.abs on the lowest value of a signed integer (such as #Int.lowestI64 or #Int.lowestI32) will cause overflow. +## This is because, for any given size of signed integer (32-bit, 64-bit, etc.) its negated lowest value turns out to be 1 higher than +## the highest value it can represent. (For this reason, calling #Num.neg on the lowest signed value will also cause overflow.) +## +## Calling this on an unsigned integer (like #U32 or #U64) never does anything. abs : Num range -> Num range ## Check diff --git a/compiler/builtins/docs/Str.roc b/compiler/builtins/docs/Str.roc index bb208869f0..450189640c 100644 --- a/compiler/builtins/docs/Str.roc +++ b/compiler/builtins/docs/Str.roc @@ -2,7 +2,13 @@ api Str provides Str, isEmpty, join ## Types -Str := Str +## A sequence of [UTF-8](https://en.wikipedia.org/wiki/UTF-8) text characters. +## +## One #Str can be up to 2 gigabytes in size. If you need to store larger +## strings than that, you can split them into smaller chunks and operate +## on those instead of on one large #Str. This often runs faster in practice, +## even for strings much smaller than 2 gigabytes. +Str : [ @Str ] ## Convert From 4033098dace2347a29689e35f66040790e6d3956 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sun, 15 Mar 2020 01:20:24 -0400 Subject: [PATCH 111/428] Update some number docs --- compiler/builtins/docs/Float.roc | 50 ++++++++------ compiler/builtins/docs/Int.roc | 109 +++++++++++++++++++++++++++---- compiler/builtins/docs/List.roc | 3 +- 3 files changed, 129 insertions(+), 33 deletions(-) diff --git a/compiler/builtins/docs/Float.roc b/compiler/builtins/docs/Float.roc index 98b21dd8b3..294550f3c4 100644 --- a/compiler/builtins/docs/Float.roc +++ b/compiler/builtins/docs/Float.roc @@ -151,12 +151,19 @@ div = \numerator, denominator -> ## ## >>> Float.pi ## >>> |> Float.mod 2.0 -#mod : Float, Float -> Result Float DivByZero +mod : Float a, Float a -> Result Float DivByZero -## Return the reciprocal of the #Float. -#recip : Float -> Result Float [ DivByZero ]* -#recip = \float -> -# 1.0 / float +tryMod : Float a, Float a -> Result (Float a) [ DivByZero ]* + +## Return the reciprocal of a #Float - that is, divides `1.0` by the given number. +## +## Crashes if given `0.0`, because division by zero is undefined in mathematics. +## +## For a version that does not crash, use #tryRecip +recip : Float a -> Result (Float a) [ DivByZero ]* + + +tryRecip : Float a -> Result (Float a) [ DivByZero ]* ## Return an approximation of the absolute value of the square root of the #Float. ## @@ -169,32 +176,39 @@ div = \numerator, denominator -> ## >>> Float.sqrt 0.0 ## ## >>> Float.sqrt -4.0 -#sqrt : Float -> Result Float InvalidSqrt +sqrt : Float a -> Result (Float a) [ InvalidSqrt ] ## Constants ## An approximation of e, specifically 2.718281828459045. -#e : Float -e = 2.718281828459045 +e : Float * ## An approximation of pi, specifically 3.141592653589793. -#pi : Float -pi = 3.141592653589793 +pi : Float * + +## Sort ascending - that is, with the lowest first, and the highest last. +## +## List.sort Float.asc [ 3.0, 6.0, 0.0 ] +## +asc : Float a, Float a -> [ Eq, Lt, Gt ] + +## Sort descending - that is, with the highest first, and the lowest last. +## +## List.sort Float.desc [ 3.0, 6.0, 0.0 ] +## +desc : Float a, Float a -> [ Eq, Lt, Gt ] ## Limits ## The highest supported #Float value you can have, which is approximately 1.8 × 10^308. ## ## If you go higher than this, your running Roc code will crash - so be careful not to! -#highest : Float -highest : Num.Num Float.FloatingPoint -highest = 1.0 +highest : Float * ## The lowest supported #Float value you can have, which is approximately -1.8 × 10^308. ## ## If you go lower than this, your running Roc code will crash - so be careful not to! -#lowest : Float -lowest = 1.0 +lowest : Float * ## The highest integer that can be represented as a #Float without # losing precision. ## It is equal to 2^53, which is approximately 9 × 10^15. @@ -206,8 +220,7 @@ lowest = 1.0 ## >>> Float.highestInt + 100 # Increasing may lose precision ## ## >>> Float.highestInt - 100 # Decreasing is fine - but watch out for lowestLosslessInt! -#highestInt : Float -highestInt = 1.0 +highestInt : Float * ## The lowest integer that can be represented as a #Float without losing precision. ## It is equal to -2^53, which is approximately -9 × 10^15. @@ -219,5 +232,4 @@ highestInt = 1.0 ## >>> Float.lowestIntVal - 100 # Decreasing may lose precision ## ## >>> Float.lowestIntVal + 100 # Increasing is fine - but watch out for highestInt! -#lowestInt : Float -lowestInt = 1.0 +lowestInt : Float * diff --git a/compiler/builtins/docs/Int.roc b/compiler/builtins/docs/Int.roc index 77fc945860..e6e235db2b 100644 --- a/compiler/builtins/docs/Int.roc +++ b/compiler/builtins/docs/Int.roc @@ -4,7 +4,72 @@ interface Int ## Types -## An integer value. All number literals without decimal points are #Int values. +## A fixed-size integer - that is, a number with no fractional component. +## +## Integers come in two flavors: signed and unsigned. Signed integers can be +## negative ("signed" refers to how they can incorporate a minus sign), +## whereas unsigned integers cannot be negative. +## +## Since integers have a fixed size, the size you choose determines both the +## range of numbers it can represent, and also how much memory it takes up. +## +## #U8 is an an example of an integer. It is an unsigned #Int that takes up 8 bits +## (aka 1 byte) in memory. The `U` is for Unsigned and the 8 is for 8 bits. +## Because it has 8 bits to work with, it can store 256 numbers (2^8), +## and because it is unsigned, its lowest value is 0. This means the 256 numbers +## it can store range from 0 to 255. +## +## #I8 is a signed integer that takes up 8 bits. The `I` is for Integer, since +## integers in mathematics are signed by default. Because it has 8 bits just +## like #U8, it can store 256 numbers (still 2^16), but because it is signed, +## the range is different. Its 256 numbers range from -128 to 127. +## +## Here are some other examples: +## +## * #U16 is like #U8, except it takes up 16 bytes in memory. It can store 65,536 numbers (2^16), ranging from 0 to 65,536. +## * #I16 is like #U16, except it is signed. It can still store the same 65,536 numbers (2^16), ranging from -32,768 to 32,767. +## +## This pattern continues up to #U128 and #I128. +## +## ## Performance notes +## +## In general, using smaller numeric sizes means your program will use less memory. +## However, if a mathematical operation results in an answer that is too big +## or too small to fit in the size available for that answer (which is typically +## the same size as the inputs), then you'll get an overflow error. +## +## As such, minimizing memory usage without causing overflows involves choosing +## number sizes based on your knowledge of what numbers you expect your program +## to encounter at runtime. +## +## Minimizing memory usage does not imply maximum runtime speed! +## CPUs are typically fastest at performing integer operations on integers that +## are the same size as that CPU's native machine word size. That means a 64-bit +## CPU is typically fastest at executing instructions on #U64 and #I64 values, +## whereas a 32-bit CPU is typically fastest on #U32 and #I32 values. +## +## Putting these factors together, here are some reasonable guidelines for optimizing performance through integer size choice: +## +## * Start by deciding if this integer should allow negative numbers, and choose signed or unsigned accordingly. +## * Next, think about the range of numbers you expect this number to hold. Choose the smallest size you will never expect to overflow, no matter the inputs your program receives. (Validating inputs for size, and presenting the user with an error if they are too big, can help guard against overflow.) +## * Finally, if a particular operation is too slow at runtime, and you know the native machine word size on which it will be running (most often either 64-bit or 32-bit), try switching to an integer of that size and see if it makes a meaningful difference. (The difference is typically extremely small.) +Int size : Num (@Int size) + +## A signed 8-bit integer, ranging from -128 to 127 +I8 : Int @I8 +U8 : Int @U8 +U16 : Int @U16 +I16 : Int @I16 +U32 : Int @U32 +I32 : Int @I32 +I64 : Int @I64 +U64 : Int @U64 +I128 : Int @I128 +U128 : Int @U128 +ILen : Int @ILen +ULen : Int @ULen + +## A 64-bit signed integer. All number literals without decimal points are compatible with #Int values. ## ## >>> 1 ## @@ -26,7 +91,7 @@ interface Int ## ## * Larger integer sizes can represent a wider range of numbers. If you absolutely need to represent numbers in a certain range, make sure to pick an integer size that can hold them! ## * Smaller integer sizes take up less memory. This savings rarely matters in variables and function arguments, but the sizes of integers that you use in data structures can add up. This can also affect whether those data structures fit in [cache lines](https://en.wikipedia.org/wiki/CPU_cache#Cache_performance), which can be a performance bottleneck. -## * CPUs typically work fastest on their native [word size](https://en.wikipedia.org/wiki/Word_(computer_architecture)). For example, 64-bit CPUs tend to work fastest on 64-bit integers. Especially if your performance profiling shows that you are CPU bound rather than memory bound, consider #Iword or #Uword. +## * CPUs typically work fastest on their native [word size](https://en.wikipedia.org/wiki/Word_(computer_architecture)). For example, 64-bit CPUs tend to work fastest on 64-bit integers. Especially if your performance profiling shows that you are CPU bound rather than memory bound, consider #ILen or #ULen. ## ## Here are the different fixed size integer types: ## @@ -68,8 +133,10 @@ interface Int ## and #Uword is the same as #U64. ## ## If any operation would result in an #Int that is either too big -## or too small to fit in that range (e.g. running `Int.highest + 1`), -## then the operation will overflow. When this happens: +## or too small to fit in that range (e.g. calling `Int.highest32 + 1`), +## then the operation will *overflow* or *underflow*, respectively. +## +## When this happens: ## ## * In a development build, you'll get an assertion failure. ## * In a release build, you'll get [wrapping overflow](https://en.wikipedia.org/wiki/Integer_overflow), which is almost always a mathematically incorrect outcome for the requested operation. (If you actually want wrapping, because you're writing something like a hash function, use functions like #Int.addWrapping.) @@ -136,21 +203,37 @@ interface Int #bitwiseNot : Int -> Int +## Sort ascending - that is, with the lowest first, and the highest last. +## +## List.sort Int.asc [ 3, 6, 0 ] +## +asc : Int a, Int a -> [ Eq, Lt, Gt ] + +## Sort descending - that is, with the highest first, and the lowest last. +## +## List.sort Int.desc [ 3, 6, 0 ] +## +desc : Int a, Int a -> [ Eq, Lt, Gt ] + +## TODO should we offer hash32 etc even if it has to do a hash64 and truncate? +## +## CAUTION: This function may give different answers in future releases of Roc, +## so be aware that if you rely on the exact answer this gives today, your +## code may break in a future Roc release. +hash64 : a -> U64 + ## Limits ## The highest number that can be stored in an #Int without overflowing its -## available memory (64 bits total) and crashing. +## available memory and crashing. ## ## Note that this is smaller than the positive version of #Int.lowest, -## which means if you call #Num.abs on #Int.lowest, it will crash! -#highest : Int -highest = 0x7fff_ffff_ffff_ffff +## which means if you call #Num.abs on #Int.lowest, it will overflow and crash! +highest : Int * ## The lowest number that can be stored in an #Int without overflowing its -## available memory (64 bits total) and crashing. +## available memory and crashing. ## ## Note that the positive version of this number is this is larger than -## #Int.highest, which means if you call #Num.abs on #Int.lowest, -## it will crash! -#lowest : Int -lowest = -0x8000_0000_0000_0000 +## #Int.highest, which means if you call #Num.abs on #Int.lowest, it will overflow and crash! +lowest : Int * diff --git a/compiler/builtins/docs/List.roc b/compiler/builtins/docs/List.roc index dc870a2acb..3af0244baf 100644 --- a/compiler/builtins/docs/List.roc +++ b/compiler/builtins/docs/List.roc @@ -59,7 +59,8 @@ fromResult : Result elem * -> List elem reverse : List elem -> List elem -sort : List elem, Sorter elem -> List elem +sort : List elem, (elem, elem -> [ Eq, Lt, Gt ]) -> List elem +sortBy : List elem, (elem -> field), (field, field -> [ Eq, Lt, Gt ]) -> List elem ## Convert each element in the list to something new, by calling a conversion ## function on each of them. Then return a new list of the converted values. From 6088a22cb64242c85ec402aa80b2de6b81e9b766 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sat, 14 Mar 2020 17:52:50 +0100 Subject: [PATCH 112/428] WIP first version that runs --- compiler/mono/src/expr.rs | 21 ++++++++++++++++++--- compiler/mono/src/lib.rs | 1 + 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/compiler/mono/src/expr.rs b/compiler/mono/src/expr.rs index 7ff0f00c83..fd321a13aa 100644 --- a/compiler/mono/src/expr.rs +++ b/compiler/mono/src/expr.rs @@ -838,6 +838,13 @@ fn from_can_when<'a>( Expr::Store(stored.into_bump_slice(), arena.alloc(ret)) } 2 => { + let loc_branches: std::vec::Vec<_> = branches.iter().map(|v| v.0.clone()).collect(); + + match crate::pattern::check(Region::zero(), &loc_branches) { + Ok(_) => {} + Err(errors) => panic!("Errors in patterns: {:?}", errors), + } + // A when-expression with exactly 2 branches compiles to a Cond. let arena = env.arena; let mut iter = branches.into_iter(); @@ -983,7 +990,7 @@ fn from_can_when<'a>( UnsupportedPattern(_region) => { panic!("TODO runtime error for unsupported pattern"); } - AppliedTag(_, _, _) + AppliedTag { .. } | StrLiteral(_) | RecordDestructure(_, _) | FloatLiteral(_) => { @@ -1176,7 +1183,11 @@ fn specialize_proc_body<'a>( #[derive(Clone, Debug, PartialEq)] pub enum Pattern<'a> { Identifier(Symbol), - AppliedTag(TagName, Vec<'a, Pattern<'a>>, Layout<'a>), + AppliedTag { + tag_name: TagName, + arguments: Vec<'a, Pattern<'a>>, + layout: Layout<'a>, + }, BitLiteral(bool), EnumLiteral(u8), IntLiteral(i64), @@ -1236,7 +1247,11 @@ fn from_can_pattern<'a>( mono_args.push(from_can_pattern(env, loc_pat.value)); } - Pattern::AppliedTag(tag_name, mono_args, layout) + Pattern::AppliedTag { + tag_name, + arguments: mono_args, + layout, + } } Err(()) => panic!("Invalid layout"), }, diff --git a/compiler/mono/src/lib.rs b/compiler/mono/src/lib.rs index 5a9f7675c5..2082770978 100644 --- a/compiler/mono/src/lib.rs +++ b/compiler/mono/src/lib.rs @@ -12,3 +12,4 @@ #![allow(clippy::large_enum_variant)] pub mod expr; pub mod layout; +pub mod pattern; From 5926ac2f01ae3e75851fc9ebb51f5486ca4c4f49 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 15 Mar 2020 00:56:35 +0100 Subject: [PATCH 113/428] stub out Tag layout --- compiler/gen/src/crane/convert.rs | 2 +- compiler/gen/src/llvm/convert.rs | 3 +++ compiler/mono/src/layout.rs | 34 +++++++++++++++++-------------- 3 files changed, 23 insertions(+), 16 deletions(-) diff --git a/compiler/gen/src/crane/convert.rs b/compiler/gen/src/crane/convert.rs index 5350de69b8..857cbdf8f9 100644 --- a/compiler/gen/src/crane/convert.rs +++ b/compiler/gen/src/crane/convert.rs @@ -10,7 +10,7 @@ pub fn type_from_layout(cfg: TargetFrontendConfig, layout: &Layout<'_>) -> Type use roc_mono::layout::Layout::*; match layout { - Pointer(_) | FunctionPointer(_, _) | Struct(_) => cfg.pointer_type(), + Pointer(_) | FunctionPointer(_, _) | Struct(_) | Tag(_) => cfg.pointer_type(), Builtin(builtin) => match builtin { Int64 => types::I64, Float64 => types::F64, diff --git a/compiler/gen/src/llvm/convert.rs b/compiler/gen/src/llvm/convert.rs index aa2e0b8143..a17c01933c 100644 --- a/compiler/gen/src/llvm/convert.rs +++ b/compiler/gen/src/llvm/convert.rs @@ -56,6 +56,9 @@ pub fn basic_type_from_layout<'ctx>( Struct(_fields) => { panic!("TODO layout_to_basic_type for Struct"); } + Tag(_fields) => { + panic!("TODO layout_to_basic_type for Tag"); + } Pointer(_layout) => { panic!("TODO layout_to_basic_type for Pointer"); } diff --git a/compiler/mono/src/layout.rs b/compiler/mono/src/layout.rs index 53459bea27..d826a36254 100644 --- a/compiler/mono/src/layout.rs +++ b/compiler/mono/src/layout.rs @@ -10,6 +10,7 @@ use roc_types::subs::{Content, FlatType, Subs, Variable}; pub enum Layout<'a> { Builtin(Builtin<'a>), Struct(&'a [(Lowercase, Layout<'a>)]), + Tag(&'a [Layout<'a>]), Pointer(&'a Layout<'a>), /// A function. The types of its arguments, then the type of its return value. FunctionPointer(&'a [Layout<'a>], &'a Layout<'a>), @@ -92,6 +93,16 @@ impl<'a> Layout<'a> { sum } + Tag(fields) => { + // the symbol is a 64-bit value, so 8 bytes + let mut sum = 8; + + for field_layout in *fields { + sum += field_layout.stack_size(pointer_size); + } + + sum + } Pointer(_) | FunctionPointer(_, _) => pointer_size, } } @@ -264,7 +275,9 @@ fn layout_from_flat_type<'a>( 0 => { panic!("TODO gracefully handle trying to instantiate Never"); } - 1 => { + // We can only unwrap a wrapper if it never becomes part of a bigger union + // therefore, the ext_var must be the literal empty tag union + 1 if ext_var == Variable::EMPTY_TAG_UNION => { // This is a wrapper. Unwrap it! let (tag, args) = tags.into_iter().next().unwrap(); @@ -294,6 +307,7 @@ fn layout_from_flat_type<'a>( // But when one-tag tag unions are optimized away, we can also use an enum for // // [ Foo [ Unit ], Bar [ Unit ] ] + let arguments_have_size_0 = || { tags.iter().all(|(_, args)| { args.iter().all(|var| { @@ -327,7 +341,8 @@ fn layout_from_flat_type<'a>( Ok(Layout::Builtin(Builtin::Byte(tag_to_u8))) } } else { - panic!("TODO handle a tag union with mutliple tags: {:?}", tags); + // panic!("TODO handle a tag union with mutliple tags: {:?}", tags); + Ok(Layout::Tag(&[])) } } } @@ -350,12 +365,7 @@ fn ext_var_is_empty_tag_union(subs: &Subs, ext_var: Variable) -> bool { // the ext_var is empty let mut ext_fields = std::vec::Vec::new(); match roc_types::pretty_print::chase_ext_tag_union(subs, ext_var, &mut ext_fields) { - Ok(()) | Err((_, Content::FlexVar(_))) => { - if !ext_fields.is_empty() { - println!("ext_tags: {:?}", ext_fields); - } - ext_fields.is_empty() - } + Ok(()) | Err((_, Content::FlexVar(_))) => ext_fields.is_empty(), Err(content) => panic!("invalid content in ext_var: {:?}", content), } } @@ -364,13 +374,7 @@ fn ext_var_is_empty_record(subs: &Subs, ext_var: Variable) -> bool { // the ext_var is empty let mut ext_fields = MutMap::default(); match roc_types::pretty_print::chase_ext_record(subs, ext_var, &mut ext_fields) { - Ok(()) | Err((_, Content::FlexVar(_))) => { - if !ext_fields.is_empty() { - println!("ext_fields: {:?}", ext_fields); - } - - ext_fields.is_empty() - } + Ok(()) | Err((_, Content::FlexVar(_))) => ext_fields.is_empty(), Err((_, content)) => panic!("invalid content in ext_var: {:?}", content), } } From 58c09aeabaedd129cebe4b42e55981ca83285b00 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 15 Mar 2020 01:10:58 +0100 Subject: [PATCH 114/428] First pass at pattern exhaustivess checking --- compiler/mono/src/expr.rs | 69 ++--- compiler/mono/src/lib.rs | 4 + compiler/mono/src/pattern.rs | 482 +++++++++++++++++++++++++++++++++++ 3 files changed, 526 insertions(+), 29 deletions(-) create mode 100644 compiler/mono/src/pattern.rs diff --git a/compiler/mono/src/expr.rs b/compiler/mono/src/expr.rs index fd321a13aa..e9d42fd544 100644 --- a/compiler/mono/src/expr.rs +++ b/compiler/mono/src/expr.rs @@ -335,7 +335,13 @@ fn pattern_to_when<'a>( (env.fresh_symbol(), body) } - AppliedTag {..} | RecordDestructure {..} | Shadowed(_, _) | UnsupportedPattern(_) => { + Shadowed(_, _) | UnsupportedPattern(_) => { + // create the runtime error here, instead of delegating to When. + // UnsupportedPattern should then never occcur in When + panic!("TODO generate runtime error here"); + } + + AppliedTag {..} | RecordDestructure {..} => { let symbol = env.fresh_symbol(); let wrapped_body = When { @@ -920,6 +926,13 @@ fn from_can_when<'a>( } } _ => { + let loc_branches: std::vec::Vec<_> = branches.iter().map(|v| v.0.clone()).collect(); + + match crate::pattern::check(Region::zero(), &loc_branches) { + Ok(_) => {} + Err(errors) => panic!("Errors in patterns: {:?}", errors), + } + // This is a when-expression with 3+ branches. let arena = env.arena; let cond = from_can(env, loc_cond.value, procs, None); @@ -949,10 +962,28 @@ fn from_can_when<'a>( let mut jumpable_branches = Vec::with_capacity_in(branches.len(), arena); let mut opt_default_branch = None; - for (loc_when_pat, loc_expr) in branches { + let mut is_last = true; + for (loc_when_pat, loc_expr) in branches.into_iter().rev() { let mono_expr = from_can(env, loc_expr.value, procs, None); let when_pat = from_can_pattern(env, loc_when_pat.value); + if is_last { + opt_default_branch = match &when_pat { + Identifier(symbol) => { + // TODO does this evaluate `cond` twice? + Some(arena.alloc(Expr::Store( + arena.alloc([(*symbol, layout.clone(), cond.clone())]), + arena.alloc(mono_expr.clone()), + ))) + } + Shadowed(_region, _ident) => { + panic!("TODO make runtime exception out of the branch"); + } + _ => Some(arena.alloc(mono_expr.clone())), + }; + is_last = false; + } + match &when_pat { IntLiteral(int) => { // Switch only compares the condition to the @@ -961,35 +992,15 @@ fn from_can_when<'a>( jumpable_branches.push((*int as u64, mono_expr)); } BitLiteral(v) => jumpable_branches.push((*v as u64, mono_expr)), - EnumLiteral(v) => jumpable_branches.push((*v as u64, mono_expr)), - Identifier(symbol) => { - // Since this is an ident, it must be - // the last pattern in the `when`. - // We can safely treat this like an `_` - // except that we need to wrap this branch - // in a `Store` so the identifier is in scope! - - // TODO does this evaluate `cond` twice? - let mono_with_store = Expr::Store( - arena.alloc([(*symbol, layout.clone(), cond.clone())]), - arena.alloc(mono_expr), - ); - - opt_default_branch = Some(arena.alloc(mono_with_store)); - } - Underscore => { - // We should always have exactly one default branch! - debug_assert!(opt_default_branch.is_none()); - - opt_default_branch = Some(arena.alloc(mono_expr)); + EnumLiteral { tag_id , .. } => jumpable_branches.push((*tag_id as u64, mono_expr)), + Identifier(_) => { + // store is handled above } + Underscore => {} Shadowed(_, _) => { panic!("TODO runtime error for shadowing in a pattern"); } - // Example: (5 = 1 + 2) is an unsupported pattern in an assignment; Int patterns aren't allowed in assignments! - UnsupportedPattern(_region) => { - panic!("TODO runtime error for unsupported pattern"); - } + UnsupportedPattern(_region) => unreachable!("When accepts all patterns"), AppliedTag { .. } | StrLiteral(_) | RecordDestructure(_, _) @@ -1189,7 +1200,7 @@ pub enum Pattern<'a> { layout: Layout<'a>, }, BitLiteral(bool), - EnumLiteral(u8), + EnumLiteral {tag_id: u8, enum_size: u8 }, IntLiteral(i64), FloatLiteral(f64), StrLiteral(Box), @@ -1238,7 +1249,7 @@ fn from_can_pattern<'a>( Pattern::BitLiteral(tag_name == top) } Ok(Layout::Builtin(Builtin::Byte(conversion))) => match conversion.get(&tag_name) { - Some(index) => Pattern::EnumLiteral(*index), + Some(index) => Pattern::EnumLiteral{ tag_id : *index, enum_size: conversion.len() as u8 }, None => unreachable!("Tag must be in its own type"), }, Ok(layout) => { diff --git a/compiler/mono/src/lib.rs b/compiler/mono/src/lib.rs index 2082770978..37bd3c4957 100644 --- a/compiler/mono/src/lib.rs +++ b/compiler/mono/src/lib.rs @@ -12,4 +12,8 @@ #![allow(clippy::large_enum_variant)] pub mod expr; pub mod layout; + +// Temporary, while we can build up test cases and optimize the exhaustiveness checking. +// For now, following this warning's advice will lead to nasty type inference errors. +#[allow(clippy::ptr_arg)] pub mod pattern; diff --git a/compiler/mono/src/pattern.rs b/compiler/mono/src/pattern.rs new file mode 100644 index 0000000000..7068d1e725 --- /dev/null +++ b/compiler/mono/src/pattern.rs @@ -0,0 +1,482 @@ +use roc_collections::all::MutMap; +use roc_module::ident::TagName; +use roc_region::all::{Located, Region}; + +use self::Pattern::*; + +#[derive(Clone, Debug, PartialEq)] +pub struct Union { + alternatives: Vec, + num_alts: usize, +} + +#[derive(Clone, Debug, PartialEq)] +pub struct Ctor { + name: TagName, + arity: usize, +} + +#[derive(Clone, Debug, PartialEq)] +pub enum Pattern { + Anything, + Literal(Literal), + Ctor(Union, TagName, std::vec::Vec), +} + +#[derive(Clone, Debug, PartialEq)] +pub enum Literal { + Num(i64), + Int(i64), + Float(f64), + Str(Box), +} + +fn simplify(pattern: &roc_can::pattern::Pattern) -> Pattern { + let mut errors = Vec::new(); + + simplify_help(pattern, &mut errors) +} + +fn simplify_help(pattern: &roc_can::pattern::Pattern, errors: &mut Vec) -> Pattern { + use roc_can::pattern::Pattern::*; + + match pattern { + IntLiteral(v) => Literal(Literal::Int(*v)), + NumLiteral(_, v) => Literal(Literal::Int(*v)), + FloatLiteral(v) => Literal(Literal::Float(*v)), + StrLiteral(v) => Literal(Literal::Str(v.clone())), + + Underscore => Anything, + Identifier(_) => Anything, + RecordDestructure { .. } => { + // TODO we must check the guard conditions! + Anything + } + + Shadowed(_region, _ident) => { + // Treat as an Anything + // code-gen will make a runtime error out of the branch + Anything + } + UnsupportedPattern(_region) => { + // Treat as an Anything + // code-gen will make a runtime error out of the branch + Anything + } + + AppliedTag { + tag_name, + arguments, + .. + } => { + let union = Union { + alternatives: Vec::new(), + num_alts: 0, + }; + let simplified_args: std::vec::Vec<_> = arguments + .iter() + .map(|v| simplify_help(&v.1.value, errors)) + .collect(); + Ctor(union, tag_name.clone(), simplified_args) + } + } +} + +/// Error + +#[derive(Clone, Debug, PartialEq)] +pub enum Error { + Incomplete(Region, Context, Vec), + Redundant(Region, Region, usize), +} + +#[derive(Clone, Debug, PartialEq)] +pub enum Context { + BadArg, + BadDestruct, + BadCase, +} + +/// Check + +pub fn check( + region: Region, + patterns: &[Located], +) -> Result<(), Vec> { + let mut errors = Vec::new(); + check_patterns(region, Context::BadArg, patterns, &mut errors); + + if errors.is_empty() { + Ok(()) + } else { + Err(errors) + } +} + +// pub fn check(module: roc_can::module::ModuleOutput) -> Result<(), Vec> { +// let mut errors = Vec::new(); +// check_declarations(&module.declarations, &mut errors); +// +// if errors.is_empty() { +// Ok(()) +// } else { +// Err(errors) +// } +// } +// +// /// CHECK DECLS +// +// fn check_declarations(decls: &[roc_can::def::Declaration], errors: &mut Vec) { +// use roc_can::def::Declaration; +// +// for decl in decls { +// Declaration::Declare(def) => check_def(def, errors), +// Declaration::DeclareRef(defs) => { +// for def in defs { +// check_def(def, errors); +// } +// } +// Declaration::InvalidCycle(_) => {} +// } +// } +// +// fn check_def(def: &roc_can::def::Def, errors: &mut Vec) { +// check_patttern +// +// +// } + +pub fn check_patterns( + region: Region, + context: Context, + patterns: &[Located], + errors: &mut Vec, +) { + match to_nonredundant_rows(region, patterns) { + Err(err) => errors.push(err), + Ok(matrix) => { + let bad_patterns = is_exhaustive(&matrix, 1); + if !bad_patterns.is_empty() { + // TODO i suspect this is like a concat in in practice? code below can panic + let heads = bad_patterns.into_iter().map(|mut v| v.remove(0)).collect(); + errors.push(Error::Incomplete(region, context, heads)); + } + } + } +} + +/// EXHAUSTIVE PATTERNS + +/// INVARIANTS: +/// +/// The initial rows "matrix" are all of length 1 +/// The initial count of items per row "n" is also 1 +/// The resulting rows are examples of missing patterns +fn is_exhaustive(matrix: &PatternMatrix, n: usize) -> PatternMatrix { + if matrix.is_empty() { + vec![std::iter::repeat(Anything).take(n).collect()] + } else if n == 0 { + vec![] + } else { + let ctors = collect_ctors(matrix); + let num_seen = ctors.len(); + + if num_seen == 0 { + let new_matrix = matrix + .iter() + .filter_map(specialize_row_by_anything) + .collect(); + let mut rest = is_exhaustive(&new_matrix, n - 1); + + for row in rest.iter_mut() { + row.push(Anything); + } + + rest + } else { + let alts = ctors.iter().next().unwrap().1; + + let alt_list = &alts.alternatives; + let num_alts = alts.num_alts; + + if num_seen < num_alts { + let new_matrix = matrix + .iter() + .filter_map(specialize_row_by_anything) + .collect(); + let rest: Vec> = is_exhaustive(&new_matrix, n - 1); + + let last: _ = alt_list + .iter() + .filter_map(|r| is_missing(alts.clone(), ctors.clone(), r)); + + let mut result = Vec::new(); + + for last_option in last { + for mut row in rest.clone() { + row.push(last_option.clone()); + + result.push(row); + } + } + + result + } else { + let is_alt_exhaustive = |Ctor { name, arity }| { + let new_matrix = matrix + .iter() + .filter_map(|r| specialize_row_by_ctor(&name, arity, r)) + .collect(); + let rest: Vec> = is_exhaustive(&new_matrix, arity + n - 1); + + let mut result = Vec::with_capacity(rest.len()); + for row in rest { + result.push(recover_ctor(alts.clone(), name.clone(), arity, row)); + } + + result + }; + + alt_list + .iter() + .cloned() + .map(is_alt_exhaustive) + .flatten() + .collect() + } + } + } +} + +fn is_missing(union: Union, ctors: MutMap, ctor: &Ctor) -> Option { + let Ctor { name, arity, .. } = ctor; + + if ctors.contains_key(&name) { + None + } else { + let anythings = std::iter::repeat(Anything).take(*arity).collect(); + Some(Pattern::Ctor(union, name.clone(), anythings)) + } +} + +fn recover_ctor( + union: Union, + tag_name: TagName, + arity: usize, + mut patterns: Vec, +) -> Vec { + // TODO ensure that this behaves the same as haskell's splitAt + let mut rest = patterns.split_off(arity); + let args = patterns; + + rest.push(Ctor(union, tag_name, args)); + + rest +} + +/// REDUNDANT PATTERNS + +/// INVARIANT: Produces a list of rows where (forall row. length row == 1) +fn to_nonredundant_rows( + overall_region: Region, + patterns: &[Located], +) -> Result>, Error> { + let mut checked_rows = Vec::with_capacity(patterns.len()); + + for loc_pat in patterns { + let region = loc_pat.region; + + let next_row = vec![simplify(&loc_pat.value)]; + + if is_useful(&checked_rows, &next_row) { + checked_rows.push(next_row); + } else { + return Err(Error::Redundant( + overall_region, + region, + checked_rows.len() + 1, + )); + } + } + + Ok(checked_rows) +} + +/// Check if a new row "vector" is useful given previous rows "matrix" +fn is_useful(matrix: &PatternMatrix, vector: &Row) -> bool { + if matrix.is_empty() { + // No rows are the same as the new vector! The vector is useful! + true + } else if vector.is_empty() { + // There is nothing left in the new vector, but we still have + // rows that match the same things. This is not a useful vector! + false + } else { + let mut vector = vector.clone(); + let first_pattern = vector.remove(0); + let patterns = vector; + + match first_pattern { + // keep checking rows that start with this Ctor or Anything + Ctor(_, name, args) => { + let new_matrix: Vec<_> = matrix + .iter() + .filter_map(|r| specialize_row_by_ctor(&name, args.len(), r)) + .collect(); + + let mut new_row = Vec::new(); + new_row.extend(args); + new_row.extend(patterns); + + is_useful(&new_matrix, &new_row) + } + + Anything => { + // check if all alts appear in matrix + match is_complete(matrix) { + Complete::No => { + // This Anything is useful because some Ctors are missing. + // But what if a previous row has an Anything? + // If so, this one is not useful. + let new_matrix: Vec<_> = matrix + .iter() + .filter_map(|r| specialize_row_by_anything(r)) + .collect(); + + is_useful(&new_matrix, &patterns) + } + Complete::Yes(alts) => { + // All Ctors are covered, so this Anything is not needed for any + // of those. But what if some of those Ctors have subpatterns + // that make them less general? If so, this actually is useful! + let is_useful_alt = |Ctor { name, arity, .. }| { + let new_matrix = matrix + .iter() + .filter_map(|r| specialize_row_by_ctor(&name, arity, r)) + .collect(); + let mut new_row: Vec = + std::iter::repeat(Anything).take(arity).collect::>(); + + new_row.extend(patterns.clone()); + + is_useful(&new_matrix, &new_row) + }; + + alts.iter().cloned().any(is_useful_alt) + } + } + } + + Literal(literal) => { + // keep checking rows that start with this Literal or Anything + let new_matrix = matrix + .iter() + .filter_map(|r| specialize_row_by_literal(&literal, r)) + .collect(); + is_useful(&new_matrix, &patterns) + } + } + } +} + +/// INVARIANT: (length row == N) ==> (length result == arity + N - 1) +fn specialize_row_by_ctor(tag_name: &TagName, arity: usize, row: &Row) -> Option { + let mut row = row.clone(); + + let head = row.pop(); + let patterns = row; + + match head { + Some(Ctor(_,name, args)) => + if &name == tag_name { + // TODO order! + let mut new_patterns = Vec::new(); + new_patterns.extend(args); + new_patterns.extend(patterns); + Some(new_patterns) + } else { + None + } + Some(Anything) => { + // TODO order! + let new_patterns = + std::iter::repeat(Anything).take(arity).chain(patterns).collect(); + Some(new_patterns) + } + Some(Literal(_)) => panic!( "Compiler bug! After type checking, constructors and literal should never align in pattern match exhaustiveness checks."), + None => panic!("Compiler error! Empty matrices should not get specialized."), + } +} + +/// INVARIANT: (length row == N) ==> (length result == N-1) +fn specialize_row_by_literal(literal: &Literal, row: &Row) -> Option { + let mut row = row.clone(); + + let head = row.pop(); + let patterns = row; + + match head { + Some(Literal(lit)) => if &lit == literal { Some(patterns) } else{ None } , + Some(Anything) => Some(patterns), + + Some(Ctor(_,_,_)) => panic!( "Compiler bug! After type checking, constructors and literals should never align in pattern match exhaustiveness checks."), + + None => panic!("Compiler error! Empty matrices should not get specialized."), + } +} + +/// INVARIANT: (length row == N) ==> (length result == N-1) +fn specialize_row_by_anything(row: &Row) -> Option { + let mut row = row.clone(); + + match row.pop() { + Some(Anything) => Some(row), + _ => None, + } +} + +/// ALL CONSTRUCTORS ARE PRESENT? + +pub enum Complete { + Yes(Vec), + No, +} + +fn is_complete(matrix: &PatternMatrix) -> Complete { + let ctors = collect_ctors(matrix); + + let mut it = ctors.values(); + + match it.next() { + None => Complete::No, + Some(Union { + alternatives, + num_alts, + .. + }) => { + if ctors.len() == *num_alts { + Complete::Yes(alternatives.to_vec()) + } else { + Complete::No + } + } + } +} + +/// COLLECT CTORS + +type RefPatternMatrix = [Vec]; +type PatternMatrix = Vec>; +type Row = Vec; + +fn collect_ctors(matrix: &RefPatternMatrix) -> MutMap { + let mut ctors = MutMap::default(); + + for row in matrix { + if let Some(Ctor(union, name, _)) = row.get(0) { + ctors.insert(name.clone(), union.clone()); + } + } + + ctors +} From cdf05bc425925b4ba6424fbcc9427b01223e6f8f Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 15 Mar 2020 02:05:53 +0100 Subject: [PATCH 115/428] remove (now unneeded!) default case --- compiler/gen/tests/test_gen.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/compiler/gen/tests/test_gen.rs b/compiler/gen/tests/test_gen.rs index 4e0102d91e..782650bc47 100644 --- a/compiler/gen/tests/test_gen.rs +++ b/compiler/gen/tests/test_gen.rs @@ -1058,7 +1058,6 @@ mod test_gen { Apple -> 1 Banana -> 2 Orange -> 3 - _ -> 4 "# ), 1, From 2e3f21b1ce8fa038ad7a139ce2bdca8aa0c128f2 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 15 Mar 2020 02:09:24 +0100 Subject: [PATCH 116/428] add commented test --- compiler/mono/tests/test_mono.rs | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/compiler/mono/tests/test_mono.rs b/compiler/mono/tests/test_mono.rs index 76a68bd0b1..278de15cfd 100644 --- a/compiler/mono/tests/test_mono.rs +++ b/compiler/mono/tests/test_mono.rs @@ -410,6 +410,7 @@ mod test_mono { }, ); } + #[test] fn three_element_enum() { let arena = Bump::new(); @@ -471,4 +472,35 @@ mod test_mono { ) }); } + + // #[test] + // fn when_on_result() { + // let arena = Bump::new(); + // + // compiles_to_with_interns( + // r#" + // x = Ok 0x3 + // + // when x is + // Err _ -> 0 + // Ok n -> n + 1 + // "#, + // |interns| { + // let home = test_home(); + // let var_x = interns.symbol(home, "x".into()); + // + // let mut fruits = MutMap::default(); + // + // fruits.insert(Global("Banana".into()), 0); + // fruits.insert(Global("Orange".into()), 1); + // fruits.insert(Global("Apple".into()), 2); + // + // let stores = [(var_x, Layout::Builtin(Builtin::Byte(fruits)), Byte(1))]; + // + // let load = Load(var_x); + // + // Store(arena.alloc(stores), arena.alloc(load)) + // }, + // ); + // } } From 58fd1d64ffe0019a8dd1bf490b6afa7fab4c999c Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 15 Mar 2020 02:13:10 +0100 Subject: [PATCH 117/428] do exhaustiveness on mono patterns --- compiler/mono/src/expr.rs | 106 ++++++++++++++++++++++++----------- compiler/mono/src/pattern.rs | 87 +++++++++------------------- 2 files changed, 99 insertions(+), 94 deletions(-) diff --git a/compiler/mono/src/expr.rs b/compiler/mono/src/expr.rs index e9d42fd544..cb201c4a66 100644 --- a/compiler/mono/src/expr.rs +++ b/compiler/mono/src/expr.rs @@ -335,7 +335,7 @@ fn pattern_to_when<'a>( (env.fresh_symbol(), body) } - Shadowed(_, _) | UnsupportedPattern(_) => { + Shadowed(_, _) | UnsupportedPattern(_) => { // create the runtime error here, instead of delegating to When. // UnsupportedPattern should then never occcur in When panic!("TODO generate runtime error here"); @@ -411,7 +411,7 @@ fn from_can<'a>( } // If it wasn't specifically an Identifier & Closure, proceed as normal. - let mono_pattern = from_can_pattern(env, loc_pattern.value); + let mono_pattern = from_can_pattern(env, &loc_pattern.value); store_pattern( env, mono_pattern, @@ -829,7 +829,7 @@ fn from_can_when<'a>( let mut stored = Vec::with_capacity_in(1, arena); let (loc_when_pattern, loc_branch) = branches.into_iter().next().unwrap(); - let mono_pattern = from_can_pattern(env, loc_when_pattern.value); + let mono_pattern = from_can_pattern(env, &loc_when_pattern.value); store_pattern( env, mono_pattern, @@ -844,7 +844,12 @@ fn from_can_when<'a>( Expr::Store(stored.into_bump_slice(), arena.alloc(ret)) } 2 => { - let loc_branches: std::vec::Vec<_> = branches.iter().map(|v| v.0.clone()).collect(); + let loc_branches: std::vec::Vec<_> = branches + .iter() + .map(|(loc_branch, _)| { + Located::at(loc_branch.region, from_can_pattern(env, &loc_branch.value)) + }) + .collect(); match crate::pattern::check(Region::zero(), &loc_branches) { Ok(_) => {} @@ -857,8 +862,8 @@ fn from_can_when<'a>( let (can_loc_when_pat1, loc_then) = iter.next().unwrap(); let (can_loc_when_pat2, loc_else) = iter.next().unwrap(); - let when_pat1 = from_can_pattern(env, can_loc_when_pat1.value); - let when_pat2 = from_can_pattern(env, can_loc_when_pat2.value); + let when_pat1 = from_can_pattern(env, &can_loc_when_pat1.value); + let when_pat2 = from_can_pattern(env, &can_loc_when_pat2.value); let cond_layout = Layout::Builtin(Builtin::Bool( TagName::Global("False".into()), @@ -926,7 +931,12 @@ fn from_can_when<'a>( } } _ => { - let loc_branches: std::vec::Vec<_> = branches.iter().map(|v| v.0.clone()).collect(); + let loc_branches: std::vec::Vec<_> = branches + .iter() + .map(|(loc_branch, _)| { + Located::at(loc_branch.region, from_can_pattern(env, &loc_branch.value)) + }) + .collect(); match crate::pattern::check(Region::zero(), &loc_branches) { Ok(_) => {} @@ -965,7 +975,7 @@ fn from_can_when<'a>( let mut is_last = true; for (loc_when_pat, loc_expr) in branches.into_iter().rev() { let mono_expr = from_can(env, loc_expr.value, procs, None); - let when_pat = from_can_pattern(env, loc_when_pat.value); + let when_pat = from_can_pattern(env, &loc_when_pat.value); if is_last { opt_default_branch = match &when_pat { @@ -976,7 +986,7 @@ fn from_can_when<'a>( arena.alloc(mono_expr.clone()), ))) } - Shadowed(_region, _ident) => { + Shadowed(_region, _ident) => { panic!("TODO make runtime exception out of the branch"); } _ => Some(arena.alloc(mono_expr.clone())), @@ -992,7 +1002,9 @@ fn from_can_when<'a>( jumpable_branches.push((*int as u64, mono_expr)); } BitLiteral(v) => jumpable_branches.push((*v as u64, mono_expr)), - EnumLiteral { tag_id , .. } => jumpable_branches.push((*tag_id as u64, mono_expr)), + EnumLiteral { tag_id, .. } => { + jumpable_branches.push((*tag_id as u64, mono_expr)) + } Identifier(_) => { // store is handled above } @@ -1198,9 +1210,13 @@ pub enum Pattern<'a> { tag_name: TagName, arguments: Vec<'a, Pattern<'a>>, layout: Layout<'a>, + union: crate::pattern::Union, }, BitLiteral(bool), - EnumLiteral {tag_id: u8, enum_size: u8 }, + EnumLiteral { + tag_id: u8, + enum_size: u8, + }, IntLiteral(i64), FloatLiteral(f64), StrLiteral(Box), @@ -1222,21 +1238,21 @@ pub struct RecordDestruct<'a> { fn from_can_pattern<'a>( env: &mut Env<'a, '_>, - can_pattern: roc_can::pattern::Pattern, + can_pattern: &roc_can::pattern::Pattern, ) -> Pattern<'a> { use roc_can::pattern::Pattern::*; match can_pattern { Underscore => Pattern::Underscore, - Identifier(symbol) => Pattern::Identifier(symbol), - IntLiteral(v) => Pattern::IntLiteral(v), - FloatLiteral(v) => Pattern::FloatLiteral(v), - StrLiteral(v) => Pattern::StrLiteral(v), - Shadowed(region, ident) => Pattern::Shadowed(region, ident), - UnsupportedPattern(region) => Pattern::UnsupportedPattern(region), + Identifier(symbol) => Pattern::Identifier(*symbol), + IntLiteral(v) => Pattern::IntLiteral(*v), + FloatLiteral(v) => Pattern::FloatLiteral(*v), + StrLiteral(v) => Pattern::StrLiteral(v.clone()), + Shadowed(region, ident) => Pattern::Shadowed(*region, ident.clone()), + UnsupportedPattern(region) => Pattern::UnsupportedPattern(*region), - NumLiteral(var, num) => match to_int_or_float(env.subs, var) { - IntOrFloat::IntType => Pattern::IntLiteral(num), - IntOrFloat::FloatType => Pattern::FloatLiteral(num as f64), + NumLiteral(var, num) => match to_int_or_float(env.subs, *var) { + IntOrFloat::IntType => Pattern::IntLiteral(*num), + IntOrFloat::FloatType => Pattern::FloatLiteral(*num as f64), }, AppliedTag { @@ -1244,23 +1260,49 @@ fn from_can_pattern<'a>( tag_name, arguments, .. - } => match Layout::from_var(env.arena, whole_var, env.subs, env.pointer_size) { + } => match Layout::from_var(env.arena, *whole_var, env.subs, env.pointer_size) { Ok(Layout::Builtin(Builtin::Bool(_bottom, top))) => { - Pattern::BitLiteral(tag_name == top) + Pattern::BitLiteral(tag_name == &top) } Ok(Layout::Builtin(Builtin::Byte(conversion))) => match conversion.get(&tag_name) { - Some(index) => Pattern::EnumLiteral{ tag_id : *index, enum_size: conversion.len() as u8 }, + Some(index) => Pattern::EnumLiteral { + tag_id: *index, + enum_size: conversion.len() as u8, + }, None => unreachable!("Tag must be in its own type"), }, Ok(layout) => { let mut mono_args = Vec::with_capacity_in(arguments.len(), env.arena); for (_, loc_pat) in arguments { - mono_args.push(from_can_pattern(env, loc_pat.value)); + mono_args.push(from_can_pattern(env, &loc_pat.value)); } + let mut fields = std::vec::Vec::new(); + let union = match roc_types::pretty_print::chase_ext_tag_union( + env.subs, + *whole_var, + &mut fields, + ) { + Ok(()) | Err((_, Content::FlexVar(_))) => { + let mut ctors = std::vec::Vec::with_capacity(fields.len()); + for (tag_name, args) in fields { + ctors.push(crate::pattern::Ctor { + name: tag_name.clone(), + arity: args.len(), + }) + } + + crate::pattern::Union { + alternatives: ctors, + } + } + Err(content) => panic!("invalid content in ext_var: {:?}", content), + }; + Pattern::AppliedTag { - tag_name, + tag_name: tag_name.clone(), arguments: mono_args, + union, layout, } } @@ -1271,11 +1313,11 @@ fn from_can_pattern<'a>( whole_var, destructs, .. - } => match Layout::from_var(env.arena, whole_var, env.subs, env.pointer_size) { + } => match Layout::from_var(env.arena, *whole_var, env.subs, env.pointer_size) { Ok(layout) => { let mut mono_destructs = Vec::with_capacity_in(destructs.len(), env.arena); for loc_rec_des in destructs { - mono_destructs.push(from_can_record_destruct(env, loc_rec_des.value)); + mono_destructs.push(from_can_record_destruct(env, &loc_rec_des.value)); } Pattern::RecordDestructure(mono_destructs, layout) @@ -1287,14 +1329,14 @@ fn from_can_pattern<'a>( fn from_can_record_destruct<'a>( env: &mut Env<'a, '_>, - can_rd: roc_can::pattern::RecordDestruct, + can_rd: &roc_can::pattern::RecordDestruct, ) -> RecordDestruct<'a> { RecordDestruct { - label: can_rd.label, + label: can_rd.label.clone(), symbol: can_rd.symbol, - guard: match can_rd.guard { + guard: match &can_rd.guard { None => None, - Some((_, loc_pattern)) => Some(from_can_pattern(env, loc_pattern.value)), + Some((_, loc_pattern)) => Some(from_can_pattern(env, &loc_pattern.value)), }, } } diff --git a/compiler/mono/src/pattern.rs b/compiler/mono/src/pattern.rs index 7068d1e725..c1b1dfb801 100644 --- a/compiler/mono/src/pattern.rs +++ b/compiler/mono/src/pattern.rs @@ -6,14 +6,13 @@ use self::Pattern::*; #[derive(Clone, Debug, PartialEq)] pub struct Union { - alternatives: Vec, - num_alts: usize, + pub alternatives: Vec, } #[derive(Clone, Debug, PartialEq)] pub struct Ctor { - name: TagName, - arity: usize, + pub name: TagName, + pub arity: usize, } #[derive(Clone, Debug, PartialEq)] @@ -25,27 +24,31 @@ pub enum Pattern { #[derive(Clone, Debug, PartialEq)] pub enum Literal { - Num(i64), Int(i64), + Bit(bool), + Byte(u8), Float(f64), Str(Box), } -fn simplify(pattern: &roc_can::pattern::Pattern) -> Pattern { +fn simplify<'a>(pattern: &crate::expr::Pattern<'a>) -> Pattern { let mut errors = Vec::new(); simplify_help(pattern, &mut errors) } -fn simplify_help(pattern: &roc_can::pattern::Pattern, errors: &mut Vec) -> Pattern { - use roc_can::pattern::Pattern::*; +fn simplify_help<'a>(pattern: &crate::expr::Pattern<'a>, errors: &mut Vec) -> Pattern { + use crate::expr::Pattern::*; match pattern { IntLiteral(v) => Literal(Literal::Int(*v)), - NumLiteral(_, v) => Literal(Literal::Int(*v)), FloatLiteral(v) => Literal(Literal::Float(*v)), StrLiteral(v) => Literal(Literal::Str(v.clone())), + // TODO make sure these are exhaustive + BitLiteral(b) => Literal(Literal::Bit(*b)), + EnumLiteral { tag_id, .. } => Literal(Literal::Byte(*tag_id)), + Underscore => Anything, Identifier(_) => Anything, RecordDestructure { .. } => { @@ -67,17 +70,14 @@ fn simplify_help(pattern: &roc_can::pattern::Pattern, errors: &mut Vec) - AppliedTag { tag_name, arguments, + union, .. } => { - let union = Union { - alternatives: Vec::new(), - num_alts: 0, - }; let simplified_args: std::vec::Vec<_> = arguments .iter() - .map(|v| simplify_help(&v.1.value, errors)) + .map(|v| simplify_help(&v, errors)) .collect(); - Ctor(union, tag_name.clone(), simplified_args) + Ctor(union.clone(), tag_name.clone(), simplified_args) } } } @@ -99,9 +99,9 @@ pub enum Context { /// Check -pub fn check( +pub fn check<'a>( region: Region, - patterns: &[Located], + patterns: &[Located>], ) -> Result<(), Vec> { let mut errors = Vec::new(); check_patterns(region, Context::BadArg, patterns, &mut errors); @@ -113,43 +113,10 @@ pub fn check( } } -// pub fn check(module: roc_can::module::ModuleOutput) -> Result<(), Vec> { -// let mut errors = Vec::new(); -// check_declarations(&module.declarations, &mut errors); -// -// if errors.is_empty() { -// Ok(()) -// } else { -// Err(errors) -// } -// } -// -// /// CHECK DECLS -// -// fn check_declarations(decls: &[roc_can::def::Declaration], errors: &mut Vec) { -// use roc_can::def::Declaration; -// -// for decl in decls { -// Declaration::Declare(def) => check_def(def, errors), -// Declaration::DeclareRef(defs) => { -// for def in defs { -// check_def(def, errors); -// } -// } -// Declaration::InvalidCycle(_) => {} -// } -// } -// -// fn check_def(def: &roc_can::def::Def, errors: &mut Vec) { -// check_patttern -// -// -// } - -pub fn check_patterns( +pub fn check_patterns<'a>( region: Region, context: Context, - patterns: &[Located], + patterns: &[Located>], errors: &mut Vec, ) { match to_nonredundant_rows(region, patterns) { @@ -197,7 +164,7 @@ fn is_exhaustive(matrix: &PatternMatrix, n: usize) -> PatternMatrix { let alts = ctors.iter().next().unwrap().1; let alt_list = &alts.alternatives; - let num_alts = alts.num_alts; + let num_alts = alt_list.len(); if num_seen < num_alts { let new_matrix = matrix @@ -277,9 +244,9 @@ fn recover_ctor( /// REDUNDANT PATTERNS /// INVARIANT: Produces a list of rows where (forall row. length row == 1) -fn to_nonredundant_rows( +fn to_nonredundant_rows<'a>( overall_region: Region, - patterns: &[Located], + patterns: &[Located>], ) -> Result>, Error> { let mut checked_rows = Vec::with_capacity(patterns.len()); @@ -449,12 +416,8 @@ fn is_complete(matrix: &PatternMatrix) -> Complete { match it.next() { None => Complete::No, - Some(Union { - alternatives, - num_alts, - .. - }) => { - if ctors.len() == *num_alts { + Some(Union { alternatives, .. }) => { + if ctors.len() == alternatives.len() { Complete::Yes(alternatives.to_vec()) } else { Complete::No @@ -473,7 +436,7 @@ fn collect_ctors(matrix: &RefPatternMatrix) -> MutMap { let mut ctors = MutMap::default(); for row in matrix { - if let Some(Ctor(union, name, _)) = row.get(0) { + if let Some(Ctor(union, name, _)) = row.get(row.len() - 1) { ctors.insert(name.clone(), union.clone()); } } From 92071457928ce33ebcc9ce92cbb35346a6b8334c Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 15 Mar 2020 14:04:57 +0100 Subject: [PATCH 118/428] remove unneeded error vec --- compiler/mono/src/pattern.rs | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/compiler/mono/src/pattern.rs b/compiler/mono/src/pattern.rs index c1b1dfb801..de05d3a0cc 100644 --- a/compiler/mono/src/pattern.rs +++ b/compiler/mono/src/pattern.rs @@ -32,12 +32,6 @@ pub enum Literal { } fn simplify<'a>(pattern: &crate::expr::Pattern<'a>) -> Pattern { - let mut errors = Vec::new(); - - simplify_help(pattern, &mut errors) -} - -fn simplify_help<'a>(pattern: &crate::expr::Pattern<'a>, errors: &mut Vec) -> Pattern { use crate::expr::Pattern::*; match pattern { @@ -73,10 +67,8 @@ fn simplify_help<'a>(pattern: &crate::expr::Pattern<'a>, errors: &mut Vec union, .. } => { - let simplified_args: std::vec::Vec<_> = arguments - .iter() - .map(|v| simplify_help(&v, errors)) - .collect(); + let simplified_args: std::vec::Vec<_> = + arguments.iter().map(|v| simplify(&v)).collect(); Ctor(union.clone(), tag_name.clone(), simplified_args) } } From 3ea748e532e3f43cfd62a45bc3592a8cb6eba527 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 15 Mar 2020 14:05:20 +0100 Subject: [PATCH 119/428] remove extra check --- compiler/mono/src/layout.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/mono/src/layout.rs b/compiler/mono/src/layout.rs index d826a36254..fa34ce90c3 100644 --- a/compiler/mono/src/layout.rs +++ b/compiler/mono/src/layout.rs @@ -277,7 +277,7 @@ fn layout_from_flat_type<'a>( } // We can only unwrap a wrapper if it never becomes part of a bigger union // therefore, the ext_var must be the literal empty tag union - 1 if ext_var == Variable::EMPTY_TAG_UNION => { + 1 => { // This is a wrapper. Unwrap it! let (tag, args) = tags.into_iter().next().unwrap(); From 8a46765feebb83487c80a3e876f5962051eb2b8e Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 15 Mar 2020 14:15:29 +0100 Subject: [PATCH 120/428] add debug assert for possible invariant --- compiler/mono/src/pattern.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/compiler/mono/src/pattern.rs b/compiler/mono/src/pattern.rs index de05d3a0cc..638587a044 100644 --- a/compiler/mono/src/pattern.rs +++ b/compiler/mono/src/pattern.rs @@ -117,6 +117,10 @@ pub fn check_patterns<'a>( let bad_patterns = is_exhaustive(&matrix, 1); if !bad_patterns.is_empty() { // TODO i suspect this is like a concat in in practice? code below can panic + // if this debug_assert! ever fails, the theory is disproven + debug_assert!( + bad_patterns.iter().map(|v| v.len()).sum::() == bad_patterns.len() + ); let heads = bad_patterns.into_iter().map(|mut v| v.remove(0)).collect(); errors.push(Error::Incomplete(region, context, heads)); } From 6e977342111c9b0359c7952dbd5bed944ca85739 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 15 Mar 2020 14:41:51 +0100 Subject: [PATCH 121/428] ensure Bit/Enum patterns are considered exhaustive --- compiler/mono/src/pattern.rs | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/compiler/mono/src/pattern.rs b/compiler/mono/src/pattern.rs index 638587a044..f8f4183fa2 100644 --- a/compiler/mono/src/pattern.rs +++ b/compiler/mono/src/pattern.rs @@ -39,9 +39,35 @@ fn simplify<'a>(pattern: &crate::expr::Pattern<'a>) -> Pattern { FloatLiteral(v) => Literal(Literal::Float(*v)), StrLiteral(v) => Literal(Literal::Str(v.clone())), - // TODO make sure these are exhaustive - BitLiteral(b) => Literal(Literal::Bit(*b)), - EnumLiteral { tag_id, .. } => Literal(Literal::Byte(*tag_id)), + // To make sure these are exhaustive, we have to "fake" a union here + // TODO: use the hash or some other integer to discriminate between constructors + BitLiteral(b) => { + let union = Union { + alternatives: vec![ + Ctor { + name: TagName::Global("False".into()), + arity: 0, + }, + Ctor { + name: TagName::Global("True".into()), + arity: 0, + }, + ], + }; + + Ctor(union, TagName::Global(format!("{}", b).into()), vec![]) + } + EnumLiteral { tag_id, enum_size } => { + let alternatives = (0..*enum_size) + .map(|id| Ctor { + name: TagName::Global(format!("{}", id).into()), + arity: 0, + }) + .collect(); + + let union = Union { alternatives }; + Ctor(union, TagName::Global(format!("{}", tag_id).into()), vec![]) + } Underscore => Anything, Identifier(_) => Anything, From 010e390fd652fb50766265268805dee1666fa6f9 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sun, 15 Mar 2020 14:30:32 -0400 Subject: [PATCH 122/428] Make using records in defs work --- compiler/gen/src/llvm/build.rs | 12 ++++----- compiler/gen/src/llvm/convert.rs | 28 +++++++++++++++------ compiler/gen/tests/test_gen.rs | 43 ++++++++++++++++++++++++++++++-- compiler/mono/src/layout.rs | 5 +++- 4 files changed, 71 insertions(+), 17 deletions(-) diff --git a/compiler/gen/src/llvm/build.rs b/compiler/gen/src/llvm/build.rs index 8007864fce..68d0125456 100644 --- a/compiler/gen/src/llvm/build.rs +++ b/compiler/gen/src/llvm/build.rs @@ -75,7 +75,7 @@ pub fn build_expr<'a, 'ctx, 'env>( ret_layout, cond_layout, } => { - let ret_type = basic_type_from_layout(env.context, &ret_layout); + let ret_type = basic_type_from_layout(env.arena, env.context, &ret_layout); let switch_args = SwitchArgs { cond_layout: cond_layout.clone(), cond_expr: cond, @@ -92,7 +92,7 @@ pub fn build_expr<'a, 'ctx, 'env>( for (symbol, layout, expr) in stores.iter() { let val = build_expr(env, &scope, parent, &expr, procs); - let expr_bt = basic_type_from_layout(context, &layout); + let expr_bt = basic_type_from_layout(env.arena, context, &layout); let alloca = create_entry_block_alloca( env, parent, @@ -209,7 +209,7 @@ pub fn build_expr<'a, 'ctx, 'env>( } Array { elem_layout, elems } => { let ctx = env.context; - let elem_type = basic_type_from_layout(ctx, elem_layout); + let elem_type = basic_type_from_layout(env.arena, ctx, elem_layout); let builder = env.builder; if elems.is_empty() { @@ -379,7 +379,7 @@ fn build_branch2<'a, 'ctx, 'env>( procs: &Procs<'a>, ) -> BasicValueEnum<'ctx> { let ret_layout = cond.ret_layout; - let ret_type = basic_type_from_layout(env.context, &ret_layout); + let ret_type = basic_type_from_layout(env.arena, env.context, &ret_layout); let cond_expr = build_expr(env, scope, parent, cond.cond, procs); @@ -576,12 +576,12 @@ pub fn build_proc_header<'a, 'ctx, 'env>( let args = proc.args; let arena = env.arena; let context = &env.context; - let ret_type = basic_type_from_layout(context, &proc.ret_layout); + let ret_type = basic_type_from_layout(arena, context, &proc.ret_layout); let mut arg_basic_types = Vec::with_capacity_in(args.len(), arena); let mut arg_symbols = Vec::new_in(arena); for (layout, arg_symbol) in args.iter() { - let arg_type = basic_type_from_layout(env.context, &layout); + let arg_type = basic_type_from_layout(arena, env.context, &layout); arg_basic_types.push(arg_type); arg_symbols.push(arg_symbol); diff --git a/compiler/gen/src/llvm/convert.rs b/compiler/gen/src/llvm/convert.rs index a17c01933c..2c8c16dd6e 100644 --- a/compiler/gen/src/llvm/convert.rs +++ b/compiler/gen/src/llvm/convert.rs @@ -1,3 +1,5 @@ +use bumpalo::collections::Vec; +use bumpalo::Bump; use inkwell::context::Context; use inkwell::types::BasicTypeEnum::{self, *}; use inkwell::types::{ArrayType, BasicType, FunctionType, PointerType, StructType}; @@ -33,6 +35,7 @@ pub fn get_array_type<'ctx>(bt_enum: &BasicTypeEnum<'ctx>, size: u32) -> ArrayTy } pub fn basic_type_from_layout<'ctx>( + arena: &Bump, context: &'ctx Context, layout: &Layout<'_>, ) -> BasicTypeEnum<'ctx> { @@ -41,20 +44,29 @@ pub fn basic_type_from_layout<'ctx>( match layout { FunctionPointer(args, ret_layout) => { - let ret_type = basic_type_from_layout(context, &ret_layout); - let mut arg_basic_types = Vec::with_capacity(args.len()); + let ret_type = basic_type_from_layout(arena, context, &ret_layout); + let mut arg_basic_types = Vec::with_capacity_in(args.len(), arena); for arg_layout in args.iter() { - arg_basic_types.push(basic_type_from_layout(context, arg_layout)); + arg_basic_types.push(basic_type_from_layout(arena, context, arg_layout)); } - let fn_type = get_fn_type(&ret_type, arg_basic_types.as_slice()); + let fn_type = get_fn_type(&ret_type, arg_basic_types.into_bump_slice()); let ptr_type = fn_type.ptr_type(AddressSpace::Generic); ptr_type.as_basic_type_enum() } - Struct(_fields) => { - panic!("TODO layout_to_basic_type for Struct"); + Struct(sorted_fields) => { + // Determine types + let mut field_types = Vec::with_capacity_in(sorted_fields.len(), arena); + + for (_, field_layout) in sorted_fields.iter() { + field_types.push(basic_type_from_layout(arena, context, field_layout)); + } + + context + .struct_type(field_types.into_bump_slice(), false) + .as_basic_type_enum() } Tag(_fields) => { panic!("TODO layout_to_basic_type for Tag"); @@ -74,8 +86,8 @@ pub fn basic_type_from_layout<'ctx>( Map(_, _) | EmptyMap => panic!("TODO layout_to_basic_type for Builtin::Map"), Set(_) | EmptySet => panic!("TODO layout_to_basic_type for Builtin::Set"), List(elem_layout) => { - let ptr_type = - basic_type_from_layout(context, elem_layout).ptr_type(AddressSpace::Generic); + let ptr_type = basic_type_from_layout(arena, context, elem_layout) + .ptr_type(AddressSpace::Generic); collection_wrapper(context, ptr_type).into() } diff --git a/compiler/gen/tests/test_gen.rs b/compiler/gen/tests/test_gen.rs index 782650bc47..0927bc4e01 100644 --- a/compiler/gen/tests/test_gen.rs +++ b/compiler/gen/tests/test_gen.rs @@ -200,7 +200,7 @@ mod test_gen { // Compute main_fn_type before moving subs to Env let layout = Layout::from_content(&arena, content, &subs, POINTER_SIZE) .unwrap_or_else(|err| panic!("Code gen error in test: could not convert to layout. Err was {:?} and Subs were {:?}", err, subs)); - let main_fn_type = basic_type_from_layout(&context, &layout) + let main_fn_type = basic_type_from_layout(&arena, &context, &layout) .fn_type(&[], false); let main_fn_name = "$Test.main"; @@ -335,7 +335,7 @@ mod test_gen { // Compute main_fn_type before moving subs to Env let layout = Layout::from_content(&arena, content, &subs, POINTER_SIZE) .unwrap_or_else(|err| panic!("Code gen error in test: could not convert to layout. Err was {:?} and Subs were {:?}", err, subs)); - let main_fn_type = basic_type_from_layout(&context, &layout) + let main_fn_type = basic_type_from_layout(&arena, &context, &layout) .fn_type(&[], false); let main_fn_name = "$Test.main"; @@ -1097,4 +1097,43 @@ mod test_gen { i64 ); } + + #[test] + fn def_record() { + assert_evals_to!( + indoc!( + r#" + rec = { y: 17, x: 15, z: 19 } + + rec.x + "# + ), + 15, + i64 + ); + + assert_evals_to!( + indoc!( + r#" + rec = { x: 15, y: 17, z: 19 } + + rec.y + "# + ), + 17, + i64 + ); + + assert_evals_to!( + indoc!( + r#" + rec = { x: 15, y: 17, z: 19 } + + rec.z + "# + ), + 19, + i64 + ); + } } diff --git a/compiler/mono/src/layout.rs b/compiler/mono/src/layout.rs index fa34ce90c3..2a7c716e32 100644 --- a/compiler/mono/src/layout.rs +++ b/compiler/mono/src/layout.rs @@ -234,7 +234,7 @@ fn layout_from_flat_type<'a>( } }; - let mut field_layouts; + let mut field_layouts: Vec<'a, (Lowercase, Layout<'a>)>; match ext_layout { Layout::Struct(more_fields) => { @@ -266,6 +266,9 @@ fn layout_from_flat_type<'a>( field_layouts.push((label.clone(), field_layout)); } + // Sort fields by label + field_layouts.sort_by(|(a, _), (b, _)| a.cmp(b)); + Ok(Layout::Struct(field_layouts.into_bump_slice())) } TagUnion(tags, ext_var) => { From d546f4340dcd07f8ef57812bec0f8278924a6a82 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sun, 15 Mar 2020 14:31:13 -0400 Subject: [PATCH 123/428] Improve an error message --- compiler/gen/src/crane/build.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/gen/src/crane/build.rs b/compiler/gen/src/crane/build.rs index 2f5cdfa971..312dc4a65c 100644 --- a/compiler/gen/src/crane/build.rs +++ b/compiler/gen/src/crane/build.rs @@ -194,7 +194,7 @@ pub fn build_expr<'a, B: Backend>( // Is there an existing function for this? let field_size = match inner_expr { Int(_) => std::mem::size_of::(), - _ => panic!("I don't yet know how to calculate the offset for {:?} when building a cranelift struct", val), + other => panic!("I don't yet know how to calculate the offset for {:?} when building a cranelift struct", other), }; let offset = i32::try_from(index * field_size) .expect("TODO handle field size conversion to i32"); From 6cd8afd5d65f07e45f021c5dfcea7b8595852903 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sun, 15 Mar 2020 15:09:31 -0400 Subject: [PATCH 124/428] Fix using structs with records --- compiler/gen/src/crane/build.rs | 27 +++++++++----------- compiler/gen/src/llvm/build.rs | 24 +++++------------ compiler/gen/tests/test_gen.rs | 39 ++++++++++++++++++++++++++++ compiler/mono/src/expr.rs | 44 ++++++++++++++++---------------- compiler/mono/tests/test_mono.rs | 43 ++++++++++++------------------- 5 files changed, 97 insertions(+), 80 deletions(-) diff --git a/compiler/gen/src/crane/build.rs b/compiler/gen/src/crane/build.rs index 312dc4a65c..407dfbdfb8 100644 --- a/compiler/gen/src/crane/build.rs +++ b/compiler/gen/src/crane/build.rs @@ -171,32 +171,29 @@ pub fn build_expr<'a, B: Backend>( name, name, scope ), }, - Struct { layout, fields } => { + Struct(sorted_fields) => { let cfg = env.cfg; + let ptr_bytes = cfg.pointer_bytes() as u32; - // Sort the fields - let mut sorted_fields = Vec::with_capacity_in(fields.len(), env.arena); - for field in fields.iter() { - sorted_fields.push(field); + // The slot size will be the sum of all the fields' sizes + let mut slot_size = 0; + + for (_, field_layout) in sorted_fields.iter() { + slot_size += field_layout.stack_size(ptr_bytes); } - sorted_fields.sort_by_key(|k| &k.0); // Create a slot let slot = builder.create_stack_slot(StackSlotData::new( StackSlotKind::ExplicitSlot, - layout.stack_size(cfg.pointer_bytes() as u32), + slot_size )); // Create instructions for storing each field's expression - for (index, (_, ref inner_expr)) in sorted_fields.iter().enumerate() { - let val = build_expr(env, &scope, module, builder, inner_expr, procs); + for (index, ( field_expr, field_layout)) in sorted_fields.iter().enumerate() { + let val = build_expr(env, &scope, module, builder, field_expr, procs); - // Is there an existing function for this? - let field_size = match inner_expr { - Int(_) => std::mem::size_of::(), - other => panic!("I don't yet know how to calculate the offset for {:?} when building a cranelift struct", other), - }; - let offset = i32::try_from(index * field_size) + let field_size = field_layout.stack_size(ptr_bytes); + let offset = i32::try_from(index * field_size as usize) .expect("TODO handle field size conversion to i32"); builder.ins().stack_store(val, slot, Offset32::new(offset)); diff --git a/compiler/gen/src/llvm/build.rs b/compiler/gen/src/llvm/build.rs index 68d0125456..ecc8563073 100644 --- a/compiler/gen/src/llvm/build.rs +++ b/compiler/gen/src/llvm/build.rs @@ -285,28 +285,18 @@ pub fn build_expr<'a, 'ctx, 'env>( } } - Struct { fields, .. } => { + Struct(sorted_fields) => { let ctx = env.context; let builder = env.builder; - // Sort the fields - let mut sorted_fields = Vec::with_capacity_in(fields.len(), env.arena); - for field in fields.iter() { - sorted_fields.push(field); - } - sorted_fields.sort_by_key(|k| &k.0); - // Determine types - let mut field_types = Vec::with_capacity_in(fields.len(), env.arena); - let mut field_vals = Vec::with_capacity_in(fields.len(), env.arena); + let num_fields = sorted_fields.len(); + let mut field_types = Vec::with_capacity_in(num_fields, env.arena); + let mut field_vals = Vec::with_capacity_in(num_fields, env.arena); - for (_, ref inner_expr) in sorted_fields.iter() { - let val = build_expr(env, &scope, parent, inner_expr, procs); - - let field_type = match inner_expr { - Int(_) => BasicTypeEnum::IntType(ctx.i64_type()), - _ => panic!("I don't yet know how to get Inkwell type for {:?}", val), - }; + for (field_expr, field_layout) in sorted_fields.iter() { + let val = build_expr(env, &scope, parent, field_expr, procs); + let field_type = basic_type_from_layout(env.arena, env.context, &field_layout); field_types.push(field_type); field_vals.push(val); diff --git a/compiler/gen/tests/test_gen.rs b/compiler/gen/tests/test_gen.rs index 0927bc4e01..944a780945 100644 --- a/compiler/gen/tests/test_gen.rs +++ b/compiler/gen/tests/test_gen.rs @@ -1136,4 +1136,43 @@ mod test_gen { i64 ); } + + #[test] + fn fn_record() { + assert_evals_to!( + indoc!( + r#" + getRec = \x -> { y: 17, x, z: 19 } + + (getRec 15).x + "# + ), + 15, + i64 + ); + + assert_evals_to!( + indoc!( + r#" + rec = { x: 15, y: 17, z: 19 } + + rec.y + "# + ), + 17, + i64 + ); + + assert_evals_to!( + indoc!( + r#" + rec = { x: 15, y: 17, z: 19 } + + rec.z + "# + ), + 19, + i64 + ); + } } diff --git a/compiler/mono/src/expr.rs b/compiler/mono/src/expr.rs index cb201c4a66..a70881e21d 100644 --- a/compiler/mono/src/expr.rs +++ b/compiler/mono/src/expr.rs @@ -184,10 +184,7 @@ pub enum Expr<'a> { name: TagName, arguments: &'a [Expr<'a>], }, - Struct { - fields: &'a [(Lowercase, Expr<'a>)], - layout: Layout<'a>, - }, + Struct(&'a [(Expr<'a>, Layout<'a>)]), Access { label: Lowercase, field_layout: Layout<'a>, @@ -626,30 +623,33 @@ fn from_can<'a>( } Record { - record_var, fields, .. + record_var, + mut fields, + .. } => { let arena = env.arena; - let mut field_bodies = Vec::with_capacity_in(fields.len(), arena); + let mut field_tuples = Vec::with_capacity_in(fields.len(), arena); - for (label, field) in fields { - let expr = from_can(env, field.loc_expr.value, procs, None); + match Layout::from_var(arena, record_var, env.subs, env.pointer_size) { + Ok(Layout::Struct(field_layouts)) => { + for (label, field_layout) in field_layouts.into_iter() { + let loc_expr = fields.remove(label).unwrap().loc_expr; + let expr = from_can(env, loc_expr.value, procs, None); - field_bodies.push((label, expr)); - } - - let struct_layout = - match Layout::from_var(arena, record_var, env.subs, env.pointer_size) { - Ok(layout) => layout, - Err(()) => { - // Invalid field! - panic!("TODO gracefully handle Record with invalid struct_layout"); + // TODO try to remove this clone + field_tuples.push((expr, field_layout.clone())); } - }; + } + Ok(_) => { + unreachable!("Somehow a Record did not end up with a Struct layout"); + } + Err(()) => { + // Invalid field! + panic!("TODO gracefully handle Record with invalid struct_layout"); + } + }; - Expr::Struct { - fields: field_bodies.into_bump_slice(), - layout: struct_layout, - } + Expr::Struct(field_tuples.into_bump_slice()) } Tag { diff --git a/compiler/mono/tests/test_mono.rs b/compiler/mono/tests/test_mono.rs index 278de15cfd..179cec98fc 100644 --- a/compiler/mono/tests/test_mono.rs +++ b/compiler/mono/tests/test_mono.rs @@ -128,7 +128,7 @@ mod test_mono { r#" f = \x -> x + 5 - { x: f 0x4, y: f 3.14 } + { y: f 3.14, x: f 0x4 } "#, { use self::Builtin::*; @@ -138,22 +138,16 @@ mod test_mono { let gen_symbol_3 = Interns::from_index(home, 3); let gen_symbol_4 = Interns::from_index(home, 4); - Struct { - fields: &[ - ( - "x".into(), - CallByName(gen_symbol_3, &[(Int(4), Builtin(Int64))]), - ), - ( - "y".into(), - CallByName(gen_symbol_4, &[(Float(3.14), Builtin(Float64))]), - ), - ], - layout: Layout::Struct(&[ - ("x".into(), Builtin(Int64)), - ("y".into(), Builtin(Float64)), - ]), - } + Struct(&[ + ( + CallByName(gen_symbol_3, &[(Int(4), Builtin(Int64))]), + Builtin(Int64), + ), + ( + CallByName(gen_symbol_4, &[(Float(3.14), Builtin(Float64))]), + Builtin(Float64), + ), + ]) }, ) } @@ -183,8 +177,8 @@ mod test_mono { fn multiway_if_expression() { compiles_to( r#" - if True then - "bar" + if True then + "bar" else if False then "foo" else @@ -288,13 +282,10 @@ mod test_mono { CallByName( gen_symbol_3, &[( - Struct { - fields: &[( - "x".into(), - CallByName(gen_symbol_4, &[(Int(4), Builtin(Int64))]), - )], - layout: Layout::Struct(&[("x".into(), Builtin(Int64))]), - }, + Struct(&[( + CallByName(gen_symbol_4, &[(Int(4), Builtin(Int64))]), + Builtin(Int64), + )]), Layout::Struct(&[("x".into(), Builtin(Int64))]), )], ) From 455fabbe880d5f28e4e9c31a17d5c06997db0e0f Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sun, 15 Mar 2020 15:21:20 -0400 Subject: [PATCH 125/428] c to the l to the i p p y --- compiler/mono/src/expr.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/mono/src/expr.rs b/compiler/mono/src/expr.rs index a70881e21d..814cb50846 100644 --- a/compiler/mono/src/expr.rs +++ b/compiler/mono/src/expr.rs @@ -632,7 +632,7 @@ fn from_can<'a>( match Layout::from_var(arena, record_var, env.subs, env.pointer_size) { Ok(Layout::Struct(field_layouts)) => { - for (label, field_layout) in field_layouts.into_iter() { + for (label, field_layout) in field_layouts.iter() { let loc_expr = fields.remove(label).unwrap().loc_expr; let expr = from_can(env, loc_expr.value, procs, None); From 505e69f9566a1577e468e2b97e22807b09343b16 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sun, 15 Mar 2020 15:25:54 -0400 Subject: [PATCH 126/428] Remove a stray space missed by cargo fmt --- compiler/gen/src/crane/build.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/gen/src/crane/build.rs b/compiler/gen/src/crane/build.rs index 407dfbdfb8..1a81e886fb 100644 --- a/compiler/gen/src/crane/build.rs +++ b/compiler/gen/src/crane/build.rs @@ -189,7 +189,7 @@ pub fn build_expr<'a, B: Backend>( )); // Create instructions for storing each field's expression - for (index, ( field_expr, field_layout)) in sorted_fields.iter().enumerate() { + for (index, (field_expr, field_layout)) in sorted_fields.iter().enumerate() { let val = build_expr(env, &scope, module, builder, field_expr, procs); let field_size = field_layout.stack_size(ptr_bytes); From 009e1fa176dd06d2c7dace8646e4f8d657d923bf Mon Sep 17 00:00:00 2001 From: Chad Stearns Date: Sun, 15 Mar 2020 20:57:32 -0400 Subject: [PATCH 127/428] Small changes to region rendering --- compiler/reporting/src/report.rs | 4 ++-- compiler/reporting/tests/test_reporting.rs | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/compiler/reporting/src/report.rs b/compiler/reporting/src/report.rs index e264ed9aff..2a42234b7e 100644 --- a/compiler/reporting/src/report.rs +++ b/compiler/reporting/src/report.rs @@ -116,12 +116,12 @@ impl ReportText { Region(region) => { for i in region.start_line..=region.end_line { buf.push_str(i.to_string().as_str()); - buf.push_str(" |"); + buf.push_str(" ┆"); let line = src_lines[i as usize]; if !line.is_empty() { - buf.push(' '); + buf.push_str(" "); buf.push_str(src_lines[i as usize]); } diff --git a/compiler/reporting/tests/test_reporting.rs b/compiler/reporting/tests/test_reporting.rs index 12d5f8bbb5..de8311bf1a 100644 --- a/compiler/reporting/tests/test_reporting.rs +++ b/compiler/reporting/tests/test_reporting.rs @@ -218,7 +218,7 @@ mod test_report { r#" x = 1 y = 2 - f = \a -> a + 4 + f = \a -> a + 4 f x "# @@ -231,9 +231,9 @@ mod test_report { })), indoc!( r#" - 1 | y = 2 - 2 | f = \a -> a + 4 - 3 |"# + 1 ┆ y = 2 + 2 ┆ f = \a -> a + 4 + 3 ┆"# ), ); } From d6e5aa3bdcae7c5f84f794ccc09c59dbe6666725 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Mon, 16 Mar 2020 00:13:02 -0400 Subject: [PATCH 128/428] Write some Str docs --- compiler/builtins/docs/Str.roc | 110 +++++++++++++++++++++++++++++++-- 1 file changed, 105 insertions(+), 5 deletions(-) diff --git a/compiler/builtins/docs/Str.roc b/compiler/builtins/docs/Str.roc index 450189640c..39664350be 100644 --- a/compiler/builtins/docs/Str.roc +++ b/compiler/builtins/docs/Str.roc @@ -2,12 +2,66 @@ api Str provides Str, isEmpty, join ## Types -## A sequence of [UTF-8](https://en.wikipedia.org/wiki/UTF-8) text characters. +## A [Unicode](https://unicode.org) text value. +## +## Dealing with text is deep topic, so by design, Roc's `Str` module sticks +## to the basics. For more advanced uses such as working with raw [code points](https://en.wikipedia.org/wiki/Code_point), +## see the [roc/unicode](roc/unicode) package, and for locale-specific text +## functions (including capitalization, as capitalization rules vary by locale) +## see the [roc/locale](roc/locale) package. +## +## ### Unicode +## +## Unicode can represent text values which span multiple languages, symbols, and emoji. +## Here are some valid Roc strings: +## +## * "Roc" +## * "鹏" +## * "🐦" +## +## Every Unicode string is a sequence of [grapheme clusters](https://unicode.org/glossary/#grapheme_cluster). +## A grapheme cluster corresponds to what a person reading a string might call +## a "character", but because the term "character" is used to mean many different +## concepts across different programming languages, we intentionally avoid it in Roc. +## Instead, we use the term "clusters" as a shorthand for "grapheme clusters." +## +## You can get the number of grapheme clusters in a string by calling `Str.countClusters` on it: +## +## >>> Str.countClusters "Roc" +## +## >>> Str.countClusters "音乐" +## +## >>> Str.countClusters "👍" +## +## > The `countClusters` function traverses the entire string to calculate its answer, +## > so it's much better for performance to use `Str.isEmpty` instead of +## > calling `Str.countClusters` and checking whether the count was `0`. +## +## ### Escape characters +## +## ### String interpolation +## +## ### Encoding +## +## Whenever any Roc string is created, its [encoding](https://en.wikipedia.org/wiki/Character_encoding) +## comes from a configuration option chosen by [the host](guide|hosts). +## Because of this, None of the functions in this module +## make assumptions about the underlying encoding. After all, different hosts +## may choose different encodings! Here are some factors hosts may consider +## when deciding which encoding to choose: +## +## * Linux APIs typically use [UTF-8](https://en.wikipedia.org/wiki/UTF-8) encoding +## * Windows APIs and Apple [Objective-C](https://en.wikipedia.org/wiki/Objective-C) APIs typically use [UTF-16](https://en.wikipedia.org/wiki/UTF-16) encoding +## * Hosts which call [C](https://en.wikipedia.org/wiki/C_%28programming_language%29) functions may choose [MUTF-8](https://en.wikipedia.org/wiki/UTF-8#Modified_UTF-8) to disallow a valid UTF-8 character which can prematurely terminate C strings +## +## > Roc strings only support Unicode, so they do not support non-Unicode character +## > encodings like [ASCII](https://en.wikipedia.org/wiki/ASCII). +## +## To write code which behaves differently depending on which encoding the host chose, +## the #Str.codeUnits function will do that. However, if you are doing encoding-specific work, +## you should take a look at the [roc/unicode](roc/unicode) pacakge; +## it has many more tools than this module does. ## -## One #Str can be up to 2 gigabytes in size. If you need to store larger -## strings than that, you can split them into smaller chunks and operate -## on those instead of on one large #Str. This often runs faster in practice, -## even for strings much smaller than 2 gigabytes. Str : [ @Str ] ## Convert @@ -59,3 +113,49 @@ padStart : Str, Int, Str -> Str padEnd : Str, Int, Str -> Str +foldClusters : Str, { start: state, step: (state, Str -> state) } -> state + +## Returns #True if the string begins with a capital letter, and #False otherwise. +## +## >>> Str.isCapitalized "hi" +## +## >>> Str.isCapitalized "Hi" +## +## >>> Str.isCapitalized " Hi" +## +## >>> Str.isCapitalized "Česká" +## +## >>> Str.isCapitalized "Э" +## +## >>> Str.isCapitalized "東京" +## +## >>> Str.isCapitalized "🐦" +## +## >>> Str.isCapitalized "" +## +## Since the rules for how to capitalize an uncapitalized string vary by locale, +## see the [roc/locale](roc/locale) package for functions which do that. +isCapitalized : Str -> Bool + + +## Deconstruct the string into raw code unit integers. (Note that code units +## are not the same as code points; to work with code points, see [roc/unicode](roc/unicode)). +## +## This returns a different tag depending on the string encoding chosen by the host. +## +## The size of an individual code unit depends on the encoding. For example, +## in UTF-8 and MUTF-8, a code unit is 8 bits, so those encodings +## are returned as `List U8`. In contrast, UTF-16 encoding uses 16-bit code units, +## so the `Utf16` tag holds a `List U16` instead. +## +## > Code units are no substitute for grapheme clusters! +## > +## > For example, `Str.countGraphemes "👍"` always returns `1` no matter what, +## > whereas `Str.codeUnits "👍"` could give you back a `List U8` with a length +## > of 4, or a `List U16` with a length of 2, neither of which is equal to +## > the correct number of grapheme clusters in that string. +## > +## > This function exists for more advanced use cases like those found in +## > [roc/unicode](roc/unicode), and using code points when grapheme clusters would +## > be more appropriate can very easily lead to bugs. +codeUnits : Str -> [ Utf8 (List U8), Mutf8 (List U8), Ucs2 (List U16), Utf16 (List U16), Utf32 (List U32) ] From aa3030ab85fbb940cce5eb506e484e175a9f1c2e Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Mon, 16 Mar 2020 01:51:12 -0400 Subject: [PATCH 129/428] Revise Str docs --- compiler/builtins/docs/Str.roc | 59 ++++++++++++++++------------------ 1 file changed, 27 insertions(+), 32 deletions(-) diff --git a/compiler/builtins/docs/Str.roc b/compiler/builtins/docs/Str.roc index 39664350be..db8ff78c1e 100644 --- a/compiler/builtins/docs/Str.roc +++ b/compiler/builtins/docs/Str.roc @@ -43,25 +43,16 @@ api Str provides Str, isEmpty, join ## ## ### Encoding ## -## Whenever any Roc string is created, its [encoding](https://en.wikipedia.org/wiki/Character_encoding) -## comes from a configuration option chosen by [the host](guide|hosts). -## Because of this, None of the functions in this module -## make assumptions about the underlying encoding. After all, different hosts -## may choose different encodings! Here are some factors hosts may consider -## when deciding which encoding to choose: -## -## * Linux APIs typically use [UTF-8](https://en.wikipedia.org/wiki/UTF-8) encoding -## * Windows APIs and Apple [Objective-C](https://en.wikipedia.org/wiki/Objective-C) APIs typically use [UTF-16](https://en.wikipedia.org/wiki/UTF-16) encoding -## * Hosts which call [C](https://en.wikipedia.org/wiki/C_%28programming_language%29) functions may choose [MUTF-8](https://en.wikipedia.org/wiki/UTF-8#Modified_UTF-8) to disallow a valid UTF-8 character which can prematurely terminate C strings -## -## > Roc strings only support Unicode, so they do not support non-Unicode character -## > encodings like [ASCII](https://en.wikipedia.org/wiki/ASCII). -## -## To write code which behaves differently depending on which encoding the host chose, -## the #Str.codeUnits function will do that. However, if you are doing encoding-specific work, -## you should take a look at the [roc/unicode](roc/unicode) pacakge; -## it has many more tools than this module does. +## Roc strings are not coupled to any particular +## [encoding](https://en.wikipedia.org/wiki/Character_encoding). As it happens, +## they are currently encoded in UTF-8, but this module is intentionally designed +## not to rely on that implementation detail so that a future release of Roc can +## potentially change it without breaking existing Roc applications. ## +## This module has functions to can convert a #Str to a #List of raw code unit integers +## in a particular encoding, but if you are doing encoding-specific work, +## you should take a look at the [roc/unicode](roc/unicode) pacakge. +## It has many more tools than this module does! Str : [ @Str ] ## Convert @@ -137,25 +128,29 @@ foldClusters : Str, { start: state, step: (state, Str -> state) } -> state ## see the [roc/locale](roc/locale) package for functions which do that. isCapitalized : Str -> Bool - -## Deconstruct the string into raw code unit integers. (Note that code units -## are not the same as code points; to work with code points, see [roc/unicode](roc/unicode)). +## ## Code Units ## -## This returns a different tag depending on the string encoding chosen by the host. +## Besides grapheme clusters, another way to break down strings is into +## raw code unit integers. ## -## The size of an individual code unit depends on the encoding. For example, -## in UTF-8 and MUTF-8, a code unit is 8 bits, so those encodings -## are returned as `List U8`. In contrast, UTF-16 encoding uses 16-bit code units, -## so the `Utf16` tag holds a `List U16` instead. +## The size of a code unit depends on the string's encoding. For example, in a +## string encoded in UTF-8, a code unit is 8 bits. This is why #Str.toUtf8 +## returns a `List U8`. In contrast, UTF-16 encoding uses 16-bit code units, +## so #Str.toUtf16 returns a `List U16` instead. ## ## > Code units are no substitute for grapheme clusters! ## > ## > For example, `Str.countGraphemes "👍"` always returns `1` no matter what, -## > whereas `Str.codeUnits "👍"` could give you back a `List U8` with a length -## > of 4, or a `List U16` with a length of 2, neither of which is equal to -## > the correct number of grapheme clusters in that string. +## > whereas `Str.toUtf8 "👍"` returns a list with a length of 4, +## > and `Str.toUtf16 "👍"` returns a list with a length of 2. ## > -## > This function exists for more advanced use cases like those found in -## > [roc/unicode](roc/unicode), and using code points when grapheme clusters would +## > These functions exists for more advanced use cases like those found in +## > [roc/unicode](roc/unicode), and using code units when grapheme clusters would ## > be more appropriate can very easily lead to bugs. -codeUnits : Str -> [ Utf8 (List U8), Mutf8 (List U8), Ucs2 (List U16), Utf16 (List U16), Utf32 (List U32) ] + +toUtf8 : Str -> List U8 + +toUtf16 : Str -> List U16 + +toUtf32 : Str -> List U16 + From 1bee949ad077f3dd9852feeceb4bd082dcacccf4 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Mon, 16 Mar 2020 02:06:12 -0400 Subject: [PATCH 130/428] Fix some Str docs --- compiler/builtins/docs/Str.roc | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/compiler/builtins/docs/Str.roc b/compiler/builtins/docs/Str.roc index db8ff78c1e..092cdc053a 100644 --- a/compiler/builtins/docs/Str.roc +++ b/compiler/builtins/docs/Str.roc @@ -5,7 +5,7 @@ api Str provides Str, isEmpty, join ## A [Unicode](https://unicode.org) text value. ## ## Dealing with text is deep topic, so by design, Roc's `Str` module sticks -## to the basics. For more advanced uses such as working with raw [code points](https://en.wikipedia.org/wiki/Code_point), +## to the basics. For more advanced use cases like working with raw [code points](https://en.wikipedia.org/wiki/Code_point), ## see the [roc/unicode](roc/unicode) package, and for locale-specific text ## functions (including capitalization, as capitalization rules vary by locale) ## see the [roc/locale](roc/locale) package. @@ -133,24 +133,18 @@ isCapitalized : Str -> Bool ## Besides grapheme clusters, another way to break down strings is into ## raw code unit integers. ## -## The size of a code unit depends on the string's encoding. For example, in a -## string encoded in UTF-8, a code unit is 8 bits. This is why #Str.toUtf8 -## returns a `List U8`. In contrast, UTF-16 encoding uses 16-bit code units, -## so #Str.toUtf16 returns a `List U16` instead. +## Code units are no substitute for grapheme clusters! +## These functions exist to support advanced use cases like those found in +## [roc/unicode](roc/unicode), and using code units when grapheme clusters would +## be more appropriate can very easily lead to bugs. ## -## > Code units are no substitute for grapheme clusters! -## > -## > For example, `Str.countGraphemes "👍"` always returns `1` no matter what, -## > whereas `Str.toUtf8 "👍"` returns a list with a length of 4, -## > and `Str.toUtf16 "👍"` returns a list with a length of 2. -## > -## > These functions exists for more advanced use cases like those found in -## > [roc/unicode](roc/unicode), and using code units when grapheme clusters would -## > be more appropriate can very easily lead to bugs. +## For example, `Str.countGraphemes "👍"` returns `1`, +## whereas `Str.toUtf8 "👍"` returns a list with a length of 4, +## and `Str.toUtf16 "👍"` returns a list with a length of 2. toUtf8 : Str -> List U8 toUtf16 : Str -> List U16 -toUtf32 : Str -> List U16 +toUtf32 : Str -> List U32 From f8dd2fb9a1ad9c5a3ad300809e6daa476faa1dfa Mon Sep 17 00:00:00 2001 From: Chad Stearns Date: Mon, 16 Mar 2020 02:22:07 -0400 Subject: [PATCH 131/428] Very start of render color terminal --- compiler/reporting/src/report.rs | 59 ++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/compiler/reporting/src/report.rs b/compiler/reporting/src/report.rs index 2a42234b7e..b9ce7b7667 100644 --- a/compiler/reporting/src/report.rs +++ b/compiler/reporting/src/report.rs @@ -11,6 +11,38 @@ pub struct Report { pub text: ReportText, } +pub struct Palette { + pub primary: Color, + pub error: Color, +} + +pub enum Color { + White, + Red, +} + + +pub const default_palette: Palette = Palette { + primary: Color::White, + error: Color::Red, +}; + + +impl Color { + pub fn render(self, str: &str) -> String { + use Color::*; + + match self { + Red => { + red(str) + } + White => { + white(str) + } + } + } +} + pub fn can_problem(filename: PathBuf, problem: Problem) -> Report { let mut texts = Vec::new(); @@ -77,6 +109,33 @@ fn newline() -> ReportText { plain_text("\n") } +fn red(str: &str) -> String { + use ReportText::*; + + let mut buf = String::new(); + + buf.push_str("\u{001b}[31m"); + buf.push_str(str); + buf.push_str("\u{001b}[0m"); + + buf +} + +fn white(str: &str) -> String { + use ReportText::*; + + let mut buf = String::new(); + + buf.push_str("\u{001b}[31m"); + buf.push_str(str); + buf.push_str(reset); + + buf +} + + +const reset: &str = "\u{001b}[0m"; + impl ReportText { /// Render to CI console output, where no colors are available. pub fn render_ci( From 9cdd7988fc80f8eb0765ede227f5f11aa7412229 Mon Sep 17 00:00:00 2001 From: Chad Stearns Date: Mon, 16 Mar 2020 02:27:38 -0400 Subject: [PATCH 132/428] Very start of render color terminal --- compiler/reporting/src/report.rs | 35 ++++++++-------- compiler/reporting/tests/test_reporting.rs | 48 ++++++++++++++++++++-- 2 files changed, 60 insertions(+), 23 deletions(-) diff --git a/compiler/reporting/src/report.rs b/compiler/reporting/src/report.rs index b9ce7b7667..484a5a6b3e 100644 --- a/compiler/reporting/src/report.rs +++ b/compiler/reporting/src/report.rs @@ -21,24 +21,18 @@ pub enum Color { Red, } - -pub const default_palette: Palette = Palette { +pub const DEFAULT_PALETTE: Palette = Palette { primary: Color::White, error: Color::Red, }; - impl Color { pub fn render(self, str: &str) -> String { use Color::*; match self { - Red => { - red(str) - } - White => { - white(str) - } + Red => red(str), + White => white(str), } } } @@ -110,8 +104,6 @@ fn newline() -> ReportText { } fn red(str: &str) -> String { - use ReportText::*; - let mut buf = String::new(); buf.push_str("\u{001b}[31m"); @@ -122,19 +114,16 @@ fn red(str: &str) -> String { } fn white(str: &str) -> String { - use ReportText::*; - let mut buf = String::new(); buf.push_str("\u{001b}[31m"); buf.push_str(str); - buf.push_str(reset); + buf.push_str(RESET); buf } - -const reset: &str = "\u{001b}[0m"; +const RESET: &str = "\u{001b}[0m"; impl ReportText { /// Render to CI console output, where no colors are available. @@ -203,13 +192,21 @@ impl ReportText { /// Render to a color terminal using ANSI escape sequences pub fn render_color_terminal( &self, - _buf: &mut String, + buf: &mut String, _subs: &mut Subs, _home: ModuleId, _src_lines: &[&str], _interns: &Interns, + palette: Palette, ) { - // TODO do the same stuff as render_ci, but with colors via ANSI terminal escape codes! - // Examples of how to do this are in the source code of https://github.com/rtfeldman/console-print + use ReportText::*; + + match self { + Plain(string) => { + buf.push_str(&palette.primary.render(string)); + } + + _ => panic!("TODO implement more ReportTexts in render color terminal"), + } } } diff --git a/compiler/reporting/tests/test_reporting.rs b/compiler/reporting/tests/test_reporting.rs index de8311bf1a..fae80d0272 100644 --- a/compiler/reporting/tests/test_reporting.rs +++ b/compiler/reporting/tests/test_reporting.rs @@ -11,7 +11,7 @@ mod helpers; mod test_report { use crate::helpers::test_home; use roc_module::symbol::{Interns, ModuleId}; - use roc_reporting::report::{can_problem, plain_text, Report, ReportText}; + use roc_reporting::report::{can_problem, DEFAULT_PALETTE, plain_text, Report, ReportText}; use roc_types::pretty_print::name_all_type_vars; use roc_types::subs::Subs; use roc_types::types; @@ -82,6 +82,38 @@ mod test_report { assert_eq!(buf, expected_rendering); } + fn report_renders_in_color_from_src(src: &str, report: Report, expected_rendering: &str) { + let (_type_problems, _can_problems, mut subs, home, interns) = infer_expr_help(src); + let mut buf: String = String::new(); + let src_lines: Vec<&str> = src.split('\n').collect(); + + report.text.render_color_terminal( + &mut buf, + &mut subs, + home, + &src_lines, + &interns, + DEFAULT_PALETTE, + ); + + assert_eq!(buf, expected_rendering); + } + + fn report_renders_in_color(report: Report, expected_rendering: &str) { + report_renders_in_color_from_src( + indoc!( + r#" + x = 1 + y = 2 + + x + "# + ), + report, + expected_rendering, + ) + } + fn report_renders_as(report: Report, expected_rendering: &str) { report_renders_as_from_src( indoc!( @@ -202,15 +234,23 @@ mod test_report { buf, indoc!( r#" - y is not used anywhere in your code. + y is not used anywhere in your code. - 1 | y = 2 + 1 ┆ y = 2 - If you didn't intend on using y then remove it so future readers of your code don't wonder why it is there."# + If you didn't intend on using y then remove it so future readers of your code don't wonder why it is there."# ) ); } + #[test] + fn report_in_color() { + report_renders_in_color( + to_simple_report(plain_text("y")), + "\u{001b}[31my\u{001b}[0m", + ); + } + #[test] fn report_region() { report_renders_as_from_src( From 0ed8f90f110e200dda21068ff48b9a9a44896b8c Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Mon, 16 Mar 2020 02:25:31 -0400 Subject: [PATCH 133/428] Fix some type signatures in Str docs --- compiler/builtins/docs/Str.roc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/builtins/docs/Str.roc b/compiler/builtins/docs/Str.roc index 092cdc053a..452f828991 100644 --- a/compiler/builtins/docs/Str.roc +++ b/compiler/builtins/docs/Str.roc @@ -66,10 +66,10 @@ Str : [ @Str ] ## but it's recommended to pass much smaller numbers instead. ## ## Passing a negative number for decimal places is equivalent to passing 0. -decimal : Int, Float -> Str +decimal : Float *, Int * -> Str ## Convert an #Int to a string. -int : Float -> Str +int : Int * -> Str ## Check From 3fa75dc2f7b073aaa64fc36ea3c1b51cf7f1be58 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Mon, 16 Mar 2020 02:26:03 -0400 Subject: [PATCH 134/428] Add Str.reverseClusters to docs --- compiler/builtins/docs/Str.roc | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/builtins/docs/Str.roc b/compiler/builtins/docs/Str.roc index 452f828991..5fab75eba7 100644 --- a/compiler/builtins/docs/Str.roc +++ b/compiler/builtins/docs/Str.roc @@ -103,6 +103,7 @@ padStart : Str, Int, Str -> Str padEnd : Str, Int, Str -> Str +reverseClusters : Str -> Str foldClusters : Str, { start: state, step: (state, Str -> state) } -> state From 6637bfb226a05b439ec204dcb591515858178e09 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Mon, 16 Mar 2020 02:39:49 -0400 Subject: [PATCH 135/428] Add some more Str docs --- compiler/builtins/docs/Str.roc | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/compiler/builtins/docs/Str.roc b/compiler/builtins/docs/Str.roc index 5fab75eba7..bfb757b542 100644 --- a/compiler/builtins/docs/Str.roc +++ b/compiler/builtins/docs/Str.roc @@ -71,6 +71,18 @@ decimal : Float *, Int * -> Str ## Convert an #Int to a string. int : Int * -> Str +## Split a string around a separator. +## +## >>> Str.splitClusters "1,2,3" "," +## +## Passing `""` for the separator is not useful; it returns the original string +## wrapped in a list. +## +## >>> Str.splitClusters "1,2,3" "" +## +## To split a string into its grapheme clusters, use #Str.clusters +splitClusters : Str, Str -> List Str + ## Check isEmpty : Str -> Bool @@ -103,6 +115,16 @@ padStart : Str, Int, Str -> Str padEnd : Str, Int, Str -> Str +## Grapheme Clusters + +## Split a string into its grapheme clusters. +## +## >>> Str.clusters "1,2,3" +## +## >>> Str.clusters "👍👍👍" +## +clusters : Str -> List Str + reverseClusters : Str -> Str foldClusters : Str, { start: state, step: (state, Str -> state) } -> state From 8c7124aba6826f398af5c4b2f3cfd18e0510af93 Mon Sep 17 00:00:00 2001 From: Folkert Date: Mon, 16 Mar 2020 01:45:10 +0100 Subject: [PATCH 136/428] first pass --- compiler/mono/src/decision_tree.rs | 836 +++++++++++++++++++++++++++++ compiler/mono/src/expr.rs | 2 +- compiler/mono/src/lib.rs | 1 + 3 files changed, 838 insertions(+), 1 deletion(-) create mode 100644 compiler/mono/src/decision_tree.rs diff --git a/compiler/mono/src/decision_tree.rs b/compiler/mono/src/decision_tree.rs new file mode 100644 index 0000000000..1c09630641 --- /dev/null +++ b/compiler/mono/src/decision_tree.rs @@ -0,0 +1,836 @@ +use crate::expr::Env; +use crate::expr::Expr; +use crate::expr::Pattern; +use roc_collections::all::{MutMap, MutSet}; +use roc_module::ident::TagName; + +/// COMPILE CASES + +type Label = u64; + +/// Users of this module will mainly interact with this function. It takes +/// some normal branches and gives out a decision tree that has "labels" at all +/// the leafs and a dictionary that maps these "labels" to the code that should +/// run. +pub fn compile<'a>(raw_branches: Vec<(Pattern<'a>, u64)>) -> DecisionTree { + let formatted = raw_branches + .into_iter() + .map(|(pattern, index)| Branch { + goal: index, + patterns: vec![(Path::Empty, pattern)], + }) + .collect(); + + to_decision_tree(formatted) +} + +#[derive(Clone, Debug, PartialEq)] +pub enum DecisionTree { + Match(Label), + Decision { + path: Path, + edges: Vec<(Test, DecisionTree)>, + default: Option>, + }, +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub enum Test { + IsCtor { tag_name: TagName, num_alts: usize }, + IsInt(i64), + // float patterns are stored as i64 so they are comparable/hashable + IsFloat(i64), + IsStr(Box), + IsBit(bool), + IsByte { tag_id: u8, num_alts: usize }, +} + +#[derive(Clone, Debug, PartialEq)] +pub enum Path { + Index { index: u64, path: Box }, + Unbox(Box), + Empty, +} + +// ACTUALLY BUILD DECISION TREES + +#[derive(Clone, Debug, PartialEq)] +struct Branch<'a> { + goal: Label, + patterns: Vec<(Path, Pattern<'a>)>, +} + +fn to_decision_tree(raw_branches: Vec) -> DecisionTree { + let branches: Vec<_> = raw_branches.into_iter().map(flatten_patterns).collect(); + + match check_for_match(&branches) { + Some(goal) => DecisionTree::Match(goal), + None => { + // TODO remove clone + let path = pick_path(branches.clone()); + + let (edges, fallback) = gather_edges(branches, &path); + + let mut decision_edges: Vec<_> = edges + .into_iter() + .map(|(a, b)| (a, to_decision_tree(b))) + .collect(); + + match (decision_edges.split_last_mut(), fallback.split_last()) { + (Some(((_tag, decision_tree), rest)), None) if rest.is_empty() => { + // TODO remove clone + decision_tree.clone() + } + (_, None) => DecisionTree::Decision { + path, + edges: decision_edges, + default: None, + }, + (None, Some(_)) => to_decision_tree(fallback), + _ => DecisionTree::Decision { + path, + edges: decision_edges, + default: Some(Box::new(to_decision_tree(fallback))), + }, + } + } + } +} + +fn is_complete(tests: &[Test]) -> bool { + let length = tests.len(); + debug_assert!(length > 0); + match tests[length - 1] { + Test::IsCtor { num_alts, .. } => length == num_alts, + Test::IsByte { num_alts, .. } => length == num_alts, + Test::IsBit(_) => length == 2, + Test::IsInt(_) => false, + Test::IsFloat(_) => false, + Test::IsStr(_) => false, + } +} + +fn flatten_patterns<'a>(branch: Branch<'a>) -> Branch<'a> { + let mut result = Vec::with_capacity(branch.patterns.len()); + + for path_pattern in branch.patterns { + flatten(path_pattern, &mut result); + } + Branch { + goal: branch.goal, + patterns: result, + } +} + +fn flatten<'a>(path_pattern: (Path, Pattern<'a>), path_patterns: &mut Vec<(Path, Pattern<'a>)>) { + match &path_pattern.1 { + Pattern::AppliedTag { union, .. } => { + if union.alternatives.len() == 1 { + // case map dearg ctorArgs of + // [arg] -> + // flatten (Unbox path, arg) otherPathPatterns + // + // args -> + // foldr flatten otherPathPatterns (subPositions path args) + // subPositions :: Path -> [Can.Pattern] -> [(Path, Can.Pattern)] + // subPositions path patterns = + // Index.indexedMap (\index pattern -> (Index index path, pattern)) patterns + // + // + // dearg :: Can.PatternCtorArg -> Can.Pattern + // dearg (Can.PatternCtorArg _ _ pattern) = + // pattern + + todo!() + } else { + path_patterns.push(path_pattern); + } + } + + _ => { + path_patterns.push(path_pattern); + } + } +} + +/// SUCCESSFULLY MATCH + +/// If the first branch has no more "decision points" we can finally take that +/// path. If that is the case we give the resulting label and a mapping from free +/// variables to "how to get their value". So a pattern like (Just (x,_)) will give +/// us something like ("x" => value.0.0) +fn check_for_match(branches: &Vec) -> Option