From 87b7bb1fac1804c5894f51df5cef0b00fa69c590 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sun, 15 Sep 2024 07:21:28 -0400 Subject: [PATCH 01/65] Fix "opaquees" in error message --- crates/compiler/load/tests/test_reporting.rs | 2 +- crates/compiler/types/src/types.rs | 7 +++++++ crates/reporting/src/error/type.rs | 4 ++-- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/crates/compiler/load/tests/test_reporting.rs b/crates/compiler/load/tests/test_reporting.rs index 6d964dcd12..418f1590dc 100644 --- a/crates/compiler/load/tests/test_reporting.rs +++ b/crates/compiler/load/tests/test_reporting.rs @@ -10971,7 +10971,7 @@ All branches in an `if` must have the same type! 4│ Recursive := [Infinitely Recursive] ^^^^^^^^^ - Recursion in opaquees is only allowed if recursion happens behind a + Recursion in opaques is only allowed if recursion happens behind a tagged union, at least one variant of which is not recursive. " ); diff --git a/crates/compiler/types/src/types.rs b/crates/compiler/types/src/types.rs index f6757247c9..8bb817a208 100644 --- a/crates/compiler/types/src/types.rs +++ b/crates/compiler/types/src/types.rs @@ -3516,6 +3516,13 @@ impl AliasKind { AliasKind::Opaque => "opaque", } } + + pub fn as_str_plural(&self) -> &'static str { + match self { + AliasKind::Structural => "aliases", + AliasKind::Opaque => "opaques", + } + } } #[derive(Clone, Debug, PartialEq, Eq)] diff --git a/crates/reporting/src/error/type.rs b/crates/reporting/src/error/type.rs index 23fed78a23..1c24ac124a 100644 --- a/crates/reporting/src/error/type.rs +++ b/crates/reporting/src/error/type.rs @@ -518,8 +518,8 @@ pub fn cyclic_alias<'b>( ) -> (RocDocBuilder<'b>, String) { let when_is_recursion_legal = alloc.reflow("Recursion in ") - .append(alloc.reflow(alias_kind.as_str())) - .append(alloc.reflow("es is only allowed if recursion happens behind a tagged union, at least one variant of which is not recursive.")); + .append(alloc.reflow(alias_kind.as_str_plural())) + .append(alloc.reflow(" is only allowed if recursion happens behind a tagged union, at least one variant of which is not recursive.")); let doc = if others.is_empty() { alloc.stack([ From b9fea8c6f42f620a8561ef5051fd41a420a747f9 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Wed, 25 Sep 2024 22:11:02 -0400 Subject: [PATCH 02/65] Add specialize_types crate --- Cargo.lock | 18 + crates/compiler/specialize_types/Cargo.toml | 23 ++ crates/compiler/specialize_types/src/lib.rs | 414 ++++++++++++++++++++ 3 files changed, 455 insertions(+) create mode 100644 crates/compiler/specialize_types/Cargo.toml create mode 100644 crates/compiler/specialize_types/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 1925825b62..9fafe1cfa7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3128,6 +3128,24 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "roc_specialize_types" +version = "0.0.1" +dependencies = [ + "arrayvec 0.7.4", + "bitvec", + "bumpalo", + "hashbrown", + "indoc", + "parking_lot", + "roc_can", + "roc_collections", + "roc_debug_flags", + "roc_region", + "roc_types", + "static_assertions", +] + [[package]] name = "roc_std" version = "0.0.1" diff --git a/crates/compiler/specialize_types/Cargo.toml b/crates/compiler/specialize_types/Cargo.toml new file mode 100644 index 0000000000..c3076b4ae5 --- /dev/null +++ b/crates/compiler/specialize_types/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "roc_specialize_types" +description = "Convert a type-checked canonical IR to a monomorphized IR by creating specializations of all functions, such that they are monomorphic in types. We will specialize again later after computing lambda sets, which happens after this pass." + +authors.workspace = true +edition.workspace = true +license.workspace = true +version.workspace = true + +[dependencies] +roc_can = { path = "../can" } +roc_region = { path = "../region" } +roc_types = { path = "../types" } +roc_collections = { path = "../collections" } +roc_debug_flags = { path = "../debug_flags" } + +bitvec.workspace = true +arrayvec.workspace = true +bumpalo.workspace = true +hashbrown.workspace = true +parking_lot.workspace = true +static_assertions.workspace = true +indoc.workspace = true diff --git a/crates/compiler/specialize_types/src/lib.rs b/crates/compiler/specialize_types/src/lib.rs new file mode 100644 index 0000000000..cebaa5b124 --- /dev/null +++ b/crates/compiler/specialize_types/src/lib.rs @@ -0,0 +1,414 @@ +use roc_can::expr::Expr; +use roc_types::subs::Subs; +use std::collections::VecDeque; + +use roc_can::expr::{Expr, WhenBranch, ClosureData, Field, FunctionDef}; +use roc_types::subs::Subs; +use std::collections::VecDeque; +use roc_region::all::{Loc, Region}; + +pub fn specialize_expr(expr: Expr, subs: &Subs) -> Expr { + let mut stack = VecDeque::new(); + stack.push_back(expr); + + let mut result_stack = VecDeque::new(); + + while let Some(current_expr) = stack.pop_back() { + let specialized = match current_expr { + Expr::When { + mut loc_cond, + cond_var, + expr_var, + region, + mut branches, + branches_cond_var, + exhaustive, + } => { + stack.push_back(*loc_cond.value); + for branch in branches.iter_mut() { + stack.push_back(branch.value.value.clone()); + if let Some(guard) = &branch.guard { + stack.push_back(guard.value.clone()); + } + } + + let specialized_cond = result_stack.pop_back().unwrap(); + let specialized_branches: Vec = branches + .into_iter() + .rev() + .map(|mut branch| { + let specialized_value = result_stack.pop_back().unwrap(); + let specialized_guard = branch.guard.map(|_| result_stack.pop_back().unwrap()); + WhenBranch { + patterns: branch.patterns, + value: Loc::at(branch.value.region, specialized_value), + guard: specialized_guard.map(|g| Loc::at(branch.value.region, g)), + redundant: branch.redundant, + } + }) + .collect(); + + Expr::When { + loc_cond: Box::new(Loc::at(loc_cond.region, specialized_cond)), + cond_var: subs.specialize_var(cond_var), + expr_var: subs.specialize_var(expr_var), + region, + branches: specialized_branches, + branches_cond_var: subs.specialize_var(branches_cond_var), + exhaustive, + } + }, + Expr::If { + cond_var, + branch_var, + mut branches, + mut final_else, + } => { + for (cond, body) in branches.iter_mut() { + stack.push_back(cond.value.clone()); + stack.push_back(body.value.clone()); + } + stack.push_back(final_else.value.clone()); + + let specialized_final_else = result_stack.pop_back().unwrap(); + let specialized_branches: Vec<(Loc, Loc)> = branches + .into_iter() + .rev() + .map(|(cond, body)| { + let specialized_body = result_stack.pop_back().unwrap(); + let specialized_cond = result_stack.pop_back().unwrap(); + ( + Loc::at(cond.region, specialized_cond), + Loc::at(body.region, specialized_body), + ) + }) + .collect(); + + Expr::If { + cond_var: subs.specialize_var(cond_var), + branch_var: subs.specialize_var(branch_var), + branches: specialized_branches, + final_else: Box::new(Loc::at(final_else.region, specialized_final_else)), + } + }, + Expr::LetRec(mut defs, mut body, illegal_cycle_mark) => { + for def in defs.iter_mut() { + stack.push_back(def.loc_expr.value.clone()); + } + stack.push_back(body.value.clone()); + + let specialized_body = result_stack.pop_back().unwrap(); + let specialized_defs: Vec = defs + .into_iter() + .rev() + .map(|mut def| { + let specialized_expr = result_stack.pop_back().unwrap(); + Def { + loc_pattern: def.loc_pattern, + loc_expr: Loc::at(def.loc_expr.region, specialized_expr), + expr_var: subs.specialize_var(def.expr_var), + pattern_vars: def.pattern_vars.into_iter().map(|(k, v)| (k, subs.specialize_var(v))).collect(), + annotation: def.annotation.map(|a| a.specialize(subs)), + } + }) + .collect(); + + Expr::LetRec( + specialized_defs, + Box::new(Loc::at(body.region, specialized_body)), + illegal_cycle_mark, + ) + }, + Expr::LetNonRec(mut def, mut body) => { + stack.push_back(def.loc_expr.value.clone()); + stack.push_back(body.value.clone()); + + let specialized_body = result_stack.pop_back().unwrap(); + let specialized_expr = result_stack.pop_back().unwrap(); + + Expr::LetNonRec( + Box::new(Def { + loc_pattern: def.loc_pattern, + loc_expr: Loc::at(def.loc_expr.region, specialized_expr), + expr_var: subs.specialize_var(def.expr_var), + pattern_vars: def.pattern_vars.into_iter().map(|(k, v)| (k, subs.specialize_var(v))).collect(), + annotation: def.annotation.map(|a| a.specialize(subs)), + }), + Box::new(Loc::at(body.region, specialized_body)), + ) + }, + Expr::Call(mut boxed_tuple, mut args, called_via) => { + stack.push_back(boxed_tuple.1.value.clone()); + for (_, arg) in args.iter_mut() { + stack.push_back(arg.value.clone()); + } + + let specialized_args: Vec<(Variable, Loc)> = args + .into_iter() + .rev() + .map(|(var, loc_expr)| { + let specialized_expr = result_stack.pop_back().unwrap(); + (subs.specialize_var(var), Loc::at(loc_expr.region, specialized_expr)) + }) + .collect(); + + let specialized_fn = result_stack.pop_back().unwrap(); + let (fn_var, _, closure_var, expr_var) = *boxed_tuple; + + Expr::Call( + Box::new(( + subs.specialize_var(fn_var), + Loc::at(boxed_tuple.1.region, specialized_fn), + subs.specialize_var(closure_var), + subs.specialize_var(expr_var), + )), + specialized_args, + called_via, + ) + }, + Expr::Closure(mut closure_data) => { + stack.push_back(closure_data.loc_body.value.clone()); + + let specialized_body = result_stack.pop_back().unwrap(); + + Expr::Closure(ClosureData { + function_type: subs.specialize_var(closure_data.function_type), + closure_type: subs.specialize_var(closure_data.closure_type), + return_type: subs.specialize_var(closure_data.return_type), + name: closure_data.name, + captured_symbols: closure_data.captured_symbols + .into_iter() + .map(|(s, v)| (s, subs.specialize_var(v))) + .collect(), + recursive: closure_data.recursive, + arguments: closure_data.arguments + .into_iter() + .map(|(v, am, p)| (subs.specialize_var(v), am, p)) + .collect(), + loc_body: Box::new(Loc::at(closure_data.loc_body.region, specialized_body)), + }) + }, + Expr::Record { record_var, mut fields } => { + for (_, field) in fields.iter_mut() { + stack.push_back(field.loc_expr.value.clone()); + } + + let specialized_fields: SendMap = fields + .into_iter() + .map(|(label, mut field)| { + let specialized_expr = result_stack.pop_back().unwrap(); + (label, Field { + var: subs.specialize_var(field.var), + region: field.region, + loc_expr: Box::new(Loc::at(field.loc_expr.region, specialized_expr)), + }) + }) + .collect(); + + Expr::Record { + record_var: subs.specialize_var(record_var), + fields: specialized_fields, + } + }, + Expr::Tuple { tuple_var, mut elems } => { + for (_, elem) in elems.iter_mut() { + stack.push_back(elem.value.clone()); + } + + let specialized_elems: Vec<(Variable, Box>)> = elems + .into_iter() + .rev() + .map(|(var, elem)| { + let specialized_expr = result_stack.pop_back().unwrap(); + (subs.specialize_var(var), Box::new(Loc::at(elem.region, specialized_expr))) + }) + .collect(); + + Expr::Tuple { + tuple_var: subs.specialize_var(tuple_var), + elems: specialized_elems, + } + }, + Expr::RecordAccess { record_var, ext_var, field_var, mut loc_expr, field } => { + stack.push_back(loc_expr.value.clone()); + + let specialized_expr = result_stack.pop_back().unwrap(); + + Expr::RecordAccess { + record_var: subs.specialize_var(record_var), + ext_var: subs.specialize_var(ext_var), + field_var: subs.specialize_var(field_var), + loc_expr: Box::new(Loc::at(loc_expr.region, specialized_expr)), + field, + } + }, + Expr::TupleAccess { tuple_var, ext_var, elem_var, mut loc_expr, index } => { + stack.push_back(loc_expr.value.clone()); + + let specialized_expr = result_stack.pop_back().unwrap(); + + Expr::TupleAccess { + tuple_var: subs.specialize_var(tuple_var), + ext_var: subs.specialize_var(ext_var), + elem_var: subs.specialize_var(elem_var), + loc_expr: Box::new(Loc::at(loc_expr.region, specialized_expr)), + index, + } + }, + Expr::Tag { tag_union_var, ext_var, name, mut arguments } => { + for (_, arg) in arguments.iter_mut() { + stack.push_back(arg.value.clone()); + } + + let specialized_arguments: Vec<(Variable, Loc)> = arguments + .into_iter() + .rev() + .map(|(var, loc_expr)| { + let specialized_expr = result_stack.pop_back().unwrap(); + (subs.specialize_var(var), Loc::at(loc_expr.region, specialized_expr)) + }) + .collect(); + + Expr::Tag { + tag_union_var: subs.specialize_var(tag_union_var), + ext_var: subs.specialize_var(ext_var), + name, + arguments: specialized_arguments, + } + }, + Expr::OpaqueRef { opaque_var, name, mut argument, specialized_def_type, type_arguments, lambda_set_variables } => { + stack.push_back(argument.1.value.clone()); + + let specialized_arg_expr = result_stack.pop_back().unwrap(); + + Expr::OpaqueRef { + opaque_var: subs.specialize_var(opaque_var), + name, + argument: Box::new((subs.specialize_var(argument.0), Loc::at(argument.1.region, specialized_arg_expr))), + specialized_def_type: Box::new(subs.specialize_type(*specialized_def_type)), + type_arguments: type_arguments.into_iter().map(|v| subs.specialize_optable_var(v)).collect(), + lambda_set_variables, + } + }, + Expr::Expect { mut loc_condition, mut loc_continuation, lookups_in_cond } => { + stack.push_back(loc_condition.value.clone()); + stack.push_back(loc_continuation.value.clone()); + + let specialized_continuation = result_stack.pop_back().unwrap(); + let specialized_condition = result_stack.pop_back().unwrap(); + + Expr::Expect { + loc_condition: Box::new(Loc::at(loc_condition.region, specialized_condition)), + loc_continuation: Box::new(Loc::at(loc_continuation.region, specialized_continuation)), + lookups_in_cond: lookups_in_cond.into_iter().map(|l| ExpectLookup { + symbol: l.symbol, + var: subs.specialize_var(l.var), + ability_info: l.ability_info, + }).collect(), + } + }, + Expr::ExpectFx { mut loc_condition, mut loc_continuation, lookups_in_cond } => { + stack.push_back(loc_condition.value.clone()); + stack.push_back(loc_continuation.value.clone()); + + let specialized_continuation = result_stack.pop_back().unwrap(); + let specialized_condition = result_stack.pop_back().unwrap(); + + Expr::ExpectFx { + loc_condition: Box::new(Loc::at(loc_condition.region, specialized_condition)), + loc_continuation: Box::new(Loc::at(loc_continuation.region, specialized_continuation)), + lookups_in_cond: lookups_in_cond.into_iter().map(|l| ExpectLookup { + symbol: l.symbol, + var: subs.specialize_var(l.var), + ability_info: l.ability_info, + }).collect(), + } + }, + Expr::Dbg { source_location, source, mut loc_message, mut loc_continuation, variable, symbol } => { + stack.push_back(loc_message.value.clone()); + stack.push_back(loc_continuation.value.clone()); + + let specialized_continuation = result_stack.pop_back().unwrap(); + let specialized_message = result_stack.pop_back().unwrap(); + + Expr::Dbg { + source_location, + source, + loc_message: Box::new(Loc::at(loc_message.region, specialized_message)), + loc_continuation: Box::new(Loc::at(loc_continuation.region, specialized_continuation)), + variable: subs.specialize_var(variable), + symbol, + } + }, + // For other expression types that don't contain nested expressions, + // implement the specialization logic directly here + Expr::Num(var, str, int_value, num_bound) => + Expr::Num(subs.specialize_var(var), str, int_value, num_bound), + Expr::Int(var1, var2, str, int_value, int_bound) => + Expr::Int(subs.specialize_var(var1), subs.specialize_var(var2), str, int_value, int_bound), + Expr::Float(var1, var2, str, f64, float_bound) => + Expr::Float(subs.specialize_var(var1), subs.specialize_var(var2), str, f64, float_bound), + Expr::Str(str) => Expr::Str(str), + Expr::SingleQuote(var1, var2, ch, single_quote_bound) => + Expr::SingleQuote(subs.specialize_var(var1), subs.specialize_var(var2), ch, single_quote_bound), + Expr::IngestedFile(path_buf, arc, var) => + Expr::IngestedFile(path_buf, arc, subs.specialize_var(var)), + Expr::Var(symbol, var) => + Expr::Var(symbol, subs.specialize_var(var)), + Expr::ParamsVar { symbol, var, params_symbol, params_var } => + Expr::ParamsVar { + symbol, + var: subs.specialize_var(var), + params_symbol, + params_var: subs.specialize_var(params_var) + }, + Expr::AbilityMember(symbol, specialization_i(struct_accessor_data) => + Expr::RecordAccessor(StructAccessorData { + name: struct_accessor_data.name, + function_var: subs.specialize_var(struct_accessor_data.function_var), + record_var: subs.specialize_var(struct_accessor_data.record_var), + closure_var: subs.specialize_var(struct_accessor_data.closure_var), + ext_var: subs.specialize_var(struct_accessor_data.ext_var), + field_var: subs.specialize_var(struct_accessor_data.field_var), + field: struct_accessor_data.field, + }), + Expr::RecordUpdate { record_var, ext_var, symbol, updates } => + Expr::RecordUpdate { + record_var: subs.specialize_var(record_var), + ext_var: subs.specialize_var(ext_var), + symbol, + updates: updates.into_iter().map(|(k, v)| (k, Field { + var: subs.specialize_var(v.var), + region: v.region, + loc_expr: Box::new(Loc::at(v.loc_expr.region, specialize_expr(v.loc_expr.value, subs))), + })).collect(), + }, + Expr::ZeroArgumentTag { closure_name, variant_var, ext_var, name } => + Expr::ZeroArgumentTag { + closure_name, + variant_var: subs.specialize_var(variant_var), + ext_var: subs.specialize_var(ext_var), + name, + }, + Expr::OpaqueWrapFunction(opaque_wrap_function_data) => + Expr::OpaqueWrapFunction(OpaqueWrapFunctionData { + opaque_name: opaque_wrap_function_data.opaque_name, + opaque_var: subs.specialize_var(opaque_wrap_function_data.opaque_var), + specialized_def_type: subs.specialize_type(opaque_wrap_function_data.specialized_def_type), + type_arguments: opaque_wrap_function_data.type_arguments.into_iter().map(|v| subs.specialize_optable_var(v)).collect(), + lambda_set_variables: opaque_wrap_function_data.lambda_set_variables, + function_name: opaque_wrap_function_data.function_name, + function_var: subs.specialize_var(opaque_wrap_function_data.function_var), + argument_var: subs.specialize_var(opaque_wrap_function_data.argument_var), + closure_var: subs.specialize_var(opaque_wrap_function_data.closure_var), + }), + Expr::TypedHole(var) => + Expr::TypedHole(subs.specialize_var(var)), + Expr::RuntimeError(runtime_error) => + Expr::RuntimeError(runtime_error), + }; + result_stack.push_back(specialized); + } + + result_stack.pop_back().unwrap() +} From f4f9bcb9dd87bdb03ed7d836b07fd2d4dbe5f52b Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Thu, 26 Sep 2024 22:09:23 -0400 Subject: [PATCH 03/65] First pass at specialize_types --- Cargo.lock | 1 + crates/compiler/specialize_types/Cargo.toml | 2 + crates/compiler/specialize_types/src/lib.rs | 568 ++++++-------------- 3 files changed, 169 insertions(+), 402 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9fafe1cfa7..26e50d9a43 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3141,6 +3141,7 @@ dependencies = [ "roc_can", "roc_collections", "roc_debug_flags", + "roc_module", "roc_region", "roc_types", "static_assertions", diff --git a/crates/compiler/specialize_types/Cargo.toml b/crates/compiler/specialize_types/Cargo.toml index c3076b4ae5..b363573ec5 100644 --- a/crates/compiler/specialize_types/Cargo.toml +++ b/crates/compiler/specialize_types/Cargo.toml @@ -13,6 +13,8 @@ roc_region = { path = "../region" } roc_types = { path = "../types" } roc_collections = { path = "../collections" } roc_debug_flags = { path = "../debug_flags" } +# roc_module is only used for TagName +roc_module = { path = "../module" } bitvec.workspace = true arrayvec.workspace = true diff --git a/crates/compiler/specialize_types/src/lib.rs b/crates/compiler/specialize_types/src/lib.rs index cebaa5b124..b961b8aab8 100644 --- a/crates/compiler/specialize_types/src/lib.rs +++ b/crates/compiler/specialize_types/src/lib.rs @@ -1,414 +1,178 @@ -use roc_can::expr::Expr; -use roc_types::subs::Subs; -use std::collections::VecDeque; +use std::cell::RefCell; +use std::collections::HashMap; +use std::rc::Rc; -use roc_can::expr::{Expr, WhenBranch, ClosureData, Field, FunctionDef}; -use roc_types::subs::Subs; -use std::collections::VecDeque; -use roc_region::all::{Loc, Region}; +use roc_types::{ + subs::{Content, FlatType, RecordFields, Subs, TagExt, UnionTags, Variable}, + types::RecordField, +}; -pub fn specialize_expr(expr: Expr, subs: &Subs) -> Expr { - let mut stack = VecDeque::new(); - stack.push_back(expr); +#[derive(Clone, Debug)] +pub enum TyContent { + TFn(Rc, Rc), + TTag(Vec<(String, Vec>)>), + TRecord(Vec<(String, Rc)>), + TPrim(Primitive), +} - let mut result_stack = VecDeque::new(); +#[derive(Clone, Debug)] +pub enum Primitive { + Str, + Int, + Erased, +} - while let Some(current_expr) = stack.pop_back() { - let specialized = match current_expr { - Expr::When { - mut loc_cond, - cond_var, - expr_var, - region, - mut branches, - branches_cond_var, - exhaustive, - } => { - stack.push_back(*loc_cond.value); - for branch in branches.iter_mut() { - stack.push_back(branch.value.value.clone()); - if let Some(guard) = &branch.guard { - stack.push_back(guard.value.clone()); - } - } +#[derive(Clone, Debug)] +pub struct Ty(RefCell); - let specialized_cond = result_stack.pop_back().unwrap(); - let specialized_branches: Vec = branches - .into_iter() - .rev() - .map(|mut branch| { - let specialized_value = result_stack.pop_back().unwrap(); - let specialized_guard = branch.guard.map(|_| result_stack.pop_back().unwrap()); - WhenBranch { - patterns: branch.patterns, - value: Loc::at(branch.value.region, specialized_value), - guard: specialized_guard.map(|g| Loc::at(branch.value.region, g)), - redundant: branch.redundant, - } - }) - .collect(); +type MonoCache = RefCell>>; - Expr::When { - loc_cond: Box::new(Loc::at(loc_cond.region, specialized_cond)), - cond_var: subs.specialize_var(cond_var), - expr_var: subs.specialize_var(expr_var), - region, - branches: specialized_branches, - branches_cond_var: subs.specialize_var(branches_cond_var), - exhaustive, - } - }, - Expr::If { - cond_var, - branch_var, - mut branches, - mut final_else, - } => { - for (cond, body) in branches.iter_mut() { - stack.push_back(cond.value.clone()); - stack.push_back(body.value.clone()); - } - stack.push_back(final_else.value.clone()); +pub fn fresh_mono_cache() -> MonoCache { + RefCell::new(HashMap::new()) +} - let specialized_final_else = result_stack.pop_back().unwrap(); - let specialized_branches: Vec<(Loc, Loc)> = branches - .into_iter() - .rev() - .map(|(cond, body)| { - let specialized_body = result_stack.pop_back().unwrap(); - let specialized_cond = result_stack.pop_back().unwrap(); - ( - Loc::at(cond.region, specialized_cond), - Loc::at(body.region, specialized_body), - ) - }) - .collect(); +fn unlink_tvar(subs: &Subs, mut var: Variable) -> Variable { + loop { + match subs.get_content_without_compacting(var) { + Content::Alias(_, _, real, _) => var = *real, + _ => break var, + } + } +} - Expr::If { - cond_var: subs.specialize_var(cond_var), - branch_var: subs.specialize_var(branch_var), - branches: specialized_branches, - final_else: Box::new(Loc::at(final_else.region, specialized_final_else)), - } - }, - Expr::LetRec(mut defs, mut body, illegal_cycle_mark) => { - for def in defs.iter_mut() { - stack.push_back(def.loc_expr.value.clone()); - } - stack.push_back(body.value.clone()); +fn ty_unfilled() -> TyContent { + TyContent::TTag(vec![("__unfilled".to_string(), vec![])]) +} - let specialized_body = result_stack.pop_back().unwrap(); - let specialized_defs: Vec = defs - .into_iter() - .rev() - .map(|mut def| { - let specialized_expr = result_stack.pop_back().unwrap(); - Def { - loc_pattern: def.loc_pattern, - loc_expr: Loc::at(def.loc_expr.region, specialized_expr), - expr_var: subs.specialize_var(def.expr_var), - pattern_vars: def.pattern_vars.into_iter().map(|(k, v)| (k, subs.specialize_var(v))).collect(), - annotation: def.annotation.map(|a| a.specialize(subs)), - } - }) - .collect(); - - Expr::LetRec( - specialized_defs, - Box::new(Loc::at(body.region, specialized_body)), - illegal_cycle_mark, - ) - }, - Expr::LetNonRec(mut def, mut body) => { - stack.push_back(def.loc_expr.value.clone()); - stack.push_back(body.value.clone()); - - let specialized_body = result_stack.pop_back().unwrap(); - let specialized_expr = result_stack.pop_back().unwrap(); - - Expr::LetNonRec( - Box::new(Def { - loc_pattern: def.loc_pattern, - loc_expr: Loc::at(def.loc_expr.region, specialized_expr), - expr_var: subs.specialize_var(def.expr_var), - pattern_vars: def.pattern_vars.into_iter().map(|(k, v)| (k, subs.specialize_var(v))).collect(), - annotation: def.annotation.map(|a| a.specialize(subs)), - }), - Box::new(Loc::at(body.region, specialized_body)), - ) - }, - Expr::Call(mut boxed_tuple, mut args, called_via) => { - stack.push_back(boxed_tuple.1.value.clone()); - for (_, arg) in args.iter_mut() { - stack.push_back(arg.value.clone()); - } - - let specialized_args: Vec<(Variable, Loc)> = args - .into_iter() - .rev() - .map(|(var, loc_expr)| { - let specialized_expr = result_stack.pop_back().unwrap(); - (subs.specialize_var(var), Loc::at(loc_expr.region, specialized_expr)) - }) - .collect(); - - let specialized_fn = result_stack.pop_back().unwrap(); - let (fn_var, _, closure_var, expr_var) = *boxed_tuple; - - Expr::Call( - Box::new(( - subs.specialize_var(fn_var), - Loc::at(boxed_tuple.1.region, specialized_fn), - subs.specialize_var(closure_var), - subs.specialize_var(expr_var), - )), - specialized_args, - called_via, - ) - }, - Expr::Closure(mut closure_data) => { - stack.push_back(closure_data.loc_body.value.clone()); - - let specialized_body = result_stack.pop_back().unwrap(); - - Expr::Closure(ClosureData { - function_type: subs.specialize_var(closure_data.function_type), - closure_type: subs.specialize_var(closure_data.closure_type), - return_type: subs.specialize_var(closure_data.return_type), - name: closure_data.name, - captured_symbols: closure_data.captured_symbols - .into_iter() - .map(|(s, v)| (s, subs.specialize_var(v))) - .collect(), - recursive: closure_data.recursive, - arguments: closure_data.arguments - .into_iter() - .map(|(v, am, p)| (subs.specialize_var(v), am, p)) - .collect(), - loc_body: Box::new(Loc::at(closure_data.loc_body.region, specialized_body)), - }) - }, - Expr::Record { record_var, mut fields } => { - for (_, field) in fields.iter_mut() { - stack.push_back(field.loc_expr.value.clone()); - } - - let specialized_fields: SendMap = fields - .into_iter() - .map(|(label, mut field)| { - let specialized_expr = result_stack.pop_back().unwrap(); - (label, Field { - var: subs.specialize_var(field.var), - region: field.region, - loc_expr: Box::new(Loc::at(field.loc_expr.region, specialized_expr)), - }) - }) - .collect(); - - Expr::Record { - record_var: subs.specialize_var(record_var), - fields: specialized_fields, - } - }, - Expr::Tuple { tuple_var, mut elems } => { - for (_, elem) in elems.iter_mut() { - stack.push_back(elem.value.clone()); - } - - let specialized_elems: Vec<(Variable, Box>)> = elems - .into_iter() - .rev() - .map(|(var, elem)| { - let specialized_expr = result_stack.pop_back().unwrap(); - (subs.specialize_var(var), Box::new(Loc::at(elem.region, specialized_expr))) - }) - .collect(); - - Expr::Tuple { - tuple_var: subs.specialize_var(tuple_var), - elems: specialized_elems, - } - }, - Expr::RecordAccess { record_var, ext_var, field_var, mut loc_expr, field } => { - stack.push_back(loc_expr.value.clone()); - - let specialized_expr = result_stack.pop_back().unwrap(); - - Expr::RecordAccess { - record_var: subs.specialize_var(record_var), - ext_var: subs.specialize_var(ext_var), - field_var: subs.specialize_var(field_var), - loc_expr: Box::new(Loc::at(loc_expr.region, specialized_expr)), - field, - } - }, - Expr::TupleAccess { tuple_var, ext_var, elem_var, mut loc_expr, index } => { - stack.push_back(loc_expr.value.clone()); - - let specialized_expr = result_stack.pop_back().unwrap(); - - Expr::TupleAccess { - tuple_var: subs.specialize_var(tuple_var), - ext_var: subs.specialize_var(ext_var), - elem_var: subs.specialize_var(elem_var), - loc_expr: Box::new(Loc::at(loc_expr.region, specialized_expr)), - index, - } - }, - Expr::Tag { tag_union_var, ext_var, name, mut arguments } => { - for (_, arg) in arguments.iter_mut() { - stack.push_back(arg.value.clone()); - } - - let specialized_arguments: Vec<(Variable, Loc)> = arguments - .into_iter() - .rev() - .map(|(var, loc_expr)| { - let specialized_expr = result_stack.pop_back().unwrap(); - (subs.specialize_var(var), Loc::at(loc_expr.region, specialized_expr)) - }) - .collect(); - - Expr::Tag { - tag_union_var: subs.specialize_var(tag_union_var), - ext_var: subs.specialize_var(ext_var), - name, - arguments: specialized_arguments, - } - }, - Expr::OpaqueRef { opaque_var, name, mut argument, specialized_def_type, type_arguments, lambda_set_variables } => { - stack.push_back(argument.1.value.clone()); - - let specialized_arg_expr = result_stack.pop_back().unwrap(); - - Expr::OpaqueRef { - opaque_var: subs.specialize_var(opaque_var), - name, - argument: Box::new((subs.specialize_var(argument.0), Loc::at(argument.1.region, specialized_arg_expr))), - specialized_def_type: Box::new(subs.specialize_type(*specialized_def_type)), - type_arguments: type_arguments.into_iter().map(|v| subs.specialize_optable_var(v)).collect(), - lambda_set_variables, - } - }, - Expr::Expect { mut loc_condition, mut loc_continuation, lookups_in_cond } => { - stack.push_back(loc_condition.value.clone()); - stack.push_back(loc_continuation.value.clone()); - - let specialized_continuation = result_stack.pop_back().unwrap(); - let specialized_condition = result_stack.pop_back().unwrap(); - - Expr::Expect { - loc_condition: Box::new(Loc::at(loc_condition.region, specialized_condition)), - loc_continuation: Box::new(Loc::at(loc_continuation.region, specialized_continuation)), - lookups_in_cond: lookups_in_cond.into_iter().map(|l| ExpectLookup { - symbol: l.symbol, - var: subs.specialize_var(l.var), - ability_info: l.ability_info, - }).collect(), - } - }, - Expr::ExpectFx { mut loc_condition, mut loc_continuation, lookups_in_cond } => { - stack.push_back(loc_condition.value.clone()); - stack.push_back(loc_continuation.value.clone()); - - let specialized_continuation = result_stack.pop_back().unwrap(); - let specialized_condition = result_stack.pop_back().unwrap(); - - Expr::ExpectFx { - loc_condition: Box::new(Loc::at(loc_condition.region, specialized_condition)), - loc_continuation: Box::new(Loc::at(loc_continuation.region, specialized_continuation)), - lookups_in_cond: lookups_in_cond.into_iter().map(|l| ExpectLookup { - symbol: l.symbol, - var: subs.specialize_var(l.var), - ability_info: l.ability_info, - }).collect(), - } - }, - Expr::Dbg { source_location, source, mut loc_message, mut loc_continuation, variable, symbol } => { - stack.push_back(loc_message.value.clone()); - stack.push_back(loc_continuation.value.clone()); - - let specialized_continuation = result_stack.pop_back().unwrap(); - let specialized_message = result_stack.pop_back().unwrap(); - - Expr::Dbg { - source_location, - source, - loc_message: Box::new(Loc::at(loc_message.region, specialized_message)), - loc_continuation: Box::new(Loc::at(loc_continuation.region, specialized_continuation)), - variable: subs.specialize_var(variable), - symbol, - } - }, - // For other expression types that don't contain nested expressions, - // implement the specialization logic directly here - Expr::Num(var, str, int_value, num_bound) => - Expr::Num(subs.specialize_var(var), str, int_value, num_bound), - Expr::Int(var1, var2, str, int_value, int_bound) => - Expr::Int(subs.specialize_var(var1), subs.specialize_var(var2), str, int_value, int_bound), - Expr::Float(var1, var2, str, f64, float_bound) => - Expr::Float(subs.specialize_var(var1), subs.specialize_var(var2), str, f64, float_bound), - Expr::Str(str) => Expr::Str(str), - Expr::SingleQuote(var1, var2, ch, single_quote_bound) => - Expr::SingleQuote(subs.specialize_var(var1), subs.specialize_var(var2), ch, single_quote_bound), - Expr::IngestedFile(path_buf, arc, var) => - Expr::IngestedFile(path_buf, arc, subs.specialize_var(var)), - Expr::Var(symbol, var) => - Expr::Var(symbol, subs.specialize_var(var)), - Expr::ParamsVar { symbol, var, params_symbol, params_var } => - Expr::ParamsVar { - symbol, - var: subs.specialize_var(var), - params_symbol, - params_var: subs.specialize_var(params_var) - }, - Expr::AbilityMember(symbol, specialization_i(struct_accessor_data) => - Expr::RecordAccessor(StructAccessorData { - name: struct_accessor_data.name, - function_var: subs.specialize_var(struct_accessor_data.function_var), - record_var: subs.specialize_var(struct_accessor_data.record_var), - closure_var: subs.specialize_var(struct_accessor_data.closure_var), - ext_var: subs.specialize_var(struct_accessor_data.ext_var), - field_var: subs.specialize_var(struct_accessor_data.field_var), - field: struct_accessor_data.field, - }), - Expr::RecordUpdate { record_var, ext_var, symbol, updates } => - Expr::RecordUpdate { - record_var: subs.specialize_var(record_var), - ext_var: subs.specialize_var(ext_var), - symbol, - updates: updates.into_iter().map(|(k, v)| (k, Field { - var: subs.specialize_var(v.var), - region: v.region, - loc_expr: Box::new(Loc::at(v.loc_expr.region, specialize_expr(v.loc_expr.value, subs))), - })).collect(), - }, - Expr::ZeroArgumentTag { closure_name, variant_var, ext_var, name } => - Expr::ZeroArgumentTag { - closure_name, - variant_var: subs.specialize_var(variant_var), - ext_var: subs.specialize_var(ext_var), - name, - }, - Expr::OpaqueWrapFunction(opaque_wrap_function_data) => - Expr::OpaqueWrapFunction(OpaqueWrapFunctionData { - opaque_name: opaque_wrap_function_data.opaque_name, - opaque_var: subs.specialize_var(opaque_wrap_function_data.opaque_var), - specialized_def_type: subs.specialize_type(opaque_wrap_function_data.specialized_def_type), - type_arguments: opaque_wrap_function_data.type_arguments.into_iter().map(|v| subs.specialize_optable_var(v)).collect(), - lambda_set_variables: opaque_wrap_function_data.lambda_set_variables, - function_name: opaque_wrap_function_data.function_name, - function_var: subs.specialize_var(opaque_wrap_function_data.function_var), - argument_var: subs.specialize_var(opaque_wrap_function_data.argument_var), - closure_var: subs.specialize_var(opaque_wrap_function_data.closure_var), - }), - Expr::TypedHole(var) => - Expr::TypedHole(subs.specialize_var(var)), - Expr::RuntimeError(runtime_error) => - Expr::RuntimeError(runtime_error), - }; - result_stack.push_back(specialized); +pub fn lower_type(cache: &MonoCache, subs: &Subs, var: Variable) -> Rc { + fn fail(s: &str, var: Variable) -> ! { + panic!("lower_type: {}: {:?}", s, var) } - result_stack.pop_back().unwrap() + fn go_content(cache: &MonoCache, subs: &Subs, content: &Content) -> TyContent { + match content { + Content::Structure(flat_type) => match flat_type { + FlatType::Apply(_, _) => unimplemented!("FlatType::Apply"), + FlatType::Func(args, closure, ret) => { + let in_ty = go(cache, subs, subs.variables[args.start as usize]); + let out_ty = go(cache, subs, *ret); + TyContent::TFn(in_ty, out_ty) + } + FlatType::Record(fields, ext) => { + let (fields, ext) = chase_fields(subs, *fields, *ext); + let fields = fields + .into_iter() + .map(|(field, var)| (field, go(cache, subs, var))) + .collect::>(); + assert!( + matches!(*go(cache, subs, ext).0.borrow(), TyContent::TTag(ref tags) if tags.is_empty()) + || matches!(*go(cache, subs, ext).0.borrow(), TyContent::TRecord(ref fields) if fields.is_empty()) + ); + TyContent::TRecord(fields) + } + FlatType::Tuple(_, _) => unimplemented!("FlatType::Tuple"), + FlatType::TagUnion(tags, ext) => { + let (tags, ext) = chase_tags(subs, *tags, *ext); + let tags = tags + .into_iter() + .map(|(tag, vars)| { + ( + tag, + vars.into_iter().map(|var| go(cache, subs, var)).collect(), + ) + }) + .collect::>(); + assert!( + matches!(*go(cache, subs, ext).0.borrow(), TyContent::TTag(ref t) if t.is_empty()) + ); + TyContent::TTag(tags) + } + FlatType::FunctionOrTagUnion(_, _, _) => { + unimplemented!("FlatType::FunctionOrTagUnion") + } + FlatType::RecursiveTagUnion(_, _, _) => { + unimplemented!("FlatType::RecursiveTagUnion") + } + FlatType::EmptyRecord => TyContent::TRecord(vec![]), + FlatType::EmptyTuple => unimplemented!("FlatType::EmptyTuple"), + FlatType::EmptyTagUnion => TyContent::TTag(vec![]), + }, + Content::FlexVar(_) => TyContent::TTag(vec![]), + Content::RigidVar(_) => fail("unexpected rigid var", Variable::NULL), + Content::FlexAbleVar(_, _) => TyContent::TTag(vec![]), + Content::RigidAbleVar(_, _) => fail("unexpected rigid able var", Variable::NULL), + Content::RecursionVar { .. } => fail("unexpected recursion var", Variable::NULL), + Content::LambdaSet(_) => fail("unexpected lambda set", Variable::NULL), + Content::ErasedLambda => TyContent::TPrim(Primitive::Erased), + Content::Alias(_, _, _, _) => fail("unexpected alias", Variable::NULL), + Content::RangedNumber(_) => unimplemented!("Content::RangedNumber"), + Content::Error => fail("error type", Variable::NULL), + } + } + + fn go(cache: &MonoCache, subs: &Subs, var: Variable) -> Rc { + let var = unlink_tvar(subs, var); + if let Some(ty) = cache.borrow().get(&var) { + return ty.clone(); + } + let ty = Rc::new(Ty(RefCell::new(ty_unfilled()))); + cache.borrow_mut().insert(var, ty.clone()); + let content = go_content(cache, subs, subs.get_content_without_compacting(var)); + *ty.0.borrow_mut() = content; + ty + } + + go(cache, subs, var) +} + +fn chase_tags( + subs: &Subs, + mut tags: UnionTags, + mut ext: TagExt, +) -> (Vec<(String, Vec)>, Variable) { + let mut all_tags = Vec::new(); + loop { + for (tag, vars) in tags.iter_from_subs(subs) { + all_tags.push((tag.0.to_string(), vars.to_vec())); + } + match subs.get_content_without_compacting(ext.var()) { + Content::Structure(FlatType::TagUnion(new_tags, new_ext)) => { + tags = *new_tags; + ext = *new_ext; + } + Content::Structure(FlatType::EmptyTagUnion) => break, + Content::FlexVar(_) | Content::FlexAbleVar(_, _) => break, + Content::Alias(_, _, real, _) => ext = TagExt::Any(*real), + _ => panic!("Invalid tag union extension"), + } + } + (all_tags, ext.var()) +} + +fn chase_fields( + subs: &Subs, + mut fields: RecordFields, + mut ext: Variable, +) -> (Vec<(String, Variable)>, Variable) { + let mut all_fields = Vec::new(); + loop { + for (field, record_field) in fields.sorted_iterator(subs, ext) { + let var = match record_field { + RecordField::Required(v) | RecordField::Optional(v) | RecordField::Demanded(v) => v, + RecordField::RigidRequired(v) | RecordField::RigidOptional(v) => v, + }; + all_fields.push((field.to_string(), var)); + } + match subs.get_content_without_compacting(ext) { + Content::Structure(FlatType::Record(new_fields, new_ext)) => { + fields = *new_fields; + ext = *new_ext; + } + Content::Structure(FlatType::EmptyRecord) => break, + Content::FlexVar(_) | Content::FlexAbleVar(_, _) => break, + Content::Alias(_, _, real, _) => ext = *real, + _ => panic!("Invalid record extension"), + } + } + (all_fields, ext) } From fc3666b4295074602750cc107382e45bb18555ef Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Fri, 27 Sep 2024 07:50:46 -0400 Subject: [PATCH 04/65] Revise specialize_types --- crates/compiler/specialize_types/src/lib.rs | 266 +++++++++++--------- 1 file changed, 149 insertions(+), 117 deletions(-) diff --git a/crates/compiler/specialize_types/src/lib.rs b/crates/compiler/specialize_types/src/lib.rs index b961b8aab8..29b799ceb4 100644 --- a/crates/compiler/specialize_types/src/lib.rs +++ b/crates/compiler/specialize_types/src/lib.rs @@ -1,139 +1,171 @@ -use std::cell::RefCell; -use std::collections::HashMap; -use std::rc::Rc; - +use roc_collections::MutMap; +use roc_module::ident::{Lowercase, TagName}; use roc_types::{ - subs::{Content, FlatType, RecordFields, Subs, TagExt, UnionTags, Variable}, + subs::{ + Content, FlatType, RecordFields, Subs, TagExt, TupleElems, UnionTags, Variable, + VariableSubsSlice, + }, types::RecordField, }; -#[derive(Clone, Debug)] -pub enum TyContent { - TFn(Rc, Rc), - TTag(Vec<(String, Vec>)>), - TRecord(Vec<(String, Rc)>), - TPrim(Primitive), +pub type MonoCache = MutMap; + +fn ty_unfilled() -> Content { + Content::Structure(FlatType::TagUnion( + UnionTags::default(), + TagExt::Any(Variable::EMPTY_TAG_UNION), + )) } -#[derive(Clone, Debug)] -pub enum Primitive { - Str, - Int, - Erased, +pub fn monomorphize_var(cache: &mut MonoCache, subs: &Subs, var: Variable) -> Variable { + let root_var = subs.get_root_key_without_compacting(var); + if cache.contains_key(&root_var) { + return root_var; + } + cache.insert(root_var, ty_unfilled()); + let content = lower_content(cache, subs, subs.get_content_without_compacting(root_var)); + cache.insert(root_var, content); + root_var } -#[derive(Clone, Debug)] -pub struct Ty(RefCell); - -type MonoCache = RefCell>>; - -pub fn fresh_mono_cache() -> MonoCache { - RefCell::new(HashMap::new()) -} - -fn unlink_tvar(subs: &Subs, mut var: Variable) -> Variable { - loop { - match subs.get_content_without_compacting(var) { - Content::Alias(_, _, real, _) => var = *real, - _ => break var, +fn lower_content(cache: &mut MonoCache, subs: &Subs, content: &Content) -> Content { + match content { + Content::Structure(flat_type) => match flat_type { + FlatType::Apply(symbol, args) => { + let new_args = args + .into_iter() + .map(|var_index| monomorphize_var(cache, subs, subs[var_index])) + .collect::>(); + Content::Structure(FlatType::Apply( + *symbol, + VariableSubsSlice::new(new_args.as_ptr() as u32, new_args.len() as u16), + )) + } + FlatType::Func(args, closure, ret) => { + let new_args = args + .into_iter() + .map(|var_index| monomorphize_var(cache, subs, subs[var_index])) + .collect::>(); + let new_closure = monomorphize_var(cache, subs, *closure); + let new_ret = monomorphize_var(cache, subs, *ret); + Content::Structure(FlatType::Func( + VariableSubsSlice::new(new_args.as_ptr() as u32, new_args.len() as u16), + new_closure, + new_ret, + )) + } + FlatType::Record(fields, ext) => { + let (fields, ext) = chase_fields(subs, *fields, *ext); + let new_fields = fields + .into_iter() + .map(|(field, var)| { + ( + Lowercase::from(field), + RecordField::Required(monomorphize_var(cache, subs, var)), + ) + }) + .collect::>(); + let new_ext = monomorphize_var(cache, subs, ext); + Content::Structure(FlatType::Record( + RecordFields::insert_into_subs(&mut Subs::new(), new_fields.into_iter()), + new_ext, + )) + } + FlatType::Tuple(elems, ext) => { + let new_elems = elems + .iter_all() + .map(|(idx, var_index)| { + ( + idx.index as usize, + monomorphize_var(cache, subs, subs[var_index]), + ) + }) + .collect::>(); + let new_ext = monomorphize_var(cache, subs, *ext); + Content::Structure(FlatType::Tuple( + TupleElems::insert_into_subs(&mut Subs::new(), new_elems.into_iter()), + new_ext, + )) + } + FlatType::TagUnion(tags, ext) => { + let (tags, ext) = chase_tags(subs, *tags, *ext); + let new_tags = tags + .into_iter() + .map(|(tag, vars)| { + ( + tag, + vars.into_iter() + .map(|var| monomorphize_var(cache, subs, var)) + .collect::>(), + ) + }) + .collect::>(); + let new_ext = TagExt::Any(monomorphize_var(cache, subs, ext)); + Content::Structure(FlatType::TagUnion( + UnionTags::insert_into_subs(&mut Subs::new(), new_tags.into_iter()), + new_ext, + )) + } + FlatType::FunctionOrTagUnion(tag_names, symbols, ext) => { + let new_ext = TagExt::Any(monomorphize_var(cache, subs, ext.var())); + Content::Structure(FlatType::FunctionOrTagUnion(*tag_names, *symbols, new_ext)) + } + FlatType::RecursiveTagUnion(rec, tags, ext) => { + let new_rec = monomorphize_var(cache, subs, *rec); + let (tags, ext_var) = chase_tags(subs, *tags, *ext); + let new_tags = tags + .into_iter() + .map(|(tag, vars)| { + ( + tag, + vars.into_iter() + .map(|var| monomorphize_var(cache, subs, var)) + .collect::>(), + ) + }) + .collect::>(); + let new_ext = TagExt::Any(monomorphize_var(cache, subs, ext_var)); + Content::Structure(FlatType::RecursiveTagUnion( + new_rec, + UnionTags::insert_into_subs(&mut Subs::new(), new_tags.into_iter()), + new_ext, + )) + } + FlatType::EmptyRecord => Content::Structure(FlatType::EmptyRecord), + FlatType::EmptyTuple => Content::Structure(FlatType::EmptyTuple), + FlatType::EmptyTagUnion => Content::Structure(FlatType::EmptyTagUnion), + }, + Content::FlexVar(opt_name) => Content::FlexVar(*opt_name), + Content::RigidVar(name) => Content::RigidVar(*name), + Content::FlexAbleVar(opt_name, abilities) => Content::FlexAbleVar(*opt_name, *abilities), + Content::RigidAbleVar(name, abilities) => Content::RigidAbleVar(*name, *abilities), + Content::RecursionVar { + structure, + opt_name, + } => Content::RecursionVar { + structure: monomorphize_var(cache, subs, *structure), + opt_name: *opt_name, + }, + Content::LambdaSet(lambda_set) => Content::LambdaSet(lambda_set.clone()), + Content::ErasedLambda => Content::ErasedLambda, + Content::Alias(symbol, args, real, kind) => { + let new_real = monomorphize_var(cache, subs, *real); + Content::Alias(*symbol, args.clone(), new_real, *kind) } + Content::RangedNumber(range) => Content::RangedNumber(*range), + Content::Error => Content::Error, } } -fn ty_unfilled() -> TyContent { - TyContent::TTag(vec![("__unfilled".to_string(), vec![])]) -} - -pub fn lower_type(cache: &MonoCache, subs: &Subs, var: Variable) -> Rc { - fn fail(s: &str, var: Variable) -> ! { - panic!("lower_type: {}: {:?}", s, var) - } - - fn go_content(cache: &MonoCache, subs: &Subs, content: &Content) -> TyContent { - match content { - Content::Structure(flat_type) => match flat_type { - FlatType::Apply(_, _) => unimplemented!("FlatType::Apply"), - FlatType::Func(args, closure, ret) => { - let in_ty = go(cache, subs, subs.variables[args.start as usize]); - let out_ty = go(cache, subs, *ret); - TyContent::TFn(in_ty, out_ty) - } - FlatType::Record(fields, ext) => { - let (fields, ext) = chase_fields(subs, *fields, *ext); - let fields = fields - .into_iter() - .map(|(field, var)| (field, go(cache, subs, var))) - .collect::>(); - assert!( - matches!(*go(cache, subs, ext).0.borrow(), TyContent::TTag(ref tags) if tags.is_empty()) - || matches!(*go(cache, subs, ext).0.borrow(), TyContent::TRecord(ref fields) if fields.is_empty()) - ); - TyContent::TRecord(fields) - } - FlatType::Tuple(_, _) => unimplemented!("FlatType::Tuple"), - FlatType::TagUnion(tags, ext) => { - let (tags, ext) = chase_tags(subs, *tags, *ext); - let tags = tags - .into_iter() - .map(|(tag, vars)| { - ( - tag, - vars.into_iter().map(|var| go(cache, subs, var)).collect(), - ) - }) - .collect::>(); - assert!( - matches!(*go(cache, subs, ext).0.borrow(), TyContent::TTag(ref t) if t.is_empty()) - ); - TyContent::TTag(tags) - } - FlatType::FunctionOrTagUnion(_, _, _) => { - unimplemented!("FlatType::FunctionOrTagUnion") - } - FlatType::RecursiveTagUnion(_, _, _) => { - unimplemented!("FlatType::RecursiveTagUnion") - } - FlatType::EmptyRecord => TyContent::TRecord(vec![]), - FlatType::EmptyTuple => unimplemented!("FlatType::EmptyTuple"), - FlatType::EmptyTagUnion => TyContent::TTag(vec![]), - }, - Content::FlexVar(_) => TyContent::TTag(vec![]), - Content::RigidVar(_) => fail("unexpected rigid var", Variable::NULL), - Content::FlexAbleVar(_, _) => TyContent::TTag(vec![]), - Content::RigidAbleVar(_, _) => fail("unexpected rigid able var", Variable::NULL), - Content::RecursionVar { .. } => fail("unexpected recursion var", Variable::NULL), - Content::LambdaSet(_) => fail("unexpected lambda set", Variable::NULL), - Content::ErasedLambda => TyContent::TPrim(Primitive::Erased), - Content::Alias(_, _, _, _) => fail("unexpected alias", Variable::NULL), - Content::RangedNumber(_) => unimplemented!("Content::RangedNumber"), - Content::Error => fail("error type", Variable::NULL), - } - } - - fn go(cache: &MonoCache, subs: &Subs, var: Variable) -> Rc { - let var = unlink_tvar(subs, var); - if let Some(ty) = cache.borrow().get(&var) { - return ty.clone(); - } - let ty = Rc::new(Ty(RefCell::new(ty_unfilled()))); - cache.borrow_mut().insert(var, ty.clone()); - let content = go_content(cache, subs, subs.get_content_without_compacting(var)); - *ty.0.borrow_mut() = content; - ty - } - - go(cache, subs, var) -} - fn chase_tags( subs: &Subs, mut tags: UnionTags, mut ext: TagExt, -) -> (Vec<(String, Vec)>, Variable) { +) -> (Vec<(TagName, Vec)>, Variable) { let mut all_tags = Vec::new(); loop { for (tag, vars) in tags.iter_from_subs(subs) { - all_tags.push((tag.0.to_string(), vars.to_vec())); + all_tags.push((tag.clone(), vars.to_vec())); } match subs.get_content_without_compacting(ext.var()) { Content::Structure(FlatType::TagUnion(new_tags, new_ext)) => { From 1f9e47e68c353e8d257116fc6981c8824555aaf1 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Fri, 27 Sep 2024 21:09:56 -0400 Subject: [PATCH 05/65] Fix a bunch of monomorphization stuff --- Cargo.lock | 11 +- Cargo.toml | 7 +- crates/compiler/specialize_types/Cargo.toml | 15 +- crates/compiler/specialize_types/src/lib.rs | 350 +++++++++++++------- crates/compiler/types/src/subs.rs | 6 + 5 files changed, 270 insertions(+), 119 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 26e50d9a43..3b61e9daa5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3138,13 +3138,22 @@ dependencies = [ "hashbrown", "indoc", "parking_lot", + "pretty_assertions", + "roc_builtins", "roc_can", "roc_collections", - "roc_debug_flags", + "roc_derive", + "roc_load", "roc_module", + "roc_parse", + "roc_problem", "roc_region", + "roc_reporting", + "roc_solve", + "roc_target", "roc_types", "static_assertions", + "test_solve_helpers", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 6dafb57e8e..eb28ad7499 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -75,6 +75,8 @@ inkwell = { git = "https://github.com/roc-lang/inkwell", branch = "inkwell-llvm- "llvm16-0", ] } +roc_specialize_types = { path = "crates/compiler/specialize_types" } + arrayvec = "0.7.2" # update roc_std/Cargo.toml on change backtrace = "0.3.67" base64-url = "1.4.13" @@ -138,7 +140,10 @@ maplit = "1.0.2" memmap2 = "0.5.10" mimalloc = { version = "0.1.34", default-features = false } nonempty = "0.8.1" -object = { version = "0.32.2", default-features = false, features = ["read", "write"] } +object = { version = "0.32.2", default-features = false, features = [ + "read", + "write", +] } packed_struct = "0.10.1" page_size = "0.5.0" palette = "0.6.1" diff --git a/crates/compiler/specialize_types/Cargo.toml b/crates/compiler/specialize_types/Cargo.toml index b363573ec5..58c46c6df3 100644 --- a/crates/compiler/specialize_types/Cargo.toml +++ b/crates/compiler/specialize_types/Cargo.toml @@ -12,8 +12,6 @@ roc_can = { path = "../can" } roc_region = { path = "../region" } roc_types = { path = "../types" } roc_collections = { path = "../collections" } -roc_debug_flags = { path = "../debug_flags" } -# roc_module is only used for TagName roc_module = { path = "../module" } bitvec.workspace = true @@ -23,3 +21,16 @@ hashbrown.workspace = true parking_lot.workspace = true static_assertions.workspace = true indoc.workspace = true + +[dev-dependencies] +roc_builtins = { path = "../builtins" } +roc_derive = { path = "../derive", features = ["debug-derived-symbols"] } +roc_load = { path = "../load" } +roc_parse = { path = "../parse" } +roc_problem = { path = "../problem" } +roc_reporting = { path = "../../reporting" } +roc_target = { path = "../roc_target" } +roc_solve = { path = "../solve" } +test_solve_helpers = { path = "../test_solve_helpers" } + +pretty_assertions.workspace = true diff --git a/crates/compiler/specialize_types/src/lib.rs b/crates/compiler/specialize_types/src/lib.rs index 29b799ceb4..28a2dc8a8d 100644 --- a/crates/compiler/specialize_types/src/lib.rs +++ b/crates/compiler/specialize_types/src/lib.rs @@ -1,210 +1,330 @@ -use roc_collections::MutMap; +use bitvec::vec::BitVec; use roc_module::ident::{Lowercase, TagName}; use roc_types::{ subs::{ - Content, FlatType, RecordFields, Subs, TagExt, TupleElems, UnionTags, Variable, - VariableSubsSlice, + Content, FlatType, RecordFields, Subs, TagExt, TupleElems, UnionLabels, UnionTags, + Variable, VariableSubsSlice, }, types::RecordField, }; -pub type MonoCache = MutMap; - -fn ty_unfilled() -> Content { - Content::Structure(FlatType::TagUnion( - UnionTags::default(), - TagExt::Any(Variable::EMPTY_TAG_UNION), - )) +#[derive(Debug, PartialEq, Eq)] +pub enum Problem { + /// Compiler bug; this should never happen! + TagUnionExtWasNotTagUnion, + RecordExtWasNotRecord, + TupleExtWasNotTuple, } -pub fn monomorphize_var(cache: &mut MonoCache, subs: &Subs, var: Variable) -> Variable { - let root_var = subs.get_root_key_without_compacting(var); - if cache.contains_key(&root_var) { - return root_var; +/// Variables that have already been monomorphized. +pub struct MonoCache { + inner: BitVec, +} + +impl MonoCache { + pub fn from_subs(subs: &Subs) -> Self { + Self { + inner: BitVec::repeat(false, subs.len()), + } } - cache.insert(root_var, ty_unfilled()); - let content = lower_content(cache, subs, subs.get_content_without_compacting(root_var)); - cache.insert(root_var, content); + + /// Returns true iff we know this Variable is monomorphic because + /// we've visited it before in the monomorphization process + /// (and either it was already monomorphic, or we made it so). + pub fn is_known_monomorphic(&self, var: Variable) -> bool { + match self.inner.get(var.index() as usize) { + Some(initialized) => { + // false if it has never been set, because we initialized all the bits to 0 + *initialized + } + None => false, + } + } + + /// Records that the given variable is now known to be monomorphic. + fn set_monomorphic(&mut self, var: Variable) { + self.inner.set(var.index() as usize, true); + } + + pub fn monomorphize_var( + &mut self, + subs: &mut Subs, + problems: &mut Vec, + var: Variable, + ) { + lower_var(self, subs, problems, var); + } +} + +fn lower_var( + cache: &mut MonoCache, + subs: &mut Subs, + problems: &mut Vec, + var: Variable, +) -> Variable { + let root_var = subs.get_root_key_without_compacting(var); + + if !cache.is_known_monomorphic(root_var) { + let content = subs.get_content_without_compacting(root_var).clone(); + let content = lower_content(cache, subs, problems, &content); + + // Update Subs so when we look up this var in the future, it's the monomorphized Content. + subs.set_content(root_var, content); + + // This var is now known to be monomorphic. + cache.set_monomorphic(root_var); + } + root_var } -fn lower_content(cache: &mut MonoCache, subs: &Subs, content: &Content) -> Content { - match content { +fn lower_content( + cache: &mut MonoCache, + subs: &mut Subs, + problems: &mut Vec, + content: &Content, +) -> Content { + match dbg!(content) { Content::Structure(flat_type) => match flat_type { FlatType::Apply(symbol, args) => { let new_args = args .into_iter() - .map(|var_index| monomorphize_var(cache, subs, subs[var_index])) - .collect::>(); + .map(|var_index| lower_var(cache, subs, problems, subs[var_index])) + // TODO there might be a way to remove this heap allocation + .collect::>(); + Content::Structure(FlatType::Apply( *symbol, - VariableSubsSlice::new(new_args.as_ptr() as u32, new_args.len() as u16), + VariableSubsSlice::insert_into_subs(subs, new_args), )) } FlatType::Func(args, closure, ret) => { let new_args = args .into_iter() - .map(|var_index| monomorphize_var(cache, subs, subs[var_index])) + .map(|var_index| lower_var(cache, subs, problems, subs[var_index])) .collect::>(); - let new_closure = monomorphize_var(cache, subs, *closure); - let new_ret = monomorphize_var(cache, subs, *ret); + let new_closure = lower_var(cache, subs, problems, *closure); + let new_ret = lower_var(cache, subs, problems, *ret); Content::Structure(FlatType::Func( - VariableSubsSlice::new(new_args.as_ptr() as u32, new_args.len() as u16), + VariableSubsSlice::insert_into_subs(subs, new_args), new_closure, new_ret, )) } FlatType::Record(fields, ext) => { - let (fields, ext) = chase_fields(subs, *fields, *ext); - let new_fields = fields - .into_iter() - .map(|(field, var)| { - ( - Lowercase::from(field), - RecordField::Required(monomorphize_var(cache, subs, var)), - ) - }) - .collect::>(); - let new_ext = monomorphize_var(cache, subs, ext); + let mut fields = flatten_fields(subs, problems, *fields, *ext); + + // Now lower all the fields we gathered. Do this in a separate pass to avoid borrow errors on Subs. + for (_, field) in fields.iter_mut() { + let var = match field { + RecordField::Required(v) | RecordField::Optional(v) | RecordField::Demanded(v) => v, + RecordField::RigidRequired(v) | RecordField::RigidOptional(v) => v, + }; + + *var = lower_var(cache, subs, problems, *var); + } + Content::Structure(FlatType::Record( - RecordFields::insert_into_subs(&mut Subs::new(), new_fields.into_iter()), - new_ext, + RecordFields::insert_into_subs(subs, fields.into_iter()), + Variable::EMPTY_RECORD, )) } FlatType::Tuple(elems, ext) => { - let new_elems = elems - .iter_all() - .map(|(idx, var_index)| { - ( - idx.index as usize, - monomorphize_var(cache, subs, subs[var_index]), - ) - }) - .collect::>(); - let new_ext = monomorphize_var(cache, subs, *ext); + let mut elems = flatten_tuple(subs, problems, *elems, *ext); + + // Now lower all the elems we gathered. Do this in a separate pass to avoid borrow errors on Subs. + for (_, var) in elems.iter_mut() { + *var = lower_var(cache, subs, problems, *var); + } + Content::Structure(FlatType::Tuple( - TupleElems::insert_into_subs(&mut Subs::new(), new_elems.into_iter()), - new_ext, + TupleElems::insert_into_subs(subs, elems.into_iter()), + Variable::EMPTY_TUPLE, )) } FlatType::TagUnion(tags, ext) => { - let (tags, ext) = chase_tags(subs, *tags, *ext); - let new_tags = tags - .into_iter() - .map(|(tag, vars)| { - ( - tag, - vars.into_iter() - .map(|var| monomorphize_var(cache, subs, var)) - .collect::>(), - ) - }) - .collect::>(); - let new_ext = TagExt::Any(monomorphize_var(cache, subs, ext)); + let mut tags = flatten_tags(subs, problems, *tags, *ext); + + // Now lower all the tags we gathered. Do this in a separate pass to avoid borrow errors on Subs. + for (_, vars) in tags.iter_mut() { + for var in vars.iter_mut() { + *var = lower_var(cache, subs, problems, *var); + } + } + Content::Structure(FlatType::TagUnion( - UnionTags::insert_into_subs(&mut Subs::new(), new_tags.into_iter()), - new_ext, + UnionTags::insert_into_subs(subs, tags.into_iter()), + TagExt::Any(Variable::EMPTY_TAG_UNION), )) } FlatType::FunctionOrTagUnion(tag_names, symbols, ext) => { - let new_ext = TagExt::Any(monomorphize_var(cache, subs, ext.var())); + let new_ext = TagExt::Any(lower_var(cache, subs, problems, ext.var())); Content::Structure(FlatType::FunctionOrTagUnion(*tag_names, *symbols, new_ext)) } FlatType::RecursiveTagUnion(rec, tags, ext) => { - let new_rec = monomorphize_var(cache, subs, *rec); - let (tags, ext_var) = chase_tags(subs, *tags, *ext); - let new_tags = tags - .into_iter() - .map(|(tag, vars)| { - ( - tag, - vars.into_iter() - .map(|var| monomorphize_var(cache, subs, var)) - .collect::>(), - ) - }) - .collect::>(); - let new_ext = TagExt::Any(monomorphize_var(cache, subs, ext_var)); + let mut new_tags = flatten_tags(subs, problems, *tags, *ext); + + // Now lower all the tags we gathered. Do this in a separate pass to avoid borrow errors on Subs. + for (_, vars) in new_tags.iter_mut() { + for var in vars.iter_mut() { + *var = lower_var(cache, subs, problems, *var); + } + } + Content::Structure(FlatType::RecursiveTagUnion( - new_rec, - UnionTags::insert_into_subs(&mut Subs::new(), new_tags.into_iter()), - new_ext, + lower_var(cache, subs, problems, *rec), + UnionTags::insert_into_subs(subs, new_tags.into_iter()), + TagExt::Any(Variable::EMPTY_TAG_UNION), )) } FlatType::EmptyRecord => Content::Structure(FlatType::EmptyRecord), FlatType::EmptyTuple => Content::Structure(FlatType::EmptyTuple), FlatType::EmptyTagUnion => Content::Structure(FlatType::EmptyTagUnion), }, - Content::FlexVar(opt_name) => Content::FlexVar(*opt_name), - Content::RigidVar(name) => Content::RigidVar(*name), - Content::FlexAbleVar(opt_name, abilities) => Content::FlexAbleVar(*opt_name, *abilities), - Content::RigidAbleVar(name, abilities) => Content::RigidAbleVar(*name, *abilities), - Content::RecursionVar { - structure, - opt_name, - } => Content::RecursionVar { - structure: monomorphize_var(cache, subs, *structure), - opt_name: *opt_name, - }, + Content::RangedNumber(_) // RangedNumber goes in Num's type parameter slot, so monomorphize it to [] + | Content::FlexVar(_) + | Content::RigidVar(_) + | Content::FlexAbleVar(_, _) + | Content::RigidAbleVar(_, _) + | Content::RecursionVar { .. } => Content::Structure(FlatType::EmptyTagUnion), Content::LambdaSet(lambda_set) => Content::LambdaSet(lambda_set.clone()), Content::ErasedLambda => Content::ErasedLambda, Content::Alias(symbol, args, real, kind) => { - let new_real = monomorphize_var(cache, subs, *real); + let new_real = lower_var(cache, subs, problems, *real); Content::Alias(*symbol, args.clone(), new_real, *kind) } - Content::RangedNumber(range) => Content::RangedNumber(*range), Content::Error => Content::Error, } } -fn chase_tags( - subs: &Subs, +fn flatten_tags( + subs: &mut Subs, + problems: &mut Vec, mut tags: UnionTags, mut ext: TagExt, -) -> (Vec<(TagName, Vec)>, Variable) { +) -> Vec<(TagName, Vec)> { let mut all_tags = Vec::new(); + + // First, collapse (recursively) all the tags in ext_var into a flat list of tags. loop { for (tag, vars) in tags.iter_from_subs(subs) { all_tags.push((tag.clone(), vars.to_vec())); } + match subs.get_content_without_compacting(ext.var()) { Content::Structure(FlatType::TagUnion(new_tags, new_ext)) => { + // Update tags and ext and loop back again to process them. tags = *new_tags; ext = *new_ext; } Content::Structure(FlatType::EmptyTagUnion) => break, Content::FlexVar(_) | Content::FlexAbleVar(_, _) => break, - Content::Alias(_, _, real, _) => ext = TagExt::Any(*real), - _ => panic!("Invalid tag union extension"), + Content::Alias(_, _, real, _) => { + // Follow the alias and process it on the next iteration of the loop. + ext = TagExt::Any(*real); + + // We just processed these tags, so don't process them again! + tags = UnionLabels::default(); + } + _ => { + // This should never happen! If it does, record a Problem and break. + problems.push(Problem::TagUnionExtWasNotTagUnion); + + break; + } } } - (all_tags, ext.var()) + + // ext should have ended up being empty + debug_assert_eq!(ext.var(), Variable::EMPTY_TAG_UNION); + + all_tags } -fn chase_fields( - subs: &Subs, +fn flatten_fields( + subs: &mut Subs, + problems: &mut Vec, mut fields: RecordFields, mut ext: Variable, -) -> (Vec<(String, Variable)>, Variable) { +) -> Vec<(Lowercase, RecordField)> { let mut all_fields = Vec::new(); + + // First, collapse (recursively) all the fields in ext into a flat list of fields. loop { - for (field, record_field) in fields.sorted_iterator(subs, ext) { - let var = match record_field { - RecordField::Required(v) | RecordField::Optional(v) | RecordField::Demanded(v) => v, - RecordField::RigidRequired(v) | RecordField::RigidOptional(v) => v, - }; - all_fields.push((field.to_string(), var)); + for (label, field) in fields.sorted_iterator(subs, ext) { + all_fields.push((label.clone(), field.clone())); } + match subs.get_content_without_compacting(ext) { Content::Structure(FlatType::Record(new_fields, new_ext)) => { + // Update fields and ext and loop back again to process them. fields = *new_fields; ext = *new_ext; } Content::Structure(FlatType::EmptyRecord) => break, Content::FlexVar(_) | Content::FlexAbleVar(_, _) => break, - Content::Alias(_, _, real, _) => ext = *real, - _ => panic!("Invalid record extension"), + Content::Alias(_, _, real, _) => { + // Follow the alias and process it on the next iteration of the loop. + ext = *real; + + // We just processed these fields, so don't process them again! + fields = RecordFields::empty(); + } + _ => { + // This should never happen! If it does, record a Problem and break. + problems.push(Problem::RecordExtWasNotRecord); + + break; + } } } - (all_fields, ext) + + // ext should have ended up being empty + debug_assert_eq!(ext, Variable::EMPTY_RECORD); + + all_fields +} + +fn flatten_tuple( + subs: &mut Subs, + problems: &mut Vec, + mut elems: TupleElems, + mut ext: Variable, +) -> Vec<(usize, Variable)> { + let mut all_elems = Vec::new(); + + // First, collapse (recursively) all the elements in ext into a flat list of elements. + loop { + for (idx, var_index) in elems.iter_all() { + all_elems.push((idx.index as usize, subs[var_index])); + } + + match subs.get_content_without_compacting(ext) { + Content::Structure(FlatType::Tuple(new_elems, new_ext)) => { + // Update elems and ext and loop back again to process them. + elems = *new_elems; + ext = *new_ext; + } + Content::Structure(FlatType::EmptyTuple) => break, + Content::FlexVar(_) | Content::FlexAbleVar(_, _) => break, + Content::Alias(_, _, real, _) => { + // Follow the alias and process it on the next iteration of the loop. + ext = *real; + + // We just processed these elements, so don't process them again! + elems = TupleElems::empty(); + } + _ => { + // This should never happen! If it does, record a Problem and break. + problems.push(Problem::TupleExtWasNotTuple); + + break; + } + } + } + + // ext should have ended up being empty + debug_assert_eq!(ext, Variable::EMPTY_TUPLE); + + all_elems } diff --git a/crates/compiler/types/src/subs.rs b/crates/compiler/types/src/subs.rs index fe22db881f..ba8ebd8fe8 100644 --- a/crates/compiler/types/src/subs.rs +++ b/crates/compiler/types/src/subs.rs @@ -2699,6 +2699,12 @@ impl VariableSubsSlice { } } +impl From for VariableSubsSlice { + fn from(var: Variable) -> Self { + Self::new(var.0, 1) + } +} + pub trait Label: Sized + Clone { fn index_subs(subs: &Subs, idx: SubsIndex) -> &Self; fn get_subs_slice(subs: &Subs, slice: SubsSlice) -> &[Self]; From 768e9cf81de29fce478c93ac415ad8ed1e9b52a7 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Fri, 27 Sep 2024 21:48:26 -0400 Subject: [PATCH 06/65] Fix monomorphizing tag-or-fn --- crates/compiler/specialize_types/src/lib.rs | 64 ++++++++++++++------- 1 file changed, 43 insertions(+), 21 deletions(-) diff --git a/crates/compiler/specialize_types/src/lib.rs b/crates/compiler/specialize_types/src/lib.rs index 28a2dc8a8d..6d41fe2f96 100644 --- a/crates/compiler/specialize_types/src/lib.rs +++ b/crates/compiler/specialize_types/src/lib.rs @@ -10,7 +10,7 @@ use roc_types::{ #[derive(Debug, PartialEq, Eq)] pub enum Problem { - /// Compiler bug; this should never happen! + // Compiler bugs; these should never happen! TagUnionExtWasNotTagUnion, RecordExtWasNotRecord, TupleExtWasNotTuple, @@ -84,13 +84,12 @@ fn lower_content( problems: &mut Vec, content: &Content, ) -> Content { - match dbg!(content) { + match content { Content::Structure(flat_type) => match flat_type { FlatType::Apply(symbol, args) => { let new_args = args .into_iter() .map(|var_index| lower_var(cache, subs, problems, subs[var_index])) - // TODO there might be a way to remove this heap allocation .collect::>(); Content::Structure(FlatType::Apply( @@ -112,7 +111,7 @@ fn lower_content( )) } FlatType::Record(fields, ext) => { - let mut fields = flatten_fields(subs, problems, *fields, *ext); + let mut fields = resolve_record_ext(subs, problems, *fields, *ext); // Now lower all the fields we gathered. Do this in a separate pass to avoid borrow errors on Subs. for (_, field) in fields.iter_mut() { @@ -130,7 +129,7 @@ fn lower_content( )) } FlatType::Tuple(elems, ext) => { - let mut elems = flatten_tuple(subs, problems, *elems, *ext); + let mut elems = resolve_tuple_ext(subs, problems, *elems, *ext); // Now lower all the elems we gathered. Do this in a separate pass to avoid borrow errors on Subs. for (_, var) in elems.iter_mut() { @@ -143,7 +142,7 @@ fn lower_content( )) } FlatType::TagUnion(tags, ext) => { - let mut tags = flatten_tags(subs, problems, *tags, *ext); + let mut tags = resolve_tag_ext(subs, problems, *tags, *ext); // Now lower all the tags we gathered. Do this in a separate pass to avoid borrow errors on Subs. for (_, vars) in tags.iter_mut() { @@ -157,15 +156,35 @@ fn lower_content( TagExt::Any(Variable::EMPTY_TAG_UNION), )) } - FlatType::FunctionOrTagUnion(tag_names, symbols, ext) => { - let new_ext = TagExt::Any(lower_var(cache, subs, problems, ext.var())); - Content::Structure(FlatType::FunctionOrTagUnion(*tag_names, *symbols, new_ext)) + FlatType::FunctionOrTagUnion(tag_names, _symbols, ext) => { + // If this is still a FunctionOrTagUnion, turn it into a TagUnion. + + // First, resolve the ext var. + let mut tags = resolve_tag_ext(subs, problems, UnionTags::default(), *ext); + + // Now lower all the tags we gathered from the ext var. + // Do this in a separate pass to avoid borrow errors on Subs. + for (_, vars) in tags.iter_mut() { + for var in vars.iter_mut() { + *var = lower_var(cache, subs, problems, *var); + } + } + + // Then, add the tag names with no payloads. (There's nothing to lower here.) + for index in tag_names.into_iter() { + tags.push(((&subs[index]).clone(), Vec::new())); + } + + Content::Structure(FlatType::TagUnion( + UnionTags::insert_into_subs(subs, tags.into_iter()), + TagExt::Any(Variable::EMPTY_TAG_UNION), + )) } FlatType::RecursiveTagUnion(rec, tags, ext) => { - let mut new_tags = flatten_tags(subs, problems, *tags, *ext); + let mut tags = resolve_tag_ext(subs, problems, *tags, *ext); // Now lower all the tags we gathered. Do this in a separate pass to avoid borrow errors on Subs. - for (_, vars) in new_tags.iter_mut() { + for (_, vars) in tags.iter_mut() { for var in vars.iter_mut() { *var = lower_var(cache, subs, problems, *var); } @@ -173,7 +192,7 @@ fn lower_content( Content::Structure(FlatType::RecursiveTagUnion( lower_var(cache, subs, problems, *rec), - UnionTags::insert_into_subs(subs, new_tags.into_iter()), + UnionTags::insert_into_subs(subs, tags.into_iter()), TagExt::Any(Variable::EMPTY_TAG_UNION), )) } @@ -197,7 +216,7 @@ fn lower_content( } } -fn flatten_tags( +fn resolve_tag_ext( subs: &mut Subs, problems: &mut Vec, mut tags: UnionTags, @@ -205,7 +224,7 @@ fn flatten_tags( ) -> Vec<(TagName, Vec)> { let mut all_tags = Vec::new(); - // First, collapse (recursively) all the tags in ext_var into a flat list of tags. + // Collapse (recursively) all the tags in ext_var into a flat list of tags. loop { for (tag, vars) in tags.iter_from_subs(subs) { all_tags.push((tag.clone(), vars.to_vec())); @@ -217,6 +236,12 @@ fn flatten_tags( tags = *new_tags; ext = *new_ext; } + Content::Structure(FlatType::FunctionOrTagUnion(tag_names, _symbols, new_ext)) => { + for index in tag_names.into_iter() { + all_tags.push((subs[index].clone(), Vec::new())); + } + ext = *new_ext; + } Content::Structure(FlatType::EmptyTagUnion) => break, Content::FlexVar(_) | Content::FlexAbleVar(_, _) => break, Content::Alias(_, _, real, _) => { @@ -235,13 +260,10 @@ fn flatten_tags( } } - // ext should have ended up being empty - debug_assert_eq!(ext.var(), Variable::EMPTY_TAG_UNION); - all_tags } -fn flatten_fields( +fn resolve_record_ext( subs: &mut Subs, problems: &mut Vec, mut fields: RecordFields, @@ -249,7 +271,7 @@ fn flatten_fields( ) -> Vec<(Lowercase, RecordField)> { let mut all_fields = Vec::new(); - // First, collapse (recursively) all the fields in ext into a flat list of fields. + // Collapse (recursively) all the fields in ext into a flat list of fields. loop { for (label, field) in fields.sorted_iterator(subs, ext) { all_fields.push((label.clone(), field.clone())); @@ -285,7 +307,7 @@ fn flatten_fields( all_fields } -fn flatten_tuple( +fn resolve_tuple_ext( subs: &mut Subs, problems: &mut Vec, mut elems: TupleElems, @@ -293,7 +315,7 @@ fn flatten_tuple( ) -> Vec<(usize, Variable)> { let mut all_elems = Vec::new(); - // First, collapse (recursively) all the elements in ext into a flat list of elements. + // Collapse (recursively) all the elements in ext into a flat list of elements. loop { for (idx, var_index) in elems.iter_all() { all_elems.push((idx.index as usize, subs[var_index])); From 4bc872152e7042d8319f3c97c1ee7f926f88771e Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Fri, 27 Sep 2024 21:54:35 -0400 Subject: [PATCH 07/65] Extract lower_vars helper --- crates/compiler/specialize_types/src/lib.rs | 34 ++++++++++----------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/crates/compiler/specialize_types/src/lib.rs b/crates/compiler/specialize_types/src/lib.rs index 6d41fe2f96..c50edf5752 100644 --- a/crates/compiler/specialize_types/src/lib.rs +++ b/crates/compiler/specialize_types/src/lib.rs @@ -132,9 +132,7 @@ fn lower_content( let mut elems = resolve_tuple_ext(subs, problems, *elems, *ext); // Now lower all the elems we gathered. Do this in a separate pass to avoid borrow errors on Subs. - for (_, var) in elems.iter_mut() { - *var = lower_var(cache, subs, problems, *var); - } + lower_vars(elems.iter_mut().map(|(_, var)| var), cache, subs, problems); Content::Structure(FlatType::Tuple( TupleElems::insert_into_subs(subs, elems.into_iter()), @@ -145,11 +143,7 @@ fn lower_content( let mut tags = resolve_tag_ext(subs, problems, *tags, *ext); // Now lower all the tags we gathered. Do this in a separate pass to avoid borrow errors on Subs. - for (_, vars) in tags.iter_mut() { - for var in vars.iter_mut() { - *var = lower_var(cache, subs, problems, *var); - } - } + lower_vars(tags.iter_mut().flat_map(|(_, vars)| vars.iter_mut()), cache, subs, problems); Content::Structure(FlatType::TagUnion( UnionTags::insert_into_subs(subs, tags.into_iter()), @@ -164,11 +158,7 @@ fn lower_content( // Now lower all the tags we gathered from the ext var. // Do this in a separate pass to avoid borrow errors on Subs. - for (_, vars) in tags.iter_mut() { - for var in vars.iter_mut() { - *var = lower_var(cache, subs, problems, *var); - } - } + lower_vars(tags.iter_mut().flat_map(|(_, vars)| vars.iter_mut()), cache, subs, problems); // Then, add the tag names with no payloads. (There's nothing to lower here.) for index in tag_names.into_iter() { @@ -184,11 +174,7 @@ fn lower_content( let mut tags = resolve_tag_ext(subs, problems, *tags, *ext); // Now lower all the tags we gathered. Do this in a separate pass to avoid borrow errors on Subs. - for (_, vars) in tags.iter_mut() { - for var in vars.iter_mut() { - *var = lower_var(cache, subs, problems, *var); - } - } + lower_vars(tags.iter_mut().flat_map(|(_, vars)| vars.iter_mut()), cache, subs, problems); Content::Structure(FlatType::RecursiveTagUnion( lower_var(cache, subs, problems, *rec), @@ -350,3 +336,15 @@ fn resolve_tuple_ext( all_elems } + +/// Lower the given vars in-place. +fn lower_vars<'a>( + vars: impl Iterator, + cache: &mut MonoCache, + subs: &mut Subs, + problems: &mut Vec, +) { + for var in vars { + *var = lower_var(cache, subs, problems, *var); + } +} From 3f3ee265044cd11c585acb8eabcf786b7974f742 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Fri, 27 Sep 2024 22:20:55 -0400 Subject: [PATCH 08/65] Add some specialization tests --- .../tests/test_specialize_types.rs | 5107 +++++++++++++++++ 1 file changed, 5107 insertions(+) create mode 100644 crates/compiler/specialize_types/tests/test_specialize_types.rs diff --git a/crates/compiler/specialize_types/tests/test_specialize_types.rs b/crates/compiler/specialize_types/tests/test_specialize_types.rs new file mode 100644 index 0000000000..ff31636df4 --- /dev/null +++ b/crates/compiler/specialize_types/tests/test_specialize_types.rs @@ -0,0 +1,5107 @@ +#[macro_use] +extern crate pretty_assertions; +#[macro_use] +extern crate indoc; + +extern crate bumpalo; + +#[cfg(test)] +mod specialize_types { + use roc_load::LoadedModule; + use roc_solve::FunctionKind; + use roc_specialize_types::MonoCache; + use test_solve_helpers::{format_problems, run_load_and_infer}; + + use roc_types::pretty_print::{name_and_print_var, DebugPrint}; + + // HELPERS + + fn infer_eq_help(src: &str) -> Result<(String, String, String), std::io::Error> { + let ( + LoadedModule { + module_id: home, + mut can_problems, + mut type_problems, + interns, + mut solved, + mut exposed_to_host, + abilities_store, + .. + }, + src, + ) = run_load_and_infer(src, [], false, FunctionKind::LambdaSet)?; + + let mut can_problems = can_problems.remove(&home).unwrap_or_default(); + let type_problems = type_problems.remove(&home).unwrap_or_default(); + + // Disregard UnusedDef problems, because those are unavoidable when + // returning a function from the test expression. + can_problems.retain(|prob| { + !matches!( + prob, + roc_problem::can::Problem::UnusedDef(_, _) + | roc_problem::can::Problem::UnusedBranchDef(..) + ) + }); + + let (can_problems, type_problems) = + format_problems(&src, home, &interns, can_problems, type_problems); + + let subs = solved.inner_mut(); + + exposed_to_host.retain(|s, _| !abilities_store.is_specialization_name(*s)); + + debug_assert!(exposed_to_host.len() == 1, "{exposed_to_host:?}"); + let (_symbol, variable) = exposed_to_host.into_iter().next().unwrap(); + let mut mono_cache = MonoCache::from_subs(subs); + let mut problems = Vec::new(); + + mono_cache.monomorphize_var(subs, &mut problems, variable); + + assert_eq!(problems, Vec::new()); + + let actual_str = name_and_print_var(variable, subs, home, &interns, DebugPrint::NOTHING); + + Ok((type_problems, can_problems, actual_str)) + } + + fn specializes_to(src: &str, expected: &str) { + let (_, can_problems, actual) = infer_eq_help(src).unwrap(); + + assert!( + can_problems.is_empty(), + "Canonicalization problems: {can_problems}" + ); + + assert_eq!(actual, expected.to_string()); + } + + fn infer_eq_without_problem(src: &str, expected: &str) { + let (type_problems, can_problems, actual) = infer_eq_help(src).unwrap(); + + assert!( + can_problems.is_empty(), + "Canonicalization problems: {can_problems}" + ); + + if !type_problems.is_empty() { + // fail with an assert, but print the problems normally so rust doesn't try to diff + // an empty vec with the problems. + panic!("expected:\n{expected:?}\ninferred:\n{actual:?}\nproblems:\n{type_problems}",); + } + assert_eq!(actual, expected.to_string()); + } + + #[test] + fn str_literal_solo() { + specializes_to("\"test\"", "Str"); + } + + #[test] + fn int_literal_solo() { + specializes_to("5", "Num []"); + } + + #[test] + fn frac_literal_solo() { + specializes_to("0.5", "Frac []"); + } + + #[test] + fn dec_literal() { + specializes_to( + indoc!( + r" + val : Dec + val = 1.2 + + val + " + ), + "Dec", + ); + } + + #[test] + fn str_starts_with() { + specializes_to("Str.startsWith", "Str, Str -> Bool"); + } + + #[test] + fn str_from_int() { + infer_eq_without_problem("Num.toStr", "Num [] -> Str"); + } + + #[test] + fn str_from_utf8() { + infer_eq_without_problem( + "Str.fromUtf8", + "List U8 -> Result Str [BadUtf8 Utf8ByteProblem U64]", + ); + } + + #[test] + fn list_concat_utf8() { + infer_eq_without_problem("List.concatUtf8", "List U8, Str -> List U8") + } + + // LIST + + #[test] + fn empty_list() { + specializes_to( + indoc!( + r" + [] + " + ), + "List *", + ); + } + + #[test] + fn list_of_lists() { + specializes_to( + indoc!( + r" + [[]] + " + ), + "List (List *)", + ); + } + + #[test] + fn triple_nested_list() { + specializes_to( + indoc!( + r" + [[[]]] + " + ), + "List (List (List *))", + ); + } + + #[test] + fn nested_empty_list() { + specializes_to( + indoc!( + r" + [[], [[]]] + " + ), + "List (List (List *))", + ); + } + + #[test] + fn list_of_one_int() { + specializes_to( + indoc!( + r" + [42] + " + ), + "List (Num *)", + ); + } + + #[test] + fn triple_nested_int_list() { + specializes_to( + indoc!( + r" + [[[5]]] + " + ), + "List (List (List (Num *)))", + ); + } + + #[test] + fn list_of_ints() { + specializes_to( + indoc!( + r" + [1, 2, 3] + " + ), + "List (Num *)", + ); + } + + #[test] + fn nested_list_of_ints() { + specializes_to( + indoc!( + r" + [[1], [2, 3]] + " + ), + "List (List (Num *))", + ); + } + + #[test] + fn list_of_one_string() { + specializes_to( + indoc!( + r#" + ["cowabunga"] + "# + ), + "List Str", + ); + } + + #[test] + fn triple_nested_string_list() { + specializes_to( + indoc!( + r#" + [[["foo"]]] + "# + ), + "List (List (List Str))", + ); + } + + #[test] + fn list_of_strings() { + specializes_to( + indoc!( + r#" + ["foo", "bar"] + "# + ), + "List Str", + ); + } + + // INTERPOLATED STRING + + #[test] + fn infer_interpolated_string() { + specializes_to( + indoc!( + r#" + whatItIs = "great" + + "type inference is $(whatItIs)!" + "# + ), + "Str", + ); + } + + #[test] + fn infer_interpolated_var() { + specializes_to( + indoc!( + r#" + whatItIs = "great" + + str = "type inference is $(whatItIs)!" + + whatItIs + "# + ), + "Str", + ); + } + + #[test] + fn infer_interpolated_field() { + specializes_to( + indoc!( + r#" + rec = { whatItIs: "great" } + + str = "type inference is $(rec.whatItIs)!" + + rec + "# + ), + "{ whatItIs : Str }", + ); + } + + // LIST MISMATCH + + #[test] + fn mismatch_heterogeneous_list() { + specializes_to( + indoc!( + r#" + ["foo", 5] + "# + ), + "List ", + ); + } + + #[test] + fn mismatch_heterogeneous_nested_list() { + specializes_to( + indoc!( + r#" + [["foo", 5]] + "# + ), + "List (List )", + ); + } + + #[test] + fn mismatch_heterogeneous_nested_empty_list() { + specializes_to( + indoc!( + r" + [[1], [[]]] + " + ), + "List ", + ); + } + + // CLOSURE + + #[test] + fn always_return_empty_record() { + specializes_to( + indoc!( + r" + \_ -> {} + " + ), + "* -> {}", + ); + } + + #[test] + fn two_arg_return_int() { + specializes_to( + indoc!( + r" + \_, _ -> 42 + " + ), + "*, * -> Num *", + ); + } + + #[test] + fn three_arg_return_string() { + specializes_to( + indoc!( + r#" + \_, _, _ -> "test!" + "# + ), + "*, *, * -> Str", + ); + } + + // DEF + + #[test] + fn def_empty_record() { + specializes_to( + indoc!( + r" + foo = {} + + foo + " + ), + "{}", + ); + } + + #[test] + fn def_string() { + specializes_to( + indoc!( + r#" + str = "thing" + + str + "# + ), + "Str", + ); + } + + #[test] + fn def_1_arg_closure() { + specializes_to( + indoc!( + r" + fn = \_ -> {} + + fn + " + ), + "* -> {}", + ); + } + + #[test] + fn applied_tag() { + infer_eq_without_problem( + indoc!( + r#" + List.map ["a", "b"] \elem -> Foo elem + "# + ), + "List [Foo Str]", + ) + } + + // Tests (TagUnion, Func) + #[test] + fn applied_tag_function() { + infer_eq_without_problem( + indoc!( + r#" + foo = Foo + + foo "hi" + "# + ), + "[Foo Str]", + ) + } + + // Tests (TagUnion, Func) + #[test] + fn applied_tag_function_list_map() { + infer_eq_without_problem( + indoc!( + r#" + List.map ["a", "b"] Foo + "# + ), + "List [Foo Str]", + ) + } + + // Tests (TagUnion, Func) + #[test] + fn applied_tag_function_list() { + infer_eq_without_problem( + indoc!( + r" + [\x -> Bar x, Foo] + " + ), + "List (a -> [Bar a, Foo a])", + ) + } + + // Tests (Func, TagUnion) + #[test] + fn applied_tag_function_list_other_way() { + infer_eq_without_problem( + indoc!( + r" + [Foo, \x -> Bar x] + " + ), + "List (a -> [Bar a, Foo a])", + ) + } + + // Tests (Func, TagUnion) + #[test] + fn applied_tag_function_record() { + infer_eq_without_problem( + indoc!( + r" + foo0 = Foo + foo1 = Foo + foo2 = Foo + + { + x: [foo0, Foo], + y: [foo1, \x -> Foo x], + z: [foo2, \x,y -> Foo x y] + } + " + ), + "{ x : List [Foo], y : List (a -> [Foo a]), z : List (b, c -> [Foo b c]) }", + ) + } + + // Tests (TagUnion, Func) + #[test] + fn applied_tag_function_with_annotation() { + infer_eq_without_problem( + indoc!( + r" + x : List [Foo I64] + x = List.map [1, 2] Foo + + x + " + ), + "List [Foo I64]", + ) + } + + #[test] + fn def_2_arg_closure() { + specializes_to( + indoc!( + r" + func = \_, _ -> 42 + + func + " + ), + "*, * -> Num *", + ); + } + + #[test] + fn def_3_arg_closure() { + specializes_to( + indoc!( + r#" + f = \_, _, _ -> "test!" + + f + "# + ), + "*, *, * -> Str", + ); + } + + #[test] + fn def_multiple_functions() { + specializes_to( + indoc!( + r#" + a = \_, _, _ -> "test!" + + b = a + + b + "# + ), + "*, *, * -> Str", + ); + } + + #[test] + fn def_multiple_strings() { + specializes_to( + indoc!( + r#" + a = "test!" + + b = a + + b + "# + ), + "Str", + ); + } + + #[test] + fn def_multiple_ints() { + specializes_to( + indoc!( + r" + c = b + + b = a + + a = 42 + + c + " + ), + "Num *", + ); + } + + #[test] + fn def_returning_closure() { + specializes_to( + indoc!( + r" + f = \z -> z + g = \z -> z + + (\x -> + a = f x + b = g x + x + ) + " + ), + "a -> a", + ); + } + + // CALLING FUNCTIONS + + #[test] + fn call_returns_int() { + specializes_to( + indoc!( + r#" + alwaysFive = \_ -> 5 + + alwaysFive "stuff" + "# + ), + "Num *", + ); + } + + #[test] + fn identity_returns_given_type() { + specializes_to( + indoc!( + r#" + identity = \a -> a + + identity "hi" + "# + ), + "Str", + ); + } + + #[test] + fn identity_infers_principal_type() { + specializes_to( + indoc!( + r" + identity = \x -> x + + y = identity 5 + + identity + " + ), + "a -> a", + ); + } + + #[test] + fn identity_works_on_incompatible_types() { + specializes_to( + indoc!( + r#" + identity = \a -> a + + x = identity 5 + y = identity "hi" + + x + "# + ), + "Num *", + ); + } + + #[test] + fn call_returns_list() { + specializes_to( + indoc!( + r" + enlist = \val -> [val] + + enlist 5 + " + ), + "List (Num *)", + ); + } + + #[test] + fn indirect_always() { + specializes_to( + indoc!( + r#" + always = \val -> (\_ -> val) + alwaysFoo = always "foo" + + alwaysFoo 42 + "# + ), + "Str", + ); + } + + #[test] + fn pizza_desugar() { + specializes_to( + indoc!( + r" + 1 |> (\a -> a) + " + ), + "Num *", + ); + } + + #[test] + fn pizza_desugar_two_arguments() { + specializes_to( + indoc!( + r#" + always2 = \a, _ -> a + + 1 |> always2 "foo" + "# + ), + "Num *", + ); + } + + #[test] + fn anonymous_identity() { + specializes_to( + indoc!( + r" + (\a -> a) 3.14 + " + ), + "Frac *", + ); + } + + #[test] + fn identity_of_identity() { + specializes_to( + indoc!( + r" + (\val -> val) (\val -> val) + " + ), + "a -> a", + ); + } + + #[test] + fn recursive_identity() { + specializes_to( + indoc!( + r" + identity = \val -> val + + identity identity + " + ), + "a -> a", + ); + } + + #[test] + fn identity_function() { + specializes_to( + indoc!( + r" + \val -> val + " + ), + "a -> a", + ); + } + + #[test] + fn use_apply() { + specializes_to( + indoc!( + r" + identity = \a -> a + apply = \f, x -> f x + + apply identity 5 + " + ), + "Num *", + ); + } + + #[test] + fn apply_function() { + specializes_to( + indoc!( + r" + \f, x -> f x + " + ), + "(a -> b), a -> b", + ); + } + + // #[test] + // TODO FIXME this should pass, but instead fails to canonicalize + // fn use_flip() { + // infer_eq( + // indoc!( + // r" + // flip = \f -> (\a b -> f b a) + // neverendingInt = \f int -> f int + // x = neverendingInt (\a -> a) 5 + + // flip neverendingInt + // " + // ), + // "(Num *, (a -> a)) -> Num *", + // ); + // } + + #[test] + fn flip_function() { + specializes_to( + indoc!( + r" + \f -> (\a, b -> f b a) + " + ), + "(a, b -> c) -> (b, a -> c)", + ); + } + + #[test] + fn always_function() { + specializes_to( + indoc!( + r" + \val -> \_ -> val + " + ), + "a -> (* -> a)", + ); + } + + #[test] + fn pass_a_function() { + specializes_to( + indoc!( + r" + \f -> f {} + " + ), + "({} -> a) -> a", + ); + } + + // OPERATORS + + // #[test] + // fn div_operator() { + // infer_eq( + // indoc!( + // r" + // \l r -> l / r + // " + // ), + // "F64, F64 -> F64", + // ); + // } + + // #[test] + // fn basic_float_division() { + // infer_eq( + // indoc!( + // r" + // 1 / 2 + // " + // ), + // "F64", + // ); + // } + + // #[test] + // fn basic_int_division() { + // infer_eq( + // indoc!( + // r" + // 1 // 2 + // " + // ), + // "Num *", + // ); + // } + + // #[test] + // fn basic_addition() { + // infer_eq( + // indoc!( + // r" + // 1 + 2 + // " + // ), + // "Num *", + // ); + // } + + // #[test] + // fn basic_circular_type() { + // infer_eq( + // indoc!( + // r" + // \x -> x x + // " + // ), + // "", + // ); + // } + + // #[test] + // fn y_combinator_has_circular_type() { + // assert_eq!( + // infer(indoc!(r" + // \f -> (\x -> f x x) (\x -> f x x) + // ")), + // Erroneous(Problem::CircularType) + // ); + // } + + // #[test] + // fn no_higher_ranked_types() { + // // This should error because it can't type of alwaysFive + // infer_eq( + // indoc!( + // r#" + // \always -> [always [], always ""] + // "# + // ), + // "", + // ); + // } + + #[test] + fn always_with_list() { + specializes_to( + indoc!( + r#" + alwaysFive = \_ -> 5 + + [alwaysFive "foo", alwaysFive []] + "# + ), + "List (Num *)", + ); + } + + #[test] + fn if_with_int_literals() { + specializes_to( + indoc!( + r" + if Bool.true then + 42 + else + 24 + " + ), + "Num *", + ); + } + + #[test] + fn when_with_int_literals() { + specializes_to( + indoc!( + r" + when 1 is + 1 -> 2 + 3 -> 4 + " + ), + "Num *", + ); + } + + // RECORDS + + #[test] + fn empty_record() { + specializes_to("{}", "{}"); + } + + #[test] + fn one_field_record() { + specializes_to("{ x: 5 }", "{ x : Num * }"); + } + + #[test] + fn two_field_record() { + specializes_to("{ x: 5, y : 3.14 }", "{ x : Num *, y : Frac * }"); + } + + #[test] + fn record_literal_accessor() { + specializes_to("{ x: 5, y : 3.14 }.x", "Num *"); + } + + #[test] + fn record_literal_accessor_function() { + specializes_to(".x { x: 5, y : 3.14 }", "Num *"); + } + + #[test] + fn tuple_literal_accessor() { + specializes_to("(5, 3.14 ).0", "Num *"); + } + + #[test] + fn tuple_literal_accessor_function() { + specializes_to(".0 (5, 3.14 )", "Num *"); + } + + #[test] + fn tuple_literal_ty() { + specializes_to("(5, 3.14 )", "( Num *, Frac * )*"); + } + + #[test] + fn tuple_literal_accessor_ty() { + specializes_to(".0", "( a )* -> a"); + specializes_to(".4", "( _, _, _, _, a )* -> a"); + specializes_to(".5", "( ... 5 omitted, a )* -> a"); + specializes_to(".200", "( ... 200 omitted, a )* -> a"); + } + + #[test] + fn tuple_accessor_generalization() { + specializes_to( + indoc!( + r#" + get0 = .0 + + { a: get0 (1, 2), b: get0 ("a", "b", "c") } + "# + ), + "{ a : Num *, b : Str }", + ); + } + + #[test] + fn record_arg() { + specializes_to("\\rec -> rec.x", "{ x : a }* -> a"); + } + + #[test] + fn record_with_bound_var() { + specializes_to( + indoc!( + r" + fn = \rec -> + x = rec.x + + rec + + fn + " + ), + "{ x : a }b -> { x : a }b", + ); + } + + #[test] + fn using_type_signature() { + specializes_to( + indoc!( + r" + bar : custom -> custom + bar = \x -> x + + bar + " + ), + "custom -> custom", + ); + } + + #[test] + fn type_signature_without_body() { + specializes_to( + indoc!( + r#" + foo: Str -> {} + + foo "hi" + "# + ), + "{}", + ); + } + + #[test] + fn type_signature_without_body_rigid() { + specializes_to( + indoc!( + r" + foo : Num * -> custom + + foo 2 + " + ), + "custom", + ); + } + + #[test] + fn accessor_function() { + specializes_to(".foo", "{ foo : a }* -> a"); + } + + #[test] + fn type_signature_without_body_record() { + specializes_to( + indoc!( + r" + { x, y } : { x : ({} -> custom), y : {} } + + x + " + ), + "{} -> custom", + ); + } + + #[test] + fn empty_record_pattern() { + specializes_to( + indoc!( + r" + # technically, an empty record can be destructured + thunk = \{} -> 42 + + xEmpty = if thunk {} == 42 then { x: {} } else { x: {} } + + when xEmpty is + { x: {} } -> {} + " + ), + "{}", + ); + } + + #[test] + fn record_type_annotation() { + // check that a closed record remains closed + specializes_to( + indoc!( + r" + foo : { x : custom } -> custom + foo = \{ x } -> x + + foo + " + ), + "{ x : custom } -> custom", + ); + } + + #[test] + fn record_update() { + specializes_to( + indoc!( + r#" + user = { year: "foo", name: "Sam" } + + { user & year: "foo" } + "# + ), + "{ name : Str, year : Str }", + ); + } + + #[test] + fn bare_tag() { + specializes_to( + indoc!( + r" + Foo + " + ), + "[Foo]", + ); + } + + #[test] + fn single_tag_pattern() { + specializes_to( + indoc!( + r" + \Foo -> 42 + " + ), + "[Foo] -> Num *", + ); + } + + #[test] + fn two_tag_pattern() { + specializes_to( + indoc!( + r" + \x -> + when x is + True -> 1 + False -> 0 + " + ), + "[False, True] -> Num *", + ); + } + + #[test] + fn tag_application() { + specializes_to( + indoc!( + r#" + Foo "happy" 12 + "# + ), + "[Foo Str (Num *)]", + ); + } + + #[test] + fn record_extraction() { + specializes_to( + indoc!( + r" + f = \x -> + when x is + { a, b: _ } -> a + + f + " + ), + "{ a : a, b : * }* -> a", + ); + } + + #[test] + fn record_field_pattern_match_with_guard() { + specializes_to( + indoc!( + r" + when { x: 5 } is + { x: 4 } -> 4 + " + ), + "Num *", + ); + } + + #[test] + fn tag_union_pattern_match() { + specializes_to( + indoc!( + r" + \Foo x -> Foo x + " + ), + "[Foo a] -> [Foo a]", + ); + } + + #[test] + fn tag_union_pattern_match_ignored_field() { + specializes_to( + indoc!( + r#" + \Foo x _ -> Foo x "y" + "# + ), + "[Foo a *] -> [Foo a Str]", + ); + } + + #[test] + fn tag_with_field() { + specializes_to( + indoc!( + r#" + when Foo "blah" is + Foo x -> x + "# + ), + "Str", + ); + } + + #[test] + fn qualified_annotation_num_integer() { + specializes_to( + indoc!( + r" + int : Num.Num (Num.Integer Num.Signed64) + + int + " + ), + "I64", + ); + } + #[test] + fn qualified_annotated_num_integer() { + specializes_to( + indoc!( + r" + int : Num.Num (Num.Integer Num.Signed64) + int = 5 + + int + " + ), + "I64", + ); + } + #[test] + fn annotation_num_integer() { + specializes_to( + indoc!( + r" + int : Num (Integer Signed64) + + int + " + ), + "I64", + ); + } + #[test] + fn annotated_num_integer() { + specializes_to( + indoc!( + r" + int : Num (Integer Signed64) + int = 5 + + int + " + ), + "I64", + ); + } + + #[test] + fn qualified_annotation_using_i128() { + specializes_to( + indoc!( + r" + int : Num.I128 + + int + " + ), + "I128", + ); + } + #[test] + fn qualified_annotated_using_i128() { + specializes_to( + indoc!( + r" + int : Num.I128 + int = 5 + + int + " + ), + "I128", + ); + } + #[test] + fn annotation_using_i128() { + specializes_to( + indoc!( + r" + int : I128 + + int + " + ), + "I128", + ); + } + #[test] + fn annotated_using_i128() { + specializes_to( + indoc!( + r" + int : I128 + int = 5 + + int + " + ), + "I128", + ); + } + + #[test] + fn qualified_annotation_using_u128() { + specializes_to( + indoc!( + r" + int : Num.U128 + + int + " + ), + "U128", + ); + } + #[test] + fn qualified_annotated_using_u128() { + specializes_to( + indoc!( + r" + int : Num.U128 + int = 5 + + int + " + ), + "U128", + ); + } + #[test] + fn annotation_using_u128() { + specializes_to( + indoc!( + r" + int : U128 + + int + " + ), + "U128", + ); + } + #[test] + fn annotated_using_u128() { + specializes_to( + indoc!( + r" + int : U128 + int = 5 + + int + " + ), + "U128", + ); + } + + #[test] + fn qualified_annotation_using_i64() { + specializes_to( + indoc!( + r" + int : Num.I64 + + int + " + ), + "I64", + ); + } + #[test] + fn qualified_annotated_using_i64() { + specializes_to( + indoc!( + r" + int : Num.I64 + int = 5 + + int + " + ), + "I64", + ); + } + #[test] + fn annotation_using_i64() { + specializes_to( + indoc!( + r" + int : I64 + + int + " + ), + "I64", + ); + } + #[test] + fn annotated_using_i64() { + specializes_to( + indoc!( + r" + int : I64 + int = 5 + + int + " + ), + "I64", + ); + } + + #[test] + fn qualified_annotation_using_u64() { + specializes_to( + indoc!( + r" + int : Num.U64 + + int + " + ), + "U64", + ); + } + #[test] + fn qualified_annotated_using_u64() { + specializes_to( + indoc!( + r" + int : Num.U64 + int = 5 + + int + " + ), + "U64", + ); + } + #[test] + fn annotation_using_u64() { + specializes_to( + indoc!( + r" + int : U64 + + int + " + ), + "U64", + ); + } + #[test] + fn annotated_using_u64() { + specializes_to( + indoc!( + r" + int : U64 + int = 5 + + int + " + ), + "U64", + ); + } + + #[test] + fn qualified_annotation_using_i32() { + specializes_to( + indoc!( + r" + int : Num.I32 + + int + " + ), + "I32", + ); + } + #[test] + fn qualified_annotated_using_i32() { + specializes_to( + indoc!( + r" + int : Num.I32 + int = 5 + + int + " + ), + "I32", + ); + } + #[test] + fn annotation_using_i32() { + specializes_to( + indoc!( + r" + int : I32 + + int + " + ), + "I32", + ); + } + #[test] + fn annotated_using_i32() { + specializes_to( + indoc!( + r" + int : I32 + int = 5 + + int + " + ), + "I32", + ); + } + + #[test] + fn qualified_annotation_using_u32() { + specializes_to( + indoc!( + r" + int : Num.U32 + + int + " + ), + "U32", + ); + } + #[test] + fn qualified_annotated_using_u32() { + specializes_to( + indoc!( + r" + int : Num.U32 + int = 5 + + int + " + ), + "U32", + ); + } + #[test] + fn annotation_using_u32() { + specializes_to( + indoc!( + r" + int : U32 + + int + " + ), + "U32", + ); + } + #[test] + fn annotated_using_u32() { + specializes_to( + indoc!( + r" + int : U32 + int = 5 + + int + " + ), + "U32", + ); + } + + #[test] + fn qualified_annotation_using_i16() { + specializes_to( + indoc!( + r" + int : Num.I16 + + int + " + ), + "I16", + ); + } + #[test] + fn qualified_annotated_using_i16() { + specializes_to( + indoc!( + r" + int : Num.I16 + int = 5 + + int + " + ), + "I16", + ); + } + #[test] + fn annotation_using_i16() { + specializes_to( + indoc!( + r" + int : I16 + + int + " + ), + "I16", + ); + } + #[test] + fn annotated_using_i16() { + specializes_to( + indoc!( + r" + int : I16 + int = 5 + + int + " + ), + "I16", + ); + } + + #[test] + fn qualified_annotation_using_u16() { + specializes_to( + indoc!( + r" + int : Num.U16 + + int + " + ), + "U16", + ); + } + #[test] + fn qualified_annotated_using_u16() { + specializes_to( + indoc!( + r" + int : Num.U16 + int = 5 + + int + " + ), + "U16", + ); + } + #[test] + fn annotation_using_u16() { + specializes_to( + indoc!( + r" + int : U16 + + int + " + ), + "U16", + ); + } + #[test] + fn annotated_using_u16() { + specializes_to( + indoc!( + r" + int : U16 + int = 5 + + int + " + ), + "U16", + ); + } + + #[test] + fn qualified_annotation_using_i8() { + specializes_to( + indoc!( + r" + int : Num.I8 + + int + " + ), + "I8", + ); + } + #[test] + fn qualified_annotated_using_i8() { + specializes_to( + indoc!( + r" + int : Num.I8 + int = 5 + + int + " + ), + "I8", + ); + } + #[test] + fn annotation_using_i8() { + specializes_to( + indoc!( + r" + int : I8 + + int + " + ), + "I8", + ); + } + #[test] + fn annotated_using_i8() { + specializes_to( + indoc!( + r" + int : I8 + int = 5 + + int + " + ), + "I8", + ); + } + + #[test] + fn qualified_annotation_using_u8() { + specializes_to( + indoc!( + r" + int : Num.U8 + + int + " + ), + "U8", + ); + } + #[test] + fn qualified_annotated_using_u8() { + specializes_to( + indoc!( + r" + int : Num.U8 + int = 5 + + int + " + ), + "U8", + ); + } + #[test] + fn annotation_using_u8() { + specializes_to( + indoc!( + r" + int : U8 + + int + " + ), + "U8", + ); + } + #[test] + fn annotated_using_u8() { + specializes_to( + indoc!( + r" + int : U8 + int = 5 + + int + " + ), + "U8", + ); + } + + #[test] + fn qualified_annotation_num_floatingpoint() { + specializes_to( + indoc!( + r" + float : Num.Num (Num.FloatingPoint Num.Binary64) + + float + " + ), + "F64", + ); + } + #[test] + fn qualified_annotated_num_floatingpoint() { + specializes_to( + indoc!( + r" + float : Num.Num (Num.FloatingPoint Num.Binary64) + float = 5.5 + + float + " + ), + "F64", + ); + } + #[test] + fn annotation_num_floatingpoint() { + specializes_to( + indoc!( + r" + float : Num (FloatingPoint Binary64) + + float + " + ), + "F64", + ); + } + #[test] + fn annotated_num_floatingpoint() { + specializes_to( + indoc!( + r" + float : Num (FloatingPoint Binary64) + float = 5.5 + + float + " + ), + "F64", + ); + } + + #[test] + fn qualified_annotation_f64() { + specializes_to( + indoc!( + r" + float : Num.F64 + + float + " + ), + "F64", + ); + } + #[test] + fn qualified_annotated_f64() { + specializes_to( + indoc!( + r" + float : Num.F64 + float = 5.5 + + float + " + ), + "F64", + ); + } + #[test] + fn annotation_f64() { + specializes_to( + indoc!( + r" + float : F64 + + float + " + ), + "F64", + ); + } + #[test] + fn annotated_f64() { + specializes_to( + indoc!( + r" + float : F64 + float = 5.5 + + float + " + ), + "F64", + ); + } + + #[test] + fn qualified_annotation_f32() { + specializes_to( + indoc!( + r" + float : Num.F32 + + float + " + ), + "F32", + ); + } + #[test] + fn qualified_annotated_f32() { + specializes_to( + indoc!( + r" + float : Num.F32 + float = 5.5 + + float + " + ), + "F32", + ); + } + #[test] + fn annotation_f32() { + specializes_to( + indoc!( + r" + float : F32 + + float + " + ), + "F32", + ); + } + #[test] + fn annotated_f32() { + specializes_to( + indoc!( + r" + float : F32 + float = 5.5 + + float + " + ), + "F32", + ); + } + + #[test] + fn fake_result_ok() { + specializes_to( + indoc!( + r" + Res a e : [Okay a, Error e] + + ok : Res I64 * + ok = Okay 5 + + ok + " + ), + "Res I64 *", + ); + } + + #[test] + fn fake_result_err() { + specializes_to( + indoc!( + r#" + Res a e : [Okay a, Error e] + + err : Res * Str + err = Error "blah" + + err + "# + ), + "Res * Str", + ); + } + + #[test] + fn basic_result_ok() { + specializes_to( + indoc!( + r" + ok : Result I64 * + ok = Ok 5 + + ok + " + ), + "Result I64 *", + ); + } + + #[test] + fn basic_result_err() { + specializes_to( + indoc!( + r#" + err : Result * Str + err = Err "blah" + + err + "# + ), + "Result * Str", + ); + } + + #[test] + fn basic_result_conditional() { + specializes_to( + indoc!( + r#" + ok : Result I64 _ + ok = Ok 5 + + err : Result _ Str + err = Err "blah" + + if 1 > 0 then + ok + else + err + "# + ), + "Result I64 Str", + ); + } + + // #[test] + // fn annotation_using_num_used() { + // // There was a problem where `I64`, because it is only an annotation + // // wasn't added to the vars_by_symbol. + // infer_eq_without_problem( + // indoc!( + // r" + // int : I64 + + // p = (\x -> x) int + + // p + // " + // ), + // "I64", + // ); + // } + + #[test] + fn num_identity() { + infer_eq_without_problem( + indoc!( + r" + numIdentity : Num.Num a -> Num.Num a + numIdentity = \x -> x + + y = numIdentity 3.14 + + { numIdentity, x : numIdentity 42, y } + " + ), + "{ numIdentity : Num a -> Num a, x : Num *, y : Frac * }", + ); + } + + #[test] + fn when_with_annotation() { + infer_eq_without_problem( + indoc!( + r" + x : Num.Num (Num.Integer Num.Signed64) + x = + when 2 is + 3 -> 4 + _ -> 5 + + x + " + ), + "I64", + ); + } + + // TODO add more realistic function when able + #[test] + fn integer_sum() { + infer_eq_without_problem( + indoc!( + r" + f = \n -> + when n is + 0 -> 0 + _ -> f n + + f + " + ), + "Num * -> Num *", + ); + } + + #[test] + fn identity_map() { + 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 + " + ), + "(a -> b), [Identity a] -> [Identity b]", + ); + } + + #[test] + fn to_bit() { + infer_eq_without_problem( + indoc!( + r" + toBit = \bool -> + when bool is + True -> 1 + False -> 0 + + toBit + " + ), + "[False, True] -> Num *", + ); + } + + // this test is related to a bug where ext_var would have an incorrect rank. + // This match has duplicate cases, but we ignore that. + #[test] + fn to_bit_record() { + specializes_to( + indoc!( + r#" + foo = \rec -> + when rec is + { x: _ } -> "1" + { y: _ } -> "2" + + foo + "# + ), + "{ x : *, y : * }* -> Str", + ); + } + + #[test] + fn from_bit() { + infer_eq_without_problem( + indoc!( + r" + fromBit = \int -> + when int is + 0 -> False + _ -> True + + fromBit + " + ), + "Num * -> [False, True]", + ); + } + + #[test] + fn result_map_explicit() { + infer_eq_without_problem( + indoc!( + r" + map : (a -> b), [Err e, Ok a] -> [Err e, Ok b] + map = \f, result -> + when result is + Ok v -> Ok (f v) + Err e -> Err e + + map + " + ), + "(a -> b), [Err e, Ok a] -> [Err e, Ok b]", + ); + } + + #[test] + fn result_map_alias() { + infer_eq_without_problem( + indoc!( + r" + Res e a : [Ok a, Err e] + + map : (a -> b), Res e a -> Res e b + map = \f, result -> + when result is + Ok v -> Ok (f v) + Err e -> Err e + + map + " + ), + "(a -> b), Res e a -> Res e b", + ); + } + + #[test] + fn record_from_load() { + infer_eq_without_problem( + indoc!( + r" + foo = \{ x } -> x + + foo { x: 5 } + " + ), + "Num *", + ); + } + + #[test] + fn defs_from_load() { + infer_eq_without_problem( + indoc!( + r" + alwaysThreePointZero = \_ -> 3.0 + + answer = 42 + + identity = \a -> a + + threePointZero = identity (alwaysThreePointZero {}) + + threePointZero + " + ), + "Frac *", + ); + } + + #[test] + fn use_as_in_signature() { + infer_eq_without_problem( + indoc!( + r#" + foo : Str.Str as Foo -> Foo + foo = \_ -> "foo" + + foo + "# + ), + "Foo -> Foo", + ); + } + + #[test] + fn use_alias_in_let() { + infer_eq_without_problem( + indoc!( + r#" + Foo : Str.Str + + foo : Foo -> Foo + foo = \_ -> "foo" + + foo + "# + ), + "Foo -> Foo", + ); + } + + #[test] + fn use_alias_with_argument_in_let() { + infer_eq_without_problem( + indoc!( + r" + Foo a : { foo : a } + + v : Foo (Num.Num (Num.Integer Num.Signed64)) + v = { foo: 42 } + + v + " + ), + "Foo I64", + ); + } + + #[test] + fn identity_alias() { + infer_eq_without_problem( + indoc!( + r" + Foo a : { foo : a } + + id : Foo a -> Foo a + id = \x -> x + + id + " + ), + "Foo a -> Foo a", + ); + } + + #[test] + fn linked_list_empty() { + infer_eq_without_problem( + indoc!( + r" + empty : [Cons a (ConsList a), Nil] as ConsList a + empty = Nil + + empty + " + ), + "ConsList a", + ); + } + + #[test] + fn linked_list_singleton() { + infer_eq_without_problem( + indoc!( + r" + singleton : a -> [Cons a (ConsList a), Nil] as ConsList a + singleton = \x -> Cons x Nil + + singleton + " + ), + "a -> ConsList a", + ); + } + + #[test] + fn peano_length() { + infer_eq_without_problem( + indoc!( + r" + Peano : [S Peano, Z] + + length : Peano -> Num.Num (Num.Integer Num.Signed64) + length = \peano -> + when peano is + Z -> 0 + S v -> length v + + length + " + ), + "Peano -> I64", + ); + } + + #[test] + fn peano_map() { + infer_eq_without_problem( + indoc!( + r" + map : [S Peano, Z] as Peano -> Peano + map = \peano -> + when peano is + Z -> Z + S v -> S (map v) + + map + " + ), + "Peano -> Peano", + ); + } + + #[test] + fn infer_linked_list_map() { + infer_eq_without_problem( + indoc!( + r" + map = \f, list -> + when list is + Nil -> Nil + Cons x xs -> + a = f x + b = map f xs + + Cons a b + + map + " + ), + "(a -> b), [Cons a c, Nil] as c -> [Cons b d, Nil] as d", + ); + } + + #[test] + fn typecheck_linked_list_map() { + infer_eq_without_problem( + indoc!( + r" + ConsList a : [Cons a (ConsList a), Nil] + + map : (a -> b), ConsList a -> ConsList b + map = \f, list -> + when list is + Nil -> Nil + Cons x xs -> + Cons (f x) (map f xs) + + map + " + ), + "(a -> b), ConsList a -> ConsList b", + ); + } + + #[test] + fn mismatch_in_alias_args_gets_reported() { + specializes_to( + indoc!( + r#" + Foo a : a + + r : Foo {} + r = {} + + s : Foo Str.Str + s = "bar" + + when {} is + _ -> s + _ -> r + "# + ), + "", + ); + } + + #[test] + fn mismatch_in_apply_gets_reported() { + specializes_to( + indoc!( + r" + r : { x : (Num.Num (Num.Integer Signed64)) } + r = { x : 1 } + + s : { left : { x : Num.Num (Num.FloatingPoint Num.Binary64) } } + s = { left: { x : 3.14 } } + + when 0 is + 1 -> s.left + 0 -> r + " + ), + "", + ); + } + + #[test] + fn mismatch_in_tag_gets_reported() { + specializes_to( + indoc!( + r" + r : [Ok Str.Str] + r = Ok 1 + + s : { left: [Ok {}] } + s = { left: Ok 3.14 } + + when 0 is + 1 -> s.left + 0 -> r + " + ), + "", + ); + } + + // TODO As intended, this fails, but it fails with the wrong error! + // + // #[test] + // fn nums() { + // infer_eq_without_problem( + // indoc!( + // r" + // s : Num * + // s = 3.1 + + // s + // " + // ), + // "", + // ); + // } + + #[test] + fn peano_map_alias() { + specializes_to( + indoc!( + r#" + app "test" provides [main] to "./platform" + + Peano : [S Peano, Z] + + map : Peano -> Peano + map = \peano -> + when peano is + Z -> Z + S rest -> S (map rest) + + main = + map + "# + ), + "Peano -> Peano", + ); + } + + #[test] + fn unit_alias() { + specializes_to( + indoc!( + r" + Unit : [Unit] + + unit : Unit + unit = Unit + + unit + " + ), + "Unit", + ); + } + + #[test] + fn rigid_in_letnonrec() { + infer_eq_without_problem( + indoc!( + r" + ConsList a : [Cons a (ConsList a), Nil] + + toEmpty : ConsList a -> ConsList a + toEmpty = \_ -> + result : ConsList a + result = Nil + + result + + toEmpty + " + ), + "ConsList a -> ConsList a", + ); + } + + #[test] + fn rigid_in_letrec_ignored() { + infer_eq_without_problem( + indoc!( + r" + ConsList a : [Cons a (ConsList a), Nil] + + toEmpty : ConsList a -> ConsList a + toEmpty = \_ -> + result : ConsList _ # TODO to enable using `a` we need scoped variables + result = Nil + + toEmpty result + + toEmpty + " + ), + "ConsList a -> ConsList a", + ); + } + + #[test] + fn rigid_in_letrec() { + infer_eq_without_problem( + indoc!( + r#" + app "test" provides [main] to "./platform" + + ConsList a : [Cons a (ConsList a), Nil] + + toEmpty : ConsList a -> ConsList a + toEmpty = \_ -> + result : ConsList _ # TODO to enable using `a` we need scoped variables + result = Nil + + toEmpty result + + main = + toEmpty + "# + ), + "ConsList a -> ConsList a", + ); + } + + #[test] + fn let_record_pattern_with_annotation() { + infer_eq_without_problem( + indoc!( + r#" + { x, y } : { x : Str.Str, y : Num.Num (Num.FloatingPoint Num.Binary64) } + { x, y } = { x : "foo", y : 3.14 } + + x + "# + ), + "Str", + ); + } + + #[test] + fn let_record_pattern_with_annotation_alias() { + specializes_to( + indoc!( + r#" + Foo : { x : Str.Str, y : Num.Num (Num.FloatingPoint Num.Binary64) } + + { x, y } : Foo + { x, y } = { x : "foo", y : 3.14 } + + x + "# + ), + "Str", + ); + } + + #[test] + fn peano_map_infer() { + specializes_to( + indoc!( + r#" + app "test" provides [main] to "./platform" + + map = + \peano -> + when peano is + Z -> Z + S rest -> map rest |> S + + + main = + map + "# + ), + "[S a, Z] as a -> [S b, Z] as b", + ); + } + + #[test] + fn peano_map_infer_nested() { + specializes_to( + indoc!( + r" + map = \peano -> + when peano is + Z -> Z + S rest -> + map rest |> S + + + map + " + ), + "[S a, Z] as a -> [S b, Z] as b", + ); + } + + #[test] + fn let_record_pattern_with_alias_annotation() { + infer_eq_without_problem( + indoc!( + r#" + Foo : { x : Str.Str, y : Num.Num (Num.FloatingPoint Num.Binary64) } + + { x, y } : Foo + { x, y } = { x : "foo", y : 3.14 } + + x + "# + ), + "Str", + ); + } + + #[test] + fn let_tag_pattern_with_annotation() { + infer_eq_without_problem( + indoc!( + r" + UserId x : [UserId I64] + UserId x = UserId 42 + + x + " + ), + "I64", + ); + } + + #[test] + fn typecheck_record_linked_list_map() { + infer_eq_without_problem( + indoc!( + r" + ConsList q : [Cons { x: q, xs: ConsList q }, Nil] + + map : (a -> b), ConsList a -> ConsList b + map = \f, list -> + when list is + Nil -> Nil + Cons { x, xs } -> + Cons { x: f x, xs : map f xs } + + map + " + ), + "(a -> b), ConsList a -> ConsList b", + ); + } + + #[test] + fn infer_record_linked_list_map() { + infer_eq_without_problem( + indoc!( + r" + map = \f, list -> + when list is + Nil -> Nil + Cons { x, xs } -> + Cons { x: f x, xs : map f xs } + + map + " + ), + "(a -> b), [Cons { x : a, xs : c }*, Nil] as c -> [Cons { x : b, xs : d }, Nil] as d", + ); + } + + #[test] + fn typecheck_mutually_recursive_tag_union_2() { + infer_eq_without_problem( + indoc!( + r" + ListA a b : [Cons a (ListB b a), Nil] + ListB a b : [Cons a (ListA b a), Nil] + + ConsList q : [Cons q (ConsList q), Nil] + + toAs : (b -> a), ListA a b -> ConsList a + toAs = \f, lista -> + when lista is + Nil -> Nil + Cons a listb -> + when listb is + Nil -> Nil + Cons b newLista -> + Cons a (Cons (f b) (toAs f newLista)) + + toAs + " + ), + "(b -> a), ListA a b -> ConsList a", + ); + } + + #[test] + fn typecheck_mutually_recursive_tag_union_listabc() { + infer_eq_without_problem( + indoc!( + r" + ListA a : [Cons a (ListB a)] + ListB a : [Cons a (ListC a)] + ListC a : [Cons a (ListA a), Nil] + + val : ListC Num.I64 + val = Cons 1 (Cons 2 (Cons 3 Nil)) + + val + " + ), + "ListC I64", + ); + } + + #[test] + fn infer_mutually_recursive_tag_union() { + infer_eq_without_problem( + indoc!( + r" + toAs = \f, lista -> + when lista is + Nil -> Nil + Cons a listb -> + when listb is + Nil -> Nil + Cons b newLista -> + Cons a (Cons (f b) (toAs f newLista)) + + toAs + " + ), + "(a -> b), [Cons c [Cons a d, Nil], Nil] as d -> [Cons c [Cons b e], Nil] as e", + ); + } + + #[test] + fn solve_list_get() { + infer_eq_without_problem( + indoc!( + r#" + List.get ["a"] 0 + "# + ), + "Result Str [OutOfBounds]", + ); + } + + #[test] + fn type_more_general_than_signature() { + infer_eq_without_problem( + indoc!( + r" + partition : U64, U64, List (Int a) -> [Pair U64 (List (Int a))] + partition = \low, high, initialList -> + when List.get initialList high is + Ok _ -> + Pair 0 [] + + Err _ -> + Pair (low - 1) initialList + + partition + " + ), + "U64, U64, List (Int a) -> [Pair U64 (List (Int a))]", + ); + } + + #[test] + fn quicksort_partition() { + infer_eq_without_problem( + indoc!( + r" + swap : U64, U64, List a -> List a + swap = \i, j, list -> + when Pair (List.get list i) (List.get list j) is + Pair (Ok atI) (Ok atJ) -> + list + |> List.set i atJ + |> List.set j atI + + _ -> + list + + partition : U64, U64, List (Int a) -> [Pair U64 (List (Int a))] + partition = \low, high, initialList -> + when List.get initialList high is + Ok pivot -> + go = \i, j, list -> + if j < high then + when List.get list j is + Ok value -> + if value <= pivot then + go (i + 1) (j + 1) (swap (i + 1) j list) + else + go i (j + 1) list + + Err _ -> + Pair i list + else + Pair i list + + when go (low - 1) low initialList is + Pair newI newList -> + Pair (newI + 1) (swap (newI + 1) high newList) + + Err _ -> + Pair (low - 1) initialList + + partition + " + ), + "U64, U64, List (Int a) -> [Pair U64 (List (Int a))]", + ); + } + + #[test] + fn identity_list() { + infer_eq_without_problem( + indoc!( + r" + idList : List a -> List a + idList = \list -> list + + foo : List I64 -> List I64 + foo = \initialList -> idList initialList + + + foo + " + ), + "List I64 -> List I64", + ); + } + + #[test] + fn list_get() { + infer_eq_without_problem( + indoc!( + r" + List.get [10, 9, 8, 7] 1 + " + ), + "Result (Num *) [OutOfBounds]", + ); + + infer_eq_without_problem( + indoc!( + r" + List.get + " + ), + "List a, U64 -> Result a [OutOfBounds]", + ); + } + + #[test] + fn use_rigid_twice() { + infer_eq_without_problem( + indoc!( + r" + id1 : q -> q + id1 = \x -> x + + id2 : q -> q + id2 = \x -> x + + { id1, id2 } + " + ), + "{ id1 : q -> q, id2 : q1 -> q1 }", + ); + } + + #[test] + fn map_insert() { + infer_eq_without_problem( + indoc!( + r" + Dict.insert + " + ), + "Dict k v, k, v -> Dict k v where k implements Hash & Eq", + ); + } + + #[test] + fn num_to_frac() { + infer_eq_without_problem( + indoc!( + r" + Num.toFrac + " + ), + "Num * -> Frac a", + ); + } + + #[test] + fn pow() { + infer_eq_without_problem( + indoc!( + r" + Num.pow + " + ), + "Frac a, Frac a -> Frac a", + ); + } + + #[test] + fn ceiling() { + infer_eq_without_problem( + indoc!( + r" + Num.ceiling + " + ), + "Frac * -> Int a", + ); + } + + #[test] + fn floor() { + infer_eq_without_problem( + indoc!( + r" + Num.floor + " + ), + "Frac * -> Int a", + ); + } + + #[test] + fn div() { + infer_eq_without_problem( + indoc!( + r" + Num.div + " + ), + "Frac a, Frac a -> Frac a", + ) + } + + #[test] + fn div_checked() { + infer_eq_without_problem( + indoc!( + r" + Num.divChecked + " + ), + "Frac a, Frac a -> Result (Frac a) [DivByZero]", + ) + } + + #[test] + fn div_ceil() { + infer_eq_without_problem( + indoc!( + r" + Num.divCeil + " + ), + "Int a, Int a -> Int a", + ); + } + + #[test] + fn div_ceil_checked() { + infer_eq_without_problem( + indoc!( + r" + Num.divCeilChecked + " + ), + "Int a, Int a -> Result (Int a) [DivByZero]", + ); + } + + #[test] + fn div_trunc() { + infer_eq_without_problem( + indoc!( + r" + Num.divTrunc + " + ), + "Int a, Int a -> Int a", + ); + } + + #[test] + fn div_trunc_checked() { + infer_eq_without_problem( + indoc!( + r" + Num.divTruncChecked + " + ), + "Int a, Int a -> Result (Int a) [DivByZero]", + ); + } + + #[test] + fn atan() { + infer_eq_without_problem( + indoc!( + r" + Num.atan + " + ), + "Frac a -> Frac a", + ); + } + + #[test] + fn min_i128() { + infer_eq_without_problem( + indoc!( + r" + Num.minI128 + " + ), + "I128", + ); + } + + #[test] + fn max_i128() { + infer_eq_without_problem( + indoc!( + r" + Num.maxI128 + " + ), + "I128", + ); + } + + #[test] + fn min_i64() { + infer_eq_without_problem( + indoc!( + r" + Num.minI64 + " + ), + "I64", + ); + } + + #[test] + fn max_i64() { + infer_eq_without_problem( + indoc!( + r" + Num.maxI64 + " + ), + "I64", + ); + } + + #[test] + fn min_u64() { + infer_eq_without_problem( + indoc!( + r" + Num.minU64 + " + ), + "U64", + ); + } + + #[test] + fn max_u64() { + infer_eq_without_problem( + indoc!( + r" + Num.maxU64 + " + ), + "U64", + ); + } + + #[test] + fn min_i32() { + infer_eq_without_problem( + indoc!( + r" + Num.minI32 + " + ), + "I32", + ); + } + + #[test] + fn max_i32() { + infer_eq_without_problem( + indoc!( + r" + Num.maxI32 + " + ), + "I32", + ); + } + + #[test] + fn min_u32() { + infer_eq_without_problem( + indoc!( + r" + Num.minU32 + " + ), + "U32", + ); + } + + #[test] + fn max_u32() { + infer_eq_without_problem( + indoc!( + r" + Num.maxU32 + " + ), + "U32", + ); + } + + #[test] + fn reconstruct_path() { + infer_eq_without_problem( + indoc!( + r" + reconstructPath : Dict position position, position -> List position where position implements Hash & Eq + reconstructPath = \cameFrom, goal -> + when Dict.get cameFrom goal is + Err KeyNotFound -> + [] + + Ok next -> + List.append (reconstructPath cameFrom next) goal + + reconstructPath + " + ), + "Dict position position, position -> List position where position implements Hash & Eq", + ); + } + + #[test] + fn use_correct_ext_record() { + // Related to a bug solved in 81fbab0b3fe4765bc6948727e603fc2d49590b1c + infer_eq_without_problem( + indoc!( + r" + f = \r -> + g = r.q + h = r.p + + 42 + + f + " + ), + "{ p : *, q : * }* -> Num *", + ); + } + + #[test] + fn use_correct_ext_tag_union() { + // related to a bug solved in 08c82bf151a85e62bce02beeed1e14444381069f + infer_eq_without_problem( + indoc!( + r#" + app "test" imports [] provides [main] to "./platform" + + boom = \_ -> boom {} + + Model position : { openSet : Set position } + + cheapestOpen : Model position -> Result position [KeyNotFound] where position implements Hash & Eq + cheapestOpen = \model -> + + folder = \resSmallestSoFar, position -> + when resSmallestSoFar is + Err _ -> resSmallestSoFar + Ok smallestSoFar -> + if position == smallestSoFar.position then resSmallestSoFar + + else + Ok { position, cost: 0.0 } + + Set.walk model.openSet (Ok { position: boom {}, cost: 0.0 }) folder + |> Result.map (\x -> x.position) + + astar : Model position -> Result position [KeyNotFound] where position implements Hash & Eq + astar = \model -> cheapestOpen model + + main = + astar + "# + ), + "Model position -> Result position [KeyNotFound] where position implements Hash & Eq", + ); + } + + #[test] + fn when_with_or_pattern_and_guard() { + infer_eq_without_problem( + indoc!( + r" + \x -> + when x is + 2 | 3 -> 0 + a if a < 20 -> 1 + 3 | 4 if Bool.false -> 2 + _ -> 3 + " + ), + "Num * -> Num *", + ); + } + + #[test] + fn sorting() { + // based on https://github.com/elm/compiler/issues/2057 + // Roc seems to do this correctly, tracking to make sure it stays that way + infer_eq_without_problem( + indoc!( + r" + sort : ConsList cm -> ConsList cm + sort = + \xs -> + f : cm, cm -> Order + f = \_, _ -> LT + + sortWith f xs + + sortBy : (x -> cmpl), ConsList x -> ConsList x + sortBy = + \_, list -> + cmp : x, x -> Order + cmp = \_, _ -> LT + + sortWith cmp list + + always = \x, _ -> x + + sortWith : (foobar, foobar -> Order), ConsList foobar -> ConsList foobar + sortWith = + \_, list -> + f = \arg -> + g arg + + g = \bs -> + when bs is + bx -> f bx + + always Nil (f list) + + Order : [LT, GT, EQ] + ConsList a : [Nil, Cons a (ConsList a)] + + { x: sortWith, y: sort, z: sortBy } + " + ), + "{ x : (foobar, foobar -> Order), ConsList foobar -> ConsList foobar, y : ConsList cm -> ConsList cm, z : (x -> cmpl), ConsList x -> ConsList x }" + ); + } + + // Like in elm, this test now fails. Polymorphic recursion (even with an explicit signature) + // yields a type error. + // + // We should at some point investigate why that is. Elm did support polymorphic recursion in + // earlier versions. + // + // #[test] + // fn wrapper() { + // // based on https://github.com/elm/compiler/issues/1964 + // // Roc seems to do this correctly, tracking to make sure it stays that way + // infer_eq_without_problem( + // indoc!( + // r" + // Type a : [TypeCtor (Type (Wrapper a))] + // + // Wrapper a : [Wrapper a] + // + // Opaque : [Opaque] + // + // encodeType1 : Type a -> Opaque + // encodeType1 = \thing -> + // when thing is + // TypeCtor v0 -> + // encodeType1 v0 + // + // encodeType1 + // " + // ), + // "Type a -> Opaque", + // ); + // } + + #[test] + fn rigids() { + infer_eq_without_problem( + indoc!( + r" + f : List a -> List a + f = \input -> + # let-polymorphism at work + x : {} -> List b + x = \{} -> [] + + when List.get input 0 is + Ok val -> List.append (x {}) val + Err _ -> input + f + " + ), + "List a -> List a", + ); + } + + #[cfg(debug_assertions)] + #[test] + #[should_panic] + fn rigid_record_quantification() { + // the ext here is qualified on the outside (because we have rank 1 types, not rank 2). + // That means e.g. `f : { bar : String, foo : I64 } -> Bool }` is a valid argument, but + // that function could not be applied to the `{ foo : I64 }` list. Therefore, this function + // is not allowed. + // + // should hit a debug_assert! in debug mode, and produce a type error in release mode + infer_eq_without_problem( + indoc!( + r" + test : ({ foo : I64 }ext -> Bool), { foo : I64 } -> Bool + test = \fn, a -> fn a + + test + " + ), + "should fail", + ); + } + + // OPTIONAL RECORD FIELDS + + #[test] + fn optional_field_unifies_with_missing() { + infer_eq_without_problem( + indoc!( + r" + negatePoint : { x : I64, y : I64, z ? Num c } -> { x : I64, y : I64, z : Num c } + + negatePoint { x: 1, y: 2 } + " + ), + "{ x : I64, y : I64, z : Num c }", + ); + } + + #[test] + fn open_optional_field_unifies_with_missing() { + infer_eq_without_problem( + indoc!( + r#" + negatePoint : { x : I64, y : I64, z ? Num c }r -> { x : I64, y : I64, z : Num c }r + + a = negatePoint { x: 1, y: 2 } + b = negatePoint { x: 1, y: 2, blah : "hi" } + + { a, b } + "# + ), + "{ a : { x : I64, y : I64, z : Num c }, b : { blah : Str, x : I64, y : I64, z : Num c1 } }", + ); + } + + #[test] + fn optional_field_unifies_with_present() { + infer_eq_without_problem( + indoc!( + r" + negatePoint : { x : Num a, y : Num b, z ? c } -> { x : Num a, y : Num b, z : c } + + negatePoint { x: 1, y: 2.1, z: 0x3 } + " + ), + "{ x : Num *, y : Frac *, z : Int * }", + ); + } + + #[test] + fn open_optional_field_unifies_with_present() { + infer_eq_without_problem( + indoc!( + r#" + negatePoint : { x : Num a, y : Num b, z ? c }r -> { x : Num a, y : Num b, z : c }r + + a = negatePoint { x: 1, y: 2.1 } + b = negatePoint { x: 1, y: 2.1, blah : "hi" } + + { a, b } + "# + ), + "{ a : { x : Num *, y : Frac *, z : c }, b : { blah : Str, x : Num *, y : Frac *, z : c1 } }", + ); + } + + #[test] + fn optional_field_function() { + infer_eq_without_problem( + indoc!( + r" + \{ x, y ? 0 } -> x + y + " + ), + "{ x : Num a, y ? Num a }* -> Num a", + ); + } + + #[test] + fn optional_field_let() { + infer_eq_without_problem( + indoc!( + r" + { x, y ? 0 } = { x: 32 } + + x + y + " + ), + "Num *", + ); + } + + #[test] + fn optional_field_when() { + infer_eq_without_problem( + indoc!( + r" + \r -> + when r is + { x, y ? 0 } -> x + y + " + ), + "{ x : Num a, y ? Num a }* -> Num a", + ); + } + + #[test] + fn optional_field_let_with_signature() { + infer_eq_without_problem( + indoc!( + r" + \rec -> + { x, y } : { x : I64, y ? Bool }* + { x, y ? Bool.false } = rec + + { x, y } + " + ), + "{ x : I64, y ? Bool }* -> { x : I64, y : Bool }", + ); + } + + #[test] + fn list_walk_backwards() { + infer_eq_without_problem( + indoc!( + r" + List.walkBackwards + " + ), + "List elem, state, (state, elem -> state) -> state", + ); + } + + #[test] + fn list_walk_backwards_example() { + infer_eq_without_problem( + indoc!( + r" + empty : List I64 + empty = + [] + + List.walkBackwards empty 0 (\a, b -> a + b) + " + ), + "I64", + ); + } + + #[test] + fn list_walk_with_index_until() { + infer_eq_without_problem( + indoc!(r"List.walkWithIndexUntil"), + "List elem, state, (state, elem, U64 -> [Break state, Continue state]) -> state", + ); + } + + #[test] + fn list_drop_at() { + infer_eq_without_problem( + indoc!( + r" + List.dropAt + " + ), + "List elem, U64 -> List elem", + ); + } + + #[test] + fn str_trim() { + infer_eq_without_problem( + indoc!( + r" + Str.trim + " + ), + "Str -> Str", + ); + } + + #[test] + fn str_trim_start() { + infer_eq_without_problem( + indoc!( + r" + Str.trimStart + " + ), + "Str -> Str", + ); + } + + #[test] + fn list_take_first() { + infer_eq_without_problem( + indoc!( + r" + List.takeFirst + " + ), + "List elem, U64 -> List elem", + ); + } + + #[test] + fn list_take_last() { + infer_eq_without_problem( + indoc!( + r" + List.takeLast + " + ), + "List elem, U64 -> List elem", + ); + } + + #[test] + fn list_sublist() { + infer_eq_without_problem( + indoc!( + r" + List.sublist + " + ), + "List elem, { len : U64, start : U64 } -> List elem", + ); + } + + #[test] + fn list_split() { + infer_eq_without_problem( + indoc!("List.split"), + "List elem, U64 -> { before : List elem, others : List elem }", + ); + } + + #[test] + fn list_drop_last() { + infer_eq_without_problem( + indoc!( + r" + List.dropLast + " + ), + "List elem, U64 -> List elem", + ); + } + + #[test] + fn list_intersperse() { + infer_eq_without_problem( + indoc!( + r" + List.intersperse + " + ), + "List elem, elem -> List elem", + ); + } + #[test] + fn function_that_captures_nothing_is_not_captured() { + // we should make sure that a function that doesn't capture anything it not itself captured + // such functions will be lifted to the top-level, and are thus globally available! + infer_eq_without_problem( + indoc!( + r" + f = \x -> x + 1 + + g = \y -> f y + + g + " + ), + "Num a -> Num a", + ); + } + + #[test] + fn double_named_rigids() { + infer_eq_without_problem( + indoc!( + r#" + app "test" provides [main] to "./platform" + + + main : List x + main = + empty : List x + empty = [] + + empty + "# + ), + "List x", + ); + } + + #[test] + fn double_tag_application() { + infer_eq_without_problem( + indoc!( + r#" + app "test" provides [main] to "./platform" + + + main = + if 1 == 1 then + Foo (Bar) 1 + else + Foo Bar 1 + "# + ), + "[Foo [Bar] (Num *)]", + ); + + infer_eq_without_problem("Foo Bar 1", "[Foo [Bar] (Num *)]"); + } + + #[test] + fn double_tag_application_pattern() { + infer_eq_without_problem( + indoc!( + r#" + app "test" provides [main] to "./platform" + + Bar : [Bar] + Foo : [Foo Bar I64, Empty] + + foo : Foo + foo = Foo Bar 1 + + main = + when foo is + Foo Bar 1 -> + Foo Bar 2 + + x -> + x + "# + ), + "[Empty, Foo [Bar] I64]", + ); + } + + #[test] + fn recursive_function_with_rigid() { + infer_eq_without_problem( + indoc!( + r#" + app "test" provides [main] to "./platform" + + State a : { count : I64, x : a } + + foo : State a -> I64 + foo = \state -> + if state.count == 0 then + 0 + else + 1 + foo { count: state.count - 1, x: state.x } + + main : I64 + main = + foo { count: 3, x: {} } + "# + ), + "I64", + ); + } + + #[test] + fn rbtree_empty() { + infer_eq_without_problem( + indoc!( + r#" + app "test" provides [main] to "./platform" + + # The color of a node. Leaves are considered Black. + NodeColor : [Red, Black] + + RBTree k v : [Node NodeColor k v (RBTree k v) (RBTree k v), Empty] + + # Create an empty dictionary. + empty : {} -> RBTree k v + empty = \{} -> + Empty + + foo : RBTree I64 I64 + foo = empty {} + + main : RBTree I64 I64 + main = + foo + "# + ), + "RBTree I64 I64", + ); + } + + #[test] + fn rbtree_insert() { + // exposed an issue where pattern variables were not introduced + // at the correct level in the constraint + // + // see 22592eff805511fbe1da63849771ee5f367a6a16 + infer_eq_without_problem( + indoc!( + r#" + app "test" provides [main] to "./platform" + + RBTree k : [Node k (RBTree k), Empty] + + balance : RBTree k -> RBTree k + balance = \left -> + when left is + Node _ Empty -> Empty + + _ -> Empty + + main : RBTree {} + main = + balance Empty + "# + ), + "RBTree {}", + ); + } + + #[test] + fn rbtree_full_remove_min() { + infer_eq_without_problem( + indoc!( + r#" + app "test" provides [main] to "./platform" + + NodeColor : [Red, Black] + + RBTree k v : [Node NodeColor k v (RBTree k v) (RBTree k v), Empty] + + moveRedLeft : RBTree k v -> RBTree k v + moveRedLeft = \dict -> + when dict is + # Node clr k v (Node lClr lK lV lLeft lRight) (Node rClr rK rV ((Node Red rlK rlV rlL rlR) as rLeft) rRight) -> + # Node clr k v (Node lClr lK lV lLeft lRight) (Node rClr rK rV rLeft rRight) -> + Node clr k v (Node _ lK lV lLeft lRight) (Node _ rK rV rLeft rRight) -> + when rLeft is + Node Red rlK rlV rlL rlR -> + Node + Red + rlK + rlV + (Node Black k v (Node Red lK lV lLeft lRight) rlL) + (Node Black rK rV rlR rRight) + + _ -> + when clr is + Black -> + Node + Black + k + v + (Node Red lK lV lLeft lRight) + (Node Red rK rV rLeft rRight) + + Red -> + Node + Black + k + v + (Node Red lK lV lLeft lRight) + (Node Red rK rV rLeft rRight) + + _ -> + dict + + balance : NodeColor, k, v, RBTree k v, RBTree k v -> RBTree k v + balance = \color, key, value, left, right -> + when right is + Node Red rK rV rLeft rRight -> + when left is + Node Red lK lV lLeft lRight -> + Node + Red + key + value + (Node Black lK lV lLeft lRight) + (Node Black rK rV rLeft rRight) + + _ -> + Node color rK rV (Node Red key value left rLeft) rRight + + _ -> + when left is + Node Red lK lV (Node Red llK llV llLeft llRight) lRight -> + Node + Red + lK + lV + (Node Black llK llV llLeft llRight) + (Node Black key value lRight right) + + _ -> + Node color key value left right + + + Key k : Num k + + removeHelpEQGT : Key k, RBTree (Key k) v -> RBTree (Key k) v where k implements Hash & Eq + removeHelpEQGT = \targetKey, dict -> + when dict is + Node color key value left right -> + if targetKey == key then + when getMin right is + Node _ minKey minValue _ _ -> + balance color minKey minValue left (removeMin right) + + Empty -> + Empty + else + balance color key value left (removeHelp targetKey right) + + Empty -> + Empty + + getMin : RBTree k v -> RBTree k v + getMin = \dict -> + when dict is + # Node _ _ _ ((Node _ _ _ _ _) as left) _ -> + Node _ _ _ left _ -> + when left is + Node _ _ _ _ _ -> getMin left + _ -> dict + + _ -> + dict + + + moveRedRight : RBTree k v -> RBTree k v + moveRedRight = \dict -> + when dict is + Node clr k v (Node lClr lK lV (Node Red llK llV llLeft llRight) lRight) (Node rClr rK rV rLeft rRight) -> + Node + Red + lK + lV + (Node Black llK llV llLeft llRight) + (Node Black k v lRight (Node Red rK rV rLeft rRight)) + + Node clr k v (Node lClr lK lV lLeft lRight) (Node rClr rK rV rLeft rRight) -> + when clr is + Black -> + Node + Black + k + v + (Node Red lK lV lLeft lRight) + (Node Red rK rV rLeft rRight) + + Red -> + Node + Black + k + v + (Node Red lK lV lLeft lRight) + (Node Red rK rV rLeft rRight) + + _ -> + dict + + + removeHelpPrepEQGT : Key k, RBTree (Key k) v, NodeColor, (Key k), v, RBTree (Key k) v, RBTree (Key k) v -> RBTree (Key k) v + removeHelpPrepEQGT = \_, dict, color, key, value, left, right -> + when left is + Node Red lK lV lLeft lRight -> + Node + color + lK + lV + lLeft + (Node Red key value lRight right) + + _ -> + when right is + Node Black _ _ (Node Black _ _ _ _) _ -> + moveRedRight dict + + Node Black _ _ Empty _ -> + moveRedRight dict + + _ -> + dict + + + removeMin : RBTree k v -> RBTree k v + removeMin = \dict -> + when dict is + Node color key value left right -> + when left is + Node lColor _ _ lLeft _ -> + when lColor is + Black -> + when lLeft is + Node Red _ _ _ _ -> + Node color key value (removeMin left) right + + _ -> + when moveRedLeft dict is # here 1 + Node nColor nKey nValue nLeft nRight -> + balance nColor nKey nValue (removeMin nLeft) nRight + + Empty -> + Empty + + _ -> + Node color key value (removeMin left) right + + _ -> + Empty + _ -> + Empty + + removeHelp : Key k, RBTree (Key k) v -> RBTree (Key k) v where k implements Hash & Eq + removeHelp = \targetKey, dict -> + when dict is + Empty -> + Empty + + Node color key value left right -> + if targetKey < key then + when left is + Node Black _ _ lLeft _ -> + when lLeft is + Node Red _ _ _ _ -> + Node color key value (removeHelp targetKey left) right + + _ -> + when moveRedLeft dict is # here 2 + Node nColor nKey nValue nLeft nRight -> + balance nColor nKey nValue (removeHelp targetKey nLeft) nRight + + Empty -> + Empty + + _ -> + Node color key value (removeHelp targetKey left) right + else + removeHelpEQGT targetKey (removeHelpPrepEQGT targetKey dict color key value left right) + + + main : RBTree I64 I64 + main = + removeHelp 1i64 Empty + "# + ), + "RBTree (Key (Integer Signed64)) I64", + ); + } + + #[test] + fn rbtree_remove_min_1() { + infer_eq_without_problem( + indoc!( + r#" + app "test" provides [main] to "./platform" + + RBTree k : [Node k (RBTree k) (RBTree k), Empty] + + removeHelp : Num k, RBTree (Num k) -> RBTree (Num k) + removeHelp = \targetKey, dict -> + when dict is + Empty -> + Empty + + Node key left right -> + if targetKey < key then + when left is + Node _ lLeft _ -> + when lLeft is + Node _ _ _ -> + Empty + + _ -> Empty + + _ -> + Node key (removeHelp targetKey left) right + else + Empty + + + main : RBTree I64 + main = + removeHelp 1 Empty + "# + ), + "RBTree I64", + ); + } + + #[test] + fn rbtree_foobar() { + infer_eq_without_problem( + indoc!( + r#" + app "test" provides [main] to "./platform" + + NodeColor : [Red, Black] + + RBTree k v : [Node NodeColor k v (RBTree k v) (RBTree k v), Empty] + + removeHelp : Num k, RBTree (Num k) v -> RBTree (Num k) v where k implements Hash & Eq + removeHelp = \targetKey, dict -> + when dict is + Empty -> + Empty + + Node color key value left right -> + if targetKey < key then + when left is + Node Black _ _ lLeft _ -> + when lLeft is + Node Red _ _ _ _ -> + Node color key value (removeHelp targetKey left) right + + _ -> + when moveRedLeft dict is # here 2 + Node nColor nKey nValue nLeft nRight -> + balance nColor nKey nValue (removeHelp targetKey nLeft) nRight + + Empty -> + Empty + + _ -> + Node color key value (removeHelp targetKey left) right + else + removeHelpEQGT targetKey (removeHelpPrepEQGT targetKey dict color key value left right) + + Key k : Num k + + balance : NodeColor, k, v, RBTree k v, RBTree k v -> RBTree k v + + moveRedLeft : RBTree k v -> RBTree k v + + removeHelpPrepEQGT : Key k, RBTree (Key k) v, NodeColor, (Key k), v, RBTree (Key k) v, RBTree (Key k) v -> RBTree (Key k) v + + removeHelpEQGT : Key k, RBTree (Key k) v -> RBTree (Key k) v where k implements Hash & Eq + removeHelpEQGT = \targetKey, dict -> + when dict is + Node color key value left right -> + if targetKey == key then + when getMin right is + Node _ minKey minValue _ _ -> + balance color minKey minValue left (removeMin right) + + Empty -> + Empty + else + balance color key value left (removeHelp targetKey right) + + Empty -> + Empty + + getMin : RBTree k v -> RBTree k v + + removeMin : RBTree k v -> RBTree k v + + main : RBTree I64 I64 + main = + removeHelp 1i64 Empty + "# + ), + "RBTree I64 I64", + ); + } + + #[test] + fn quicksort_partition_help() { + infer_eq_without_problem( + indoc!( + r#" + app "test" provides [partitionHelp] to "./platform" + + swap : U64, U64, List a -> List a + swap = \i, j, list -> + when Pair (List.get list i) (List.get list j) is + Pair (Ok atI) (Ok atJ) -> + list + |> List.set i atJ + |> List.set j atI + + _ -> + [] + + partitionHelp : U64, U64, List (Num a), U64, (Num a) -> [Pair U64 (List (Num a))] + partitionHelp = \i, j, list, high, pivot -> + if j < high then + when List.get list j is + Ok value -> + if value <= pivot then + partitionHelp (i + 1) (j + 1) (swap (i + 1) j list) high pivot + else + partitionHelp i (j + 1) list high pivot + + Err _ -> + Pair i list + else + Pair i list + "# + ), + "U64, U64, List (Num a), U64, Num a -> [Pair U64 (List (Num a))]", + ); + } + + #[test] + fn rbtree_old_balance_simplified() { + infer_eq_without_problem( + indoc!( + r#" + app "test" provides [main] to "./platform" + + RBTree k : [Node k (RBTree k) (RBTree k), Empty] + + balance : k, RBTree k -> RBTree k + balance = \key, left -> + Node key left Empty + + main : RBTree I64 + main = + balance 0 Empty + "# + ), + "RBTree I64", + ); + } + + #[test] + fn rbtree_balance_simplified() { + infer_eq_without_problem( + indoc!( + r#" + app "test" provides [main] to "./platform" + + RBTree k : [Node k (RBTree k) (RBTree k), Empty] + + node = \x,y,z -> Node x y z + + balance : k, RBTree k -> RBTree k + balance = \key, left -> + node key left Empty + + main : RBTree I64 + main = + balance 0 Empty + "# + ), + "RBTree I64", + ); + } + + #[test] + fn rbtree_balance() { + infer_eq_without_problem( + indoc!( + r#" + app "test" provides [main] to "./platform" + + NodeColor : [Red, Black] + + RBTree k v : [Node NodeColor k v (RBTree k v) (RBTree k v), Empty] + + balance : NodeColor, k, v, RBTree k v, RBTree k v -> RBTree k v + balance = \color, key, value, left, right -> + when right is + Node Red rK rV rLeft rRight -> + when left is + Node Red lK lV lLeft lRight -> + Node + Red + key + value + (Node Black lK lV lLeft lRight) + (Node Black rK rV rLeft rRight) + + _ -> + Node color rK rV (Node Red key value left rLeft) rRight + + _ -> + when left is + Node Red lK lV (Node Red llK llV llLeft llRight) lRight -> + Node + Red + lK + lV + (Node Black llK llV llLeft llRight) + (Node Black key value lRight right) + + _ -> + Node color key value left right + + main : RBTree I64 I64 + main = + balance Red 0 0 Empty Empty + "# + ), + "RBTree I64 I64", + ); + } + + #[test] + fn pattern_rigid_problem() { + infer_eq_without_problem( + indoc!( + r#" + app "test" provides [main] to "./platform" + + RBTree k : [Node k (RBTree k) (RBTree k), Empty] + + balance : k, RBTree k -> RBTree k + balance = \key, left -> + when left is + Node _ _ lRight -> + Node key lRight Empty + + _ -> + Empty + + + main : RBTree I64 + main = + balance 0 Empty + "# + ), + "RBTree I64", + ); + } + + #[test] + fn expr_to_str() { + infer_eq_without_problem( + indoc!( + r#" + app "test" provides [main] to "./platform" + + Expr : [Add Expr Expr, Val I64, Var I64] + + printExpr : Expr -> Str + printExpr = \e -> + when e is + Add a b -> + "Add (" + |> Str.concat (printExpr a) + |> Str.concat ") (" + |> Str.concat (printExpr b) + |> Str.concat ")" + Val v -> Num.toStr v + Var v -> "Var " |> Str.concat (Num.toStr v) + + main : Str + main = printExpr (Var 3) + "# + ), + "Str", + ); + } + + #[test] + fn int_type_let_polymorphism() { + infer_eq_without_problem( + indoc!( + r#" + app "test" provides [main] to "./platform" + + x = 4 + + f : U8 -> U32 + f = \z -> Num.intCast z + + y = f x + + main = + x + "# + ), + "Num *", + ); + } + + #[test] + fn rigid_type_variable_problem() { + // see https://github.com/roc-lang/roc/issues/1162 + infer_eq_without_problem( + indoc!( + r#" + app "test" provides [main] to "./platform" + + RBTree k : [Node k (RBTree k) (RBTree k), Empty] + + balance : a, RBTree a -> RBTree a + balance = \key, left -> + when left is + Node _ _ lRight -> + Node key lRight Empty + + _ -> + Empty + + + main : RBTree {} + main = + balance {} Empty + "# + ), + "RBTree {}", + ); + } + + #[test] + fn inference_var_inside_arrow() { + infer_eq_without_problem( + indoc!( + r" + id : _ -> _ + id = \x -> x + id + " + ), + "a -> a", + ) + } + + #[test] + fn inference_var_inside_ctor() { + infer_eq_without_problem( + indoc!( + r#" + canIGo : _ -> Result.Result _ _ + canIGo = \color -> + when color is + "green" -> Ok "go!" + "yellow" -> Err (SlowIt "whoa, let's slow down!") + "red" -> Err (StopIt "absolutely not") + _ -> Err (UnknownColor "this is a weird stoplight") + canIGo + "# + ), + "Str -> Result Str [SlowIt Str, StopIt Str, UnknownColor Str]", + ) + } + + #[test] + fn inference_var_inside_ctor_linked() { + infer_eq_without_problem( + indoc!( + r" + swapRcd: {x: _, y: _} -> {x: _, y: _} + swapRcd = \{x, y} -> {x: y, y: x} + swapRcd + " + ), + "{ x : a, y : b } -> { x : b, y : a }", + ) + } + + #[test] + fn inference_var_link_with_rigid() { + infer_eq_without_problem( + indoc!( + r" + swapRcd: {x: tx, y: ty} -> {x: _, y: _} + swapRcd = \{x, y} -> {x: y, y: x} + swapRcd + " + ), + "{ x : tx, y : ty } -> { x : ty, y : tx }", + ) + } + + #[test] + fn inference_var_inside_tag_ctor() { + infer_eq_without_problem( + indoc!( + r#" + badComics: [True, False] -> [CowTools _, Thagomizer _] + badComics = \c -> + when c is + True -> CowTools "The Far Side" + False -> Thagomizer "The Far Side" + badComics + "# + ), + "[False, True] -> [CowTools Str, Thagomizer Str]", + ) + } + + #[test] + fn inference_var_tag_union_ext() { + // TODO: we should really be inferring [Blue, Orange]a -> [Lavender, Peach]a here. + // See https://github.com/roc-lang/roc/issues/2053 + infer_eq_without_problem( + indoc!( + r" + pastelize: _ -> [Lavender, Peach]_ + pastelize = \color -> + when color is + Blue -> Lavender + Orange -> Peach + col -> col + pastelize + " + ), + "[Blue, Lavender, Orange, Peach]a -> [Blue, Lavender, Orange, Peach]a", + ) + } + + #[test] + fn inference_var_rcd_union_ext() { + infer_eq_without_problem( + indoc!( + r#" + setRocEmail : _ -> { name: Str, email: Str }_ + setRocEmail = \person -> + { person & email: "$(person.name)@roclang.com" } + setRocEmail + "# + ), + "{ email : Str, name : Str }a -> { email : Str, name : Str }a", + ) + } + + #[test] + fn issue_2217() { + infer_eq_without_problem( + indoc!( + r" + LinkedList elem : [Empty, Prepend (LinkedList elem) elem] + + fromList : List elem -> LinkedList elem + fromList = \elems -> List.walk elems Empty Prepend + + fromList + " + ), + "List elem -> LinkedList elem", + ) + } + + #[test] + fn issue_2217_inlined() { + infer_eq_without_problem( + indoc!( + r" + fromList : List elem -> [Empty, Prepend (LinkedList elem) elem] as LinkedList elem + fromList = \elems -> List.walk elems Empty Prepend + + fromList + " + ), + "List elem -> LinkedList elem", + ) + } + + #[test] + fn infer_union_input_position1() { + infer_eq_without_problem( + indoc!( + r" + \tag -> + when tag is + A -> X + B -> Y + " + ), + "[A, B] -> [X, Y]", + ) + } + + #[test] + fn infer_union_input_position2() { + infer_eq_without_problem( + indoc!( + r" + \tag -> + when tag is + A -> X + B -> Y + _ -> Z + " + ), + "[A, B]* -> [X, Y, Z]", + ) + } + + #[test] + fn infer_union_input_position3() { + infer_eq_without_problem( + indoc!( + r" + \tag -> + when tag is + A M -> X + A N -> Y + " + ), + "[A [M, N]] -> [X, Y]", + ) + } + + #[test] + fn infer_union_input_position4() { + infer_eq_without_problem( + indoc!( + r" + \tag -> + when tag is + A M -> X + A N -> Y + A _ -> Z + " + ), + "[A [M, N]*] -> [X, Y, Z]", + ) + } + + #[test] + fn infer_union_input_position5() { + infer_eq_without_problem( + indoc!( + r" + \tag -> + when tag is + A (M J) -> X + A (N K) -> X + " + ), + "[A [M [J], N [K]]] -> [X]", + ) + } + + #[test] + fn infer_union_input_position6() { + infer_eq_without_problem( + indoc!( + r" + \tag -> + when tag is + A M -> X + B -> X + A N -> X + " + ), + "[A [M, N], B] -> [X]", + ) + } + + #[test] + fn infer_union_input_position7() { + infer_eq_without_problem( + indoc!( + r" + \tag -> + when tag is + A -> X + t -> t + " + ), + "[A, X]a -> [A, X]a", + ) + } + + #[test] + fn infer_union_input_position8() { + infer_eq_without_problem( + indoc!( + r" + \opt -> + when opt is + Some ({tag: A}) -> 1 + Some ({tag: B}) -> 1 + None -> 0 + " + ), + "[None, Some { tag : [A, B] }*] -> Num *", + ) + } + + #[test] + fn infer_union_input_position9() { + infer_eq_without_problem( + indoc!( + r#" + opt : [Some Str, None] + opt = Some "" + rcd = { opt } + + when rcd is + { opt: Some s } -> s + { opt: None } -> "?" + "# + ), + "Str", + ) + } + + #[test] + fn infer_union_input_position10() { + infer_eq_without_problem( + indoc!( + r" + \r -> + when r is + { x: Blue, y ? 3 } -> y + { x: Red, y ? 5 } -> y + " + ), + "{ x : [Blue, Red], y ? Num a }* -> Num a", + ) + } + + #[test] + // Issue #2299 + fn infer_union_argument_position() { + infer_eq_without_problem( + indoc!( + r" + \UserId id -> id + 1 + " + ), + "[UserId (Num a)] -> Num a", + ) + } + + #[test] + fn infer_union_def_position() { + infer_eq_without_problem( + indoc!( + r" + \email -> + Email str = email + Str.isEmpty str + " + ), + "[Email Str] -> Bool", + ) + } + + #[test] + fn numeric_literal_suffixes() { + infer_eq_without_problem( + indoc!( + r" + { + u8: 123u8, + u16: 123u16, + u32: 123u32, + u64: 123u64, + u128: 123u128, + + i8: 123i8, + i16: 123i16, + i32: 123i32, + i64: 123i64, + i128: 123i128, + + bu8: 0b11u8, + bu16: 0b11u16, + bu32: 0b11u32, + bu64: 0b11u64, + bu128: 0b11u128, + + bi8: 0b11i8, + bi16: 0b11i16, + bi32: 0b11i32, + bi64: 0b11i64, + bi128: 0b11i128, + + dec: 123.0dec, + f32: 123.0f32, + f64: 123.0f64, + + fdec: 123dec, + ff32: 123f32, + ff64: 123f64, + } + " + ), + r"{ bi128 : I128, bi16 : I16, bi32 : I32, bi64 : I64, bi8 : I8, bu128 : U128, bu16 : U16, bu32 : U32, bu64 : U64, bu8 : U8, dec : Dec, f32 : F32, f64 : F64, fdec : Dec, ff32 : F32, ff64 : F64, i128 : I128, i16 : I16, i32 : I32, i64 : I64, i8 : I8, u128 : U128, u16 : U16, u32 : U32, u64 : U64, u8 : U8 }", + ) + } + + #[test] + fn numeric_literal_suffixes_in_pattern() { + infer_eq_without_problem( + indoc!( + r" + { + u8: (\n -> + when n is + 123u8 -> n + _ -> n), + u16: (\n -> + when n is + 123u16 -> n + _ -> n), + u32: (\n -> + when n is + 123u32 -> n + _ -> n), + u64: (\n -> + when n is + 123u64 -> n + _ -> n), + u128: (\n -> + when n is + 123u128 -> n + _ -> n), + + i8: (\n -> + when n is + 123i8 -> n + _ -> n), + i16: (\n -> + when n is + 123i16 -> n + _ -> n), + i32: (\n -> + when n is + 123i32 -> n + _ -> n), + i64: (\n -> + when n is + 123i64 -> n + _ -> n), + i128: (\n -> + when n is + 123i128 -> n + _ -> n), + + bu8: (\n -> + when n is + 0b11u8 -> n + _ -> n), + bu16: (\n -> + when n is + 0b11u16 -> n + _ -> n), + bu32: (\n -> + when n is + 0b11u32 -> n + _ -> n), + bu64: (\n -> + when n is + 0b11u64 -> n + _ -> n), + bu128: (\n -> + when n is + 0b11u128 -> n + _ -> n), + + bi8: (\n -> + when n is + 0b11i8 -> n + _ -> n), + bi16: (\n -> + when n is + 0b11i16 -> n + _ -> n), + bi32: (\n -> + when n is + 0b11i32 -> n + _ -> n), + bi64: (\n -> + when n is + 0b11i64 -> n + _ -> n), + bi128: (\n -> + when n is + 0b11i128 -> n + _ -> n), + + dec: (\n -> + when n is + 123.0dec -> n + _ -> n), + f32: (\n -> + when n is + 123.0f32 -> n + _ -> n), + f64: (\n -> + when n is + 123.0f64 -> n + _ -> n), + + fdec: (\n -> + when n is + 123dec -> n + _ -> n), + ff32: (\n -> + when n is + 123f32 -> n + _ -> n), + ff64: (\n -> + when n is + 123f64 -> n + _ -> n), + } + " + ), + r"{ bi128 : I128 -> I128, bi16 : I16 -> I16, bi32 : I32 -> I32, bi64 : I64 -> I64, bi8 : I8 -> I8, bu128 : U128 -> U128, bu16 : U16 -> U16, bu32 : U32 -> U32, bu64 : U64 -> U64, bu8 : U8 -> U8, dec : Dec -> Dec, f32 : F32 -> F32, f64 : F64 -> F64, fdec : Dec -> Dec, ff32 : F32 -> F32, ff64 : F64 -> F64, i128 : I128 -> I128, i16 : I16 -> I16, i32 : I32 -> I32, i64 : I64 -> I64, i8 : I8 -> I8, u128 : U128 -> U128, u16 : U16 -> U16, u32 : U32 -> U32, u64 : U64 -> U64, u8 : U8 -> U8 }", + ) + } +} From 83b723f221304db78c24f1b8d23697a455cf4992 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sat, 28 Sep 2024 22:22:28 -0400 Subject: [PATCH 09/65] Fix a bunch of tests --- crates/compiler/specialize_types/src/lib.rs | 10 +- .../tests/test_specialize_types.rs | 565 ++++++------------ 2 files changed, 175 insertions(+), 400 deletions(-) diff --git a/crates/compiler/specialize_types/src/lib.rs b/crates/compiler/specialize_types/src/lib.rs index c50edf5752..bbd84f16a6 100644 --- a/crates/compiler/specialize_types/src/lib.rs +++ b/crates/compiler/specialize_types/src/lib.rs @@ -157,10 +157,10 @@ fn lower_content( let mut tags = resolve_tag_ext(subs, problems, UnionTags::default(), *ext); // Now lower all the tags we gathered from the ext var. - // Do this in a separate pass to avoid borrow errors on Subs. + // (Do this in a separate pass to avoid borrow errors on Subs.) lower_vars(tags.iter_mut().flat_map(|(_, vars)| vars.iter_mut()), cache, subs, problems); - // Then, add the tag names with no payloads. (There's nothing to lower here.) + // Then, add the tag names with no payloads. (There are no variables to lower here.) for index in tag_names.into_iter() { tags.push(((&subs[index]).clone(), Vec::new())); } @@ -287,9 +287,6 @@ fn resolve_record_ext( } } - // ext should have ended up being empty - debug_assert_eq!(ext, Variable::EMPTY_RECORD); - all_fields } @@ -331,9 +328,6 @@ fn resolve_tuple_ext( } } - // ext should have ended up being empty - debug_assert_eq!(ext, Variable::EMPTY_TUPLE); - all_elems } diff --git a/crates/compiler/specialize_types/tests/test_specialize_types.rs b/crates/compiler/specialize_types/tests/test_specialize_types.rs index ff31636df4..0c99a9ee7b 100644 --- a/crates/compiler/specialize_types/tests/test_specialize_types.rs +++ b/crates/compiler/specialize_types/tests/test_specialize_types.rs @@ -149,134 +149,57 @@ mod specialize_types { #[test] fn empty_list() { - specializes_to( - indoc!( - r" - [] - " - ), - "List *", - ); + specializes_to("[]", "List []"); } #[test] fn list_of_lists() { - specializes_to( - indoc!( - r" - [[]] - " - ), - "List (List *)", - ); + specializes_to("[[]]", "List (List [])"); } #[test] fn triple_nested_list() { - specializes_to( - indoc!( - r" - [[[]]] - " - ), - "List (List (List *))", - ); + specializes_to("[[[]]]", "List (List (List []))"); } #[test] fn nested_empty_list() { - specializes_to( - indoc!( - r" - [[], [[]]] - " - ), - "List (List (List *))", - ); + specializes_to("[[], [[]]]", "List (List (List []))"); } #[test] fn list_of_one_int() { - specializes_to( - indoc!( - r" - [42] - " - ), - "List (Num *)", - ); + specializes_to("[42]", "List (Num [])"); } #[test] fn triple_nested_int_list() { - specializes_to( - indoc!( - r" - [[[5]]] - " - ), - "List (List (List (Num *)))", - ); + specializes_to("[[[5]]]", "List (List (List (Num [])))"); } #[test] fn list_of_ints() { - specializes_to( - indoc!( - r" - [1, 2, 3] - " - ), - "List (Num *)", - ); + specializes_to("[1, 2, 3]", "List (Num [])"); } #[test] fn nested_list_of_ints() { - specializes_to( - indoc!( - r" - [[1], [2, 3]] - " - ), - "List (List (Num *))", - ); + specializes_to("[[1], [2, 3]]", "List (List (Num []))"); } #[test] fn list_of_one_string() { - specializes_to( - indoc!( - r#" - ["cowabunga"] - "# - ), - "List Str", - ); + specializes_to(r#"["cowabunga"]"#, "List Str"); } #[test] fn triple_nested_string_list() { - specializes_to( - indoc!( - r#" - [[["foo"]]] - "# - ), - "List (List (List Str))", - ); + specializes_to(r#"[[["foo"]]]"#, "List (List (List Str))"); } #[test] fn list_of_strings() { - specializes_to( - indoc!( - r#" - ["foo", "bar"] - "# - ), - "List Str", - ); + specializes_to(r#"["foo", "bar"]"#, "List Str"); } // INTERPOLATED STRING @@ -300,12 +223,12 @@ mod specialize_types { specializes_to( indoc!( r#" - whatItIs = "great" + whatItIs = "great" - str = "type inference is $(whatItIs)!" + str = "type inference is $(whatItIs)!" - whatItIs - "# + whatItIs + "# ), "Str", ); @@ -316,12 +239,12 @@ mod specialize_types { specializes_to( indoc!( r#" - rec = { whatItIs: "great" } + rec = { whatItIs: "great" } - str = "type inference is $(rec.whatItIs)!" + str = "type inference is $(rec.whatItIs)!" - rec - "# + rec + "# ), "{ whatItIs : Str }", ); @@ -375,7 +298,7 @@ mod specialize_types { \_ -> {} " ), - "* -> {}", + "[] -> {}", ); } @@ -387,7 +310,7 @@ mod specialize_types { \_, _ -> 42 " ), - "*, * -> Num *", + "[], [] -> Num []", ); } @@ -399,7 +322,7 @@ mod specialize_types { \_, _, _ -> "test!" "# ), - "*, *, * -> Str", + "[], [], [] -> Str", ); } @@ -443,7 +366,7 @@ mod specialize_types { fn " ), - "* -> {}", + "[] -> {}", ); } @@ -496,7 +419,7 @@ mod specialize_types { [\x -> Bar x, Foo] " ), - "List (a -> [Bar a, Foo a])", + "List ([] -> [Bar [], Foo []])", ) } @@ -509,7 +432,7 @@ mod specialize_types { [Foo, \x -> Bar x] " ), - "List (a -> [Bar a, Foo a])", + "List ([] -> [Bar [], Foo []])", ) } @@ -530,7 +453,7 @@ mod specialize_types { } " ), - "{ x : List [Foo], y : List (a -> [Foo a]), z : List (b, c -> [Foo b c]) }", + "{ x : List [Foo], y : List ([] -> [Foo []]), z : List ([], [] -> [Foo [] []]) }", ) } @@ -560,7 +483,7 @@ mod specialize_types { func " ), - "*, * -> Num *", + "[], [] -> Num []", ); } @@ -574,7 +497,7 @@ mod specialize_types { f "# ), - "*, *, * -> Str", + "[], [], [] -> Str", ); } @@ -590,7 +513,7 @@ mod specialize_types { b "# ), - "*, *, * -> Str", + "[], [], [] -> Str", ); } @@ -624,7 +547,7 @@ mod specialize_types { c " ), - "Num *", + "Num []", ); } @@ -643,7 +566,7 @@ mod specialize_types { ) " ), - "a -> a", + "[] -> []", ); } @@ -659,7 +582,7 @@ mod specialize_types { alwaysFive "stuff" "# ), - "Num *", + "Num []", ); } @@ -689,7 +612,7 @@ mod specialize_types { identity " ), - "a -> a", + "[] -> []", ); } @@ -706,7 +629,7 @@ mod specialize_types { x "# ), - "Num *", + "Num []", ); } @@ -720,7 +643,7 @@ mod specialize_types { enlist 5 " ), - "List (Num *)", + "List (Num [])", ); } @@ -747,7 +670,7 @@ mod specialize_types { 1 |> (\a -> a) " ), - "Num *", + "Num []", ); } @@ -761,7 +684,7 @@ mod specialize_types { 1 |> always2 "foo" "# ), - "Num *", + "Num []", ); } @@ -773,7 +696,7 @@ mod specialize_types { (\a -> a) 3.14 " ), - "Frac *", + "Frac []", ); } @@ -785,7 +708,7 @@ mod specialize_types { (\val -> val) (\val -> val) " ), - "a -> a", + "[] -> []", ); } @@ -799,7 +722,7 @@ mod specialize_types { identity identity " ), - "a -> a", + "[] -> []", ); } @@ -811,7 +734,7 @@ mod specialize_types { \val -> val " ), - "a -> a", + "[] -> []", ); } @@ -826,7 +749,7 @@ mod specialize_types { apply identity 5 " ), - "Num *", + "Num []", ); } @@ -838,7 +761,7 @@ mod specialize_types { \f, x -> f x " ), - "(a -> b), a -> b", + "([] -> []), [] -> []", ); } @@ -855,7 +778,7 @@ mod specialize_types { // flip neverendingInt // " // ), - // "(Num *, (a -> a)) -> Num *", + // "(Num [], (a -> a)) -> Num []", // ); // } @@ -867,7 +790,7 @@ mod specialize_types { \f -> (\a, b -> f b a) " ), - "(a, b -> c) -> (b, a -> c)", + "([], [] -> []) -> ([], [] -> [])", ); } @@ -879,7 +802,7 @@ mod specialize_types { \val -> \_ -> val " ), - "a -> (* -> a)", + "[] -> ([] -> [])", ); } @@ -891,7 +814,7 @@ mod specialize_types { \f -> f {} " ), - "({} -> a) -> a", + "({} -> []) -> []", ); } @@ -929,7 +852,7 @@ mod specialize_types { // 1 // 2 // " // ), - // "Num *", + // "Num []", // ); // } @@ -941,7 +864,7 @@ mod specialize_types { // 1 + 2 // " // ), - // "Num *", + // "Num []", // ); // } @@ -990,7 +913,7 @@ mod specialize_types { [alwaysFive "foo", alwaysFive []] "# ), - "List (Num *)", + "List (Num [])", ); } @@ -1005,7 +928,7 @@ mod specialize_types { 24 " ), - "Num *", + "Num []", ); } @@ -1019,7 +942,7 @@ mod specialize_types { 3 -> 4 " ), - "Num *", + "Num []", ); } @@ -1032,45 +955,45 @@ mod specialize_types { #[test] fn one_field_record() { - specializes_to("{ x: 5 }", "{ x : Num * }"); + specializes_to("{ x: 5 }", "{ x : Num [] }"); } #[test] fn two_field_record() { - specializes_to("{ x: 5, y : 3.14 }", "{ x : Num *, y : Frac * }"); + specializes_to("{ x: 5, y : 3.14 }", "{ x : Num [], y : Frac [] }"); } #[test] fn record_literal_accessor() { - specializes_to("{ x: 5, y : 3.14 }.x", "Num *"); + specializes_to("{ x: 5, y : 3.14 }.x", "Num []"); } #[test] fn record_literal_accessor_function() { - specializes_to(".x { x: 5, y : 3.14 }", "Num *"); + specializes_to(".x { x: 5, y : 3.14 }", "Num []"); } #[test] fn tuple_literal_accessor() { - specializes_to("(5, 3.14 ).0", "Num *"); + specializes_to("(5, 3.14 ).0", "Num []"); } #[test] fn tuple_literal_accessor_function() { - specializes_to(".0 (5, 3.14 )", "Num *"); + specializes_to(".0 (5, 3.14 )", "Num []"); } #[test] fn tuple_literal_ty() { - specializes_to("(5, 3.14 )", "( Num *, Frac * )*"); + specializes_to("(5, 3.14 )", "( Num [], Frac [] )"); } #[test] fn tuple_literal_accessor_ty() { - specializes_to(".0", "( a )* -> a"); - specializes_to(".4", "( _, _, _, _, a )* -> a"); - specializes_to(".5", "( ... 5 omitted, a )* -> a"); - specializes_to(".200", "( ... 200 omitted, a )* -> a"); + specializes_to(".0", "( [] ) -> []"); + specializes_to(".4", "( _, _, _, _, [] ) -> []"); + specializes_to(".5", "( ... 5 omitted, [] ) -> []"); + specializes_to(".200", "( ... 200 omitted, [] ) -> []"); } #[test] @@ -1083,13 +1006,13 @@ mod specialize_types { { a: get0 (1, 2), b: get0 ("a", "b", "c") } "# ), - "{ a : Num *, b : Str }", + "{ a : Num [], b : Str }", ); } #[test] fn record_arg() { - specializes_to("\\rec -> rec.x", "{ x : a }* -> a"); + specializes_to("\\rec -> rec.x", "{ x : [] } -> []"); } #[test] @@ -1105,7 +1028,7 @@ mod specialize_types { fn " ), - "{ x : a }b -> { x : a }b", + "{ x : [] } -> { x : [] }", ); } @@ -1120,7 +1043,7 @@ mod specialize_types { bar " ), - "custom -> custom", + "[] -> []", ); } @@ -1148,13 +1071,13 @@ mod specialize_types { foo 2 " ), - "custom", + "[]", ); } #[test] fn accessor_function() { - specializes_to(".foo", "{ foo : a }* -> a"); + specializes_to(".foo", "{ foo : [] } -> []"); } #[test] @@ -1167,7 +1090,7 @@ mod specialize_types { x " ), - "{} -> custom", + "{} -> []", ); } @@ -1201,7 +1124,7 @@ mod specialize_types { foo " ), - "{ x : custom } -> custom", + "{ x : [] } -> []", ); } @@ -1239,7 +1162,7 @@ mod specialize_types { \Foo -> 42 " ), - "[Foo] -> Num *", + "[Foo] -> Num []", ); } @@ -1254,7 +1177,7 @@ mod specialize_types { False -> 0 " ), - "[False, True] -> Num *", + "[False, True] -> Num []", ); } @@ -1266,7 +1189,7 @@ mod specialize_types { Foo "happy" 12 "# ), - "[Foo Str (Num *)]", + "[Foo Str (Num [])]", ); } @@ -1282,7 +1205,7 @@ mod specialize_types { f " ), - "{ a : a, b : * }* -> a", + "{ a : [], b : [] } -> []", ); } @@ -1295,7 +1218,7 @@ mod specialize_types { { x: 4 } -> 4 " ), - "Num *", + "Num []", ); } @@ -1307,7 +1230,7 @@ mod specialize_types { \Foo x -> Foo x " ), - "[Foo a] -> [Foo a]", + "[Foo []] -> [Foo []]", ); } @@ -1319,7 +1242,7 @@ mod specialize_types { \Foo x _ -> Foo x "y" "# ), - "[Foo a *] -> [Foo a Str]", + "[Foo [] []] -> [Foo [] Str]", ); } @@ -2119,7 +2042,7 @@ mod specialize_types { ok " ), - "Res I64 *", + "Res I64 []", ); } @@ -2136,7 +2059,7 @@ mod specialize_types { err "# ), - "Res * Str", + "Res [] Str", ); } @@ -2151,7 +2074,7 @@ mod specialize_types { ok " ), - "Result I64 *", + "Result I64 []", ); } @@ -2166,7 +2089,7 @@ mod specialize_types { err "# ), - "Result * Str", + "Result [] Str", ); } @@ -2222,7 +2145,7 @@ mod specialize_types { { numIdentity, x : numIdentity 42, y } " ), - "{ numIdentity : Num a -> Num a, x : Num *, y : Frac * }", + "{ numIdentity : Num [] -> Num [], x : Num [], y : Frac [] }", ); } @@ -2258,7 +2181,7 @@ mod specialize_types { f " ), - "Num * -> Num *", + "Num [] -> Num []", ); } @@ -2274,7 +2197,7 @@ mod specialize_types { map " ), - "(a -> b), [Identity a] -> [Identity b]", + "([] -> []), [Identity []] -> [Identity []]", ); } @@ -2291,7 +2214,7 @@ mod specialize_types { toBit " ), - "[False, True] -> Num *", + "[False, True] -> Num []", ); } @@ -2310,7 +2233,7 @@ mod specialize_types { foo "# ), - "{ x : *, y : * }* -> Str", + "{ x : [], y : [] } -> Str", ); } @@ -2327,7 +2250,7 @@ mod specialize_types { fromBit " ), - "Num * -> [False, True]", + "Num [] -> [False, True]", ); } @@ -2345,7 +2268,7 @@ mod specialize_types { map " ), - "(a -> b), [Err e, Ok a] -> [Err e, Ok b]", + "([] -> []), [Err [], Ok []] -> [Err [], Ok []]", ); } @@ -2365,7 +2288,7 @@ mod specialize_types { map " ), - "(a -> b), Res e a -> Res e b", + "([] -> []), Res [] [] -> Res [] []", ); } @@ -2379,7 +2302,7 @@ mod specialize_types { foo { x: 5 } " ), - "Num *", + "Num []", ); } @@ -2399,7 +2322,7 @@ mod specialize_types { threePointZero " ), - "Frac *", + "Frac []", ); } @@ -2465,7 +2388,7 @@ mod specialize_types { id " ), - "Foo a -> Foo a", + "Foo [] -> Foo []", ); } @@ -2480,7 +2403,7 @@ mod specialize_types { empty " ), - "ConsList a", + "ConsList []", ); } @@ -2495,7 +2418,7 @@ mod specialize_types { singleton " ), - "a -> ConsList a", + "[] -> ConsList []", ); } @@ -2554,7 +2477,7 @@ mod specialize_types { map " ), - "(a -> b), [Cons a c, Nil] as c -> [Cons b d, Nil] as d", + "([] -> []), [Cons [] c, Nil] as c -> [Cons [] d, Nil] as d", ); } @@ -2575,7 +2498,7 @@ mod specialize_types { map " ), - "(a -> b), ConsList a -> ConsList b", + "([] -> []), ConsList [] -> ConsList []", ); } @@ -2715,7 +2638,7 @@ mod specialize_types { toEmpty " ), - "ConsList a -> ConsList a", + "ConsList [] -> ConsList []", ); } @@ -2736,7 +2659,7 @@ mod specialize_types { toEmpty " ), - "ConsList a -> ConsList a", + "ConsList [] -> ConsList []", ); } @@ -2760,7 +2683,7 @@ mod specialize_types { toEmpty "# ), - "ConsList a -> ConsList a", + "ConsList [] -> ConsList []", ); } @@ -2886,7 +2809,7 @@ mod specialize_types { map " ), - "(a -> b), ConsList a -> ConsList b", + "([] -> []), ConsList [] -> ConsList []", ); } @@ -2904,7 +2827,7 @@ mod specialize_types { map " ), - "(a -> b), [Cons { x : a, xs : c }*, Nil] as c -> [Cons { x : b, xs : d }, Nil] as d", + "(a -> b), [Cons { x : a, xs : c }, Nil] as c -> [Cons { x : b, xs : d }, Nil] as d", ); } @@ -2931,7 +2854,7 @@ mod specialize_types { toAs " ), - "(b -> a), ListA a b -> ConsList a", + "([] -> []), ListA [] [] -> ConsList []", ); } @@ -3004,7 +2927,7 @@ mod specialize_types { partition " ), - "U64, U64, List (Int a) -> [Pair U64 (List (Int a))]", + "U64, U64, List (Int []) -> [Pair U64 (List (Int []))]", ); } @@ -3052,7 +2975,7 @@ mod specialize_types { partition " ), - "U64, U64, List (Int a) -> [Pair U64 (List (Int a))]", + "U64, U64, List (Int []) -> [Pair U64 (List (Int []))]", ); } @@ -3083,7 +3006,7 @@ mod specialize_types { List.get [10, 9, 8, 7] 1 " ), - "Result (Num *) [OutOfBounds]", + "Result (Num []) [OutOfBounds]", ); infer_eq_without_problem( @@ -3092,7 +3015,7 @@ mod specialize_types { List.get " ), - "List a, U64 -> Result a [OutOfBounds]", + "List [], U64 -> Result [] [OutOfBounds]", ); } @@ -3110,272 +3033,130 @@ mod specialize_types { { id1, id2 } " ), - "{ id1 : q -> q, id2 : q1 -> q1 }", + "{ id1 : [] -> [], id2 : [] -> [] }", ); } #[test] fn map_insert() { infer_eq_without_problem( - indoc!( - r" - Dict.insert - " - ), + "Dict.insert", "Dict k v, k, v -> Dict k v where k implements Hash & Eq", ); } #[test] fn num_to_frac() { - infer_eq_without_problem( - indoc!( - r" - Num.toFrac - " - ), - "Num * -> Frac a", - ); + infer_eq_without_problem("Num.toFrac", "Num [] -> Frac []"); } #[test] fn pow() { - infer_eq_without_problem( - indoc!( - r" - Num.pow - " - ), - "Frac a, Frac a -> Frac a", - ); + infer_eq_without_problem("Num.pow", "Frac [], Frac [] -> Frac []"); } #[test] fn ceiling() { - infer_eq_without_problem( - indoc!( - r" - Num.ceiling - " - ), - "Frac * -> Int a", - ); + infer_eq_without_problem("Num.ceiling", "Frac [] -> Int []"); } #[test] fn floor() { - infer_eq_without_problem( - indoc!( - r" - Num.floor - " - ), - "Frac * -> Int a", - ); + infer_eq_without_problem("Num.floor", "Frac [] -> Int []"); } #[test] fn div() { - infer_eq_without_problem( - indoc!( - r" - Num.div - " - ), - "Frac a, Frac a -> Frac a", - ) + infer_eq_without_problem("Num.div", "Frac [], Frac [] -> Frac []") } #[test] fn div_checked() { infer_eq_without_problem( - indoc!( - r" - Num.divChecked - " - ), - "Frac a, Frac a -> Result (Frac a) [DivByZero]", + "Num.divChecked", + "Frac [], Frac [] -> Result (Frac []) [DivByZero]", ) } #[test] fn div_ceil() { - infer_eq_without_problem( - indoc!( - r" - Num.divCeil - " - ), - "Int a, Int a -> Int a", - ); + infer_eq_without_problem("Num.divCeil", "Int [], Int [] -> Int []"); } #[test] fn div_ceil_checked() { infer_eq_without_problem( - indoc!( - r" - Num.divCeilChecked - " - ), - "Int a, Int a -> Result (Int a) [DivByZero]", + "Num.divCeilChecked", + "Int [], Int [] -> Result (Int []) [DivByZero]", ); } #[test] fn div_trunc() { - infer_eq_without_problem( - indoc!( - r" - Num.divTrunc - " - ), - "Int a, Int a -> Int a", - ); + infer_eq_without_problem("Num.divTrunc", "Int [], Int [] -> Int []"); } #[test] fn div_trunc_checked() { infer_eq_without_problem( - indoc!( - r" - Num.divTruncChecked - " - ), - "Int a, Int a -> Result (Int a) [DivByZero]", + "Num.divTruncChecked", + "Int [], Int [] -> Result (Int []) [DivByZero]", ); } #[test] fn atan() { - infer_eq_without_problem( - indoc!( - r" - Num.atan - " - ), - "Frac a -> Frac a", - ); + infer_eq_without_problem("Num.atan", "Frac [] -> Frac []"); } #[test] fn min_i128() { - infer_eq_without_problem( - indoc!( - r" - Num.minI128 - " - ), - "I128", - ); + infer_eq_without_problem("Num.minI128", "I128"); } #[test] fn max_i128() { - infer_eq_without_problem( - indoc!( - r" - Num.maxI128 - " - ), - "I128", - ); + infer_eq_without_problem("Num.maxI128", "I128"); } #[test] fn min_i64() { - infer_eq_without_problem( - indoc!( - r" - Num.minI64 - " - ), - "I64", - ); + infer_eq_without_problem("Num.minI64", "I64"); } #[test] fn max_i64() { - infer_eq_without_problem( - indoc!( - r" - Num.maxI64 - " - ), - "I64", - ); + infer_eq_without_problem("Num.maxI64", "I64"); } #[test] fn min_u64() { - infer_eq_without_problem( - indoc!( - r" - Num.minU64 - " - ), - "U64", - ); + infer_eq_without_problem("Num.minU64", "U64"); } #[test] fn max_u64() { - infer_eq_without_problem( - indoc!( - r" - Num.maxU64 - " - ), - "U64", - ); + infer_eq_without_problem("Num.maxU64", "U64"); } #[test] fn min_i32() { - infer_eq_without_problem( - indoc!( - r" - Num.minI32 - " - ), - "I32", - ); + infer_eq_without_problem("Num.minI32", "I32"); } #[test] fn max_i32() { - infer_eq_without_problem( - indoc!( - r" - Num.maxI32 - " - ), - "I32", - ); + infer_eq_without_problem("Num.maxI32", "I32"); } #[test] fn min_u32() { - infer_eq_without_problem( - indoc!( - r" - Num.minU32 - " - ), - "U32", - ); + infer_eq_without_problem("Num.minU32", "U32"); } #[test] fn max_u32() { - infer_eq_without_problem( - indoc!( - r" - Num.maxU32 - " - ), - "U32", - ); + infer_eq_without_problem("Num.maxU32", "U32"); } #[test] @@ -3414,7 +3195,7 @@ mod specialize_types { f " ), - "{ p : *, q : * }* -> Num *", + "{ p : [], q : [] } -> Num []", ); } @@ -3469,7 +3250,7 @@ mod specialize_types { _ -> 3 " ), - "Num * -> Num *", + "Num [] -> Num []", ); } @@ -3516,7 +3297,7 @@ mod specialize_types { { x: sortWith, y: sort, z: sortBy } " ), - "{ x : (foobar, foobar -> Order), ConsList foobar -> ConsList foobar, y : ConsList cm -> ConsList cm, z : (x -> cmpl), ConsList x -> ConsList x }" + "{ x : ([], [] -> Order), ConsList [] -> ConsList [], y : ConsList [] -> ConsList [], z : ([] -> []), ConsList [] -> ConsList [] }" ); } @@ -3569,7 +3350,7 @@ mod specialize_types { f " ), - "List a -> List a", + "List [] -> List []", ); } @@ -3608,7 +3389,7 @@ mod specialize_types { negatePoint { x: 1, y: 2 } " ), - "{ x : I64, y : I64, z : Num c }", + "{ x : I64, y : I64, z : Num [] }", ); } @@ -3625,7 +3406,7 @@ mod specialize_types { { a, b } "# ), - "{ a : { x : I64, y : I64, z : Num c }, b : { blah : Str, x : I64, y : I64, z : Num c1 } }", + "{ a : { x : I64, y : I64, z : Num [] }, b : { blah : Str, x : I64, y : I64, z : Num [] } }", ); } @@ -3639,7 +3420,7 @@ mod specialize_types { negatePoint { x: 1, y: 2.1, z: 0x3 } " ), - "{ x : Num *, y : Frac *, z : Int * }", + "{ x : Num [], y : Frac [], z : Int [] }", ); } @@ -3656,7 +3437,7 @@ mod specialize_types { { a, b } "# ), - "{ a : { x : Num *, y : Frac *, z : c }, b : { blah : Str, x : Num *, y : Frac *, z : c1 } }", + "{ a : { x : Num [], y : Frac [], z : c }, b : { blah : Str, x : Num [], y : Frac [], z : c1 } }", ); } @@ -3668,7 +3449,7 @@ mod specialize_types { \{ x, y ? 0 } -> x + y " ), - "{ x : Num a, y ? Num a }* -> Num a", + "{ x : Num [], y ? Num [] } -> Num []", ); } @@ -3682,7 +3463,7 @@ mod specialize_types { x + y " ), - "Num *", + "Num []", ); } @@ -3696,7 +3477,7 @@ mod specialize_types { { x, y ? 0 } -> x + y " ), - "{ x : Num a, y ? Num a }* -> Num a", + "{ x : Num a, y ? Num a } -> Num a", ); } @@ -3712,7 +3493,7 @@ mod specialize_types { { x, y } " ), - "{ x : I64, y ? Bool }* -> { x : I64, y : Bool }", + "{ x : I64, y ? Bool }-> { x : I64, y : Bool }", ); } @@ -3724,7 +3505,7 @@ mod specialize_types { List.walkBackwards " ), - "List elem, state, (state, elem -> state) -> state", + "List [], [], ([], [] -> []) -> []", ); } @@ -3748,7 +3529,7 @@ mod specialize_types { fn list_walk_with_index_until() { infer_eq_without_problem( indoc!(r"List.walkWithIndexUntil"), - "List elem, state, (state, elem, U64 -> [Break state, Continue state]) -> state", + "List [], [], ([], [], U64 -> [Break [], Continue []]) -> []", ); } @@ -3760,7 +3541,7 @@ mod specialize_types { List.dropAt " ), - "List elem, U64 -> List elem", + "List [], U64 -> List []", ); } @@ -3796,7 +3577,7 @@ mod specialize_types { List.takeFirst " ), - "List elem, U64 -> List elem", + "List [], U64 -> List []", ); } @@ -3808,7 +3589,7 @@ mod specialize_types { List.takeLast " ), - "List elem, U64 -> List elem", + "List [], U64 -> List []", ); } @@ -3820,7 +3601,7 @@ mod specialize_types { List.sublist " ), - "List elem, { len : U64, start : U64 } -> List elem", + "List [], { len : U64, start : U64 } -> List []", ); } @@ -3828,7 +3609,7 @@ mod specialize_types { fn list_split() { infer_eq_without_problem( indoc!("List.split"), - "List elem, U64 -> { before : List elem, others : List elem }", + "List [], U64 -> { before : List [], others : List [] }", ); } @@ -3840,7 +3621,7 @@ mod specialize_types { List.dropLast " ), - "List elem, U64 -> List elem", + "List [], U64 -> List []", ); } @@ -3852,7 +3633,7 @@ mod specialize_types { List.intersperse " ), - "List elem, elem -> List elem", + "List [], [] -> List []", ); } #[test] @@ -3869,7 +3650,7 @@ mod specialize_types { g " ), - "Num a -> Num a", + "Num [] -> Num []", ); } @@ -3889,7 +3670,7 @@ mod specialize_types { empty "# ), - "List x", + "List []", ); } @@ -3908,10 +3689,10 @@ mod specialize_types { Foo Bar 1 "# ), - "[Foo [Bar] (Num *)]", + "[Foo [Bar] (Num [])]", ); - infer_eq_without_problem("Foo Bar 1", "[Foo [Bar] (Num *)]"); + infer_eq_without_problem("Foo Bar 1", "[Foo [Bar] (Num [])]"); } #[test] @@ -4403,7 +4184,7 @@ mod specialize_types { Pair i list "# ), - "U64, U64, List (Num a), U64, Num a -> [Pair U64 (List (Num a))]", + "U64, U64, List (Num []), U64, Num [] -> [Pair U64 (List (Num []))]", ); } @@ -4577,7 +4358,7 @@ mod specialize_types { x "# ), - "Num *", + "Num []", ); } @@ -4620,7 +4401,7 @@ mod specialize_types { id " ), - "a -> a", + "[] -> []", ) } @@ -4653,7 +4434,7 @@ mod specialize_types { swapRcd " ), - "{ x : a, y : b } -> { x : b, y : a }", + "{ x : [], y : [] } -> { x : [], y : [] }", ) } @@ -4667,7 +4448,7 @@ mod specialize_types { swapRcd " ), - "{ x : tx, y : ty } -> { x : ty, y : tx }", + "{ x : [], y : [] } -> { x : [], y : [] }", ) } @@ -4704,7 +4485,7 @@ mod specialize_types { pastelize " ), - "[Blue, Lavender, Orange, Peach]a -> [Blue, Lavender, Orange, Peach]a", + "[Blue, Lavender, Orange, Peach] -> [Blue, Lavender, Orange, Peach]", ) } @@ -4719,7 +4500,7 @@ mod specialize_types { setRocEmail "# ), - "{ email : Str, name : Str }a -> { email : Str, name : Str }a", + "{ email : Str, name : Str } -> { email : Str, name : Str }", ) } @@ -4736,7 +4517,7 @@ mod specialize_types { fromList " ), - "List elem -> LinkedList elem", + "List [] -> LinkedList []", ) } @@ -4751,7 +4532,7 @@ mod specialize_types { fromList " ), - "List elem -> LinkedList elem", + "List [] -> LinkedList []", ) } @@ -4782,7 +4563,7 @@ mod specialize_types { _ -> Z " ), - "[A, B]* -> [X, Y, Z]", + "[A, B] -> [X, Y, Z]", ) } @@ -4813,7 +4594,7 @@ mod specialize_types { A _ -> Z " ), - "[A [M, N]*] -> [X, Y, Z]", + "[A [M, N]] -> [X, Y, Z]", ) } @@ -4859,7 +4640,7 @@ mod specialize_types { t -> t " ), - "[A, X]a -> [A, X]a", + "[A, X] -> [A, X]", ) } @@ -4875,7 +4656,7 @@ mod specialize_types { None -> 0 " ), - "[None, Some { tag : [A, B] }*] -> Num *", + "[None, Some { tag : [A, B] }] -> Num []", ) } @@ -4908,7 +4689,7 @@ mod specialize_types { { x: Red, y ? 5 } -> y " ), - "{ x : [Blue, Red], y ? Num a }* -> Num a", + "{ x : [Blue, Red], y ? Num [] } -> Num []", ) } @@ -4921,7 +4702,7 @@ mod specialize_types { \UserId id -> id + 1 " ), - "[UserId (Num a)] -> Num a", + "[UserId (Num [])] -> Num []", ) } From a787f220c83aecda44db08f8e1bf6a9870002e91 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Mon, 7 Oct 2024 19:55:14 -0400 Subject: [PATCH 10/65] Get specialize_types tests passing --- crates/compiler/specialize_types/src/expr.rs | 646 ++++++++++++++++++ crates/compiler/specialize_types/src/lib.rs | 5 + .../tests/test_specialize_types.rs | 123 ++-- 3 files changed, 711 insertions(+), 63 deletions(-) create mode 100644 crates/compiler/specialize_types/src/expr.rs diff --git a/crates/compiler/specialize_types/src/expr.rs b/crates/compiler/specialize_types/src/expr.rs new file mode 100644 index 0000000000..8233b90081 --- /dev/null +++ b/crates/compiler/specialize_types/src/expr.rs @@ -0,0 +1,646 @@ +use roc_can::expr::{ + ClosureData, Expr, Field, OpaqueWrapFunctionData, StructAccessorData, WhenBranch, + WhenBranchPattern, +}; +use roc_region::all::Loc; +use roc_types::subs::{ + AliasVariables, Content, Descriptor, FlatType, LambdaSet, RecordFields, Subs, SubsSlice, + TupleElems, UnionLabels, Variable, VariableSubsSlice, +}; +use roc_types::types::{Type, Uls}; + +pub fn monomorphize(expr: Loc, subs: &mut Subs) -> Loc { + Loc { + region: expr.region, + value: monomorphize_expr(expr.value, subs), + } +} + +fn monomorphize_expr(expr: Expr, subs: &mut Subs) -> Expr { + match expr { + Expr::Num(var, str, int_value, bound) => { + Expr::Num(monomorphize_var(var, subs), str, int_value, bound) + } + Expr::Int(var1, var2, str, int_value, bound) => Expr::Int( + monomorphize_var(var1, subs), + monomorphize_var(var2, subs), + str, + int_value, + bound, + ), + Expr::Float(var1, var2, str, float_value, bound) => Expr::Float( + monomorphize_var(var1, subs), + monomorphize_var(var2, subs), + str, + float_value, + bound, + ), + Expr::Str(s) => Expr::Str(s), + Expr::IngestedFile(path, bytes, var) => { + Expr::IngestedFile(path, bytes, monomorphize_var(var, subs)) + } + Expr::SingleQuote(var1, var2, c, bound) => Expr::SingleQuote( + monomorphize_var(var1, subs), + monomorphize_var(var2, subs), + c, + bound, + ), + Expr::List { + elem_var, + loc_elems, + } => Expr::List { + elem_var: monomorphize_var(elem_var, subs), + loc_elems: loc_elems + .into_iter() + .map(|loc_elem| monomorphize(loc_elem, subs)) + .collect(), + }, + Expr::Var(symbol, var) => Expr::Var(symbol, monomorphize_var(var, subs)), + Expr::ParamsVar { + symbol, + var, + params_symbol, + params_var, + } => Expr::ParamsVar { + symbol, + var: monomorphize_var(var, subs), + params_symbol, + params_var: monomorphize_var(params_var, subs), + }, + Expr::AbilityMember(symbol, spec_id, var) => { + Expr::AbilityMember(symbol, spec_id, monomorphize_var(var, subs)) + } + Expr::When { + cond_var, + expr_var, + region, + loc_cond, + branches, + branches_cond_var, + exhaustive, + } => Expr::When { + cond_var: monomorphize_var(cond_var, subs), + expr_var: monomorphize_var(expr_var, subs), + region, + loc_cond: Box::new(monomorphize(*loc_cond, subs)), + branches: branches + .into_iter() + .map(|branch| monomorphize_when_branch(branch, subs)) + .collect(), + branches_cond_var: monomorphize_var(branches_cond_var, subs), + exhaustive, + }, + Expr::If { + cond_var, + branch_var, + branches, + final_else, + } => Expr::If { + cond_var: monomorphize_var(cond_var, subs), + branch_var: monomorphize_var(branch_var, subs), + branches: branches + .into_iter() + .map(|(cond, expr)| (monomorphize(cond, subs), monomorphize(expr, subs))) + .collect(), + final_else: Box::new(monomorphize(*final_else, subs)), + }, + Expr::LetRec(defs, expr, cycle_mark) => Expr::LetRec( + defs.into_iter() + .map(|def| monomorphize_def(def, subs)) + .collect(), + Box::new(monomorphize(*expr, subs)), + cycle_mark, + ), + Expr::LetNonRec(def, expr) => Expr::LetNonRec( + Box::new(monomorphize_def(*def, subs)), + Box::new(monomorphize(*expr, subs)), + ), + Expr::Call(boxed, args, called_via) => { + let (fn_var, loc_expr, lambda_set_var, ret_var) = *boxed; + Expr::Call( + Box::new(( + monomorphize_var(fn_var, subs), + monomorphize(loc_expr, subs), + monomorphize_var(lambda_set_var, subs), + monomorphize_var(ret_var, subs), + )), + args.into_iter() + .map(|(var, loc_expr)| { + (monomorphize_var(var, subs), monomorphize(loc_expr, subs)) + }) + .collect(), + called_via, + ) + } + Expr::Closure(closure_data) => Expr::Closure(monomorphize_closure_data(closure_data, subs)), + Expr::Record { record_var, fields } => Expr::Record { + record_var: monomorphize_var(record_var, subs), + fields: fields + .into_iter() + .map(|(k, v)| (k, monomorphize_field(v, subs))) + .collect(), + }, + Expr::EmptyRecord => Expr::EmptyRecord, + Expr::Tuple { tuple_var, elems } => Expr::Tuple { + tuple_var: monomorphize_var(tuple_var, subs), + elems: elems + .into_iter() + .map(|(var, loc_expr)| { + ( + monomorphize_var(var, subs), + Box::new(monomorphize(*loc_expr, subs)), + ) + }) + .collect(), + }, + Expr::ImportParams(module_id, region, params) => Expr::ImportParams( + module_id, + region, + params.map(|(var, expr)| { + ( + monomorphize_var(var, subs), + Box::new(monomorphize_expr(*expr, subs)), + ) + }), + ), + Expr::Crash { msg, ret_var } => Expr::Crash { + msg: Box::new(monomorphize(*msg, subs)), + ret_var: monomorphize_var(ret_var, subs), + }, + Expr::RecordAccess { + record_var, + ext_var, + field_var, + loc_expr, + field, + } => Expr::RecordAccess { + record_var: monomorphize_var(record_var, subs), + ext_var: monomorphize_var(ext_var, subs), + field_var: monomorphize_var(field_var, subs), + loc_expr: Box::new(monomorphize(*loc_expr, subs)), + field, + }, + Expr::RecordAccessor(data) => { + Expr::RecordAccessor(monomorphize_struct_accessor_data(data, subs)) + } + Expr::TupleAccess { + tuple_var, + ext_var, + elem_var, + loc_expr, + index, + } => Expr::TupleAccess { + tuple_var: monomorphize_var(tuple_var, subs), + ext_var: monomorphize_var(ext_var, subs), + elem_var: monomorphize_var(elem_var, subs), + loc_expr: Box::new(monomorphize(*loc_expr, subs)), + index, + }, + Expr::RecordUpdate { + record_var, + ext_var, + symbol, + updates, + } => Expr::RecordUpdate { + record_var: monomorphize_var(record_var, subs), + ext_var: monomorphize_var(ext_var, subs), + symbol, + updates: updates + .into_iter() + .map(|(k, v)| (k, monomorphize_field(v, subs))) + .collect(), + }, + Expr::Tag { + tag_union_var, + ext_var, + name, + arguments, + } => Expr::Tag { + tag_union_var: monomorphize_var(tag_union_var, subs), + ext_var: monomorphize_var(ext_var, subs), + name, + arguments: arguments + .into_iter() + .map(|(var, loc_expr)| (monomorphize_var(var, subs), monomorphize(loc_expr, subs))) + .collect(), + }, + Expr::ZeroArgumentTag { + closure_name, + variant_var, + ext_var, + name, + } => Expr::ZeroArgumentTag { + closure_name, + variant_var: monomorphize_var(variant_var, subs), + ext_var: monomorphize_var(ext_var, subs), + name, + }, + Expr::OpaqueRef { + opaque_var, + name, + argument, + specialized_def_type, + type_arguments, + lambda_set_variables, + } => Expr::OpaqueRef { + opaque_var: monomorphize_var(opaque_var, subs), + name, + argument: Box::new(( + monomorphize_var(argument.0, subs), + monomorphize(argument.1, subs), + )), + specialized_def_type: Box::new(monomorphize_type(*specialized_def_type, subs)), + type_arguments, + lambda_set_variables, + }, + Expr::OpaqueWrapFunction(data) => { + Expr::OpaqueWrapFunction(monomorphize_opaque_wrap_function_data(data, subs)) + } + Expr::Expect { + loc_condition, + loc_continuation, + lookups_in_cond, + } => Expr::Expect { + loc_condition: Box::new(monomorphize(*loc_condition, subs)), + loc_continuation: Box::new(monomorphize(*loc_continuation, subs)), + lookups_in_cond, + }, + Expr::ExpectFx { + loc_condition, + loc_continuation, + lookups_in_cond, + } => Expr::ExpectFx { + loc_condition: Box::new(monomorphize(*loc_condition, subs)), + loc_continuation: Box::new(monomorphize(*loc_continuation, subs)), + lookups_in_cond, + }, + Expr::Dbg { + source_location, + source, + loc_message, + loc_continuation, + variable, + symbol, + } => Expr::Dbg { + source_location, + source, + loc_message: Box::new(monomorphize(*loc_message, subs)), + loc_continuation: Box::new(monomorphize(*loc_continuation, subs)), + variable: monomorphize_var(variable, subs), + symbol, + }, + Expr::TypedHole(var) => Expr::TypedHole(monomorphize_var(var, subs)), + Expr::RuntimeError(error) => Expr::RuntimeError(error), + Expr::RunLowLevel { op, args, ret_var } => Expr::RunLowLevel { + op, + args: args + .into_iter() + .map(|(var, expr)| (monomorphize_var(var, subs), monomorphize_expr(expr, subs))) + .collect(), + ret_var: monomorphize_var(ret_var, subs), + }, + Expr::ForeignCall { + foreign_symbol, + args, + ret_var, + } => Expr::ForeignCall { + foreign_symbol, + args: args + .into_iter() + .map(|(var, expr)| (monomorphize_var(var, subs), monomorphize_expr(expr, subs))) + .collect(), + ret_var: monomorphize_var(ret_var, subs), + }, + } +} + +fn monomorphize_var(var: Variable, subs: &mut Subs) -> Variable { + let root = subs.get_root_key_without_compacting(var); + let content = subs.get_content_without_compacting(root).clone(); + + match content { + Content::Structure(flat_type) => match flat_type { + FlatType::Apply(symbol, args) => { + let new_args: Vec = args + .into_iter() + .map(|arg| monomorphize_var(subs[arg], subs)) + .collect(); + let new_slice = VariableSubsSlice::insert_into_subs(subs, new_args); + let new_flat_type = FlatType::Apply(symbol, new_slice); + subs.fresh(Descriptor::from(Content::Structure(new_flat_type))) + } + FlatType::Func(args, closure_var, ret_var) => { + let new_args: Vec = args + .into_iter() + .map(|arg| monomorphize_var(subs[arg], subs)) + .collect(); + let new_args_slice = VariableSubsSlice::insert_into_subs(subs, new_args); + let new_closure_var = monomorphize_var(closure_var, subs); + let new_ret_var = monomorphize_var(ret_var, subs); + let new_flat_type = FlatType::Func(new_args_slice, new_closure_var, new_ret_var); + subs.fresh(Descriptor::from(Content::Structure(new_flat_type))) + } + FlatType::Record(record_fields, ext_var) => { + let new_variables: Vec = record_fields + .variables() + .into_iter() + .map(|v| monomorphize_var(subs[v], subs)) + .collect(); + let new_variables_slice = VariableSubsSlice::insert_into_subs(subs, new_variables); + let new_record_fields = RecordFields { + length: record_fields.length, + field_names_start: record_fields.field_names_start, + variables_start: new_variables_slice.start, + field_types_start: record_fields.field_types_start, + }; + let new_ext_var = monomorphize_var(ext_var, subs); + let new_flat_type = FlatType::Record(new_record_fields, new_ext_var); + subs.fresh(Descriptor::from(Content::Structure(new_flat_type))) + } + FlatType::Tuple(tuple_elems, ext_var) => { + let new_variables: Vec = tuple_elems + .variables() + .into_iter() + .map(|v| monomorphize_var(subs[v], subs)) + .collect(); + let new_variables_slice = VariableSubsSlice::insert_into_subs(subs, new_variables); + let new_tuple_elems = TupleElems { + length: tuple_elems.length, + elem_index_start: tuple_elems.elem_index_start, + variables_start: new_variables_slice.start, + }; + let new_ext_var = monomorphize_var(ext_var, subs); + let new_flat_type = FlatType::Tuple(new_tuple_elems, new_ext_var); + subs.fresh(Descriptor::from(Content::Structure(new_flat_type))) + } + FlatType::TagUnion(union_labels, tag_ext) => { + let new_variable_slices = + SubsSlice::reserve_variable_slices(subs, union_labels.len()); + for (old_slice_index, new_slice_index) in union_labels + .variables() + .into_iter() + .zip(new_variable_slices) + { + let old_slice = subs[old_slice_index]; + let new_variables: Vec = old_slice + .into_iter() + .map(|v| monomorphize_var(subs[v], subs)) + .collect(); + let new_slice = VariableSubsSlice::insert_into_subs(subs, new_variables); + subs[new_slice_index] = new_slice; + } + let new_union_labels = + UnionLabels::from_slices(union_labels.labels(), new_variable_slices); + let new_tag_ext = tag_ext.map(|v| monomorphize_var(v, subs)); + let new_flat_type = FlatType::TagUnion(new_union_labels, new_tag_ext); + subs.fresh(Descriptor::from(Content::Structure(new_flat_type))) + } + FlatType::FunctionOrTagUnion(tag_names, symbols, tag_ext) => { + let new_tag_ext = tag_ext.map(|v| monomorphize_var(v, subs)); + let new_flat_type = FlatType::FunctionOrTagUnion(tag_names, symbols, new_tag_ext); + subs.fresh(Descriptor::from(Content::Structure(new_flat_type))) + } + FlatType::RecursiveTagUnion(rec_var, union_labels, tag_ext) => { + let new_rec_var = monomorphize_var(rec_var, subs); + let new_variable_slices = + SubsSlice::reserve_variable_slices(subs, union_labels.len()); + for (old_slice_index, new_slice_index) in union_labels + .variables() + .into_iter() + .zip(new_variable_slices) + { + let old_slice = subs[old_slice_index]; + let new_variables: Vec = old_slice + .into_iter() + .map(|v| monomorphize_var(subs[v], subs)) + .collect(); + let new_slice = VariableSubsSlice::insert_into_subs(subs, new_variables); + subs[new_slice_index] = new_slice; + } + let new_union_labels = + UnionLabels::from_slices(union_labels.labels(), new_variable_slices); + let new_tag_ext = tag_ext.map(|v| monomorphize_var(v, subs)); + let new_flat_type = + FlatType::RecursiveTagUnion(new_rec_var, new_union_labels, new_tag_ext); + subs.fresh(Descriptor::from(Content::Structure(new_flat_type))) + } + FlatType::EmptyRecord | FlatType::EmptyTuple | FlatType::EmptyTagUnion => var, + }, + Content::Alias(symbol, alias_variables, aliased_var, alias_kind) => { + let new_variables: Vec = alias_variables + .all_variables() + .into_iter() + .map(|v| monomorphize_var(subs[v], subs)) + .collect(); + let new_variables_slice = VariableSubsSlice::insert_into_subs(subs, new_variables); + let new_alias_variables = AliasVariables { + variables_start: new_variables_slice.start, + all_variables_len: alias_variables.all_variables_len, + lambda_set_variables_len: alias_variables.lambda_set_variables_len, + type_variables_len: alias_variables.type_variables_len, + }; + let new_aliased_var = monomorphize_var(aliased_var, subs); + let new_content = + Content::Alias(symbol, new_alias_variables, new_aliased_var, alias_kind); + subs.fresh(Descriptor::from(new_content)) + } + Content::LambdaSet(lambda_set) => { + let new_solved_slices = + SubsSlice::reserve_variable_slices(subs, lambda_set.solved.len()); + for (old_slice_index, new_slice_index) in lambda_set + .solved + .variables() + .into_iter() + .zip(new_solved_slices) + { + let old_slice = subs[old_slice_index]; + let new_variables: Vec = old_slice + .into_iter() + .map(|v| monomorphize_var(subs[v], subs)) + .collect(); + let new_slice = VariableSubsSlice::insert_into_subs(subs, new_variables); + subs[new_slice_index] = new_slice; + } + let new_solved = + UnionLabels::from_slices(lambda_set.solved.labels(), new_solved_slices); + let new_recursion_var = lambda_set.recursion_var.map(|v| monomorphize_var(v, subs)); + let new_unspecialized = + SubsSlice::reserve_uls_slice(subs, lambda_set.unspecialized.len()); + for (i, uls) in lambda_set.unspecialized.into_iter().enumerate() { + let Uls(var, sym, region) = subs[uls]; + let new_var = monomorphize_var(var, subs); + subs[new_unspecialized.into_iter().nth(i).unwrap()] = Uls(new_var, sym, region); + } + let new_ambient_function = monomorphize_var(lambda_set.ambient_function, subs); + let new_lambda_set = LambdaSet { + solved: new_solved, + recursion_var: new_recursion_var, + unspecialized: new_unspecialized, + ambient_function: new_ambient_function, + }; + let new_content = Content::LambdaSet(new_lambda_set); + subs.fresh(Descriptor::from(new_content)) + } + Content::FlexVar(_) + | Content::RigidVar(_) + | Content::FlexAbleVar(_, _) + | Content::RigidAbleVar(_, _) + | Content::RecursionVar { .. } + | Content::ErasedLambda + | Content::RangedNumber(_) + | Content::Error => var, + } +} + +fn monomorphize_when_branch(branch: WhenBranch, subs: &mut Subs) -> WhenBranch { + WhenBranch { + patterns: branch + .patterns + .into_iter() + .map(|p| WhenBranchPattern { + pattern: p.pattern, + degenerate: p.degenerate, + }) + .collect(), + value: monomorphize(branch.value, subs), + guard: branch.guard.map(|g| monomorphize(g, subs)), + redundant: branch.redundant, + } +} + +fn monomorphize_def(def: roc_can::def::Def, subs: &mut Subs) -> roc_can::def::Def { + roc_can::def::Def { + loc_pattern: def.loc_pattern, + loc_expr: monomorphize(def.loc_expr, subs), + expr_var: monomorphize_var(def.expr_var, subs), + pattern_vars: def + .pattern_vars + .into_iter() + .map(|(k, v)| (k, monomorphize_var(v, subs))) + .collect(), + annotation: def.annotation.map(|a| monomorphize_annotation(a, subs)), + } +} + +fn monomorphize_closure_data(data: ClosureData, subs: &mut Subs) -> ClosureData { + ClosureData { + function_type: monomorphize_var(data.function_type, subs), + closure_type: monomorphize_var(data.closure_type, subs), + return_type: monomorphize_var(data.return_type, subs), + name: data.name, + captured_symbols: data + .captured_symbols + .into_iter() + .map(|(s, v)| (s, monomorphize_var(v, subs))) + .collect(), + recursive: data.recursive, + arguments: data + .arguments + .into_iter() + .map(|(v, m, p)| (monomorphize_var(v, subs), m, p)) + .collect(), + loc_body: Box::new(monomorphize(*data.loc_body, subs)), + } +} + +fn monomorphize_field(field: Field, subs: &mut Subs) -> Field { + Field { + var: monomorphize_var(field.var, subs), + region: field.region, + loc_expr: Box::new(monomorphize(*field.loc_expr, subs)), + } +} + +fn monomorphize_struct_accessor_data( + data: StructAccessorData, + subs: &mut Subs, +) -> StructAccessorData { + StructAccessorData { + name: data.name, + function_var: monomorphize_var(data.function_var, subs), + record_var: monomorphize_var(data.record_var, subs), + closure_var: monomorphize_var(data.closure_var, subs), + ext_var: monomorphize_var(data.ext_var, subs), + field_var: monomorphize_var(data.field_var, subs), + field: data.field, + } +} + +fn monomorphize_opaque_wrap_function_data( + data: OpaqueWrapFunctionData, + subs: &mut Subs, +) -> OpaqueWrapFunctionData { + OpaqueWrapFunctionData { + opaque_name: data.opaque_name, + opaque_var: monomorphize_var(data.opaque_var, subs), + specialized_def_type: monomorphize_type(data.specialized_def_type, subs), + type_arguments: data.type_arguments, + lambda_set_variables: data.lambda_set_variables, + function_name: data.function_name, + function_var: monomorphize_var(data.function_var, subs), + argument_var: monomorphize_var(data.argument_var, subs), + closure_var: monomorphize_var(data.closure_var, subs), + } +} + +fn monomorphize_annotation( + annotation: roc_can::def::Annotation, + subs: &mut Subs, +) -> roc_can::def::Annotation { + roc_can::def::Annotation { + signature: monomorphize_type(annotation.signature, subs), + introduced_variables: annotation.introduced_variables, + aliases: annotation.aliases, + region: annotation.region, + } +} + +fn monomorphize_type(typ: Type, subs: &mut Subs) -> Type { + match typ { + Type::Tuple(elems, ext) => Type::Tuple( + elems + .into_iter() + .map(|(idx, elem)| (idx, monomorphize_type(elem, subs))) + .collect(), + ext, + ), + Type::Record(fields, ext) => Type::Record( + fields + .into_iter() + .map(|(name, field_type)| { + (name, field_type.map(|t| monomorphize_type(t.clone(), subs))) + }) + .collect(), + ext, + ), + Type::Apply(name, args, region) => Type::Apply( + name, + args.into_iter() + .map(|arg| arg.map(|t| monomorphize_type(t.clone(), subs))) + .collect(), + region, + ), + Type::Function(args, ret, ext) => Type::Function( + args.into_iter() + .map(|arg| monomorphize_type(arg, subs)) + .collect(), + Box::new(monomorphize_type(*ret, subs)), + ext, + ), + Type::TagUnion(tags, ext) => Type::TagUnion( + tags.into_iter() + .map(|(name, tag_types)| { + ( + name, + tag_types + .into_iter() + .map(|t| monomorphize_type(t, subs)) + .collect(), + ) + }) + .collect(), + ext, + ), + other => other, + } +} diff --git a/crates/compiler/specialize_types/src/lib.rs b/crates/compiler/specialize_types/src/lib.rs index bbd84f16a6..3cdca22925 100644 --- a/crates/compiler/specialize_types/src/lib.rs +++ b/crates/compiler/specialize_types/src/lib.rs @@ -1,3 +1,8 @@ +/// Given a Subs that's been populated from type inference, and a Variable, +/// ensure that Variable is monomorphic by going through and creating +/// specializations of that type wherever necessary. +/// +/// This only operates at the type level. It does not create new function implementations (for example). use bitvec::vec::BitVec; use roc_module::ident::{Lowercase, TagName}; use roc_types::{ diff --git a/crates/compiler/specialize_types/tests/test_specialize_types.rs b/crates/compiler/specialize_types/tests/test_specialize_types.rs index 0c99a9ee7b..0035011e8c 100644 --- a/crates/compiler/specialize_types/tests/test_specialize_types.rs +++ b/crates/compiler/specialize_types/tests/test_specialize_types.rs @@ -2460,33 +2460,33 @@ mod specialize_types { ); } - #[test] - fn infer_linked_list_map() { - infer_eq_without_problem( - indoc!( - r" - map = \f, list -> - when list is - Nil -> Nil - Cons x xs -> - a = f x - b = map f xs + // #[test] + // fn infer_linked_list_map() { + // infer_eq_without_problem( + // indoc!( + // r" + // map = \f, list -> + // when list is + // Nil -> Nil + // Cons x xs -> + // a = f x + // b = map f xs - Cons a b + // Cons a b - map - " - ), - "([] -> []), [Cons [] c, Nil] as c -> [Cons [] d, Nil] as d", - ); - } + // map + // " + // ), + // "([] -> []), [Cons [] c, Nil] as c -> [Cons [] d, Nil] as d", + // ); + // } #[test] fn typecheck_linked_list_map() { infer_eq_without_problem( indoc!( r" - ConsList a : [Cons a (ConsList a), Nil] + ConsList a := [Cons a (ConsList a), Nil] map : (a -> b), ConsList a -> ConsList b map = \f, list -> @@ -2813,42 +2813,42 @@ mod specialize_types { ); } - #[test] - fn infer_record_linked_list_map() { - infer_eq_without_problem( - indoc!( - r" - map = \f, list -> - when list is - Nil -> Nil - Cons { x, xs } -> - Cons { x: f x, xs : map f xs } + // #[test] + // fn infer_record_linked_list_map() { + // infer_eq_without_problem( + // indoc!( + // r" + // map = \f, list -> + // when list is + // Nil -> Nil + // Cons { x, xs } -> + // Cons { x: f x, xs : map f xs } - map - " - ), - "(a -> b), [Cons { x : a, xs : c }, Nil] as c -> [Cons { x : b, xs : d }, Nil] as d", - ); - } + // map + // " + // ), + // "(a -> b), [Cons { x : a, xs : c }, Nil] as c -> [Cons { x : b, xs : d }, Nil] as d", + // ); + // } #[test] fn typecheck_mutually_recursive_tag_union_2() { infer_eq_without_problem( indoc!( r" - ListA a b : [Cons a (ListB b a), Nil] - ListB a b : [Cons a (ListA b a), Nil] + ListA a b := [Cons a (ListB b a), Nil] + ListB a b := [Cons a (ListA b a), Nil] ConsList q : [Cons q (ConsList q), Nil] toAs : (b -> a), ListA a b -> ConsList a - toAs = \f, lista -> + toAs = \f, @ListA lista -> when lista is Nil -> Nil - Cons a listb -> + Cons a (@ListB listb) -> when listb is Nil -> Nil - Cons b newLista -> + Cons b (@ListA newLista) -> Cons a (Cons (f b) (toAs f newLista)) toAs @@ -2877,26 +2877,26 @@ mod specialize_types { ); } - #[test] - fn infer_mutually_recursive_tag_union() { - infer_eq_without_problem( - indoc!( - r" - toAs = \f, lista -> - when lista is - Nil -> Nil - Cons a listb -> - when listb is - Nil -> Nil - Cons b newLista -> - Cons a (Cons (f b) (toAs f newLista)) + // #[test] + // fn infer_mutually_recursive_tag_union() { + // infer_eq_without_problem( + // indoc!( + // r" + // toAs = \f, lista -> + // when lista is + // Nil -> Nil + // Cons a listb -> + // when listb is + // Nil -> Nil + // Cons b newLista -> + // Cons a (Cons (f b) (toAs f newLista)) - toAs - " - ), - "(a -> b), [Cons c [Cons a d, Nil], Nil] as d -> [Cons c [Cons b e], Nil] as e", - ); - } + // toAs + // " + // ), + // "(a -> b), [Cons c [Cons a d, Nil], Nil] as d -> [Cons c [Cons b e], Nil] as e", + // ); + // } #[test] fn solve_list_get() { @@ -3039,10 +3039,7 @@ mod specialize_types { #[test] fn map_insert() { - infer_eq_without_problem( - "Dict.insert", - "Dict k v, k, v -> Dict k v where k implements Hash & Eq", - ); + infer_eq_without_problem("Dict.insert", "Dict [] [], [], [] -> Dict [] []"); } #[test] From 5dda595277a4747c2eb635cd68483603ba31fa3f Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Tue, 8 Oct 2024 07:54:53 -0400 Subject: [PATCH 11/65] Remove extra sepcialize tests --- .../tests/test_specialize_types.rs | 378 +++++++++--------- 1 file changed, 188 insertions(+), 190 deletions(-) diff --git a/crates/compiler/specialize_types/tests/test_specialize_types.rs b/crates/compiler/specialize_types/tests/test_specialize_types.rs index 0035011e8c..de8cead601 100644 --- a/crates/compiler/specialize_types/tests/test_specialize_types.rs +++ b/crates/compiler/specialize_types/tests/test_specialize_types.rs @@ -983,18 +983,18 @@ mod specialize_types { specializes_to(".0 (5, 3.14 )", "Num []"); } - #[test] - fn tuple_literal_ty() { - specializes_to("(5, 3.14 )", "( Num [], Frac [] )"); - } + // #[test] + // fn tuple_literal_ty() { + // specializes_to("(5, 3.14 )", "( Num [], Frac [] )"); + // } - #[test] - fn tuple_literal_accessor_ty() { - specializes_to(".0", "( [] ) -> []"); - specializes_to(".4", "( _, _, _, _, [] ) -> []"); - specializes_to(".5", "( ... 5 omitted, [] ) -> []"); - specializes_to(".200", "( ... 200 omitted, [] ) -> []"); - } + // #[test] + // fn tuple_literal_accessor_ty() { + // specializes_to(".0", "( [] ) -> []"); + // specializes_to(".4", "( _, _, _, _, [] ) -> []"); + // specializes_to(".5", "( ... 5 omitted, [] ) -> []"); + // specializes_to(".200", "( ... 200 omitted, [] ) -> []"); + // } #[test] fn tuple_accessor_generalization() { @@ -2481,26 +2481,26 @@ mod specialize_types { // ); // } - #[test] - fn typecheck_linked_list_map() { - infer_eq_without_problem( - indoc!( - r" - ConsList a := [Cons a (ConsList a), Nil] + // #[test] + // fn typecheck_linked_list_map() { + // infer_eq_without_problem( + // indoc!( + // r" + // ConsList a := [Cons a (ConsList a), Nil] - map : (a -> b), ConsList a -> ConsList b - map = \f, list -> - when list is - Nil -> Nil - Cons x xs -> - Cons (f x) (map f xs) + // map : (a -> b), ConsList a -> ConsList b + // map = \f, list -> + // when list is + // Nil -> Nil + // Cons x xs -> + // Cons (f x) (map f xs) - map - " - ), - "([] -> []), ConsList [] -> ConsList []", - ); - } + // map + // " + // ), + // "([] -> []), ConsList [] -> ConsList []", + // ); + // } #[test] fn mismatch_in_alias_args_gets_reported() { @@ -2719,46 +2719,44 @@ mod specialize_types { ); } - #[test] - fn peano_map_infer() { - specializes_to( - indoc!( - r#" - app "test" provides [main] to "./platform" + // #[test] + // fn peano_map_infer() { + // specializes_to( + // indoc!( + // r#" + // app "test" provides [main] to "./platform" - map = - \peano -> - when peano is - Z -> Z - S rest -> map rest |> S + // map = + // \peano -> + // when peano is + // Z -> Z + // S rest -> map rest |> S + // main = + // map + // "# + // ), + // "[S a, Z] as a -> [S b, Z] as b", + // ); + // } - main = - map - "# - ), - "[S a, Z] as a -> [S b, Z] as b", - ); - } + // #[test] + // fn peano_map_infer_nested() { + // specializes_to( + // indoc!( + // r" + // map = \peano -> + // when peano is + // Z -> Z + // S rest -> + // map rest |> S - #[test] - fn peano_map_infer_nested() { - specializes_to( - indoc!( - r" - map = \peano -> - when peano is - Z -> Z - S rest -> - map rest |> S - - - map - " - ), - "[S a, Z] as a -> [S b, Z] as b", - ); - } + // map + // " + // ), + // "[S a, Z] as a -> [S b, Z] as b", + // ); + // } #[test] fn let_record_pattern_with_alias_annotation() { @@ -2831,32 +2829,32 @@ mod specialize_types { // ); // } - #[test] - fn typecheck_mutually_recursive_tag_union_2() { - infer_eq_without_problem( - indoc!( - r" - ListA a b := [Cons a (ListB b a), Nil] - ListB a b := [Cons a (ListA b a), Nil] + // #[test] + // fn typecheck_mutually_recursive_tag_union_2() { + // infer_eq_without_problem( + // indoc!( + // r" + // ListA a b := [Cons a (ListB b a), Nil] + // ListB a b := [Cons a (ListA b a), Nil] - ConsList q : [Cons q (ConsList q), Nil] + // ConsList q : [Cons q (ConsList q), Nil] - toAs : (b -> a), ListA a b -> ConsList a - toAs = \f, @ListA lista -> - when lista is - Nil -> Nil - Cons a (@ListB listb) -> - when listb is - Nil -> Nil - Cons b (@ListA newLista) -> - Cons a (Cons (f b) (toAs f newLista)) + // toAs : (b -> a), ListA a b -> ConsList a + // toAs = \f, @ListA lista -> + // when lista is + // Nil -> Nil + // Cons a (@ListB listb) -> + // when listb is + // Nil -> Nil + // Cons b (@ListA newLista) -> + // Cons a (Cons (f b) (toAs f newLista)) - toAs - " - ), - "([] -> []), ListA [] [] -> ConsList []", - ); - } + // toAs + // " + // ), + // "([] -> []), ListA [] [] -> ConsList []", + // ); + // } #[test] fn typecheck_mutually_recursive_tag_union_listabc() { @@ -3156,26 +3154,26 @@ mod specialize_types { infer_eq_without_problem("Num.maxU32", "U32"); } - #[test] - fn reconstruct_path() { - infer_eq_without_problem( - indoc!( - r" - reconstructPath : Dict position position, position -> List position where position implements Hash & Eq - reconstructPath = \cameFrom, goal -> - when Dict.get cameFrom goal is - Err KeyNotFound -> - [] + // #[test] + // fn reconstruct_path() { + // infer_eq_without_problem( + // indoc!( + // r" + // reconstructPath : Dict position position, position -> List position where position implements Hash & Eq + // reconstructPath = \cameFrom, goal -> + // when Dict.get cameFrom goal is + // Err KeyNotFound -> + // [] - Ok next -> - List.append (reconstructPath cameFrom next) goal + // Ok next -> + // List.append (reconstructPath cameFrom next) goal - reconstructPath - " - ), - "Dict position position, position -> List position where position implements Hash & Eq", - ); - } + // reconstructPath + // " + // ), + // "Dict position position, position -> List position where position implements Hash & Eq", + // ); + // } #[test] fn use_correct_ext_record() { @@ -3196,43 +3194,43 @@ mod specialize_types { ); } - #[test] - fn use_correct_ext_tag_union() { - // related to a bug solved in 08c82bf151a85e62bce02beeed1e14444381069f - infer_eq_without_problem( - indoc!( - r#" - app "test" imports [] provides [main] to "./platform" + // #[test] + // fn use_correct_ext_tag_union() { + // // related to a bug solved in 08c82bf151a85e62bce02beeed1e14444381069f + // infer_eq_without_problem( + // indoc!( + // r#" + // app "test" imports [] provides [main] to "./platform" - boom = \_ -> boom {} + // boom = \_ -> boom {} - Model position : { openSet : Set position } + // Model position : { openSet : Set position } - cheapestOpen : Model position -> Result position [KeyNotFound] where position implements Hash & Eq - cheapestOpen = \model -> + // cheapestOpen : Model position -> Result position [KeyNotFound] where position implements Hash & Eq + // cheapestOpen = \model -> - folder = \resSmallestSoFar, position -> - when resSmallestSoFar is - Err _ -> resSmallestSoFar - Ok smallestSoFar -> - if position == smallestSoFar.position then resSmallestSoFar + // folder = \resSmallestSoFar, position -> + // when resSmallestSoFar is + // Err _ -> resSmallestSoFar + // Ok smallestSoFar -> + // if position == smallestSoFar.position then resSmallestSoFar - else - Ok { position, cost: 0.0 } + // else + // Ok { position, cost: 0.0 } - Set.walk model.openSet (Ok { position: boom {}, cost: 0.0 }) folder - |> Result.map (\x -> x.position) + // Set.walk model.openSet (Ok { position: boom {}, cost: 0.0 }) folder + // |> Result.map (\x -> x.position) - astar : Model position -> Result position [KeyNotFound] where position implements Hash & Eq - astar = \model -> cheapestOpen model + // astar : Model position -> Result position [KeyNotFound] where position implements Hash & Eq + // astar = \model -> cheapestOpen model - main = - astar - "# - ), - "Model position -> Result position [KeyNotFound] where position implements Hash & Eq", - ); - } + // main = + // astar + // "# + // ), + // "Model position -> Result position [KeyNotFound] where position implements Hash & Eq", + // ); + // } #[test] fn when_with_or_pattern_and_guard() { @@ -3390,22 +3388,22 @@ mod specialize_types { ); } - #[test] - fn open_optional_field_unifies_with_missing() { - infer_eq_without_problem( - indoc!( - r#" - negatePoint : { x : I64, y : I64, z ? Num c }r -> { x : I64, y : I64, z : Num c }r + // #[test] + // fn open_optional_field_unifies_with_missing() { + // infer_eq_without_problem( + // indoc!( + // r#" + // negatePoint : { x : I64, y : I64, z ? Num c }r -> { x : I64, y : I64, z : Num c }r - a = negatePoint { x: 1, y: 2 } - b = negatePoint { x: 1, y: 2, blah : "hi" } + // a = negatePoint { x: 1, y: 2 } + // b = negatePoint { x: 1, y: 2, blah : "hi" } - { a, b } - "# - ), - "{ a : { x : I64, y : I64, z : Num [] }, b : { blah : Str, x : I64, y : I64, z : Num [] } }", - ); - } + // { a, b } + // "# + // ), + // "{ a : { x : I64, y : I64, z : Num [] }, b : { blah : Str, x : I64, y : I64, z : Num [] } }", + // ); + // } #[test] fn optional_field_unifies_with_present() { @@ -3421,22 +3419,22 @@ mod specialize_types { ); } - #[test] - fn open_optional_field_unifies_with_present() { - infer_eq_without_problem( - indoc!( - r#" - negatePoint : { x : Num a, y : Num b, z ? c }r -> { x : Num a, y : Num b, z : c }r + // #[test] + // fn open_optional_field_unifies_with_present() { + // infer_eq_without_problem( + // indoc!( + // r#" + // negatePoint : { x : Num a, y : Num b, z ? c }r -> { x : Num a, y : Num b, z : c }r - a = negatePoint { x: 1, y: 2.1 } - b = negatePoint { x: 1, y: 2.1, blah : "hi" } + // a = negatePoint { x: 1, y: 2.1 } + // b = negatePoint { x: 1, y: 2.1, blah : "hi" } - { a, b } - "# - ), - "{ a : { x : Num [], y : Frac [], z : c }, b : { blah : Str, x : Num [], y : Frac [], z : c1 } }", - ); - } + // { a, b } + // "# + // ), + // "{ a : { x : Num [], y : Frac [], z : c }, b : { blah : Str, x : Num [], y : Frac [], z : c1 } }", + // ); + // } #[test] fn optional_field_function() { @@ -3464,35 +3462,35 @@ mod specialize_types { ); } - #[test] - fn optional_field_when() { - infer_eq_without_problem( - indoc!( - r" - \r -> - when r is - { x, y ? 0 } -> x + y - " - ), - "{ x : Num a, y ? Num a } -> Num a", - ); - } + // #[test] + // fn optional_field_when() { + // infer_eq_without_problem( + // indoc!( + // r" + // \r -> + // when r is + // { x, y ? 0 } -> x + y + // " + // ), + // "{ x : Num a, y ? Num a } -> Num a", + // ); + // } - #[test] - fn optional_field_let_with_signature() { - infer_eq_without_problem( - indoc!( - r" - \rec -> - { x, y } : { x : I64, y ? Bool }* - { x, y ? Bool.false } = rec + // #[test] + // fn optional_field_let_with_signature() { + // infer_eq_without_problem( + // indoc!( + // r" + // \rec -> + // { x, y } : { x : I64, y ? Bool }* + // { x, y ? Bool.false } = rec - { x, y } - " - ), - "{ x : I64, y ? Bool }-> { x : I64, y : Bool }", - ); - } + // { x, y } + // " + // ), + // "{ x : I64, y ? Bool }-> { x : I64, y : Bool }", + // ); + // } #[test] fn list_walk_backwards() { From 79744808e6602683690e096240fb4c64c3185aa1 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Tue, 8 Oct 2024 13:24:00 -0400 Subject: [PATCH 12/65] clippy --- crates/compiler/specialize_types/src/lib.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/crates/compiler/specialize_types/src/lib.rs b/crates/compiler/specialize_types/src/lib.rs index 3cdca22925..10c6ef7a1f 100644 --- a/crates/compiler/specialize_types/src/lib.rs +++ b/crates/compiler/specialize_types/src/lib.rs @@ -70,7 +70,7 @@ fn lower_var( let root_var = subs.get_root_key_without_compacting(var); if !cache.is_known_monomorphic(root_var) { - let content = subs.get_content_without_compacting(root_var).clone(); + let content = *subs.get_content_without_compacting(root_var); let content = lower_content(cache, subs, problems, &content); // Update Subs so when we look up this var in the future, it's the monomorphized Content. @@ -129,7 +129,7 @@ fn lower_content( } Content::Structure(FlatType::Record( - RecordFields::insert_into_subs(subs, fields.into_iter()), + RecordFields::insert_into_subs(subs, fields), Variable::EMPTY_RECORD, )) } @@ -140,7 +140,7 @@ fn lower_content( lower_vars(elems.iter_mut().map(|(_, var)| var), cache, subs, problems); Content::Structure(FlatType::Tuple( - TupleElems::insert_into_subs(subs, elems.into_iter()), + TupleElems::insert_into_subs(subs, elems), Variable::EMPTY_TUPLE, )) } @@ -151,7 +151,7 @@ fn lower_content( lower_vars(tags.iter_mut().flat_map(|(_, vars)| vars.iter_mut()), cache, subs, problems); Content::Structure(FlatType::TagUnion( - UnionTags::insert_into_subs(subs, tags.into_iter()), + UnionTags::insert_into_subs(subs, tags), TagExt::Any(Variable::EMPTY_TAG_UNION), )) } @@ -167,11 +167,11 @@ fn lower_content( // Then, add the tag names with no payloads. (There are no variables to lower here.) for index in tag_names.into_iter() { - tags.push(((&subs[index]).clone(), Vec::new())); + tags.push(((subs[index]).clone(), Vec::new())); } Content::Structure(FlatType::TagUnion( - UnionTags::insert_into_subs(subs, tags.into_iter()), + UnionTags::insert_into_subs(subs, tags), TagExt::Any(Variable::EMPTY_TAG_UNION), )) } @@ -183,7 +183,7 @@ fn lower_content( Content::Structure(FlatType::RecursiveTagUnion( lower_var(cache, subs, problems, *rec), - UnionTags::insert_into_subs(subs, tags.into_iter()), + UnionTags::insert_into_subs(subs, tags), TagExt::Any(Variable::EMPTY_TAG_UNION), )) } @@ -197,11 +197,11 @@ fn lower_content( | Content::FlexAbleVar(_, _) | Content::RigidAbleVar(_, _) | Content::RecursionVar { .. } => Content::Structure(FlatType::EmptyTagUnion), - Content::LambdaSet(lambda_set) => Content::LambdaSet(lambda_set.clone()), + Content::LambdaSet(lambda_set) => Content::LambdaSet(*lambda_set), Content::ErasedLambda => Content::ErasedLambda, Content::Alias(symbol, args, real, kind) => { let new_real = lower_var(cache, subs, problems, *real); - Content::Alias(*symbol, args.clone(), new_real, *kind) + Content::Alias(*symbol, *args, new_real, *kind) } Content::Error => Content::Error, } @@ -265,7 +265,7 @@ fn resolve_record_ext( // Collapse (recursively) all the fields in ext into a flat list of fields. loop { for (label, field) in fields.sorted_iterator(subs, ext) { - all_fields.push((label.clone(), field.clone())); + all_fields.push((label.clone(), field)); } match subs.get_content_without_compacting(ext) { From 58aaf3247500a2419e2f80b76af49ae7c19af262 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Wed, 9 Oct 2024 20:27:43 -0400 Subject: [PATCH 13/65] Unwrap type aliases more --- crates/compiler/specialize_types/src/lib.rs | 249 ++++++++++---------- 1 file changed, 122 insertions(+), 127 deletions(-) diff --git a/crates/compiler/specialize_types/src/lib.rs b/crates/compiler/specialize_types/src/lib.rs index 10c6ef7a1f..492e5744da 100644 --- a/crates/compiler/specialize_types/src/lib.rs +++ b/crates/compiler/specialize_types/src/lib.rs @@ -10,7 +10,7 @@ use roc_types::{ Content, FlatType, RecordFields, Subs, TagExt, TupleElems, UnionLabels, UnionTags, Variable, VariableSubsSlice, }, - types::RecordField, + types::{AliasKind, RecordField}, }; #[derive(Debug, PartialEq, Eq)] @@ -70,8 +70,127 @@ fn lower_var( let root_var = subs.get_root_key_without_compacting(var); if !cache.is_known_monomorphic(root_var) { - let content = *subs.get_content_without_compacting(root_var); - let content = lower_content(cache, subs, problems, &content); + let content = match *subs.get_content_without_compacting(root_var) { + Content::Structure(flat_type) => match flat_type { + FlatType::Apply(symbol, args) => { + let new_args = args + .into_iter() + .map(|var_index| lower_var(cache, subs, problems, subs[var_index])) + .collect::>(); + + Content::Structure(FlatType::Apply( + symbol, + VariableSubsSlice::insert_into_subs(subs, new_args), + )) + } + FlatType::Func(args, closure, ret) => { + let new_args = args + .into_iter() + .map(|var_index| lower_var(cache, subs, problems, subs[var_index])) + .collect::>(); + let new_closure = lower_var(cache, subs, problems, closure); + let new_ret = lower_var(cache, subs, problems, ret); + Content::Structure(FlatType::Func( + VariableSubsSlice::insert_into_subs(subs, new_args), + new_closure, + new_ret, + )) + } + FlatType::Record(fields, ext) => { + let mut fields = resolve_record_ext(subs, problems, fields, ext); + + // Now lower all the fields we gathered. Do this in a separate pass to avoid borrow errors on Subs. + for (_, field) in fields.iter_mut() { + let var = match field { + RecordField::Required(v) | RecordField::Optional(v) | RecordField::Demanded(v) => v, + RecordField::RigidRequired(v) | RecordField::RigidOptional(v) => v, + }; + + *var = lower_var(cache, subs, problems, *var); + } + + Content::Structure(FlatType::Record( + RecordFields::insert_into_subs(subs, fields), + Variable::EMPTY_RECORD, + )) + } + FlatType::Tuple(elems, ext) => { + let mut elems = resolve_tuple_ext(subs, problems, elems, ext); + + // Now lower all the elems we gathered. Do this in a separate pass to avoid borrow errors on Subs. + lower_vars(elems.iter_mut().map(|(_, var)| var), cache, subs, problems); + + Content::Structure(FlatType::Tuple( + TupleElems::insert_into_subs(subs, elems), + Variable::EMPTY_TUPLE, + )) + } + FlatType::TagUnion(tags, ext) => { + let mut tags = resolve_tag_ext(subs, problems, tags, ext); + + // Now lower all the tags we gathered. Do this in a separate pass to avoid borrow errors on Subs. + lower_vars(tags.iter_mut().flat_map(|(_, vars)| vars.iter_mut()), cache, subs, problems); + + Content::Structure(FlatType::TagUnion( + UnionTags::insert_into_subs(subs, tags), + TagExt::Any(Variable::EMPTY_TAG_UNION), + )) + } + FlatType::FunctionOrTagUnion(tag_names, _symbols, ext) => { + // If this is still a FunctionOrTagUnion, turn it into a TagUnion. + + // First, resolve the ext var. + let mut tags = resolve_tag_ext(subs, problems, UnionTags::default(), ext); + + // Now lower all the tags we gathered from the ext var. + // (Do this in a separate pass to avoid borrow errors on Subs.) + lower_vars(tags.iter_mut().flat_map(|(_, vars)| vars.iter_mut()), cache, subs, problems); + + // Then, add the tag names with no payloads. (There are no variables to lower here.) + for index in tag_names.into_iter() { + tags.push(((subs[index]).clone(), Vec::new())); + } + + Content::Structure(FlatType::TagUnion( + UnionTags::insert_into_subs(subs, tags), + TagExt::Any(Variable::EMPTY_TAG_UNION), + )) + } + FlatType::RecursiveTagUnion(rec, tags, ext) => { + let mut tags = resolve_tag_ext(subs, problems, tags, ext); + + // Now lower all the tags we gathered. Do this in a separate pass to avoid borrow errors on Subs. + lower_vars(tags.iter_mut().flat_map(|(_, vars)| vars.iter_mut()), cache, subs, problems); + + Content::Structure(FlatType::RecursiveTagUnion( + lower_var(cache, subs, problems, rec), + UnionTags::insert_into_subs(subs, tags), + TagExt::Any(Variable::EMPTY_TAG_UNION), + )) + } + FlatType::EmptyRecord => Content::Structure(FlatType::EmptyRecord), + FlatType::EmptyTuple => Content::Structure(FlatType::EmptyTuple), + FlatType::EmptyTagUnion => Content::Structure(FlatType::EmptyTagUnion), + }, + Content::RangedNumber(_) // RangedNumber goes in Num's type parameter slot, so monomorphize it to [] + | Content::FlexVar(_) + | Content::RigidVar(_) + | Content::FlexAbleVar(_, _) + | Content::RigidAbleVar(_, _) + | Content::RecursionVar { .. } => Content::Structure(FlatType::EmptyTagUnion), + Content::LambdaSet(lambda_set) => Content::LambdaSet(lambda_set), + Content::ErasedLambda => Content::ErasedLambda, + Content::Alias(_symbol, _args, real, AliasKind::Structural) => { + // Unwrap type aliases. + let new_real = lower_var(cache, subs, problems, real); + cache.set_monomorphic(new_real); + return new_real; + } + Content::Alias(symbol, args, real, AliasKind::Opaque) => { + Content::Alias(symbol, args, lower_var(cache, subs, problems, real), AliasKind::Opaque) + } + Content::Error => Content::Error, + }; // Update Subs so when we look up this var in the future, it's the monomorphized Content. subs.set_content(root_var, content); @@ -83,130 +202,6 @@ fn lower_var( root_var } -fn lower_content( - cache: &mut MonoCache, - subs: &mut Subs, - problems: &mut Vec, - content: &Content, -) -> Content { - match content { - Content::Structure(flat_type) => match flat_type { - FlatType::Apply(symbol, args) => { - let new_args = args - .into_iter() - .map(|var_index| lower_var(cache, subs, problems, subs[var_index])) - .collect::>(); - - Content::Structure(FlatType::Apply( - *symbol, - VariableSubsSlice::insert_into_subs(subs, new_args), - )) - } - FlatType::Func(args, closure, ret) => { - let new_args = args - .into_iter() - .map(|var_index| lower_var(cache, subs, problems, subs[var_index])) - .collect::>(); - let new_closure = lower_var(cache, subs, problems, *closure); - let new_ret = lower_var(cache, subs, problems, *ret); - Content::Structure(FlatType::Func( - VariableSubsSlice::insert_into_subs(subs, new_args), - new_closure, - new_ret, - )) - } - FlatType::Record(fields, ext) => { - let mut fields = resolve_record_ext(subs, problems, *fields, *ext); - - // Now lower all the fields we gathered. Do this in a separate pass to avoid borrow errors on Subs. - for (_, field) in fields.iter_mut() { - let var = match field { - RecordField::Required(v) | RecordField::Optional(v) | RecordField::Demanded(v) => v, - RecordField::RigidRequired(v) | RecordField::RigidOptional(v) => v, - }; - - *var = lower_var(cache, subs, problems, *var); - } - - Content::Structure(FlatType::Record( - RecordFields::insert_into_subs(subs, fields), - Variable::EMPTY_RECORD, - )) - } - FlatType::Tuple(elems, ext) => { - let mut elems = resolve_tuple_ext(subs, problems, *elems, *ext); - - // Now lower all the elems we gathered. Do this in a separate pass to avoid borrow errors on Subs. - lower_vars(elems.iter_mut().map(|(_, var)| var), cache, subs, problems); - - Content::Structure(FlatType::Tuple( - TupleElems::insert_into_subs(subs, elems), - Variable::EMPTY_TUPLE, - )) - } - FlatType::TagUnion(tags, ext) => { - let mut tags = resolve_tag_ext(subs, problems, *tags, *ext); - - // Now lower all the tags we gathered. Do this in a separate pass to avoid borrow errors on Subs. - lower_vars(tags.iter_mut().flat_map(|(_, vars)| vars.iter_mut()), cache, subs, problems); - - Content::Structure(FlatType::TagUnion( - UnionTags::insert_into_subs(subs, tags), - TagExt::Any(Variable::EMPTY_TAG_UNION), - )) - } - FlatType::FunctionOrTagUnion(tag_names, _symbols, ext) => { - // If this is still a FunctionOrTagUnion, turn it into a TagUnion. - - // First, resolve the ext var. - let mut tags = resolve_tag_ext(subs, problems, UnionTags::default(), *ext); - - // Now lower all the tags we gathered from the ext var. - // (Do this in a separate pass to avoid borrow errors on Subs.) - lower_vars(tags.iter_mut().flat_map(|(_, vars)| vars.iter_mut()), cache, subs, problems); - - // Then, add the tag names with no payloads. (There are no variables to lower here.) - for index in tag_names.into_iter() { - tags.push(((subs[index]).clone(), Vec::new())); - } - - Content::Structure(FlatType::TagUnion( - UnionTags::insert_into_subs(subs, tags), - TagExt::Any(Variable::EMPTY_TAG_UNION), - )) - } - FlatType::RecursiveTagUnion(rec, tags, ext) => { - let mut tags = resolve_tag_ext(subs, problems, *tags, *ext); - - // Now lower all the tags we gathered. Do this in a separate pass to avoid borrow errors on Subs. - lower_vars(tags.iter_mut().flat_map(|(_, vars)| vars.iter_mut()), cache, subs, problems); - - Content::Structure(FlatType::RecursiveTagUnion( - lower_var(cache, subs, problems, *rec), - UnionTags::insert_into_subs(subs, tags), - TagExt::Any(Variable::EMPTY_TAG_UNION), - )) - } - FlatType::EmptyRecord => Content::Structure(FlatType::EmptyRecord), - FlatType::EmptyTuple => Content::Structure(FlatType::EmptyTuple), - FlatType::EmptyTagUnion => Content::Structure(FlatType::EmptyTagUnion), - }, - Content::RangedNumber(_) // RangedNumber goes in Num's type parameter slot, so monomorphize it to [] - | Content::FlexVar(_) - | Content::RigidVar(_) - | Content::FlexAbleVar(_, _) - | Content::RigidAbleVar(_, _) - | Content::RecursionVar { .. } => Content::Structure(FlatType::EmptyTagUnion), - Content::LambdaSet(lambda_set) => Content::LambdaSet(*lambda_set), - Content::ErasedLambda => Content::ErasedLambda, - Content::Alias(symbol, args, real, kind) => { - let new_real = lower_var(cache, subs, problems, *real); - Content::Alias(*symbol, *args, new_real, *kind) - } - Content::Error => Content::Error, - } -} - fn resolve_tag_ext( subs: &mut Subs, problems: &mut Vec, From 0d459cc84bc9d1d45faf43de0e97a01564d18a7c Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Wed, 9 Oct 2024 22:54:29 -0400 Subject: [PATCH 14/65] wip --- crates/compiler/specialize_types/src/lib.rs | 2 + .../specialize_types/src/mono_type.rs | 76 ++++ .../specialize_types/src/specialize_expr.rs | 310 ++++++++++++++++ .../specialize_types/src/specialize_type.rs | 340 ++++++++++++++++++ 4 files changed, 728 insertions(+) create mode 100644 crates/compiler/specialize_types/src/mono_type.rs create mode 100644 crates/compiler/specialize_types/src/specialize_expr.rs create mode 100644 crates/compiler/specialize_types/src/specialize_type.rs diff --git a/crates/compiler/specialize_types/src/lib.rs b/crates/compiler/specialize_types/src/lib.rs index 492e5744da..cd19b4dcdb 100644 --- a/crates/compiler/specialize_types/src/lib.rs +++ b/crates/compiler/specialize_types/src/lib.rs @@ -13,6 +13,8 @@ use roc_types::{ types::{AliasKind, RecordField}, }; +mod mono_type; + #[derive(Debug, PartialEq, Eq)] pub enum Problem { // Compiler bugs; these should never happen! diff --git a/crates/compiler/specialize_types/src/mono_type.rs b/crates/compiler/specialize_types/src/mono_type.rs new file mode 100644 index 0000000000..381fe8f661 --- /dev/null +++ b/crates/compiler/specialize_types/src/mono_type.rs @@ -0,0 +1,76 @@ +use core::fmt; + +/// A slice into the Vec of MonoTypes +/// +/// The starting position is a u32 which should be plenty +/// We limit slices to u16::MAX = 65535 elements +pub struct MonoSlice { + pub start: u32, + pub length: u16, + _marker: std::marker::PhantomData, +} + +impl Copy for MonoSlice {} + +impl Clone for MonoSlice { + fn clone(&self) -> Self { + *self + } +} + +impl std::fmt::Debug for MonoSlice { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "MonoSlice {{ start: {}, length: {} }}", + self.start, self.length + ) + } +} + +#[derive(Clone, Copy, Debug)] +struct Symbol { + inner: u64, +} + +#[derive(Clone, Copy, Debug)] +struct MonoTypeId { + inner: u32, +} + +#[derive(Clone, Copy, Debug)] +pub enum MonoType { + Apply(Symbol, MonoSlice), + Func { + args: MonoSlice, + ret: MonoTypeId, + }, + Record(RecordFields), + Tuple(TupleElems), + TagUnion(UnionTags), + EmptyRecord, + EmptyTuple, + EmptyTagUnion, +} + +#[derive(Clone, Copy, Debug)] +pub struct RecordFields { + pub length: u16, + pub field_names_start: u32, + pub field_type_ids_start: u32, + pub field_types_start: u32, +} + +#[derive(Clone, Copy, Debug)] +pub struct TupleElems { + pub length: u16, + pub elem_index_start: u32, + pub type_ids_start: u32, +} + +#[derive(Clone, Copy, Debug)] +pub struct UnionTags { + pub length: u16, + pub labels_start: u32, + pub values_start: u32, +} diff --git a/crates/compiler/specialize_types/src/specialize_expr.rs b/crates/compiler/specialize_types/src/specialize_expr.rs new file mode 100644 index 0000000000..f3abe0d620 --- /dev/null +++ b/crates/compiler/specialize_types/src/specialize_expr.rs @@ -0,0 +1,310 @@ +use crate::expr::{self, Declarations, Expr, FunctionDef}; +use crate::specialize_type::{MonoCache, Problem}; +use crate::subs::{Subs, Variable}; +use crate::symbol::Symbol; +use roc_collections::VecMap; + +struct Context { + symbols: Symbol, + fresh_tvar: Box Variable>, + specializations: Specializations, +} + +struct Specializations { + symbols: Symbol, + fenv: Vec<(Symbol, expr::FunctionDef)>, + specializations: Vec<(SpecializationKey, NeededSpecialization)>, +} + +#[derive(PartialEq, Eq)] +struct SpecializationKey(Symbol, Variable); + +struct NeededSpecialization { + def: expr::FunctionDef, + name_new: Symbol, + t_new: Variable, + specialized: Option, +} + +impl Specializations { + fn make(symbols: Symbol, program: &expr::Declarations) -> Self { + let fenv = program + .iter_top_down() + .filter_map(|(_, tag)| match tag { + expr::DeclarationTag::Function(idx) + | expr::DeclarationTag::Recursive(idx) + | expr::DeclarationTag::TailRecursive(idx) => { + let func = &program.function_bodies[idx.index()]; + Some((func.value.name, func.value.clone())) + } + _ => None, + }) + .collect(); + + Specializations { + symbols, + fenv, + specializations: Vec::new(), + } + } + + fn specialize_fn( + &mut self, + mono_cache: &mut MonoCache, + name: Symbol, + t_new: Variable, + ) -> Option { + let specialization = (name, t_new); + + if let Some((_, needed)) = self + .specializations + .iter() + .find(|(key, _)| *key == specialization) + { + Some(needed.name_new) + } else { + let def = self.fenv.iter().find(|(n, _)| *n == name)?.1.clone(); + let name_new = self.symbols.fresh_symbol_named(name); + let needed_specialization = NeededSpecialization { + def, + name_new, + t_new, + specialized: None, + }; + self.specializations + .push((specialization, needed_specialization)); + Some(name_new) + } + } + + fn next_needed_specialization(&mut self) -> Option<&mut NeededSpecialization> { + self.specializations.iter_mut().find_map(|(_, ns)| { + if ns.specialized.is_none() { + Some(ns) + } else { + None + } + }) + } + + fn solved_specializations(&self) -> Vec { + self.specializations + .iter() + .filter_map(|(_, ns)| ns.specialized.clone()) + .collect() + } +} + +fn specialize_expr( + ctx: &mut Context, + ty_cache: &mut Subs, + mono_cache: &mut MonoCache, + expr: &Expr, +) -> Expr { + match expr { + Expr::Var(x) => { + if let Some(y) = ctx + .specializations + .specialize_fn(mono_cache, *x, expr.get_type()) + { + Expr::Var(y) + } else { + expr.clone() + } + } + Expr::Int(i) => Expr::Int(*i), + Expr::Str(s) => Expr::Str(s.clone()), + Expr::Tag(t, args) => { + let new_args = args + .iter() + .map(|a| specialize_expr(ctx, ty_cache, mono_cache, a)) + .collect(); + Expr::Tag(*t, new_args) + } + Expr::Record(fields) => { + let new_fields = fields + .iter() + .map(|(f, e)| (*f, specialize_expr(ctx, ty_cache, mono_cache, e))) + .collect(); + Expr::Record(new_fields) + } + Expr::Access(e, f) => { + Expr::Access(Box::new(specialize_expr(ctx, ty_cache, mono_cache, e)), *f) + } + Expr::Let(def, rest) => { + let new_def = match def { + expr::Def::Letfn(f) => expr::Def::Letfn(FunctionDef { + recursive: f.recursive, + bind: ( + mono_cache.monomorphize_var(ty_cache, &mut Vec::new(), f.bind.0), + f.bind.1, + ), + arg: ( + mono_cache.monomorphize_var(ty_cache, &mut Vec::new(), f.arg.0), + f.arg.1, + ), + body: Box::new(specialize_expr(ctx, ty_cache, mono_cache, &f.body)), + }), + expr::Def::Letval(v) => expr::Def::Letval(expr::Letval { + bind: ( + mono_cache.monomorphize_var(ty_cache, &mut Vec::new(), v.bind.0), + v.bind.1, + ), + body: Box::new(specialize_expr(ctx, ty_cache, mono_cache, &v.body)), + }), + }; + let new_rest = Box::new(specialize_expr(ctx, ty_cache, mono_cache, rest)); + Expr::Let(Box::new(new_def), new_rest) + } + Expr::Clos { arg, body } => { + let new_arg = ( + mono_cache.monomorphize_var(ty_cache, &mut Vec::new(), arg.0), + arg.1, + ); + let new_body = Box::new(specialize_expr(ctx, ty_cache, mono_cache, body)); + Expr::Clos { + arg: new_arg, + body: new_body, + } + } + Expr::Call(f, a) => { + let new_f = Box::new(specialize_expr(ctx, ty_cache, mono_cache, f)); + let new_a = Box::new(specialize_expr(ctx, ty_cache, mono_cache, a)); + Expr::Call(new_f, new_a) + } + Expr::KCall(kfn, args) => { + let new_args = args + .iter() + .map(|a| specialize_expr(ctx, ty_cache, mono_cache, a)) + .collect(); + Expr::KCall(*kfn, new_args) + } + Expr::When(e, branches) => { + let new_e = Box::new(specialize_expr(ctx, ty_cache, mono_cache, e)); + let new_branches = branches + .iter() + .map(|(p, e)| { + let new_p = specialize_pattern(ctx, ty_cache, mono_cache, p); + let new_e = specialize_expr(ctx, ty_cache, mono_cache, e); + (new_p, new_e) + }) + .collect(); + Expr::When(new_e, new_branches) + } + } +} + +fn specialize_pattern( + ctx: &mut Context, + ty_cache: &mut Subs, + mono_cache: &mut MonoCache, + pattern: &expr::Pattern, +) -> expr::Pattern { + match pattern { + expr::Pattern::PVar(x) => expr::Pattern::PVar(*x), + expr::Pattern::PTag(tag, args) => { + let new_args = args + .iter() + .map(|a| specialize_pattern(ctx, ty_cache, mono_cache, a)) + .collect(); + expr::Pattern::PTag(*tag, new_args) + } + } +} + +fn specialize_let_fn( + ctx: &mut Context, + ty_cache: &mut Subs, + mono_cache: &mut MonoCache, + t_new: Variable, + name_new: Symbol, + f: &expr::FunctionDef, +) -> expr::Def { + mono_cache.monomorphize_var(ty_cache, &mut Vec::new(), t_new); + let t = mono_cache.monomorphize_var(ty_cache, &mut Vec::new(), f.bind.0); + let t_arg = mono_cache.monomorphize_var(ty_cache, &mut Vec::new(), f.arg.0); + let body = specialize_expr(ctx, ty_cache, mono_cache, &f.body); + + expr::Def::Letfn(FunctionDef { + recursive: f.recursive, + bind: (t, name_new), + arg: (t_arg, f.arg.1), + body: Box::new(body), + }) +} + +fn specialize_let_val(ctx: &mut Context, v: &expr::Letval) -> expr::Def { + let mut ty_cache = Subs::new(); + let mut mono_cache = MonoCache::from_subs(&ty_cache); + let t = mono_cache.monomorphize_var(&mut ty_cache, &mut Vec::new(), v.bind.0); + let body = specialize_expr(ctx, &mut ty_cache, &mut mono_cache, &v.body); + expr::Def::Letval(expr::Letval { + bind: (t, v.bind.1), + body: Box::new(body), + }) +} + +fn specialize_run_def(ctx: &mut Context, run: &expr::Run) -> expr::Run { + let mut ty_cache = Subs::new(); + let mut mono_cache = MonoCache::from_subs(&ty_cache); + let t = mono_cache.monomorphize_var(&mut ty_cache, &mut Vec::new(), run.bind.0); + let body = specialize_expr(ctx, &mut ty_cache, &mut mono_cache, &run.body); + expr::Run { + bind: (t, run.bind.1), + body: Box::new(body), + ty: run.ty, + } +} + +fn make_context( + symbols: Symbol, + fresh_tvar: Box Variable>, + program: &expr::Declarations, +) -> Context { + Context { + symbols, + fresh_tvar, + specializations: Specializations::make(symbols, program), + } +} + +fn loop_specializations(ctx: &mut Context) { + while let Some(needed) = ctx.specializations.next_needed_specialization() { + let mut ty_cache = Subs::new(); + let mut mono_cache = MonoCache::from_subs(&ty_cache); + let def = specialize_let_fn( + ctx, + &mut ty_cache, + &mut mono_cache, + needed.t_new, + needed.name_new, + &needed.def, + ); + needed.specialized = Some(def); + } +} + +pub fn lower(ctx: &mut Context, program: &expr::Declarations) -> expr::Declarations { + let mut new_program = expr::Declarations::new(); + + for (idx, tag) in program.iter_top_down() { + match tag { + expr::DeclarationTag::Value => { + let def = specialize_let_val(ctx, &program.expressions[idx]); + new_program.push_def(def); + } + expr::DeclarationTag::Run(run_idx) => { + let run = specialize_run_def(ctx, &program.expressions[run_idx.index()]); + new_program.push_run(run); + } + _ => {} + } + } + + loop_specializations(ctx); + + let other_defs = ctx.specializations.solved_specializations(); + new_program.extend(other_defs); + + new_program +} diff --git a/crates/compiler/specialize_types/src/specialize_type.rs b/crates/compiler/specialize_types/src/specialize_type.rs new file mode 100644 index 0000000000..81a330527f --- /dev/null +++ b/crates/compiler/specialize_types/src/specialize_type.rs @@ -0,0 +1,340 @@ +/// Given a Subs that's been populated from type inference, and a Variable, +/// ensure that Variable is monomorphic by going through and creating +/// specializations of that type wherever necessary. +/// +/// This only operates at the type level. It does not create new function implementations (for example). +use bitvec::vec::BitVec; +use roc_module::ident::{Lowercase, TagName}; +use roc_types::{ + subs::{ + Content, FlatType, RecordFields, Subs, TagExt, TupleElems, UnionLabels, UnionTags, + Variable, VariableSubsSlice, + }, + types::RecordField, +}; + +#[derive(Debug, PartialEq, Eq)] +pub enum Problem { + // Compiler bugs; these should never happen! + TagUnionExtWasNotTagUnion, + RecordExtWasNotRecord, + TupleExtWasNotTuple, +} + +/// Variables that have already been monomorphized. +pub struct MonoCache { + inner: BitVec, +} + +impl MonoCache { + pub fn from_subs(subs: &Subs) -> Self { + Self { + inner: BitVec::repeat(false, subs.len()), + } + } + + /// Returns true iff we know this Variable is monomorphic because + /// we've visited it before in the monomorphization process + /// (and either it was already monomorphic, or we made it so). + pub fn is_known_monomorphic(&self, var: Variable) -> bool { + match self.inner.get(var.index() as usize) { + Some(initialized) => { + // false if it has never been set, because we initialized all the bits to 0 + *initialized + } + None => false, + } + } + + /// Records that the given variable is now known to be monomorphic. + fn set_monomorphic(&mut self, var: Variable) { + self.inner.set(var.index() as usize, true); + } + + pub fn monomorphize_var( + &mut self, + subs: &mut Subs, + problems: &mut Vec, + var: Variable, + ) { + lower_var(self, subs, problems, var); + } +} + +fn lower_var( + cache: &mut MonoCache, + subs: &mut Subs, + problems: &mut Vec, + var: Variable, +) -> Variable { + let root_var = subs.get_root_key_without_compacting(var); + + if !cache.is_known_monomorphic(root_var) { + let content = match *subs.get_content_without_compacting(root_var) { + Content::Structure(flat_type) => match flat_type { + FlatType::Apply(symbol, args) => { + let new_args = args + .into_iter() + .map(|var_index| lower_var(cache, subs, problems, subs[var_index])) + .collect::>(); + + Content::Structure(FlatType::Apply( + *symbol, + VariableSubsSlice::insert_into_subs(subs, new_args), + )) + } + FlatType::Func(args, closure, ret) => { + let new_args = args + .into_iter() + .map(|var_index| lower_var(cache, subs, problems, subs[var_index])) + .collect::>(); + let new_closure = lower_var(cache, subs, problems, *closure); + let new_ret = lower_var(cache, subs, problems, *ret); + Content::Structure(FlatType::Func( + VariableSubsSlice::insert_into_subs(subs, new_args), + new_closure, + new_ret, + )) + } + FlatType::Record(fields, ext) => { + let mut fields = resolve_record_ext(subs, problems, *fields, *ext); + + // Now lower all the fields we gathered. Do this in a separate pass to avoid borrow errors on Subs. + for (_, field) in fields.iter_mut() { + let var = match field { + RecordField::Required(v) | RecordField::Optional(v) | RecordField::Demanded(v) => v, + RecordField::RigidRequired(v) | RecordField::RigidOptional(v) => v, + }; + + *var = lower_var(cache, subs, problems, *var); + } + + Content::Structure(FlatType::Record( + RecordFields::insert_into_subs(subs, fields), + Variable::EMPTY_RECORD, + )) + } + FlatType::Tuple(elems, ext) => { + let mut elems = resolve_tuple_ext(subs, problems, *elems, *ext); + + // Now lower all the elems we gathered. Do this in a separate pass to avoid borrow errors on Subs. + lower_vars(elems.iter_mut().map(|(_, var)| var), cache, subs, problems); + + Content::Structure(FlatType::Tuple( + TupleElems::insert_into_subs(subs, elems), + Variable::EMPTY_TUPLE, + )) + } + FlatType::TagUnion(tags, ext) => { + let mut tags = resolve_tag_ext(subs, problems, *tags, *ext); + + // Now lower all the tags we gathered. Do this in a separate pass to avoid borrow errors on Subs. + lower_vars(tags.iter_mut().flat_map(|(_, vars)| vars.iter_mut()), cache, subs, problems); + + Content::Structure(FlatType::TagUnion( + UnionTags::insert_into_subs(subs, tags), + TagExt::Any(Variable::EMPTY_TAG_UNION), + )) + } + FlatType::FunctionOrTagUnion(tag_names, _symbols, ext) => { + // If this is still a FunctionOrTagUnion, turn it into a TagUnion. + + // First, resolve the ext var. + let mut tags = resolve_tag_ext(subs, problems, UnionTags::default(), *ext); + + // Now lower all the tags we gathered from the ext var. + // (Do this in a separate pass to avoid borrow errors on Subs.) + lower_vars(tags.iter_mut().flat_map(|(_, vars)| vars.iter_mut()), cache, subs, problems); + + // Then, add the tag names with no payloads. (There are no variables to lower here.) + for index in tag_names.into_iter() { + tags.push(((subs[index]).clone(), Vec::new())); + } + + Content::Structure(FlatType::TagUnion( + UnionTags::insert_into_subs(subs, tags), + TagExt::Any(Variable::EMPTY_TAG_UNION), + )) + } + FlatType::RecursiveTagUnion(rec, tags, ext) => { + let mut tags = resolve_tag_ext(subs, problems, *tags, *ext); + + // Now lower all the tags we gathered. Do this in a separate pass to avoid borrow errors on Subs. + lower_vars(tags.iter_mut().flat_map(|(_, vars)| vars.iter_mut()), cache, subs, problems); + + Content::Structure(FlatType::RecursiveTagUnion( + lower_var(cache, subs, problems, *rec), + UnionTags::insert_into_subs(subs, tags), + TagExt::Any(Variable::EMPTY_TAG_UNION), + )) + } + FlatType::EmptyRecord => Content::Structure(FlatType::EmptyRecord), + FlatType::EmptyTuple => Content::Structure(FlatType::EmptyTuple), + FlatType::EmptyTagUnion => Content::Structure(FlatType::EmptyTagUnion), + }, + Content::RangedNumber(_) // RangedNumber goes in Num's type parameter slot, so monomorphize it to [] + | Content::FlexVar(_) + | Content::RigidVar(_) + | Content::FlexAbleVar(_, _) + | Content::RigidAbleVar(_, _) + | Content::RecursionVar { .. } => Content::Structure(FlatType::EmptyTagUnion), + Content::LambdaSet(lambda_set) => Content::LambdaSet(*lambda_set), + Content::ErasedLambda => Content::ErasedLambda, + Content::Alias(symbol, args, real, kind) => { + let todo = (); // TODO we should unwrap this, but doing that probably means changing this from root_var to other stuff. + let new_real = lower_var(cache, subs, problems, *real); + Content::Alias(*symbol, *args, new_real, *kind) + } + Content::Error => Content::Error, + }; + + // Update Subs so when we look up this var in the future, it's the monomorphized Content. + subs.set_content(root_var, content); + + // This var is now known to be monomorphic. + cache.set_monomorphic(root_var); + } + + root_var +} + +fn resolve_tag_ext( + subs: &mut Subs, + problems: &mut Vec, + mut tags: UnionTags, + mut ext: TagExt, +) -> Vec<(TagName, Vec)> { + let mut all_tags = Vec::new(); + + // Collapse (recursively) all the tags in ext_var into a flat list of tags. + loop { + for (tag, vars) in tags.iter_from_subs(subs) { + all_tags.push((tag.clone(), vars.to_vec())); + } + + match subs.get_content_without_compacting(ext.var()) { + Content::Structure(FlatType::TagUnion(new_tags, new_ext)) => { + // Update tags and ext and loop back again to process them. + tags = *new_tags; + ext = *new_ext; + } + Content::Structure(FlatType::FunctionOrTagUnion(tag_names, _symbols, new_ext)) => { + for index in tag_names.into_iter() { + all_tags.push((subs[index].clone(), Vec::new())); + } + ext = *new_ext; + } + Content::Structure(FlatType::EmptyTagUnion) => break, + Content::FlexVar(_) | Content::FlexAbleVar(_, _) => break, + Content::Alias(_, _, real, _) => { + // Follow the alias and process it on the next iteration of the loop. + ext = TagExt::Any(*real); + + // We just processed these tags, so don't process them again! + tags = UnionLabels::default(); + } + _ => { + // This should never happen! If it does, record a Problem and break. + problems.push(Problem::TagUnionExtWasNotTagUnion); + + break; + } + } + } + + all_tags +} + +fn resolve_record_ext( + subs: &mut Subs, + problems: &mut Vec, + mut fields: RecordFields, + mut ext: Variable, +) -> Vec<(Lowercase, RecordField)> { + let mut all_fields = Vec::new(); + + // Collapse (recursively) all the fields in ext into a flat list of fields. + loop { + for (label, field) in fields.sorted_iterator(subs, ext) { + all_fields.push((label.clone(), field)); + } + + match subs.get_content_without_compacting(ext) { + Content::Structure(FlatType::Record(new_fields, new_ext)) => { + // Update fields and ext and loop back again to process them. + fields = *new_fields; + ext = *new_ext; + } + Content::Structure(FlatType::EmptyRecord) => break, + Content::FlexVar(_) | Content::FlexAbleVar(_, _) => break, + Content::Alias(_, _, real, _) => { + // Follow the alias and process it on the next iteration of the loop. + ext = *real; + + // We just processed these fields, so don't process them again! + fields = RecordFields::empty(); + } + _ => { + // This should never happen! If it does, record a Problem and break. + problems.push(Problem::RecordExtWasNotRecord); + + break; + } + } + } + + all_fields +} + +fn resolve_tuple_ext( + subs: &mut Subs, + problems: &mut Vec, + mut elems: TupleElems, + mut ext: Variable, +) -> Vec<(usize, Variable)> { + let mut all_elems = Vec::new(); + + // Collapse (recursively) all the elements in ext into a flat list of elements. + loop { + for (idx, var_index) in elems.iter_all() { + all_elems.push((idx.index as usize, subs[var_index])); + } + + match subs.get_content_without_compacting(ext) { + Content::Structure(FlatType::Tuple(new_elems, new_ext)) => { + // Update elems and ext and loop back again to process them. + elems = *new_elems; + ext = *new_ext; + } + Content::Structure(FlatType::EmptyTuple) => break, + Content::FlexVar(_) | Content::FlexAbleVar(_, _) => break, + Content::Alias(_, _, real, _) => { + // Follow the alias and process it on the next iteration of the loop. + ext = *real; + + // We just processed these elements, so don't process them again! + elems = TupleElems::empty(); + } + _ => { + // This should never happen! If it does, record a Problem and break. + problems.push(Problem::TupleExtWasNotTuple); + + break; + } + } + } + + all_elems +} + +/// Lower the given vars in-place. +fn lower_vars<'a>( + vars: impl Iterator, + cache: &mut MonoCache, + subs: &mut Subs, + problems: &mut Vec, +) { + for var in vars { + *var = lower_var(cache, subs, problems, *var); + } +} From 3520145378581d6c92675015bf64b695de547a2a Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Mon, 14 Oct 2024 22:13:50 -0400 Subject: [PATCH 15/65] Make specialize_types lower to a new representation --- Cargo.lock | 5 + Cargo.toml | 1 + crates/compiler/collections/src/lib.rs | 2 + crates/compiler/collections/src/push.rs | 3 + crates/compiler/specialize_types/Cargo.toml | 2 + crates/compiler/specialize_types/src/lib.rs | 1 + .../specialize_types/src/mono_type.rs | 125 +++++------ .../specialize_types/src/specialize_type.rs | 204 +++++++++--------- crates/soa/Cargo.toml | 12 ++ crates/soa/src/either_index.rs | 66 ++++++ crates/soa/src/lib.rs | 9 + crates/soa/src/soa_index.rs | 98 +++++++++ crates/soa/src/soa_slice.rs | 151 +++++++++++++ crates/soa/src/soa_slice2.rs | 139 ++++++++++++ 14 files changed, 654 insertions(+), 164 deletions(-) create mode 100644 crates/compiler/collections/src/push.rs create mode 100644 crates/soa/Cargo.toml create mode 100644 crates/soa/src/either_index.rs create mode 100644 crates/soa/src/lib.rs create mode 100644 crates/soa/src/soa_index.rs create mode 100644 crates/soa/src/soa_slice.rs create mode 100644 crates/soa/src/soa_slice2.rs diff --git a/Cargo.lock b/Cargo.lock index 3b61e9daa5..2427583a2b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3152,6 +3152,7 @@ dependencies = [ "roc_solve", "roc_target", "roc_types", + "soa", "static_assertions", "test_solve_helpers", ] @@ -3622,6 +3623,10 @@ version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" +[[package]] +name = "soa" +version = "0.0.1" + [[package]] name = "socket2" version = "0.4.9" diff --git a/Cargo.toml b/Cargo.toml index eb28ad7499..074ac9ed11 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -75,6 +75,7 @@ inkwell = { git = "https://github.com/roc-lang/inkwell", branch = "inkwell-llvm- "llvm16-0", ] } +soa = { path = "crates/soa" } roc_specialize_types = { path = "crates/compiler/specialize_types" } arrayvec = "0.7.2" # update roc_std/Cargo.toml on change diff --git a/crates/compiler/collections/src/lib.rs b/crates/compiler/collections/src/lib.rs index 09af7b0b44..36850f2fa7 100644 --- a/crates/compiler/collections/src/lib.rs +++ b/crates/compiler/collections/src/lib.rs @@ -4,6 +4,7 @@ #![allow(clippy::large_enum_variant)] pub mod all; +mod push; mod reference_matrix; mod small_string_interner; mod small_vec; @@ -12,6 +13,7 @@ mod vec_map; mod vec_set; pub use all::{default_hasher, BumpMap, ImEntry, ImMap, ImSet, MutMap, MutSet, SendMap}; +pub use push::Push; pub use reference_matrix::{ReferenceMatrix, Sccs, TopologicalSort}; pub use small_string_interner::SmallStringInterner; pub use small_vec::SmallVec; diff --git a/crates/compiler/collections/src/push.rs b/crates/compiler/collections/src/push.rs new file mode 100644 index 0000000000..87066b20c6 --- /dev/null +++ b/crates/compiler/collections/src/push.rs @@ -0,0 +1,3 @@ +pub trait Push { + fn push(&mut self, entry: T); +} diff --git a/crates/compiler/specialize_types/Cargo.toml b/crates/compiler/specialize_types/Cargo.toml index 58c46c6df3..eb076bf4cf 100644 --- a/crates/compiler/specialize_types/Cargo.toml +++ b/crates/compiler/specialize_types/Cargo.toml @@ -22,6 +22,8 @@ parking_lot.workspace = true static_assertions.workspace = true indoc.workspace = true +soa.workspace = true + [dev-dependencies] roc_builtins = { path = "../builtins" } roc_derive = { path = "../derive", features = ["debug-derived-symbols"] } diff --git a/crates/compiler/specialize_types/src/lib.rs b/crates/compiler/specialize_types/src/lib.rs index cd19b4dcdb..43c1b96446 100644 --- a/crates/compiler/specialize_types/src/lib.rs +++ b/crates/compiler/specialize_types/src/lib.rs @@ -14,6 +14,7 @@ use roc_types::{ }; mod mono_type; +mod specialize_type; #[derive(Debug, PartialEq, Eq)] pub enum Problem { diff --git a/crates/compiler/specialize_types/src/mono_type.rs b/crates/compiler/specialize_types/src/mono_type.rs index 381fe8f661..5b72a8cdd7 100644 --- a/crates/compiler/specialize_types/src/mono_type.rs +++ b/crates/compiler/specialize_types/src/mono_type.rs @@ -1,76 +1,79 @@ -use core::fmt; +use roc_module::{ident::Lowercase, symbol::Symbol}; +use soa::{Id, Slice, Slice2}; -/// A slice into the Vec of MonoTypes -/// -/// The starting position is a u32 which should be plenty -/// We limit slices to u16::MAX = 65535 elements -pub struct MonoSlice { - pub start: u32, - pub length: u16, - _marker: std::marker::PhantomData, +/// For now, a heap-allocated string. In the future, this will be something else. +struct RecordFieldName(String); + +pub struct MonoTypes { + // TODO } -impl Copy for MonoSlice {} +impl MonoTypes { + pub fn add_apply( + &mut self, + symbol: Symbol, + args: impl IntoIterator>, + ) -> Id { + todo!("extend"); + } -impl Clone for MonoSlice { - fn clone(&self) -> Self { - *self + pub(crate) fn add_function( + &self, + new_closure: Id, + new_ret: Id, + new_args: impl IntoIterator>, + ) -> Id { + todo!("extend") + } + + pub(crate) fn add_record( + &self, + ext: impl Iterator)>, + ) -> Id { + todo!("extend") } } -impl std::fmt::Debug for MonoSlice { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "MonoSlice {{ start: {}, length: {} }}", - self.start, self.length - ) - } -} - -#[derive(Clone, Copy, Debug)] -struct Symbol { - inner: u64, -} - -#[derive(Clone, Copy, Debug)] -struct MonoTypeId { - inner: u32, -} +// TODO: we can make all of this take up a minimal amount of memory as follows: +// 1. Arrange it so that for each MonoType variant we need at most one length and one start index. +// 2. Store all MonoType discriminants in one array (there are only 5 of them, so u3 is plenty; +// if we discard record field names, can unify record and tuple and use u2 for the 4 variants) +// 3. Store all the MonoType variant slice lengths in a separate array (u8 should be plenty) +// 4. Store all the MonoType start indices in a separate array (u32 should be plenty) #[derive(Clone, Copy, Debug)] pub enum MonoType { - Apply(Symbol, MonoSlice), - Func { - args: MonoSlice, - ret: MonoTypeId, + Apply { + // TODO: the symbol can be stored adjacency style immediately before + // the first type arg. (This means the args slice must always + // have a valid start index even if there are no args.) This will + // work regardless of whether the Symbl is 32 or 64 bits, because + // its alignment will never be smaller than the alignment of an Index. + symbol: Symbol, + args: Slice>, }, - Record(RecordFields), - Tuple(TupleElems), - TagUnion(UnionTags), - EmptyRecord, - EmptyTuple, - EmptyTagUnion, -} -#[derive(Clone, Copy, Debug)] -pub struct RecordFields { - pub length: u16, - pub field_names_start: u32, - pub field_type_ids_start: u32, - pub field_types_start: u32, -} + Func { + /// The first element in this slice is the capture type, + /// followed by the return type, and then the rest are the + /// function's arg types. These are all stored in one slice + /// because having them adjacent is best for cache locality, + /// and storing separate Ids for each would make this variant bigger. + capture_ret_args: Slice>, + }, -#[derive(Clone, Copy, Debug)] -pub struct TupleElems { - pub length: u16, - pub elem_index_start: u32, - pub type_ids_start: u32, -} + /// Slice of (field_name, field_type) pairs. + Record( + // TODO: since both tuple elements are the same size and alignment, + // we can store them as all of the one followed by all of the other, + // and therefore store only length and start index. + Slice2, Id>, + ), -#[derive(Clone, Copy, Debug)] -pub struct UnionTags { - pub length: u16, - pub labels_start: u32, - pub values_start: u32, + /// Each element in the slice represents a different tuple element. + /// The index in the slice corresponds to the index in the tuple. + Tuple(Slice>), + + /// Slice of (tag_name, tag_payload_types) pairs + TagUnion(Slice2, Slice>>), } diff --git a/crates/compiler/specialize_types/src/specialize_type.rs b/crates/compiler/specialize_types/src/specialize_type.rs index 81a330527f..b8a978ae5a 100644 --- a/crates/compiler/specialize_types/src/specialize_type.rs +++ b/crates/compiler/specialize_types/src/specialize_type.rs @@ -3,15 +3,18 @@ /// specializations of that type wherever necessary. /// /// This only operates at the type level. It does not create new function implementations (for example). -use bitvec::vec::BitVec; +use crate::mono_type::{MonoType, MonoTypes}; +use core::iter; +use roc_collections::{Push, VecMap}; use roc_module::ident::{Lowercase, TagName}; use roc_types::{ subs::{ - Content, FlatType, RecordFields, Subs, TagExt, TupleElems, UnionLabels, UnionTags, - Variable, VariableSubsSlice, + Content, FlatType, RecordFields, SortedFieldIterator, Subs, TagExt, TupleElems, + UnionLabels, UnionTags, Variable, }, types::RecordField, }; +use soa::Id; #[derive(Debug, PartialEq, Eq)] pub enum Problem { @@ -23,96 +26,74 @@ pub enum Problem { /// Variables that have already been monomorphized. pub struct MonoCache { - inner: BitVec, + inner: VecMap>, } impl MonoCache { pub fn from_subs(subs: &Subs) -> Self { Self { - inner: BitVec::repeat(false, subs.len()), + inner: VecMap::with_capacity(subs.len()), } } - /// Returns true iff we know this Variable is monomorphic because - /// we've visited it before in the monomorphization process - /// (and either it was already monomorphic, or we made it so). - pub fn is_known_monomorphic(&self, var: Variable) -> bool { - match self.inner.get(var.index() as usize) { - Some(initialized) => { - // false if it has never been set, because we initialized all the bits to 0 - *initialized - } - None => false, - } - } - - /// Records that the given variable is now known to be monomorphic. - fn set_monomorphic(&mut self, var: Variable) { - self.inner.set(var.index() as usize, true); - } - pub fn monomorphize_var( &mut self, subs: &mut Subs, - problems: &mut Vec, + mono_types: &mut MonoTypes, + problems: &mut impl Push, var: Variable, - ) { - lower_var(self, subs, problems, var); + ) -> Id { + lower_var(self, subs, mono_types, problems, var) } } fn lower_var( cache: &mut MonoCache, subs: &mut Subs, - problems: &mut Vec, + mono_types: &mut MonoTypes, + problems: &mut impl Push, var: Variable, -) -> Variable { +) -> Id { let root_var = subs.get_root_key_without_compacting(var); - if !cache.is_known_monomorphic(root_var) { - let content = match *subs.get_content_without_compacting(root_var) { + // TODO: we could replace this cache by having Subs store a Content::Monomorphic(Id) + // and then overwrite it rather than having a separate cache. That memory is already in cache + // for sure, and the lookups should be faster because they're O(1) but don't require hashing. + if let Some(mono_id) = cache.inner.get(&root_var) { + return *mono_id; + } + + let mono_id = match *subs.get_content_without_compacting(root_var) { Content::Structure(flat_type) => match flat_type { FlatType::Apply(symbol, args) => { let new_args = args .into_iter() - .map(|var_index| lower_var(cache, subs, problems, subs[var_index])) - .collect::>(); + .map(|var_index| lower_var(cache, subs, mono_types, problems, subs[var_index])); - Content::Structure(FlatType::Apply( - *symbol, - VariableSubsSlice::insert_into_subs(subs, new_args), - )) + mono_types.add_apply(symbol, new_args) } FlatType::Func(args, closure, ret) => { let new_args = args .into_iter() - .map(|var_index| lower_var(cache, subs, problems, subs[var_index])) - .collect::>(); - let new_closure = lower_var(cache, subs, problems, *closure); - let new_ret = lower_var(cache, subs, problems, *ret); - Content::Structure(FlatType::Func( - VariableSubsSlice::insert_into_subs(subs, new_args), - new_closure, - new_ret, - )) + .map(|var_index| lower_var(cache, subs, mono_types, problems, subs[var_index])); + + let new_closure = lower_var(cache, subs, mono_types, problems, closure); + let new_ret = lower_var(cache, subs, mono_types, problems, ret); + + mono_types.add_function(new_closure, new_ret, new_args) } FlatType::Record(fields, ext) => { - let mut fields = resolve_record_ext(subs, problems, *fields, *ext); - - // Now lower all the fields we gathered. Do this in a separate pass to avoid borrow errors on Subs. - for (_, field) in fields.iter_mut() { - let var = match field { - RecordField::Required(v) | RecordField::Optional(v) | RecordField::Demanded(v) => v, - RecordField::RigidRequired(v) | RecordField::RigidOptional(v) => v, - }; - - *var = lower_var(cache, subs, problems, *var); - } - - Content::Structure(FlatType::Record( - RecordFields::insert_into_subs(subs, fields), - Variable::EMPTY_RECORD, - )) + let todo = (); // TODO the basic design here is to make an iterator that does what we want, + // so that it's no_std, doesn't allocate, etc. - just gives modularity. + mono_types.add_record( + LowerRecordIterator { + cache, + subs, + mono_types, + problems, + fields: fields.sorted_iterator(subs, ext), + ext, + }) } FlatType::Tuple(elems, ext) => { let mut elems = resolve_tuple_ext(subs, problems, *elems, *ext); @@ -188,19 +169,16 @@ fn lower_var( Content::Error => Content::Error, }; - // Update Subs so when we look up this var in the future, it's the monomorphized Content. - subs.set_content(root_var, content); + // This var is now known to be monomorphic, so we don't repeat this work again later. + cache.inner.insert(root_var, mono_id); - // This var is now known to be monomorphic. - cache.set_monomorphic(root_var); - } - - root_var + mono_id } fn resolve_tag_ext( subs: &mut Subs, - problems: &mut Vec, + mono_types: &mut MonoTypes, + problems: &mut impl Push, mut tags: UnionTags, mut ext: TagExt, ) -> Vec<(TagName, Vec)> { @@ -245,50 +223,69 @@ fn resolve_tag_ext( all_tags } -fn resolve_record_ext( - subs: &mut Subs, - problems: &mut Vec, - mut fields: RecordFields, - mut ext: Variable, -) -> Vec<(Lowercase, RecordField)> { - let mut all_fields = Vec::new(); +struct LowerRecordIterator<'c, 's, 'm, 'p, 'r, P: Push> { + cache: &'c mut MonoCache, + subs: &'s mut Subs, + mono_types: &'m mut MonoTypes, + problems: &'p mut P, + fields: SortedFieldIterator<'r>, + ext: Variable, +} - // Collapse (recursively) all the fields in ext into a flat list of fields. - loop { - for (label, field) in fields.sorted_iterator(subs, ext) { - all_fields.push((label.clone(), field)); - } +impl<'c, 's, 'm, 'p, 'r, P> Iterator for LowerRecordIterator<'c, 's, 'm, 'p, 'r, P> +where + P: Push, +{ + type Item = (Lowercase, Id); - match subs.get_content_without_compacting(ext) { - Content::Structure(FlatType::Record(new_fields, new_ext)) => { - // Update fields and ext and loop back again to process them. - fields = *new_fields; - ext = *new_ext; + fn next(&mut self) -> Option { + // Collapse (recursively) all the fields in ext into a flat list of fields. + loop { + if let Some((label, field)) = self.fields.next() { + let var = match field { + RecordField::Demanded(var) => var, + RecordField::Required(var) => var, + RecordField::Optional(var) => var, + RecordField::RigidRequired(var) => var, + RecordField::RigidOptional(var) => var, + }; + + return Some(( + label, + lower_var(self.cache, self.subs, self.mono_types, self.problems, var), + )); } - Content::Structure(FlatType::EmptyRecord) => break, - Content::FlexVar(_) | Content::FlexAbleVar(_, _) => break, - Content::Alias(_, _, real, _) => { - // Follow the alias and process it on the next iteration of the loop. - ext = *real; - // We just processed these fields, so don't process them again! - fields = RecordFields::empty(); - } - _ => { - // This should never happen! If it does, record a Problem and break. - problems.push(Problem::RecordExtWasNotRecord); + match self.subs.get_content_without_compacting(self.ext) { + Content::Structure(FlatType::Record(new_fields, new_ext)) => { + // Update fields and ext and loop back again to process them. + self.fields = new_fields.sorted_iterator(self.subs, self.ext); + self.ext = *new_ext; + } + Content::Structure(FlatType::EmptyRecord) => return None, + Content::FlexVar(_) | Content::FlexAbleVar(_, _) => return None, + Content::Alias(_, _, real, _) => { + // Follow the alias and process it on the next iteration of the loop. + self.ext = *real; - break; + // We just processed these fields, so don't process them again! + self.fields = Box::new(iter::empty()); + } + _ => { + // This should never happen! If it does, record a Problem and break. + self.problems.push(Problem::RecordExtWasNotRecord); + + return None; + } } } } - - all_fields } fn resolve_tuple_ext( subs: &mut Subs, - problems: &mut Vec, + mono_types: &mut MonoTypes, + problems: &mut impl Push, mut elems: TupleElems, mut ext: Variable, ) -> Vec<(usize, Variable)> { @@ -332,9 +329,10 @@ fn lower_vars<'a>( vars: impl Iterator, cache: &mut MonoCache, subs: &mut Subs, - problems: &mut Vec, + mono_types: &mut MonoTypes, + problems: &mut impl Push, ) { for var in vars { - *var = lower_var(cache, subs, problems, *var); + *var = lower_var(cache, subs, mono_types, problems, *var); } } diff --git a/crates/soa/Cargo.toml b/crates/soa/Cargo.toml new file mode 100644 index 0000000000..53720bb380 --- /dev/null +++ b/crates/soa/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "soa" +description = "Struct-of-Array helpers" + +authors.workspace = true +edition.workspace = true +license.workspace = true +version.workspace = true + +[dependencies] + +[dev-dependencies] diff --git a/crates/soa/src/either_index.rs b/crates/soa/src/either_index.rs new file mode 100644 index 0000000000..34c9db1b0c --- /dev/null +++ b/crates/soa/src/either_index.rs @@ -0,0 +1,66 @@ +use core::{ + fmt::{self, Formatter}, + marker::PhantomData, +}; + +use crate::soa_index::Index; + +#[derive(PartialEq, Eq)] +pub struct EitherIndex { + index: u32, + _marker: PhantomData<(T, U)>, +} + +impl Clone for EitherIndex { + fn clone(&self) -> Self { + *self + } +} + +impl Copy for EitherIndex {} + +impl fmt::Debug for EitherIndex { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "EitherIndex({})", self.index) + } +} + +impl EitherIndex { + const MASK: u32 = 1 << 31; + + pub const fn from_left(input: Index) -> Self { + assert!(input.index & Self::MASK == 0); + + Self { + index: input.index, + _marker: PhantomData, + } + } + + pub const fn from_right(input: Index) -> Self { + assert!(input.index & Self::MASK == 0); + + Self { + index: input.index | Self::MASK, + _marker: std::marker::PhantomData, + } + } + + pub const fn split(self) -> Result, Index> { + if self.index & Self::MASK == 0 { + Ok(Index { + index: self.index, + _marker: PhantomData, + }) + } else { + Err(Index { + index: self.index ^ Self::MASK, + _marker: PhantomData, + }) + } + } + + pub fn decrement_index(&mut self) { + self.index = self.index.saturating_sub(1); + } +} diff --git a/crates/soa/src/lib.rs b/crates/soa/src/lib.rs new file mode 100644 index 0000000000..bace9f156b --- /dev/null +++ b/crates/soa/src/lib.rs @@ -0,0 +1,9 @@ +mod either_index; +mod soa_index; +mod soa_slice; +mod soa_slice2; + +pub use either_index::*; +pub use soa_index::*; +pub use soa_slice::*; +pub use soa_slice2::*; diff --git a/crates/soa/src/soa_index.rs b/crates/soa/src/soa_index.rs new file mode 100644 index 0000000000..c01d2b6e3d --- /dev/null +++ b/crates/soa/src/soa_index.rs @@ -0,0 +1,98 @@ +use core::{ + any, + cmp::Ordering, + fmt::{self, Formatter}, + hash::{Hash, Hasher}, + marker::PhantomData, +}; + +use crate::soa_slice::Slice; + +pub type Id = Index; + +/// An index into an array of values, based +/// on an offset into the array rather than a pointer. +/// +/// Unlike a Rust pointer, this is a u32 offset +/// rather than usize. +pub struct Index { + pub index: u32, + pub _marker: PhantomData, +} + +impl PartialEq for Index { + fn eq(&self, other: &Self) -> bool { + self.index == other.index + } +} + +impl Eq for Index {} + +impl PartialOrd for Index { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for Index { + fn cmp(&self, other: &Self) -> Ordering { + self.index.cmp(&other.index) + } +} + +impl fmt::Debug for Index { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "Index<{}>({})", any::type_name::(), self.index) + } +} + +// derive of copy and clone does not play well with PhantomData + +impl Copy for Index {} + +impl Clone for Index { + fn clone(&self) -> Self { + *self + } +} + +impl Hash for Index { + fn hash(&self, state: &mut H) { + self.index.hash(state); + } +} + +impl Index { + pub const fn new(start: u32) -> Self { + Self { + index: start, + _marker: PhantomData, + } + } + + pub const fn as_slice(self) -> Slice { + Slice { + start: self.index, + length: 1, + _marker: PhantomData, + } + } + + pub const fn index(self) -> usize { + self.index as usize + } +} + +impl core::ops::Index> for [T] { + type Output = T; + + fn index(&self, index: Index) -> &Self::Output { + &self[index.index()] + } +} + +impl core::ops::IndexMut> for [T] { + fn index_mut(&mut self, index: Index) -> &mut Self::Output { + &mut self[index.index()] + } +} diff --git a/crates/soa/src/soa_slice.rs b/crates/soa/src/soa_slice.rs new file mode 100644 index 0000000000..f6088086c2 --- /dev/null +++ b/crates/soa/src/soa_slice.rs @@ -0,0 +1,151 @@ +use core::{fmt, marker::PhantomData, ops::Range}; + +use crate::soa_index::Index; + +/// A slice into an array of values, based +/// on an offset into the array rather than a pointer. +/// +/// Unlike a Rust slice, this is a u32 offset +/// rather than a pointer, and the length is u16. +#[derive(PartialEq, Eq, PartialOrd, Ord)] +pub struct Slice { + pub start: u32, + pub length: u16, + pub _marker: core::marker::PhantomData, +} + +impl fmt::Debug for Slice { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "Slice<{}> {{ start: {}, length: {} }}", + core::any::type_name::(), + self.start, + self.length + ) + } +} + +// derive of copy and clone does not play well with PhantomData + +impl Copy for Slice {} + +impl Clone for Slice { + fn clone(&self) -> Self { + *self + } +} + +impl Default for Slice { + fn default() -> Self { + Self::empty() + } +} + +impl Slice { + pub const fn empty() -> Self { + Self { + start: 0, + length: 0, + _marker: PhantomData, + } + } + + pub const fn start(self) -> u32 { + self.start + } + + pub fn advance(&mut self, amount: u32) { + self.start += amount + } + + pub fn get_slice<'a>(&self, elems: &'a [T]) -> &'a [T] { + &elems[self.indices()] + } + + pub fn get_slice_mut<'a>(&self, elems: &'a mut [T]) -> &'a mut [T] { + &mut elems[self.indices()] + } + + #[inline(always)] + pub const fn indices(&self) -> Range { + self.start as usize..(self.start as usize + self.length as usize) + } + + pub const fn len(&self) -> usize { + self.length as usize + } + + pub const fn is_empty(&self) -> bool { + self.len() == 0 + } + + pub fn at_start(&self) -> Index { + Index { + index: self.start, + _marker: PhantomData, + } + } + + pub fn at(&self, i: usize) -> Index { + Index { + index: self.start + i as u32, + _marker: PhantomData, + } + } + + pub const fn new(start: u32, length: u16) -> Self { + Self { + start, + length, + _marker: PhantomData, + } + } +} + +impl IntoIterator for Slice { + type Item = Index; + type IntoIter = SliceIterator; + + fn into_iter(self) -> Self::IntoIter { + SliceIterator { + slice: self, + current: self.start, + } + } +} + +pub struct SliceIterator { + slice: Slice, + current: u32, +} + +impl Iterator for SliceIterator { + type Item = Index; + + fn next(&mut self) -> Option { + if self.current < self.slice.start + self.slice.length as u32 { + let index = Index { + index: self.current, + _marker: PhantomData, + }; + + self.current += 1; + + Some(index) + } else { + None + } + } + + fn size_hint(&self) -> (usize, Option) { + let remaining = (self.slice.start + self.slice.length as u32 - self.current) as usize; + (remaining, Some(remaining)) + } +} + +impl ExactSizeIterator for SliceIterator {} + +pub trait GetSlice { + fn get_slice(&self, slice: Slice) -> &[T]; +} diff --git a/crates/soa/src/soa_slice2.rs b/crates/soa/src/soa_slice2.rs new file mode 100644 index 0000000000..821953b331 --- /dev/null +++ b/crates/soa/src/soa_slice2.rs @@ -0,0 +1,139 @@ +use core::{fmt, marker::PhantomData, ops::Range}; + +use crate::{soa_index::Index, soa_slice::Slice}; + +/// Two slices of the same length, each based on a different +/// offset into the same array (rather than a pointer). +/// +/// Unlike a Rust slice, this is a u32 offset +/// rather than a pointer, and the length is u16. +#[derive(PartialEq, Eq, PartialOrd, Ord)] +pub struct Slice2 { + pub start1: u32, + pub start2: u32, + pub length: u16, + pub _marker: core::marker::PhantomData<(T, U)>, +} + +impl fmt::Debug for Slice2 { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "Slice2<{}, {}> {{ start1: {}, start2: {}, length: {} }}", + core::any::type_name::(), + core::any::type_name::(), + self.start1, + self.start2, + self.length + ) + } +} + +// derive of copy and clone does not play well with PhantomData + +impl Copy for Slice2 {} + +impl Clone for Slice2 { + fn clone(&self) -> Self { + *self + } +} + +impl Default for Slice2 { + fn default() -> Self { + Self::empty() + } +} + +impl Slice2 { + pub const fn empty() -> Self { + Self { + start1: 0, + start2: 0, + length: 0, + _marker: PhantomData, + } + } + + pub const fn slice_first(self) -> Slice { + Slice { + start: self.start1, + length: self.length, + _marker: PhantomData, + } + } + + pub const fn slice_second(self) -> Slice { + Slice { + start: self.start2, + length: self.length, + _marker: PhantomData, + } + } + + pub const fn len(&self) -> usize { + self.length as usize + } + + pub const fn is_empty(&self) -> bool { + self.len() == 0 + } + + pub const fn new(start1: u32, start2: u32, length: u16) -> Self { + Self { + start1, + start2, + length, + _marker: PhantomData, + } + } +} + +impl IntoIterator for Slice2 { + type Item = (Index, Index); + type IntoIter = SliceIterator; + + fn into_iter(self) -> Self::IntoIter { + SliceIterator { + slice: self, + offset: 0, + } + } +} + +pub struct SliceIterator { + slice: Slice2, + offset: u32, +} + +impl Iterator for SliceIterator { + type Item = (Index, Index); + + fn next(&mut self) -> Option { + let offset = self.offset; + + if offset < self.slice.length as u32 { + let index1 = Index { + index: self.slice.start1 + offset, + _marker: PhantomData, + }; + let index2 = Index { + index: self.slice.start2 + offset, + _marker: PhantomData, + }; + + self.offset += 1; + + Some((index1, index2)) + } else { + None + } + } + + fn size_hint(&self) -> (usize, Option) { + let remaining = (self.slice.length as u32 - self.offset) as usize; + (remaining, Some(remaining)) + } +} + +impl ExactSizeIterator for SliceIterator {} From e947cd78b9240551247af76d276bed672fc4792e Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Tue, 15 Oct 2024 09:25:52 -0400 Subject: [PATCH 16/65] Use LowerTupleIterator approach --- .../specialize_types/src/debug_info.rs | 1 + crates/compiler/specialize_types/src/lib.rs | 348 +----------------- .../specialize_types/src/mono_type.rs | 7 +- .../specialize_types/src/specialize_type.rs | 132 +++++-- 4 files changed, 115 insertions(+), 373 deletions(-) create mode 100644 crates/compiler/specialize_types/src/debug_info.rs diff --git a/crates/compiler/specialize_types/src/debug_info.rs b/crates/compiler/specialize_types/src/debug_info.rs new file mode 100644 index 0000000000..fd7ca52423 --- /dev/null +++ b/crates/compiler/specialize_types/src/debug_info.rs @@ -0,0 +1 @@ +pub struct DebugInfo; diff --git a/crates/compiler/specialize_types/src/lib.rs b/crates/compiler/specialize_types/src/lib.rs index 43c1b96446..8e0e6de9f8 100644 --- a/crates/compiler/specialize_types/src/lib.rs +++ b/crates/compiler/specialize_types/src/lib.rs @@ -1,347 +1,7 @@ -/// Given a Subs that's been populated from type inference, and a Variable, -/// ensure that Variable is monomorphic by going through and creating -/// specializations of that type wherever necessary. -/// -/// This only operates at the type level. It does not create new function implementations (for example). -use bitvec::vec::BitVec; -use roc_module::ident::{Lowercase, TagName}; -use roc_types::{ - subs::{ - Content, FlatType, RecordFields, Subs, TagExt, TupleElems, UnionLabels, UnionTags, - Variable, VariableSubsSlice, - }, - types::{AliasKind, RecordField}, -}; - +mod debug_info; mod mono_type; mod specialize_type; -#[derive(Debug, PartialEq, Eq)] -pub enum Problem { - // Compiler bugs; these should never happen! - TagUnionExtWasNotTagUnion, - RecordExtWasNotRecord, - TupleExtWasNotTuple, -} - -/// Variables that have already been monomorphized. -pub struct MonoCache { - inner: BitVec, -} - -impl MonoCache { - pub fn from_subs(subs: &Subs) -> Self { - Self { - inner: BitVec::repeat(false, subs.len()), - } - } - - /// Returns true iff we know this Variable is monomorphic because - /// we've visited it before in the monomorphization process - /// (and either it was already monomorphic, or we made it so). - pub fn is_known_monomorphic(&self, var: Variable) -> bool { - match self.inner.get(var.index() as usize) { - Some(initialized) => { - // false if it has never been set, because we initialized all the bits to 0 - *initialized - } - None => false, - } - } - - /// Records that the given variable is now known to be monomorphic. - fn set_monomorphic(&mut self, var: Variable) { - self.inner.set(var.index() as usize, true); - } - - pub fn monomorphize_var( - &mut self, - subs: &mut Subs, - problems: &mut Vec, - var: Variable, - ) { - lower_var(self, subs, problems, var); - } -} - -fn lower_var( - cache: &mut MonoCache, - subs: &mut Subs, - problems: &mut Vec, - var: Variable, -) -> Variable { - let root_var = subs.get_root_key_without_compacting(var); - - if !cache.is_known_monomorphic(root_var) { - let content = match *subs.get_content_without_compacting(root_var) { - Content::Structure(flat_type) => match flat_type { - FlatType::Apply(symbol, args) => { - let new_args = args - .into_iter() - .map(|var_index| lower_var(cache, subs, problems, subs[var_index])) - .collect::>(); - - Content::Structure(FlatType::Apply( - symbol, - VariableSubsSlice::insert_into_subs(subs, new_args), - )) - } - FlatType::Func(args, closure, ret) => { - let new_args = args - .into_iter() - .map(|var_index| lower_var(cache, subs, problems, subs[var_index])) - .collect::>(); - let new_closure = lower_var(cache, subs, problems, closure); - let new_ret = lower_var(cache, subs, problems, ret); - Content::Structure(FlatType::Func( - VariableSubsSlice::insert_into_subs(subs, new_args), - new_closure, - new_ret, - )) - } - FlatType::Record(fields, ext) => { - let mut fields = resolve_record_ext(subs, problems, fields, ext); - - // Now lower all the fields we gathered. Do this in a separate pass to avoid borrow errors on Subs. - for (_, field) in fields.iter_mut() { - let var = match field { - RecordField::Required(v) | RecordField::Optional(v) | RecordField::Demanded(v) => v, - RecordField::RigidRequired(v) | RecordField::RigidOptional(v) => v, - }; - - *var = lower_var(cache, subs, problems, *var); - } - - Content::Structure(FlatType::Record( - RecordFields::insert_into_subs(subs, fields), - Variable::EMPTY_RECORD, - )) - } - FlatType::Tuple(elems, ext) => { - let mut elems = resolve_tuple_ext(subs, problems, elems, ext); - - // Now lower all the elems we gathered. Do this in a separate pass to avoid borrow errors on Subs. - lower_vars(elems.iter_mut().map(|(_, var)| var), cache, subs, problems); - - Content::Structure(FlatType::Tuple( - TupleElems::insert_into_subs(subs, elems), - Variable::EMPTY_TUPLE, - )) - } - FlatType::TagUnion(tags, ext) => { - let mut tags = resolve_tag_ext(subs, problems, tags, ext); - - // Now lower all the tags we gathered. Do this in a separate pass to avoid borrow errors on Subs. - lower_vars(tags.iter_mut().flat_map(|(_, vars)| vars.iter_mut()), cache, subs, problems); - - Content::Structure(FlatType::TagUnion( - UnionTags::insert_into_subs(subs, tags), - TagExt::Any(Variable::EMPTY_TAG_UNION), - )) - } - FlatType::FunctionOrTagUnion(tag_names, _symbols, ext) => { - // If this is still a FunctionOrTagUnion, turn it into a TagUnion. - - // First, resolve the ext var. - let mut tags = resolve_tag_ext(subs, problems, UnionTags::default(), ext); - - // Now lower all the tags we gathered from the ext var. - // (Do this in a separate pass to avoid borrow errors on Subs.) - lower_vars(tags.iter_mut().flat_map(|(_, vars)| vars.iter_mut()), cache, subs, problems); - - // Then, add the tag names with no payloads. (There are no variables to lower here.) - for index in tag_names.into_iter() { - tags.push(((subs[index]).clone(), Vec::new())); - } - - Content::Structure(FlatType::TagUnion( - UnionTags::insert_into_subs(subs, tags), - TagExt::Any(Variable::EMPTY_TAG_UNION), - )) - } - FlatType::RecursiveTagUnion(rec, tags, ext) => { - let mut tags = resolve_tag_ext(subs, problems, tags, ext); - - // Now lower all the tags we gathered. Do this in a separate pass to avoid borrow errors on Subs. - lower_vars(tags.iter_mut().flat_map(|(_, vars)| vars.iter_mut()), cache, subs, problems); - - Content::Structure(FlatType::RecursiveTagUnion( - lower_var(cache, subs, problems, rec), - UnionTags::insert_into_subs(subs, tags), - TagExt::Any(Variable::EMPTY_TAG_UNION), - )) - } - FlatType::EmptyRecord => Content::Structure(FlatType::EmptyRecord), - FlatType::EmptyTuple => Content::Structure(FlatType::EmptyTuple), - FlatType::EmptyTagUnion => Content::Structure(FlatType::EmptyTagUnion), - }, - Content::RangedNumber(_) // RangedNumber goes in Num's type parameter slot, so monomorphize it to [] - | Content::FlexVar(_) - | Content::RigidVar(_) - | Content::FlexAbleVar(_, _) - | Content::RigidAbleVar(_, _) - | Content::RecursionVar { .. } => Content::Structure(FlatType::EmptyTagUnion), - Content::LambdaSet(lambda_set) => Content::LambdaSet(lambda_set), - Content::ErasedLambda => Content::ErasedLambda, - Content::Alias(_symbol, _args, real, AliasKind::Structural) => { - // Unwrap type aliases. - let new_real = lower_var(cache, subs, problems, real); - cache.set_monomorphic(new_real); - return new_real; - } - Content::Alias(symbol, args, real, AliasKind::Opaque) => { - Content::Alias(symbol, args, lower_var(cache, subs, problems, real), AliasKind::Opaque) - } - Content::Error => Content::Error, - }; - - // Update Subs so when we look up this var in the future, it's the monomorphized Content. - subs.set_content(root_var, content); - - // This var is now known to be monomorphic. - cache.set_monomorphic(root_var); - } - - root_var -} - -fn resolve_tag_ext( - subs: &mut Subs, - problems: &mut Vec, - mut tags: UnionTags, - mut ext: TagExt, -) -> Vec<(TagName, Vec)> { - let mut all_tags = Vec::new(); - - // Collapse (recursively) all the tags in ext_var into a flat list of tags. - loop { - for (tag, vars) in tags.iter_from_subs(subs) { - all_tags.push((tag.clone(), vars.to_vec())); - } - - match subs.get_content_without_compacting(ext.var()) { - Content::Structure(FlatType::TagUnion(new_tags, new_ext)) => { - // Update tags and ext and loop back again to process them. - tags = *new_tags; - ext = *new_ext; - } - Content::Structure(FlatType::FunctionOrTagUnion(tag_names, _symbols, new_ext)) => { - for index in tag_names.into_iter() { - all_tags.push((subs[index].clone(), Vec::new())); - } - ext = *new_ext; - } - Content::Structure(FlatType::EmptyTagUnion) => break, - Content::FlexVar(_) | Content::FlexAbleVar(_, _) => break, - Content::Alias(_, _, real, _) => { - // Follow the alias and process it on the next iteration of the loop. - ext = TagExt::Any(*real); - - // We just processed these tags, so don't process them again! - tags = UnionLabels::default(); - } - _ => { - // This should never happen! If it does, record a Problem and break. - problems.push(Problem::TagUnionExtWasNotTagUnion); - - break; - } - } - } - - all_tags -} - -fn resolve_record_ext( - subs: &mut Subs, - problems: &mut Vec, - mut fields: RecordFields, - mut ext: Variable, -) -> Vec<(Lowercase, RecordField)> { - let mut all_fields = Vec::new(); - - // Collapse (recursively) all the fields in ext into a flat list of fields. - loop { - for (label, field) in fields.sorted_iterator(subs, ext) { - all_fields.push((label.clone(), field)); - } - - match subs.get_content_without_compacting(ext) { - Content::Structure(FlatType::Record(new_fields, new_ext)) => { - // Update fields and ext and loop back again to process them. - fields = *new_fields; - ext = *new_ext; - } - Content::Structure(FlatType::EmptyRecord) => break, - Content::FlexVar(_) | Content::FlexAbleVar(_, _) => break, - Content::Alias(_, _, real, _) => { - // Follow the alias and process it on the next iteration of the loop. - ext = *real; - - // We just processed these fields, so don't process them again! - fields = RecordFields::empty(); - } - _ => { - // This should never happen! If it does, record a Problem and break. - problems.push(Problem::RecordExtWasNotRecord); - - break; - } - } - } - - all_fields -} - -fn resolve_tuple_ext( - subs: &mut Subs, - problems: &mut Vec, - mut elems: TupleElems, - mut ext: Variable, -) -> Vec<(usize, Variable)> { - let mut all_elems = Vec::new(); - - // Collapse (recursively) all the elements in ext into a flat list of elements. - loop { - for (idx, var_index) in elems.iter_all() { - all_elems.push((idx.index as usize, subs[var_index])); - } - - match subs.get_content_without_compacting(ext) { - Content::Structure(FlatType::Tuple(new_elems, new_ext)) => { - // Update elems and ext and loop back again to process them. - elems = *new_elems; - ext = *new_ext; - } - Content::Structure(FlatType::EmptyTuple) => break, - Content::FlexVar(_) | Content::FlexAbleVar(_, _) => break, - Content::Alias(_, _, real, _) => { - // Follow the alias and process it on the next iteration of the loop. - ext = *real; - - // We just processed these elements, so don't process them again! - elems = TupleElems::empty(); - } - _ => { - // This should never happen! If it does, record a Problem and break. - problems.push(Problem::TupleExtWasNotTuple); - - break; - } - } - } - - all_elems -} - -/// Lower the given vars in-place. -fn lower_vars<'a>( - vars: impl Iterator, - cache: &mut MonoCache, - subs: &mut Subs, - problems: &mut Vec, -) { - for var in vars { - *var = lower_var(cache, subs, problems, *var); - } -} +pub use debug_info::*; +pub use mono_type::*; +pub use specialize_type::*; diff --git a/crates/compiler/specialize_types/src/mono_type.rs b/crates/compiler/specialize_types/src/mono_type.rs index 5b72a8cdd7..410fdeae28 100644 --- a/crates/compiler/specialize_types/src/mono_type.rs +++ b/crates/compiler/specialize_types/src/mono_type.rs @@ -1,4 +1,4 @@ -use roc_module::{ident::Lowercase, symbol::Symbol}; +use roc_module::symbol::Symbol; use soa::{Id, Slice, Slice2}; /// For now, a heap-allocated string. In the future, this will be something else. @@ -26,9 +26,10 @@ impl MonoTypes { todo!("extend") } - pub(crate) fn add_record( + /// We only use the field labels for sorting later; we don't care what format they're in otherwise. + pub(crate) fn add_record( &self, - ext: impl Iterator)>, + fields: impl Iterator)>, ) -> Id { todo!("extend") } diff --git a/crates/compiler/specialize_types/src/specialize_type.rs b/crates/compiler/specialize_types/src/specialize_type.rs index b8a978ae5a..03248f2b77 100644 --- a/crates/compiler/specialize_types/src/specialize_type.rs +++ b/crates/compiler/specialize_types/src/specialize_type.rs @@ -3,7 +3,10 @@ /// specializations of that type wherever necessary. /// /// This only operates at the type level. It does not create new function implementations (for example). -use crate::mono_type::{MonoType, MonoTypes}; +use crate::{ + debug_info::DebugInfo, + mono_type::{self, MonoType, MonoTypes}, +}; use core::iter; use roc_collections::{Push, VecMap}; use roc_module::ident::{Lowercase, TagName}; @@ -41,19 +44,33 @@ impl MonoCache { subs: &mut Subs, mono_types: &mut MonoTypes, problems: &mut impl Push, + debug_info: &mut Option, var: Variable, ) -> Id { - lower_var(self, subs, mono_types, problems, var) + lower_var( + Env { + cache: self, + subs, + mono_types, + problems, + debug_info, + }, + var, + ) } } -fn lower_var( - cache: &mut MonoCache, - subs: &mut Subs, - mono_types: &mut MonoTypes, - problems: &mut impl Push, - var: Variable, -) -> Id { +struct Env<'c, 'd, 'm, 'p, 's, P: Push> { + cache: &'c mut MonoCache, + subs: &'s mut Subs, + mono_types: &'m mut MonoTypes, + problems: &'p mut P, + debug_info: &'d mut Option, +} + +fn lower_var>(env: Env<'_, '_, '_, '_, '_, P>, var: Variable) -> Id { + let cache = env.cache; + let subs = env.subs; let root_var = subs.get_root_key_without_compacting(var); // TODO: we could replace this cache by having Subs store a Content::Monomorphic(Id) @@ -63,28 +80,31 @@ fn lower_var( return *mono_id; } + let mono_types = env.mono_types; + let problems = env.problems; + + // Convert the Content to a MonoType, often by passing an iterator. None of these iterators introduce allocations. let mono_id = match *subs.get_content_without_compacting(root_var) { Content::Structure(flat_type) => match flat_type { FlatType::Apply(symbol, args) => { let new_args = args .into_iter() - .map(|var_index| lower_var(cache, subs, mono_types, problems, subs[var_index])); + .map(|var_index| lower_var(env, subs[var_index])); mono_types.add_apply(symbol, new_args) } FlatType::Func(args, closure, ret) => { let new_args = args .into_iter() - .map(|var_index| lower_var(cache, subs, mono_types, problems, subs[var_index])); + .map(|var_index| lower_var(env, subs[var_index])); - let new_closure = lower_var(cache, subs, mono_types, problems, closure); - let new_ret = lower_var(cache, subs, mono_types, problems, ret); + let new_closure = lower_var(env, closure); + let new_ret = lower_var(env, ret); mono_types.add_function(new_closure, new_ret, new_args) } FlatType::Record(fields, ext) => { - let todo = (); // TODO the basic design here is to make an iterator that does what we want, - // so that it's no_std, doesn't allocate, etc. - just gives modularity. + let todo = (); // TODO populate debuginfo (if it's Some, meaning we want it) mono_types.add_record( LowerRecordIterator { cache, @@ -96,15 +116,16 @@ fn lower_var( }) } FlatType::Tuple(elems, ext) => { - let mut elems = resolve_tuple_ext(subs, problems, *elems, *ext); - - // Now lower all the elems we gathered. Do this in a separate pass to avoid borrow errors on Subs. - lower_vars(elems.iter_mut().map(|(_, var)| var), cache, subs, problems); - - Content::Structure(FlatType::Tuple( - TupleElems::insert_into_subs(subs, elems), - Variable::EMPTY_TUPLE, - )) + let todo = (); // TODO populate debuginfo (if it's Some, meaning we want it) + mono_types.add_tuple( + LowerTupleIterator { + cache, + subs, + mono_types, + problems, + elems: elems.sorted_iterator(subs, ext), + ext, + }) } FlatType::TagUnion(tags, ext) => { let mut tags = resolve_tag_ext(subs, problems, *tags, *ext); @@ -228,7 +249,66 @@ struct LowerRecordIterator<'c, 's, 'm, 'p, 'r, P: Push> { subs: &'s mut Subs, mono_types: &'m mut MonoTypes, problems: &'p mut P, - fields: SortedFieldIterator<'r>, + fields: SortedFieldIterator<'r>, // TODO impl Iterator + ext: Variable, +} + +impl<'c, 's, 'm, 'p, 'r, P> Iterator for LowerRecordIterator<'c, 's, 'm, 'p, 'r, P> +where + P: Push, +{ + type Item = (Lowercase, Id); + + fn next(&mut self) -> Option { + // Collapse (recursively) all the fields in ext into a flat list of fields. + loop { + if let Some((label, field)) = self.fields.next() { + let var = match field { + RecordField::Demanded(var) => var, + RecordField::Required(var) => var, + RecordField::Optional(var) => var, + RecordField::RigidRequired(var) => var, + RecordField::RigidOptional(var) => var, + }; + + return Some(( + label, + lower_var(self.cache, self.subs, self.mono_types, self.problems, var), + )); + } + + match self.subs.get_content_without_compacting(self.ext) { + Content::Structure(FlatType::Record(new_fields, new_ext)) => { + // Update fields and ext and loop back again to process them. + self.fields = new_fields.sorted_iterator(self.subs, self.ext); + self.ext = *new_ext; + } + Content::Structure(FlatType::EmptyRecord) => return None, + Content::FlexVar(_) | Content::FlexAbleVar(_, _) => return None, + Content::Alias(_, _, real, _) => { + // Follow the alias and process it on the next iteration of the loop. + self.ext = *real; + + // We just processed these fields, so don't process them again! + self.fields = Box::new(iter::empty()); + } + _ => { + // This should never happen! If it does, record a Problem and break. + self.problems.push(Problem::RecordExtWasNotRecord); + + return None; + } + } + } + } +} + +struct LowerTupleIterator<'c, 's, 'm, 'p, 'r, P: Push> { + cache: &'c mut MonoCache, + subs: &'s mut Subs, + mono_types: &'m mut MonoTypes, + problems: &'p mut P, + fields: SortedElemsIterator<'r>, ext: Variable, } @@ -333,6 +413,6 @@ fn lower_vars<'a>( problems: &mut impl Push, ) { for var in vars { - *var = lower_var(cache, subs, mono_types, problems, *var); + *var = lower_var(env, *var); } } From 98535bfbce663537d7fbf1b295b79c61b147ee6f Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Fri, 18 Oct 2024 22:32:41 -0400 Subject: [PATCH 17/65] wip --- Cargo.toml | 3 + crates/compiler/can/src/expr.rs | 52 +- crates/compiler/can/src/pattern.rs | 1 + crates/compiler/collections/src/push.rs | 6 + .../specialize_types/src/debug_info.rs | 6 + .../specialize_types/src/foreign_symbol.rs | 37 + crates/compiler/specialize_types/src/lib.rs | 18 +- .../specialize_types/src/mono_expr.rs | 279 + .../compiler/specialize_types/src/mono_ir.rs | 235 + .../specialize_types/src/mono_module.rs | 24 + .../compiler/specialize_types/src/mono_num.rs | 90 + .../specialize_types/src/mono_struct.rs | 13 + .../specialize_types/src/mono_type.rs | 247 +- .../specialize_types/src/specialize_expr.rs | 4 +- .../specialize_types/src/specialize_type.rs | 494 +- .../tests/test_specialize_expr.rs | 4889 +++++++++++++++++ .../tests/test_specialize_types.rs | 16 +- crates/compiler/test_gen/src/helpers/llvm.rs | 7 + crates/limits/Cargo.toml | 13 + crates/limits/src/limits.rs | 7 + crates/soa/src/lib.rs | 6 +- crates/soa/src/soa_slice.rs | 102 +- crates/soa/src/soa_slice2.rs | 2 +- crates/soa/src/soa_slice3.rs | 152 + crates/test_compile/Cargo.toml | 20 + crates/test_compile/src/help_parse.rs | 20 + crates/test_compile/src/lib.rs | 2 + crates/test_compile/src/without_indent.rs | 100 + 28 files changed, 6508 insertions(+), 337 deletions(-) create mode 100644 crates/compiler/specialize_types/src/foreign_symbol.rs create mode 100644 crates/compiler/specialize_types/src/mono_expr.rs create mode 100644 crates/compiler/specialize_types/src/mono_ir.rs create mode 100644 crates/compiler/specialize_types/src/mono_module.rs create mode 100644 crates/compiler/specialize_types/src/mono_num.rs create mode 100644 crates/compiler/specialize_types/src/mono_struct.rs create mode 100644 crates/compiler/specialize_types/tests/test_specialize_expr.rs create mode 100644 crates/limits/Cargo.toml create mode 100644 crates/limits/src/limits.rs create mode 100644 crates/soa/src/soa_slice3.rs create mode 100644 crates/test_compile/Cargo.toml create mode 100644 crates/test_compile/src/help_parse.rs create mode 100644 crates/test_compile/src/lib.rs create mode 100644 crates/test_compile/src/without_indent.rs diff --git a/Cargo.toml b/Cargo.toml index 074ac9ed11..3b16d84ff5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -201,6 +201,9 @@ winapi = { version = "0.3.9", features = ["memoryapi"] } winit = "0.26.1" wyhash = "0.5.0" +# Testing +test_compile = { path = "crates/test_compile" } + # Optimizations based on https://deterministic.space/high-performance-rust.html [profile.release] codegen-units = 1 diff --git a/crates/compiler/can/src/expr.rs b/crates/compiler/can/src/expr.rs index 18f295bb0f..219e610c4b 100644 --- a/crates/compiler/can/src/expr.rs +++ b/crates/compiler/can/src/expr.rs @@ -84,6 +84,54 @@ impl Display for IntValue { } } +impl IntValue { + pub fn as_u8(self) -> u8 { + self.as_u128() as u8 + } + + pub fn as_i8(self) -> i8 { + self.as_i128() as i8 + } + + pub fn as_u16(self) -> u16 { + self.as_u128() as u16 + } + + pub fn as_i16(self) -> i16 { + self.as_i128() as i16 + } + + pub fn as_u32(self) -> u32 { + self.as_u128() as u32 + } + + pub fn as_i32(self) -> i32 { + self.as_i128() as i32 + } + + pub fn as_u64(self) -> u64 { + self.as_u128() as u64 + } + + pub fn as_i64(self) -> i64 { + self.as_i128() as i64 + } + + pub fn as_u128(self) -> u128 { + match self { + IntValue::I128(i128) => i128::from_ne_bytes(i128) as u128, + IntValue::U128(u128) => u128::from_ne_bytes(u128), + } + } + + pub fn as_i128(self) -> i128 { + match self { + IntValue::I128(i128) => i128::from_ne_bytes(i128), + IntValue::U128(u128) => u128::from_ne_bytes(u128) as i128, + } + } +} + #[derive(Clone, Debug)] pub enum Expr { // Literals @@ -103,7 +151,7 @@ pub enum Expr { loc_elems: Vec>, }, - // An ingested files, it's bytes, and the type variable. + // An ingested files, its bytes, and the type variable. IngestedFile(Box, Arc>, Variable), // Lookups @@ -130,7 +178,7 @@ pub enum Expr { /// The actual condition of the when expression. loc_cond: Box>, cond_var: Variable, - /// Result type produced by the branches. + /// Type of each branch (and therefore the type of the entire `when` expression) expr_var: Variable, region: Region, /// The branches of the when, and the type of the condition that they expect to be matched diff --git a/crates/compiler/can/src/pattern.rs b/crates/compiler/can/src/pattern.rs index ffd097e3ae..f1b59ef10a 100644 --- a/crates/compiler/can/src/pattern.rs +++ b/crates/compiler/can/src/pattern.rs @@ -207,6 +207,7 @@ pub struct ListPatterns { /// [ .., A, B ] -> patterns = [A, B], rest = 0 /// [ A, .., B ] -> patterns = [A, B], rest = 1 /// [ A, B, .. ] -> patterns = [A, B], rest = 2 + /// Optionally, the rest pattern can be named - e.g. `[ A, B, ..others ]` pub opt_rest: Option<(usize, Option>)>, } diff --git a/crates/compiler/collections/src/push.rs b/crates/compiler/collections/src/push.rs index 87066b20c6..9ebd4d6ccc 100644 --- a/crates/compiler/collections/src/push.rs +++ b/crates/compiler/collections/src/push.rs @@ -1,3 +1,9 @@ pub trait Push { fn push(&mut self, entry: T); } + +impl Push for Vec { + fn push(&mut self, entry: T) { + self.push(entry); + } +} diff --git a/crates/compiler/specialize_types/src/debug_info.rs b/crates/compiler/specialize_types/src/debug_info.rs index fd7ca52423..f3d7829b45 100644 --- a/crates/compiler/specialize_types/src/debug_info.rs +++ b/crates/compiler/specialize_types/src/debug_info.rs @@ -1 +1,7 @@ pub struct DebugInfo; + +impl DebugInfo { + pub fn new() -> Self { + DebugInfo + } +} diff --git a/crates/compiler/specialize_types/src/foreign_symbol.rs b/crates/compiler/specialize_types/src/foreign_symbol.rs new file mode 100644 index 0000000000..56caf0b012 --- /dev/null +++ b/crates/compiler/specialize_types/src/foreign_symbol.rs @@ -0,0 +1,37 @@ +use roc_module::ident::ForeignSymbol; +use soa::Id; + +#[derive(Debug)] +pub struct ForeignSymbols { + inner: Vec, +} + +impl ForeignSymbols { + pub fn get(&mut self, id: ForeignSymbolId) -> &ForeignSymbol { + // Safety: we only ever get indices that correspond to actual Vec entries + unsafe { self.inner.get_unchecked(id.inner.index()) } + } + + pub(crate) fn new() -> Self { + Self { + inner: Default::default(), + } + } +} + +impl ForeignSymbols { + pub fn push(&mut self, entry: ForeignSymbol) -> ForeignSymbolId { + let id = self.inner.len(); + + self.inner.push(entry); + + ForeignSymbolId { + inner: Id::new(id as u32), + } + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct ForeignSymbolId { + inner: Id, +} diff --git a/crates/compiler/specialize_types/src/lib.rs b/crates/compiler/specialize_types/src/lib.rs index 8e0e6de9f8..4439b342aa 100644 --- a/crates/compiler/specialize_types/src/lib.rs +++ b/crates/compiler/specialize_types/src/lib.rs @@ -1,7 +1,19 @@ mod debug_info; +mod foreign_symbol; +mod mono_expr; +mod mono_ir; +mod mono_module; +mod mono_num; +mod mono_struct; mod mono_type; +// mod specialize_expr; mod specialize_type; -pub use debug_info::*; -pub use mono_type::*; -pub use specialize_type::*; +pub use debug_info::DebugInfo; +pub use foreign_symbol::{ForeignSymbolId, ForeignSymbols}; +pub use mono_expr::Env; +pub use mono_ir::MonoExpr; +pub use mono_num::Number; +pub use mono_struct::MonoFieldId; +pub use mono_type::{MonoType, MonoTypeId, MonoTypes}; +pub use specialize_type::{MonoCache, RecordFieldIds, TupleElemIds}; diff --git a/crates/compiler/specialize_types/src/mono_expr.rs b/crates/compiler/specialize_types/src/mono_expr.rs new file mode 100644 index 0000000000..90c3b88606 --- /dev/null +++ b/crates/compiler/specialize_types/src/mono_expr.rs @@ -0,0 +1,279 @@ +use crate::{ + mono_ir::{MonoExpr, MonoExprId, MonoExprs}, + mono_num::Number, + mono_type::{MonoType, MonoTypes, Primitive}, + specialize_type::{MonoCache, Problem, RecordFieldIds, TupleElemIds}, + DebugInfo, +}; +use roc_can::expr::{Expr, IntValue}; +use roc_collections::Push; +use roc_types::subs::Subs; + +pub struct Env<'c, 'd, 's, 't, P> { + subs: &'s mut Subs, + types_cache: &'c mut MonoCache, + mono_types: &'t mut MonoTypes, + mono_exprs: &'t mut MonoExprs, + record_field_ids: RecordFieldIds, + tuple_elem_ids: TupleElemIds, + debug_info: &'d mut Option, + problems: P, +} + +impl<'c, 'd, 's, 't, P: Push> Env<'c, 'd, 's, 't, P> { + pub fn to_mono_expr(&mut self, can_expr: Expr) -> Option { + let problems = &mut self.problems; + let mono_types = &mut self.mono_types; + let mut mono_from_var = |var| { + self.types_cache.monomorphize_var( + self.subs, + mono_types, + &mut self.record_field_ids, + &mut self.tuple_elem_ids, + problems, + &mut self.debug_info, + var, + ) + }; + + let mut add = |expr| self.mono_exprs.add(expr); + macro_rules! compiler_bug { + ($problem:expr) => {{ + problems.push($problem); + Some(add(MonoExpr::CompilerBug($problem))) + }}; + } + + let mono_expr = match can_expr { + Expr::Num(var, _str, int_value, _bound) => match mono_from_var(var) { + Some(mono_id) => match mono_types.get(mono_id) { + MonoType::Primitive(primitive) => to_num(*primitive, int_value, problems), + other => { + return compiler_bug!(Problem::NumSpecializedToWrongType(Some(*other))); + } + }, + None => { + return compiler_bug!(Problem::NumSpecializedToWrongType(None)); + } + }, + Expr::Int(var, _precision_var, _str, val, _bound) => match mono_from_var(var) { + Some(mono_id) => match mono_types.get(mono_id) { + MonoType::Primitive(primitive) => to_int(*primitive, val, problems), + other => { + return compiler_bug!(Problem::NumSpecializedToWrongType(Some(*other))); + } + }, + None => { + return compiler_bug!(Problem::NumSpecializedToWrongType(None)); + } + }, + _ => todo!(), + // Expr::Float(var, _precision_var, _str, val, _bound) => { + // match mono_from_var(var) { + // Some(mono_id) => { + // match mono_types.get(mono_id) { + // MonoType::Number(number) => { + // Some(mono_types.add(to_frac(number, val, problems))) + // } + // _ => { + // // TODO push problem and return Malformed expr: Num specialized to something that wasn't a number, namely: _____ + // } + // } + // } + // None => { + // // TODO push problem and return Malformed expr: Num's type param specialized to Unit somehow + // } + // } + // } + // Expr::SingleQuote(variable, _precision_var, _char, _bound) => { + // // Single quote monomorphizes to an integer. + // // TODO let's just start writing some tests for converting numbers and single quotes and strings. + // // TODO also, verify that doing nonsense like a type annotation of Num {} is handled gracefully. + // } + // Expr::Str(_) => todo!(), + // Expr::List { + // elem_var, + // loc_elems, + // } => todo!(), + // Expr::IngestedFile(path_buf, arc, variable) => todo!(), + // Expr::Var(symbol, variable) => todo!(), + // Expr::ParamsVar { + // symbol, + // var, + // params_symbol, + // params_var, + // } => todo!(), + // Expr::AbilityMember(symbol, specialization_id, variable) => todo!(), + // Expr::When { + // loc_cond, + // cond_var, + // expr_var, + // region, + // branches, + // branches_cond_var, + // exhaustive, + // } => todo!(), + // Expr::If { + // cond_var, + // branch_var, + // branches, + // final_else, + // } => todo!(), + // Expr::LetRec(vec, loc, illegal_cycle_mark) => todo!(), + // Expr::LetNonRec(def, loc) => todo!(), + // Expr::Call(_, vec, called_via) => todo!(), + // Expr::RunLowLevel { op, args, ret_var } => todo!(), + // Expr::ForeignCall { + // foreign_symbol, + // args, + // ret_var, + // } => todo!(), + // Expr::Closure(closure_data) => todo!(), + // Expr::Record { record_var, fields } => todo!(), + // Expr::EmptyRecord => todo!(), + // Expr::Tuple { tuple_var, elems } => todo!(), + // Expr::ImportParams(module_id, region, _) => todo!(), + // Expr::Crash { msg, ret_var } => todo!(), + // Expr::RecordAccess { + // record_var, + // ext_var, + // field_var, + // loc_expr, + // field, + // } => todo!(), + // Expr::RecordAccessor(struct_accessor_data) => todo!(), + // Expr::TupleAccess { + // tuple_var, + // ext_var, + // elem_var, + // loc_expr, + // index, + // } => todo!(), + // Expr::RecordUpdate { + // record_var, + // ext_var, + // symbol, + // updates, + // } => todo!(), + // Expr::Tag { + // tag_union_var, + // ext_var, + // name, + // arguments, + // } => todo!(), + // Expr::ZeroArgumentTag { + // closure_name, + // variant_var, + // ext_var, + // name, + // } => todo!(), + // Expr::OpaqueRef { + // opaque_var, + // name, + // argument, + // specialized_def_type, + // type_arguments, + // lambda_set_variables, + // } => todo!(), + // Expr::OpaqueWrapFunction(opaque_wrap_function_data) => todo!(), + // Expr::Expect { + // loc_condition, + // loc_continuation, + // lookups_in_cond, + // } => todo!(), + // Expr::ExpectFx { + // loc_condition, + // loc_continuation, + // lookups_in_cond, + // } => todo!(), + // Expr::Dbg { + // source_location, + // source, + // loc_message, + // loc_continuation, + // variable, + // symbol, + // } => todo!(), + // Expr::TypedHole(variable) => todo!(), + Expr::RuntimeError(_runtime_error) => { + todo!("generate a MonoExpr::Crash based on the runtime error"); + } + }; + + Some(add(mono_expr)) + } +} + +/// Convert a number literal (e.g. `42`) to a monomorphized type. +/// This is allowed to convert to either an integer or a fraction. +fn to_num(primitive: Primitive, val: IntValue, problems: &mut impl Push) -> MonoExpr { + match primitive { + Primitive::Dec => MonoExpr::Number(Number::Dec(val.as_i128())), + Primitive::F32 => MonoExpr::Number(Number::F32(val.as_i128() as f32)), + Primitive::F64 => MonoExpr::Number(Number::F64(val.as_i128() as f64)), + Primitive::U8 => MonoExpr::Number(Number::U8(val.as_i128() as u8)), + Primitive::I8 => MonoExpr::Number(Number::I8(val.as_i128() as i8)), + Primitive::U16 => MonoExpr::Number(Number::U16(val.as_i128() as u16)), + Primitive::I16 => MonoExpr::Number(Number::I16(val.as_i128() as i16)), + Primitive::U32 => MonoExpr::Number(Number::U32(val.as_i128() as u32)), + Primitive::I32 => MonoExpr::Number(Number::I32(val.as_i128() as i32)), + Primitive::U64 => MonoExpr::Number(Number::U64(val.as_i128() as u64)), + Primitive::I64 => MonoExpr::Number(Number::I64(val.as_i128() as i64)), + Primitive::U128 => MonoExpr::Number(Number::U128(val.as_u128())), + Primitive::I128 => MonoExpr::Number(Number::I128(val.as_i128())), + Primitive::Str => { + let problem = Problem::NumSpecializedToWrongType(Some(MonoType::Primitive(primitive))); + problems.push(problem); + MonoExpr::CompilerBug(problem) + } + } +} + +/// Convert an integer literal (e.g. `0x5`) to a monomorphized type. +/// If somehow its type was not an integer type, that's a compiler bug! +fn to_int(primitive: Primitive, val: IntValue, problems: &mut impl Push) -> MonoExpr { + match primitive { + // These are ordered roughly by most to least common integer types + Primitive::U64 => MonoExpr::Number(Number::U64(val.as_u64())), + Primitive::U8 => MonoExpr::Number(Number::U8(val.as_u8())), + Primitive::I64 => MonoExpr::Number(Number::I64(val.as_i64())), + Primitive::I32 => MonoExpr::Number(Number::I32(val.as_i32())), + Primitive::U16 => MonoExpr::Number(Number::U16(val.as_u16())), + Primitive::U32 => MonoExpr::Number(Number::U32(val.as_u32())), + Primitive::I128 => MonoExpr::Number(Number::I128(val.as_i128())), + Primitive::U128 => MonoExpr::Number(Number::U128(val.as_u128())), + Primitive::I16 => MonoExpr::Number(Number::I16(val.as_i16())), + Primitive::I8 => MonoExpr::Number(Number::I8(val.as_i8())), + Primitive::Str | Primitive::Dec | Primitive::F32 | Primitive::F64 => { + let problem = Problem::NumSpecializedToWrongType(Some(MonoType::Primitive(primitive))); + problems.push(problem); + MonoExpr::CompilerBug(problem) + } + } +} + +// /// Convert a fraction literal (e.g. `4.2`) to a monomorphized type. +// /// If somehow its type was not a fraction type, that's a compiler bug! +// fn to_frac( +// number: mono_type::Number, +// val: FracValue, +// problems: &mut impl Push, +// ) -> MonoExpr { +// match number { +// mono_type::Number::Dec => Number::Dec(val.to_dec()), +// mono_type::Number::F32 => Number::F32(val.to_f32()), +// mono_type::Number::F64 => Number::F64(val.to_f64()), +// mono_type::Number::U8 +// | mono_type::Number::I8 +// | mono_type::Number::U16 +// | mono_type::Number::I16 +// | mono_type::Number::U32 +// | mono_type::Number::I32 +// | mono_type::Number::U64 +// | mono_type::Number::I64 +// | mono_type::Number::U128 +// | mono_type::Number::I128 => { +// // TODO push problem of frac monomorphizing to int, return Malformed expr +// } +// } +// } diff --git a/crates/compiler/specialize_types/src/mono_ir.rs b/crates/compiler/specialize_types/src/mono_ir.rs new file mode 100644 index 0000000000..f86f71f2cd --- /dev/null +++ b/crates/compiler/specialize_types/src/mono_ir.rs @@ -0,0 +1,235 @@ +use crate::{ + foreign_symbol::ForeignSymbolId, mono_module::InternedStrId, mono_num::Number, + mono_struct::MonoFieldId, mono_type::MonoTypeId, specialize_type::Problem, +}; +use roc_can::expr::Recursive; +use roc_collections::soa::Slice; +use roc_module::low_level::LowLevel; +use roc_module::symbol::Symbol; +use soa::{Id, NonEmptySlice, Slice2, Slice3}; + +#[derive(Clone, Copy, Debug)] +pub struct MonoPatternId { + inner: u32, +} + +pub type IdentId = Symbol; // TODO make this an Index into an array local to this module + +#[derive(Clone, Copy, Debug)] +pub struct Def { + pub pattern: MonoPatternId, + /// Named variables in the pattern, e.g. `a` in `Ok a ->` + pub pattern_vars: Slice2, + pub expr: MonoExprId, + pub expr_type: MonoTypeId, +} + +#[derive(Debug)] +pub struct MonoExprs { + exprs: Vec, +} + +impl MonoExprs { + pub fn add(&mut self, expr: MonoExpr) -> MonoExprId { + let index = self.exprs.len() as u32; + self.exprs.push(expr); + + MonoExprId { + inner: Id::new(index), + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct MonoExprId { + inner: Id, +} + +#[derive(Clone, Copy, Debug)] +pub enum MonoExpr { + Str, + Number(Number), + List { + elem_type: MonoTypeId, + elems: Slice, + }, + Lookup(IdentId, MonoTypeId), + + /// Like Lookup, but from a module with params + ParameterizedLookup { + name: IdentId, + lookup_type: MonoTypeId, + params_name: IdentId, + params_type: MonoTypeId, + }, + + // Branching + When { + /// The actual condition of the when expression. + cond: MonoExprId, + cond_type: MonoTypeId, + /// Type of each branch (and therefore the type of the entire `when` expression) + branch_type: MonoTypeId, + /// Note: if the branches weren't exhaustive, we will have already generated a default + /// branch which crashes if it's reached. (The compiler will have reported an error already; + /// this is for if you want to run anyway.) + branches: NonEmptySlice, + }, + If { + /// Type of each branch (and therefore the type of the entire `if` expression) + branch_type: MonoTypeId, + branches: Slice<(MonoExprId, MonoExprId)>, + final_else: Option, + }, + + // Let + LetRec { + defs: Slice, + ending_expr: MonoExprId, + }, + LetNonRec { + def: Def, + ending_expr: MonoExprId, + }, + + /// This is *only* for calling functions, not for tag application. + /// The Tag variant contains any applied values inside it. + Call { + fn_type: MonoTypeId, + fn_expr: MonoExprId, + args: Slice2, + /// This is the type of the closure based only on canonical IR info, + /// not considering what other closures might later influence it. + /// Lambda set specialization may change this type later! + closure_type: MonoTypeId, + }, + RunLowLevel { + op: LowLevel, + args: Slice<(MonoTypeId, MonoExprId)>, + ret_type: MonoTypeId, + }, + ForeignCall { + foreign_symbol: ForeignSymbolId, + args: Slice<(MonoTypeId, MonoExprId)>, + ret_type: MonoTypeId, + }, + + Lambda { + fn_type: MonoTypeId, + arguments: Slice<(MonoTypeId, MonoPatternId)>, + body: MonoExprId, + captured_symbols: Slice<(IdentId, MonoTypeId)>, + recursive: Recursive, + }, + + /// Either a record literal or a tuple literal. + /// Rather than storing field names, we instead store a u16 field index. + Struct { + struct_type: MonoTypeId, + fields: Slice2, + }, + + /// The "crash" keyword. Importantly, during code gen we must mark this as "nothing happens after this" + Crash { + msg: MonoExprId, + /// The type of the `crash` expression (which will have unified to whatever's around it) + expr_type: MonoTypeId, + }, + + /// Look up exactly one field on a record, tuple, or tag payload. + /// At this point we've already unified those concepts and have + /// converted (for example) record field names to indices, and have + /// also dropped all fields that have no runtime representation (e.g. empty records). + /// + /// In a later compilation phase, these indices will be re-sorted + /// by alignment and converted to byte offsets, but we in this + /// phase we aren't concerned with alignment or sizes, just indices. + StructAccess { + record_expr: MonoExprId, + record_type: MonoTypeId, + field_type: MonoTypeId, + field_id: MonoFieldId, + }, + + RecordUpdate { + record_type: MonoTypeId, + record_name: IdentId, + updates: Slice2, + }, + + /// Same as BigTag but with u8 discriminant instead of u16 + SmallTag { + discriminant: u8, + tag_union_type: MonoTypeId, + args: Slice2, + }, + + /// Same as SmallTag but with u16 discriminant instead of u8 + BigTag { + discriminant: u16, + tag_union_type: MonoTypeId, + args: Slice2, + }, + + Expect { + condition: MonoExprId, + continuation: MonoExprId, + /// If the expectation fails, we print the values of all the named variables + /// in the final expr. These are those values. + lookups_in_cond: Slice2, + }, + Dbg { + source_location: InternedStrId, + source: InternedStrId, + msg: MonoExprId, + continuation: MonoExprId, + expr_type: MonoTypeId, + name: IdentId, + }, + CompilerBug(Problem), +} + +#[derive(Clone, Copy, Debug)] +pub struct WhenBranch { + pub patterns: Slice, + pub body: MonoExprId, + pub guard: Option, +} + +#[derive(Clone, Copy, Debug)] +pub enum MonoPattern { + Identifier(IdentId), + As(MonoPatternId, IdentId), + StrLiteral(InternedStrId), + NumberLiteral(Number), + AppliedTag { + tag_union_type: MonoTypeId, + tag_name: IdentId, + args: Slice, + }, + StructDestructure { + struct_type: MonoTypeId, + destructs: Slice3, + }, + List { + elem_type: MonoTypeId, + patterns: Slice, + + /// Where a rest pattern splits patterns before and after it, if it does at all. + /// If present, patterns at index >= the rest index appear after the rest pattern. + /// For example: + /// [ .., A, B ] -> patterns = [A, B], rest = 0 + /// [ A, .., B ] -> patterns = [A, B], rest = 1 + /// [ A, B, .. ] -> patterns = [A, B], rest = 2 + /// Optionally, the rest pattern can be named - e.g. `[ A, B, ..others ]` + opt_rest: Option<(u16, Option)>, + }, + Underscore, +} + +#[derive(Clone, Copy, Debug)] +pub enum DestructType { + Required, + Optional(MonoTypeId, MonoExprId), + Guard(MonoTypeId, MonoPatternId), +} diff --git a/crates/compiler/specialize_types/src/mono_module.rs b/crates/compiler/specialize_types/src/mono_module.rs new file mode 100644 index 0000000000..e82454e691 --- /dev/null +++ b/crates/compiler/specialize_types/src/mono_module.rs @@ -0,0 +1,24 @@ +use roc_types::subs::Subs; + +use crate::{foreign_symbol::ForeignSymbols, mono_type::MonoTypes, DebugInfo}; + +pub struct MonoModule { + mono_types: MonoTypes, + foreign_symbols: ForeignSymbols, + interned_strings: Vec, + debug_info: DebugInfo, +} + +impl MonoModule { + pub fn from_typed_can_module(subs: &Subs) -> Self { + Self { + mono_types: MonoTypes::new(), + foreign_symbols: ForeignSymbols::new(), + interned_strings: Vec::new(), + debug_info: DebugInfo, + } + } +} + +#[derive(Clone, Copy, Debug)] +pub struct InternedStrId(u32); diff --git a/crates/compiler/specialize_types/src/mono_num.rs b/crates/compiler/specialize_types/src/mono_num.rs new file mode 100644 index 0000000000..e435f91a7a --- /dev/null +++ b/crates/compiler/specialize_types/src/mono_num.rs @@ -0,0 +1,90 @@ +use roc_can::expr::IntValue; + +#[derive(Clone, Copy, Debug)] +pub enum Number { + I8(i8), + U8(u8), + I16(i16), + U16(u16), + I32(i32), + U32(u32), + I64(i64), + U64(u64), + I128(i128), + U128(u128), + F32(f32), + F64(f64), + Dec(i128), +} + +impl Number { + fn u8(val: IntValue) -> Self { + match val { + IntValue::I128(i128) => Self::U8(i128::from_ne_bytes(i128) as u8), + IntValue::U128(u128) => Self::U8(u128::from_ne_bytes(u128) as u8), + } + } + + fn i8(val: IntValue) -> Self { + match val { + IntValue::I128(i128) => Self::I8(i128::from_ne_bytes(i128) as i8), + IntValue::U128(u128) => Self::I8(u128::from_ne_bytes(u128) as i8), + } + } + + fn u16(val: IntValue) -> Self { + match val { + IntValue::I128(i128) => Self::U16(i128::from_ne_bytes(i128) as u16), + IntValue::U128(u128) => Self::U16(u128::from_ne_bytes(u128) as u16), + } + } + + fn i16(val: IntValue) -> Self { + match val { + IntValue::I128(i128) => Self::I16(i128::from_ne_bytes(i128) as i16), + IntValue::U128(u128) => Self::I16(u128::from_ne_bytes(u128) as i16), + } + } + + fn u32(val: IntValue) -> Self { + match val { + IntValue::I128(i128) => Self::U32(i128::from_ne_bytes(i128) as u32), + IntValue::U128(u128) => Self::U32(u128::from_ne_bytes(u128) as u32), + } + } + + fn i32(val: IntValue) -> Self { + match val { + IntValue::I128(i128) => Self::I32(i128::from_ne_bytes(i128) as i32), + IntValue::U128(u128) => Self::I32(u128::from_ne_bytes(u128) as i32), + } + } + + fn u64(val: IntValue) -> Self { + match val { + IntValue::I128(i128) => Self::U64(i128::from_ne_bytes(i128) as u64), + IntValue::U128(u128) => Self::U64(u128::from_ne_bytes(u128) as u64), + } + } + + fn i64(val: IntValue) -> Self { + match val { + IntValue::I128(i128) => Self::I64(i128::from_ne_bytes(i128) as i64), + IntValue::U128(u128) => Self::I64(u128::from_ne_bytes(u128) as i64), + } + } + + fn u128(val: IntValue) -> Self { + match val { + IntValue::I128(i128) => Self::U128(i128::from_ne_bytes(i128) as u128), + IntValue::U128(u128) => Self::U128(u128::from_ne_bytes(u128)), + } + } + + fn i128(val: IntValue) -> Self { + match val { + IntValue::I128(i128) => Self::I128(i128::from_ne_bytes(i128)), + IntValue::U128(u128) => Self::I128(u128::from_ne_bytes(u128) as i128), + } + } +} diff --git a/crates/compiler/specialize_types/src/mono_struct.rs b/crates/compiler/specialize_types/src/mono_struct.rs new file mode 100644 index 0000000000..5a6b59b56a --- /dev/null +++ b/crates/compiler/specialize_types/src/mono_struct.rs @@ -0,0 +1,13 @@ +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct MonoFieldId { + inner: u16, +} +impl MonoFieldId { + pub(crate) fn new(index: u16) -> Self { + Self { inner: index } + } + + pub fn as_index(self) -> usize { + self.inner as usize + } +} diff --git a/crates/compiler/specialize_types/src/mono_type.rs b/crates/compiler/specialize_types/src/mono_type.rs index 410fdeae28..e7706109e7 100644 --- a/crates/compiler/specialize_types/src/mono_type.rs +++ b/crates/compiler/specialize_types/src/mono_type.rs @@ -1,37 +1,139 @@ -use roc_module::symbol::Symbol; -use soa::{Id, Slice, Slice2}; +use core::num::NonZeroU16; +use soa::{Index, NonEmptySlice, Slice}; -/// For now, a heap-allocated string. In the future, this will be something else. -struct RecordFieldName(String); +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct MonoTypeId { + inner: Index, +} + +impl MonoTypeId { + fn new(inner: Index) -> Self { + Self { inner } + } +} pub struct MonoTypes { - // TODO + entries: Vec, + ids: Vec, + slices: Vec<(NonZeroU16, MonoTypeId)>, // TODO make this a Vec2 } impl MonoTypes { - pub fn add_apply( - &mut self, - symbol: Symbol, - args: impl IntoIterator>, - ) -> Id { - todo!("extend"); + pub fn new() -> Self { + Self { + entries: Vec::new(), + ids: Vec::new(), + slices: Vec::new(), + } + } + pub fn get(&self, id: MonoTypeId) -> &MonoType { + todo!("builtins are stored inline"); + // Overall strategy: + // - Look at the three high bits to figure out which of the 8 MonoTypes we're dealing with + // - The non-parameterized builtins have 000 as their high bits, and the whole MonoTypeId can be cast to a Primitive. + // - The parameterized builtins don't need to store a length, just an index. We store that index inline. + // - The non-builtins all store a length and an index. We store the index inline, and the length out of band. + // - Dictionaries store their second param adjacent to the first. + // - This means we use 2 bits for discriminant and another 2 bits for which parameterized type it is + // - This means we get 29-bit indices, so a maximum of ~500M MonoTypes per module. Should be plenty. + // - In the future, we can promote common collection types (e.g. List Str, List U8) to Primitives. + } + + pub(crate) fn add_primitive(&mut self, primitive: Primitive) -> MonoTypeId { + todo!("if it's one of the hardcoded ones, find the associated MonoTypeId; otherwise, store it etc."); } pub(crate) fn add_function( - &self, - new_closure: Id, - new_ret: Id, - new_args: impl IntoIterator>, - ) -> Id { - todo!("extend") + &mut self, + ret: Option, + args: impl IntoIterator, + ) -> MonoTypeId { + let mono_type = match ret { + Some(ret) => { + let ret_then_args = { + let start = self.ids.len(); + self.ids.push(ret); + self.ids.extend(args); + // Safety: we definitely have at least 2 elements in here, even if the iterator is empty. + let length = + unsafe { NonZeroU16::new_unchecked((self.ids.len() - start) as u16) }; + + NonEmptySlice::new(start as u32, length) + }; + + MonoType::Func { ret_then_args } + } + None => { + let args = { + let start = self.ids.len(); + self.ids.extend(args); + let length = (self.ids.len() - start) as u16; + + Slice::new(start as u32, length) + }; + + MonoType::VoidFunc { args } + } + }; + + let index = self.entries.len(); + self.entries.push(mono_type); + MonoTypeId::new(Index::new(index as u32)) } - /// We only use the field labels for sorting later; we don't care what format they're in otherwise. - pub(crate) fn add_record( - &self, - fields: impl Iterator)>, - ) -> Id { - todo!("extend") + /// This should only be given iterators with at least 2 elements in them. + /// We receive the fields in sorted order (e.g. by record field name or by tuple index). + /// A later compiler phase will stable-sort them by alignment (we don't deal with alignment here), + /// and that phase will also sort its DebugInfo struct fields in the same way. + pub(crate) unsafe fn add_struct_unchecked( + &mut self, + fields: impl Iterator, + ) -> MonoTypeId { + let start = self.ids.len(); + self.extend_ids(fields); + let len = self.ids.len() - start; + let non_empty_slice = + // Safety: This definitely has at least 2 elements in it, because we just added them. + unsafe { NonEmptySlice::new_unchecked(start as u32, len as u16)}; + let index = self.entries.len(); + self.entries.push(MonoType::Struct(non_empty_slice)); + MonoTypeId::new(Index::new(index as u32)) + } + + /// We receive the payloads in sorted order (sorted by tag name). + pub(crate) fn add_tag_union( + &mut self, + first_payload: MonoTypeId, + second_payload: MonoTypeId, + other_payloads: impl Iterator, + ) -> MonoTypeId { + let start = self.ids.len(); + self.ids.push(first_payload); + self.ids.push(second_payload); + self.extend_ids(other_payloads); + let len = self.ids.len() - start; + let non_empty_slice = + // Safety: This definiely has at least 2 elements in it, because we just added them. + unsafe { NonEmptySlice::new_unchecked(start as u32, len as u16)}; + let index = self.entries.len(); + self.entries.push(MonoType::Struct(non_empty_slice)); + MonoTypeId::new(Index::new(index as u32)) + } + + fn extend_ids(&mut self, iter: impl Iterator) -> Slice { + let start = self.ids.len(); + self.ids.extend(iter); + let length = self.ids.len() - start; + + Slice::new(start as u32, length as u16) + } + + pub(crate) fn add(&mut self, entry: MonoType) -> MonoTypeId { + let id = Index::new(self.entries.len() as u32); + + self.entries.push(entry); + + MonoTypeId { inner: id } } } @@ -42,39 +144,68 @@ impl MonoTypes { // 3. Store all the MonoType variant slice lengths in a separate array (u8 should be plenty) // 4. Store all the MonoType start indices in a separate array (u32 should be plenty) -#[derive(Clone, Copy, Debug)] -pub enum MonoType { - Apply { - // TODO: the symbol can be stored adjacency style immediately before - // the first type arg. (This means the args slice must always - // have a valid start index even if there are no args.) This will - // work regardless of whether the Symbl is 32 or 64 bits, because - // its alignment will never be smaller than the alignment of an Index. - symbol: Symbol, - args: Slice>, - }, - - Func { - /// The first element in this slice is the capture type, - /// followed by the return type, and then the rest are the - /// function's arg types. These are all stored in one slice - /// because having them adjacent is best for cache locality, - /// and storing separate Ids for each would make this variant bigger. - capture_ret_args: Slice>, - }, - - /// Slice of (field_name, field_type) pairs. - Record( - // TODO: since both tuple elements are the same size and alignment, - // we can store them as all of the one followed by all of the other, - // and therefore store only length and start index. - Slice2, Id>, - ), - - /// Each element in the slice represents a different tuple element. - /// The index in the slice corresponds to the index in the tuple. - Tuple(Slice>), - - /// Slice of (tag_name, tag_payload_types) pairs - TagUnion(Slice2, Slice>>), +/// Primitive means "Builtin type that has no type parameters" (so, numbers, Str, and Unit) +/// +/// In the future, we may promote common builtin types to Primitives, e.g. List U8, List Str, etc. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Primitive { + Str, + Dec, + F32, + F64, + U8, + I8, + U16, + I16, + U32, + I32, + U64, + I64, + U128, + I128, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum MonoType { + Primitive(Primitive), + Box(MonoTypeId), + List(MonoTypeId), + /// Records, tuples, and tag union payloads all end up here. (Empty ones are handled separate.) + /// + /// Slice of field types, ordered alphabetically by field name (or by tuple elem index). + /// The strings for the field names (or tuple indices) are stored out of band in DebugInfo, + /// which references this MonoTypeId. A later compiler phase will sort these by alignment + /// (this phase is not aware of alignment), and will sort the DebugInfo structs accordingly. + Struct(NonEmptySlice), + + /// Slice of payloads, where each payload is a struct or Unit. (Empty tag unions become Unit.) + /// + /// These have already been sorted alphabetically by tag name, and the tag name strings + /// have already been recorded out of band in DebugInfo. + TagUnion(NonEmptySlice), + + /// A function that has a return value and 0 or more arguments. + /// To avoid wasting memory, we store the return value first in the nonempty slice, + /// and then the arguments after it. + Func { + ret_then_args: NonEmptySlice, + }, + + /// A function that does not have a return value (e.g. the return value was {}, which + /// got eliminated), and which has 0 or more arguments. + /// This has to be its own variant because the other function variant uses one slice to + /// store the return value followed by the arguments. Without this separate variant, + /// the other one couldn't distinguish between a function with a return value and 0 arguments, + /// and a function with 1 argument but no return value. + VoidFunc { + args: Slice, + }, + // This last slot is tentatively reserved for Dict, because in the past we've discussed wanting to + // implement Dict in Zig (for performance) instead of on top of List, like it is as of this writing. + // + // Assuming we do that, Set would still be implemented as a Dict with a unit type for the value, + // so we would only need one variant for both. + // + // The second type param would be stored adjacent to the first, so we only need to store one index. + // Dict(MonoTypeId), } diff --git a/crates/compiler/specialize_types/src/specialize_expr.rs b/crates/compiler/specialize_types/src/specialize_expr.rs index f3abe0d620..217fa97e3b 100644 --- a/crates/compiler/specialize_types/src/specialize_expr.rs +++ b/crates/compiler/specialize_types/src/specialize_expr.rs @@ -1,8 +1,8 @@ use crate::expr::{self, Declarations, Expr, FunctionDef}; use crate::specialize_type::{MonoCache, Problem}; -use crate::subs::{Subs, Variable}; -use crate::symbol::Symbol; use roc_collections::VecMap; +use roc_module::symbol::Symbol; +use roc_types::subs::{Subs, Variable}; struct Context { symbols: Symbol, diff --git a/crates/compiler/specialize_types/src/specialize_type.rs b/crates/compiler/specialize_types/src/specialize_type.rs index 03248f2b77..7503a51ba3 100644 --- a/crates/compiler/specialize_types/src/specialize_type.rs +++ b/crates/compiler/specialize_types/src/specialize_type.rs @@ -5,31 +5,42 @@ /// This only operates at the type level. It does not create new function implementations (for example). use crate::{ debug_info::DebugInfo, - mono_type::{self, MonoType, MonoTypes}, + mono_type::{MonoTypeId, MonoTypes}, + MonoFieldId, MonoType, }; -use core::iter; use roc_collections::{Push, VecMap}; use roc_module::ident::{Lowercase, TagName}; -use roc_types::{ - subs::{ - Content, FlatType, RecordFields, SortedFieldIterator, Subs, TagExt, TupleElems, - UnionLabels, UnionTags, Variable, - }, - types::RecordField, +use roc_types::subs::{ + Content, FlatType, RecordFields, Subs, TagExt, TupleElems, UnionLabels, UnionTags, Variable, }; -use soa::Id; -#[derive(Debug, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Problem { // Compiler bugs; these should never happen! TagUnionExtWasNotTagUnion, RecordExtWasNotRecord, TupleExtWasNotTuple, + /// This can be either an integer specializing to a fractional number type (or vice versa), + /// or the type paramter specializing to a non-numeric type (e.g. Num Str), which should + /// have been caught during type-checking and changed to an Error type. + NumSpecializedToWrongType( + Option, // `None` means it specialized to Unit + ), } +/// For MonoTypes that are records, store their field indices. +pub type RecordFieldIds = VecMap>; + +/// For MonoTypes that are tuples, store their element indices. +/// (These are not necessarily the same as their position in the monomorphized tuple, +/// because we may have deleted some zero-sized types in the middle - yet expressions +/// will still refer to e.g. `tuple.1`, so we still need to know which element `.1` +/// referred to originally before we deleted things. +pub type TupleElemIds = VecMap>; + /// Variables that have already been monomorphized. pub struct MonoCache { - inner: VecMap>, + inner: VecMap, } impl MonoCache { @@ -39,161 +50,199 @@ impl MonoCache { } } + /// Returns None if it monomorphizes to a type that should be eliminated + /// (e.g. a zero-sized type like empty record, empty tuple, a record of just those, etc.) pub fn monomorphize_var( &mut self, - subs: &mut Subs, + subs: &Subs, mono_types: &mut MonoTypes, + field_indices: &mut RecordFieldIds, + elem_indices: &mut TupleElemIds, problems: &mut impl Push, debug_info: &mut Option, var: Variable, - ) -> Id { - lower_var( - Env { - cache: self, - subs, - mono_types, - problems, - debug_info, - }, - var, - ) + ) -> Option { + let mut env = Env { + cache: self, + mono_types, + field_ids: field_indices, + elem_ids: elem_indices, + problems, + debug_info, + }; + + lower_var(&mut env, subs, var) } } -struct Env<'c, 'd, 'm, 'p, 's, P: Push> { +struct Env<'c, 'd, 'e, 'f, 'm, 'p, P: Push> { cache: &'c mut MonoCache, - subs: &'s mut Subs, mono_types: &'m mut MonoTypes, + field_ids: &'f mut RecordFieldIds, + elem_ids: &'e mut TupleElemIds, problems: &'p mut P, debug_info: &'d mut Option, } -fn lower_var>(env: Env<'_, '_, '_, '_, '_, P>, var: Variable) -> Id { - let cache = env.cache; - let subs = env.subs; +fn lower_var>( + env: &mut Env<'_, '_, '_, '_, '_, '_, P>, + subs: &Subs, + var: Variable, +) -> Option { let root_var = subs.get_root_key_without_compacting(var); - // TODO: we could replace this cache by having Subs store a Content::Monomorphic(Id) + // TODO: we could replace this cache by having Subs store a Content::Monomorphic(MonoTypeId) // and then overwrite it rather than having a separate cache. That memory is already in cache // for sure, and the lookups should be faster because they're O(1) but don't require hashing. - if let Some(mono_id) = cache.inner.get(&root_var) { - return *mono_id; + if let Some(mono_id) = env.cache.inner.get(&root_var) { + return Some(*mono_id); } - let mono_types = env.mono_types; - let problems = env.problems; - // Convert the Content to a MonoType, often by passing an iterator. None of these iterators introduce allocations. - let mono_id = match *subs.get_content_without_compacting(root_var) { - Content::Structure(flat_type) => match flat_type { - FlatType::Apply(symbol, args) => { - let new_args = args - .into_iter() - .map(|var_index| lower_var(env, subs[var_index])); + let opt_mono_id = match *subs.get_content_without_compacting(root_var) { + Content::Structure(flat_type) => match flat_type { + FlatType::Apply(symbol, args) => { + let new_args = args + .into_iter() + .flat_map(|var_index| lower_var(env, subs, subs[var_index])); - mono_types.add_apply(symbol, new_args) - } - FlatType::Func(args, closure, ret) => { - let new_args = args - .into_iter() - .map(|var_index| lower_var(env, subs[var_index])); - - let new_closure = lower_var(env, closure); - let new_ret = lower_var(env, ret); - - mono_types.add_function(new_closure, new_ret, new_args) - } - FlatType::Record(fields, ext) => { - let todo = (); // TODO populate debuginfo (if it's Some, meaning we want it) - mono_types.add_record( - LowerRecordIterator { - cache, - subs, - mono_types, - problems, - fields: fields.sorted_iterator(subs, ext), - ext, - }) - } - FlatType::Tuple(elems, ext) => { - let todo = (); // TODO populate debuginfo (if it's Some, meaning we want it) - mono_types.add_tuple( - LowerTupleIterator { - cache, - subs, - mono_types, - problems, - elems: elems.sorted_iterator(subs, ext), - ext, - }) - } - FlatType::TagUnion(tags, ext) => { - let mut tags = resolve_tag_ext(subs, problems, *tags, *ext); - - // Now lower all the tags we gathered. Do this in a separate pass to avoid borrow errors on Subs. - lower_vars(tags.iter_mut().flat_map(|(_, vars)| vars.iter_mut()), cache, subs, problems); - - Content::Structure(FlatType::TagUnion( - UnionTags::insert_into_subs(subs, tags), - TagExt::Any(Variable::EMPTY_TAG_UNION), - )) - } - FlatType::FunctionOrTagUnion(tag_names, _symbols, ext) => { - // If this is still a FunctionOrTagUnion, turn it into a TagUnion. - - // First, resolve the ext var. - let mut tags = resolve_tag_ext(subs, problems, UnionTags::default(), *ext); - - // Now lower all the tags we gathered from the ext var. - // (Do this in a separate pass to avoid borrow errors on Subs.) - lower_vars(tags.iter_mut().flat_map(|(_, vars)| vars.iter_mut()), cache, subs, problems); - - // Then, add the tag names with no payloads. (There are no variables to lower here.) - for index in tag_names.into_iter() { - tags.push(((subs[index]).clone(), Vec::new())); - } - - Content::Structure(FlatType::TagUnion( - UnionTags::insert_into_subs(subs, tags), - TagExt::Any(Variable::EMPTY_TAG_UNION), - )) - } - FlatType::RecursiveTagUnion(rec, tags, ext) => { - let mut tags = resolve_tag_ext(subs, problems, *tags, *ext); - - // Now lower all the tags we gathered. Do this in a separate pass to avoid borrow errors on Subs. - lower_vars(tags.iter_mut().flat_map(|(_, vars)| vars.iter_mut()), cache, subs, problems); - - Content::Structure(FlatType::RecursiveTagUnion( - lower_var(cache, subs, problems, *rec), - UnionTags::insert_into_subs(subs, tags), - TagExt::Any(Variable::EMPTY_TAG_UNION), - )) - } - FlatType::EmptyRecord => Content::Structure(FlatType::EmptyRecord), - FlatType::EmptyTuple => Content::Structure(FlatType::EmptyTuple), - FlatType::EmptyTagUnion => Content::Structure(FlatType::EmptyTagUnion), - }, - Content::RangedNumber(_) // RangedNumber goes in Num's type parameter slot, so monomorphize it to [] - | Content::FlexVar(_) - | Content::RigidVar(_) - | Content::FlexAbleVar(_, _) - | Content::RigidAbleVar(_, _) - | Content::RecursionVar { .. } => Content::Structure(FlatType::EmptyTagUnion), - Content::LambdaSet(lambda_set) => Content::LambdaSet(*lambda_set), - Content::ErasedLambda => Content::ErasedLambda, - Content::Alias(symbol, args, real, kind) => { - let todo = (); // TODO we should unwrap this, but doing that probably means changing this from root_var to other stuff. - let new_real = lower_var(cache, subs, problems, *real); - Content::Alias(*symbol, *args, new_real, *kind) + todo!("maybe instead of making new_args, branch and then call lower_var to create Primitive, List, Box, etc."); } - Content::Error => Content::Error, - }; + // FlatType::Func(args, _capture, ret) => { + // let mono_args = args + // .into_iter() + // .flat_map(|var_index| lower_var(env, subs[var_index])); - // This var is now known to be monomorphic, so we don't repeat this work again later. - cache.inner.insert(root_var, mono_id); + // let todo = (); // TODO populate debuginfo (if it's Some, meaning we want it) + // let func = lower_var(env, ret); + // Some(env.mono_types.add_function(func, mono_args)) + // } + _ => { + todo!(); + } /* + FlatType::Record(fields, ext) => { + let mut labeled_mono_ids = lower_record(env, fields, ext); - mono_id + // Handle the special cases of 0 fields and 1 field. + match labeled_mono_ids.first() { + Some((label, first_field_id)) => { + if labeled_mono_ids.len() == 1 { + // If we ended up with a single field, return it unwrapped. + let todo = (); // TODO populate debuginfo using the label (if it's Some, meaning we want it) + let todo = (); // To preserve debuginfo, we need to actually clone this mono_id and not just return the same one. + return Some(*first_field_id); + } + } + None => { + // If we ended up with an empty record, + // after removing other empty things, return None. + return None; + } + } + + // Now we know we have at least 2 fields, so sort them by field name. + // This can be unstable sort because all field names are known to be unique, + // so sorting unstable won't be observable (and is faster than stable). + labeled_mono_ids.sort_unstable_by(|(label1, _), (label2, _)| label1.cmp(label2)); + + let todo = (); // TODO populate debuginfo (if it's Some, meaning we want it) + + // Safety: we already verified that this has at least 2 elements, and + // we would have early returned before this point if we had fewer than 2. + let mono_id = unsafe { + mono_types.add_struct_unchecked(labeled_mono_ids.iter().map(|(_label, mono_id)| *mono_id)) + }; + + let labeled_indices = VecMap::from_iter(labeled_mono_ids.into_iter().enumerate().map(|(index, (label, _mono_id))| (label, MonoFieldId::new(index as u16)))); + + env.field_ids.insert(mono_id, labeled_indices); + + Some(mono_id) + } + FlatType::Tuple(elems, ext) => { + let indexed_mono_ids = lower_tuple(env, elems, ext); + + // This can be unstable sort because all indices are known to be unique, + // so sorting unstable won't be observable (and is faster than stable). + indexed_mono_ids.sort_unstable_by(|(index1, _), (index2, _)| index1.cmp(index2)); + + let todo = (); // TODO populate debuginfo (if it's Some, meaning we want it) + mono_types.add_struct(indexed_mono_ids.iter().map(|(_, mono_id)| *mono_id)) + } + FlatType::TagUnion(tags, ext) => { + let tagged_payload_ids = lower_tag_union(env, tags, ext); + + // This can be unstable sort because all tag names are known to be unique, + // so sorting unstable won't be observable (and is faster than stable). + tagged_payload_ids.sort_unstable_by(|(tag1, _), (tag2, _)| tag1.cmp(tag2)); + + let todo = (); // TODO populate debuginfo (if it's Some, meaning we want it) + mono_types.add_tag_union(tagged_payload_ids.iter().map(|(_, mono_id)| *mono_id)) + } + FlatType::FunctionOrTagUnion(tag_names, _symbols, ext) => { + // If this is still a FunctionOrTagUnion, turn it into a TagUnion. + + // First, resolve the ext var. + let mut tags = resolve_tag_ext(subs, problems, UnionTags::default(), *ext); + + // Now lower all the tags we gathered from the ext var. + // (Do this in a separate pass to avoid borrow errors on Subs.) + lower_vars(tags.iter_mut().flat_map(|(_, vars)| vars.iter_mut()), cache, subs, problems); + + // Then, add the tag names with no payloads. (There are no variables to lower here.) + for index in tag_names.into_iter() { + tags.push(((subs[index]).clone(), Vec::new())); + } + + Content::Structure(FlatType::TagUnion( + UnionTags::insert_into_subs(subs, tags), + TagExt::Any(Variable::EMPTY_TAG_UNION), + )) + } + FlatType::RecursiveTagUnion(rec, tags, ext) => { + let mut tags = resolve_tag_ext(subs, problems, *tags, *ext); + + // Now lower all the tags we gathered. Do this in a separate pass to avoid borrow errors on Subs. + lower_vars(tags.iter_mut().flat_map(|(_, vars)| vars.iter_mut()), cache, subs, problems); + + Content::Structure(FlatType::RecursiveTagUnion( + lower_var(cache, subs, problems, *rec), + UnionTags::insert_into_subs(subs, tags), + TagExt::Any(Variable::EMPTY_TAG_UNION), + )) + } + FlatType::EmptyRecord| + FlatType::EmptyTuple | + FlatType::EmptyTagUnion => None, + }, + Content::RangedNumber(_) // RangedNumber goes in Num's type parameter slot, so monomorphize it to [] + | Content::FlexVar(_) + | Content::RigidVar(_) + | Content::FlexAbleVar(_, _) + | Content::RigidAbleVar(_, _) + | Content::RecursionVar { .. } => Content::Structure(FlatType::EmptyTagUnion), + Content::LambdaSet(lambda_set) => Content::LambdaSet(lambda_set), + Content::ErasedLambda => Content::ErasedLambda, + Content::Alias(symbol, args, real, kind) => { + let todo = (); // TODO we should unwrap this, but doing that probably means changing this from root_var to other stuff. + let new_real = lower_var(cache, subs, problems, *real); + Content::Alias(*symbol, *args, new_real, *kind) + } + Content::Error => Content::Error, + */ + }, + _ => { + todo!(); + } + }; + + if let Some(mono_id) = opt_mono_id { + // This var is now known to be monomorphic, so we don't repeat this work again later. + // (We don't insert entries for Unit values.) + env.cache.inner.insert(root_var, mono_id); + } + + opt_mono_id } fn resolve_tag_ext( @@ -244,119 +293,45 @@ fn resolve_tag_ext( all_tags } -struct LowerRecordIterator<'c, 's, 'm, 'p, 'r, P: Push> { - cache: &'c mut MonoCache, - subs: &'s mut Subs, - mono_types: &'m mut MonoTypes, - problems: &'p mut P, - fields: SortedFieldIterator<'r>, // TODO impl Iterator - ext: Variable, -} +fn lower_record>( + env: &mut Env<'_, '_, '_, '_, '_, '_, P>, + subs: &Subs, + mut fields: RecordFields, + mut ext: Variable, +) -> Vec<(Lowercase, Option)> { + let mut labeled_mono_ids = Vec::with_capacity(fields.len()); -impl<'c, 's, 'm, 'p, 'r, P> Iterator for LowerRecordIterator<'c, 's, 'm, 'p, 'r, P> -where - P: Push, -{ - type Item = (Lowercase, Id); + // Collapse (recursively) all the fields in ext into a flat list of fields. + loop { + // Add all the current fields to the answer. + labeled_mono_ids.extend( + fields + .sorted_iterator(subs, ext) + .map(|(label, field)| (label, lower_var(env, subs, *field.as_inner()))), + ); - fn next(&mut self) -> Option { - // Collapse (recursively) all the fields in ext into a flat list of fields. - loop { - if let Some((label, field)) = self.fields.next() { - let var = match field { - RecordField::Demanded(var) => var, - RecordField::Required(var) => var, - RecordField::Optional(var) => var, - RecordField::RigidRequired(var) => var, - RecordField::RigidOptional(var) => var, - }; - - return Some(( - label, - lower_var(self.cache, self.subs, self.mono_types, self.problems, var), - )); + // If the ext record is nonempty, set its fields to be the next ones we handle, and loop back. + match subs.get_content_without_compacting(ext) { + Content::Structure(FlatType::Record(new_fields, new_ext)) => { + // Update fields and ext and loop back again to process them. + fields = *new_fields; + ext = *new_ext; } + Content::Structure(FlatType::EmptyRecord) + | Content::FlexVar(_) + | Content::FlexAbleVar(_, _) => return labeled_mono_ids, + Content::Alias(_, _, real, _) => { + // Follow the alias and process it on the next iteration of the loop. + ext = *real; - match self.subs.get_content_without_compacting(self.ext) { - Content::Structure(FlatType::Record(new_fields, new_ext)) => { - // Update fields and ext and loop back again to process them. - self.fields = new_fields.sorted_iterator(self.subs, self.ext); - self.ext = *new_ext; - } - Content::Structure(FlatType::EmptyRecord) => return None, - Content::FlexVar(_) | Content::FlexAbleVar(_, _) => return None, - Content::Alias(_, _, real, _) => { - // Follow the alias and process it on the next iteration of the loop. - self.ext = *real; - - // We just processed these fields, so don't process them again! - self.fields = Box::new(iter::empty()); - } - _ => { - // This should never happen! If it does, record a Problem and break. - self.problems.push(Problem::RecordExtWasNotRecord); - - return None; - } + // We just processed these fields, so don't process them again! + fields = RecordFields::empty(); } - } - } -} + _ => { + // This should never happen! If it does, record a Problem and early return. + env.problems.push(Problem::RecordExtWasNotRecord); -struct LowerTupleIterator<'c, 's, 'm, 'p, 'r, P: Push> { - cache: &'c mut MonoCache, - subs: &'s mut Subs, - mono_types: &'m mut MonoTypes, - problems: &'p mut P, - fields: SortedElemsIterator<'r>, - ext: Variable, -} - -impl<'c, 's, 'm, 'p, 'r, P> Iterator for LowerRecordIterator<'c, 's, 'm, 'p, 'r, P> -where - P: Push, -{ - type Item = (Lowercase, Id); - - fn next(&mut self) -> Option { - // Collapse (recursively) all the fields in ext into a flat list of fields. - loop { - if let Some((label, field)) = self.fields.next() { - let var = match field { - RecordField::Demanded(var) => var, - RecordField::Required(var) => var, - RecordField::Optional(var) => var, - RecordField::RigidRequired(var) => var, - RecordField::RigidOptional(var) => var, - }; - - return Some(( - label, - lower_var(self.cache, self.subs, self.mono_types, self.problems, var), - )); - } - - match self.subs.get_content_without_compacting(self.ext) { - Content::Structure(FlatType::Record(new_fields, new_ext)) => { - // Update fields and ext and loop back again to process them. - self.fields = new_fields.sorted_iterator(self.subs, self.ext); - self.ext = *new_ext; - } - Content::Structure(FlatType::EmptyRecord) => return None, - Content::FlexVar(_) | Content::FlexAbleVar(_, _) => return None, - Content::Alias(_, _, real, _) => { - // Follow the alias and process it on the next iteration of the loop. - self.ext = *real; - - // We just processed these fields, so don't process them again! - self.fields = Box::new(iter::empty()); - } - _ => { - // This should never happen! If it does, record a Problem and break. - self.problems.push(Problem::RecordExtWasNotRecord); - - return None; - } + return labeled_mono_ids; } } } @@ -404,15 +379,16 @@ fn resolve_tuple_ext( all_elems } -/// Lower the given vars in-place. -fn lower_vars<'a>( - vars: impl Iterator, - cache: &mut MonoCache, - subs: &mut Subs, - mono_types: &mut MonoTypes, - problems: &mut impl Push, -) { - for var in vars { - *var = lower_var(env, *var); - } -} +// /// Lower the given vars in-place. +// fn lower_vars<'a>( +// vars: impl Iterator, +// cache: &mut MonoCache, +// subs: &mut Subs, +// mono_types: &mut MonoTypes, +// problems: &mut impl Push, +// ) { +// for var in vars { +// if let Some(var) = lower_var(env, *var) // hmm not sure if this is still a good idea as a helper function +// *var = ; +// } +// } diff --git a/crates/compiler/specialize_types/tests/test_specialize_expr.rs b/crates/compiler/specialize_types/tests/test_specialize_expr.rs new file mode 100644 index 0000000000..4e1941cc88 --- /dev/null +++ b/crates/compiler/specialize_types/tests/test_specialize_expr.rs @@ -0,0 +1,4889 @@ +#[macro_use] +extern crate pretty_assertions; +#[macro_use] +extern crate indoc; + +extern crate bumpalo; + +#[cfg(test)] +mod specialize_types { + use roc_load::LoadedModule; + use roc_solve::FunctionKind; + use roc_specialize_types::{ + DebugInfo, MonoCache, MonoExpr, MonoTypes, RecordFieldIds, TupleElemIds, + }; + use test_solve_helpers::{format_problems, run_load_and_infer}; + + use roc_types::pretty_print::{name_and_print_var, DebugPrint}; + + // HELPERS + + fn infer_eq_help(src: &str) -> Result<(String, String, MonoExpr, String), std::io::Error> { + let ( + LoadedModule { + module_id: home, + mut can_problems, + mut type_problems, + interns, + mut solved, + mut exposed_to_host, + abilities_store, + .. + }, + src, + ) = run_load_and_infer(src, [], false, FunctionKind::LambdaSet)?; + + let mut can_problems = can_problems.remove(&home).unwrap_or_default(); + let type_problems = type_problems.remove(&home).unwrap_or_default(); + + // Disregard UnusedDef problems, because those are unavoidable when + // returning a function from the test expression. + can_problems.retain(|prob| { + !matches!( + prob, + roc_problem::can::Problem::UnusedDef(_, _) + | roc_problem::can::Problem::UnusedBranchDef(..) + ) + }); + + let (can_problems, type_problems) = + format_problems(&src, home, &interns, can_problems, type_problems); + + let subs = solved.inner_mut(); + + exposed_to_host.retain(|s, _| !abilities_store.is_specialization_name(*s)); + + debug_assert!(exposed_to_host.len() == 1, "{exposed_to_host:?}"); + let (symbol, variable) = exposed_to_host.into_iter().next().unwrap(); + let mut mono_cache = MonoCache::from_subs(subs); + let mut mono_types = MonoTypes::new(); + let debug_info = DebugInfo::new(); + let mut record_field_ids = RecordFieldIds::new(); + let mut tuple_elem_ids = TupleElemIds::new(); + let mut problems = Vec::new(); + + mono_cache.monomorphize_var( + subs, + &mut mono_types, + &mut record_field_ids, + &mut tuple_elem_ids, + &mut problems, + &mut Some(debug_info), + variable, + ); + + assert_eq!(problems, Vec::new()); + + let type_str = name_and_print_var(variable, subs, home, &interns, DebugPrint::NOTHING); + + Ok((type_problems, can_problems, mono_expr, type_str)) + } + + fn specializes_to(src: &str, expected_expr: MonoExpr, expected_type_str: &str) { + let (_, can_problems, actual_expr, actual_type_str) = infer_eq_help(src).unwrap(); + + assert!( + can_problems.is_empty(), + "Canonicalization problems: {can_problems}" + ); + + assert_eq!( + (expected_expr, expected_type_str), + (actual_expr, &actual_type_str) + ); + } + + fn infer_eq_without_problem(src: &str, expected: &str) { + let (type_problems, can_problems, actual) = infer_eq_help(src).unwrap(); + + assert!( + can_problems.is_empty(), + "Canonicalization problems: {can_problems}" + ); + + if !type_problems.is_empty() { + // fail with an assert, but print the problems normally so rust doesn't try to diff + // an empty vec with the problems. + panic!("expected:\n{expected:?}\ninferred:\n{actual:?}\nproblems:\n{type_problems}",); + } + assert_eq!(actual, expected.to_string()); + } + + #[test] + fn str_literal_solo() { + specializes_to("\"test\"", MonoExpr::Str); + } + + // #[test] + // fn int_literal_solo() { + // specializes_to("5", "Num []"); + // } + + // #[test] + // fn frac_literal_solo() { + // specializes_to("0.5", "Frac []"); + // } + + // #[test] + // fn dec_literal() { + // specializes_to( + // indoc!( + // r" + // val : Dec + // val = 1.2 + + // val + // " + // ), + // "Dec", + // ); + // } + + // #[test] + // fn str_starts_with() { + // specializes_to("Str.startsWith", "Str, Str -> Bool"); + // } + + // #[test] + // fn str_from_int() { + // infer_eq_without_problem("Num.toStr", "Num [] -> Str"); + // } + + // #[test] + // fn str_from_utf8() { + // infer_eq_without_problem( + // "Str.fromUtf8", + // "List U8 -> Result Str [BadUtf8 Utf8ByteProblem U64]", + // ); + // } + + // #[test] + // fn list_concat_utf8() { + // infer_eq_without_problem("List.concatUtf8", "List U8, Str -> List U8") + // } + + // // LIST + + // #[test] + // fn empty_list() { + // specializes_to("[]", "List []"); + // } + + // #[test] + // fn list_of_lists() { + // specializes_to("[[]]", "List (List [])"); + // } + + // #[test] + // fn triple_nested_list() { + // specializes_to("[[[]]]", "List (List (List []))"); + // } + + // #[test] + // fn nested_empty_list() { + // specializes_to("[[], [[]]]", "List (List (List []))"); + // } + + // #[test] + // fn list_of_one_int() { + // specializes_to("[42]", "List (Num [])"); + // } + + // #[test] + // fn triple_nested_int_list() { + // specializes_to("[[[5]]]", "List (List (List (Num [])))"); + // } + + // #[test] + // fn list_of_ints() { + // specializes_to("[1, 2, 3]", "List (Num [])"); + // } + + // #[test] + // fn nested_list_of_ints() { + // specializes_to("[[1], [2, 3]]", "List (List (Num []))"); + // } + + // #[test] + // fn list_of_one_string() { + // specializes_to(r#"["cowabunga"]"#, "List Str"); + // } + + // #[test] + // fn triple_nested_string_list() { + // specializes_to(r#"[[["foo"]]]"#, "List (List (List Str))"); + // } + + // #[test] + // fn list_of_strings() { + // specializes_to(r#"["foo", "bar"]"#, "List Str"); + // } + + // // INTERPOLATED STRING + + // #[test] + // fn infer_interpolated_string() { + // specializes_to( + // indoc!( + // r#" + // whatItIs = "great" + + // "type inference is $(whatItIs)!" + // "# + // ), + // "Str", + // ); + // } + + // #[test] + // fn infer_interpolated_var() { + // specializes_to( + // indoc!( + // r#" + // whatItIs = "great" + + // str = "type inference is $(whatItIs)!" + + // whatItIs + // "# + // ), + // "Str", + // ); + // } + + // #[test] + // fn infer_interpolated_field() { + // specializes_to( + // indoc!( + // r#" + // rec = { whatItIs: "great" } + + // str = "type inference is $(rec.whatItIs)!" + + // rec + // "# + // ), + // "{ whatItIs : Str }", + // ); + // } + + // // LIST MISMATCH + + // #[test] + // fn mismatch_heterogeneous_list() { + // specializes_to( + // indoc!( + // r#" + // ["foo", 5] + // "# + // ), + // "List ", + // ); + // } + + // #[test] + // fn mismatch_heterogeneous_nested_list() { + // specializes_to( + // indoc!( + // r#" + // [["foo", 5]] + // "# + // ), + // "List (List )", + // ); + // } + + // #[test] + // fn mismatch_heterogeneous_nested_empty_list() { + // specializes_to( + // indoc!( + // r" + // [[1], [[]]] + // " + // ), + // "List ", + // ); + // } + + // // CLOSURE + + // #[test] + // fn always_return_empty_record() { + // specializes_to( + // indoc!( + // r" + // \_ -> {} + // " + // ), + // "[] -> {}", + // ); + // } + + // #[test] + // fn two_arg_return_int() { + // specializes_to( + // indoc!( + // r" + // \_, _ -> 42 + // " + // ), + // "[], [] -> Num []", + // ); + // } + + // #[test] + // fn three_arg_return_string() { + // specializes_to( + // indoc!( + // r#" + // \_, _, _ -> "test!" + // "# + // ), + // "[], [], [] -> Str", + // ); + // } + + // // DEF + + // #[test] + // fn def_empty_record() { + // specializes_to( + // indoc!( + // r" + // foo = {} + + // foo + // " + // ), + // "{}", + // ); + // } + + // #[test] + // fn def_string() { + // specializes_to( + // indoc!( + // r#" + // str = "thing" + + // str + // "# + // ), + // "Str", + // ); + // } + + // #[test] + // fn def_1_arg_closure() { + // specializes_to( + // indoc!( + // r" + // fn = \_ -> {} + + // fn + // " + // ), + // "[] -> {}", + // ); + // } + + // #[test] + // fn applied_tag() { + // infer_eq_without_problem( + // indoc!( + // r#" + // List.map ["a", "b"] \elem -> Foo elem + // "# + // ), + // "List [Foo Str]", + // ) + // } + + // // Tests (TagUnion, Func) + // #[test] + // fn applied_tag_function() { + // infer_eq_without_problem( + // indoc!( + // r#" + // foo = Foo + + // foo "hi" + // "# + // ), + // "[Foo Str]", + // ) + // } + + // // Tests (TagUnion, Func) + // #[test] + // fn applied_tag_function_list_map() { + // infer_eq_without_problem( + // indoc!( + // r#" + // List.map ["a", "b"] Foo + // "# + // ), + // "List [Foo Str]", + // ) + // } + + // // Tests (TagUnion, Func) + // #[test] + // fn applied_tag_function_list() { + // infer_eq_without_problem( + // indoc!( + // r" + // [\x -> Bar x, Foo] + // " + // ), + // "List ([] -> [Bar [], Foo []])", + // ) + // } + + // // Tests (Func, TagUnion) + // #[test] + // fn applied_tag_function_list_other_way() { + // infer_eq_without_problem( + // indoc!( + // r" + // [Foo, \x -> Bar x] + // " + // ), + // "List ([] -> [Bar [], Foo []])", + // ) + // } + + // // Tests (Func, TagUnion) + // #[test] + // fn applied_tag_function_record() { + // infer_eq_without_problem( + // indoc!( + // r" + // foo0 = Foo + // foo1 = Foo + // foo2 = Foo + + // { + // x: [foo0, Foo], + // y: [foo1, \x -> Foo x], + // z: [foo2, \x,y -> Foo x y] + // } + // " + // ), + // "{ x : List [Foo], y : List ([] -> [Foo []]), z : List ([], [] -> [Foo [] []]) }", + // ) + // } + + // // Tests (TagUnion, Func) + // #[test] + // fn applied_tag_function_with_annotation() { + // infer_eq_without_problem( + // indoc!( + // r" + // x : List [Foo I64] + // x = List.map [1, 2] Foo + + // x + // " + // ), + // "List [Foo I64]", + // ) + // } + + // #[test] + // fn def_2_arg_closure() { + // specializes_to( + // indoc!( + // r" + // func = \_, _ -> 42 + + // func + // " + // ), + // "[], [] -> Num []", + // ); + // } + + // #[test] + // fn def_3_arg_closure() { + // specializes_to( + // indoc!( + // r#" + // f = \_, _, _ -> "test!" + + // f + // "# + // ), + // "[], [], [] -> Str", + // ); + // } + + // #[test] + // fn def_multiple_functions() { + // specializes_to( + // indoc!( + // r#" + // a = \_, _, _ -> "test!" + + // b = a + + // b + // "# + // ), + // "[], [], [] -> Str", + // ); + // } + + // #[test] + // fn def_multiple_strings() { + // specializes_to( + // indoc!( + // r#" + // a = "test!" + + // b = a + + // b + // "# + // ), + // "Str", + // ); + // } + + // #[test] + // fn def_multiple_ints() { + // specializes_to( + // indoc!( + // r" + // c = b + + // b = a + + // a = 42 + + // c + // " + // ), + // "Num []", + // ); + // } + + // #[test] + // fn def_returning_closure() { + // specializes_to( + // indoc!( + // r" + // f = \z -> z + // g = \z -> z + + // (\x -> + // a = f x + // b = g x + // x + // ) + // " + // ), + // "[] -> []", + // ); + // } + + // // CALLING FUNCTIONS + + // #[test] + // fn call_returns_int() { + // specializes_to( + // indoc!( + // r#" + // alwaysFive = \_ -> 5 + + // alwaysFive "stuff" + // "# + // ), + // "Num []", + // ); + // } + + // #[test] + // fn identity_returns_given_type() { + // specializes_to( + // indoc!( + // r#" + // identity = \a -> a + + // identity "hi" + // "# + // ), + // "Str", + // ); + // } + + // #[test] + // fn identity_infers_principal_type() { + // specializes_to( + // indoc!( + // r" + // identity = \x -> x + + // y = identity 5 + + // identity + // " + // ), + // "[] -> []", + // ); + // } + + // #[test] + // fn identity_works_on_incompatible_types() { + // specializes_to( + // indoc!( + // r#" + // identity = \a -> a + + // x = identity 5 + // y = identity "hi" + + // x + // "# + // ), + // "Num []", + // ); + // } + + // #[test] + // fn call_returns_list() { + // specializes_to( + // indoc!( + // r" + // enlist = \val -> [val] + + // enlist 5 + // " + // ), + // "List (Num [])", + // ); + // } + + // #[test] + // fn indirect_always() { + // specializes_to( + // indoc!( + // r#" + // always = \val -> (\_ -> val) + // alwaysFoo = always "foo" + + // alwaysFoo 42 + // "# + // ), + // "Str", + // ); + // } + + // #[test] + // fn pizza_desugar() { + // specializes_to( + // indoc!( + // r" + // 1 |> (\a -> a) + // " + // ), + // "Num []", + // ); + // } + + // #[test] + // fn pizza_desugar_two_arguments() { + // specializes_to( + // indoc!( + // r#" + // always2 = \a, _ -> a + + // 1 |> always2 "foo" + // "# + // ), + // "Num []", + // ); + // } + + // #[test] + // fn anonymous_identity() { + // specializes_to( + // indoc!( + // r" + // (\a -> a) 3.14 + // " + // ), + // "Frac []", + // ); + // } + + // #[test] + // fn identity_of_identity() { + // specializes_to( + // indoc!( + // r" + // (\val -> val) (\val -> val) + // " + // ), + // "[] -> []", + // ); + // } + + // #[test] + // fn recursive_identity() { + // specializes_to( + // indoc!( + // r" + // identity = \val -> val + + // identity identity + // " + // ), + // "[] -> []", + // ); + // } + + // #[test] + // fn identity_function() { + // specializes_to( + // indoc!( + // r" + // \val -> val + // " + // ), + // "[] -> []", + // ); + // } + + // #[test] + // fn use_apply() { + // specializes_to( + // indoc!( + // r" + // identity = \a -> a + // apply = \f, x -> f x + + // apply identity 5 + // " + // ), + // "Num []", + // ); + // } + + // #[test] + // fn apply_function() { + // specializes_to( + // indoc!( + // r" + // \f, x -> f x + // " + // ), + // "([] -> []), [] -> []", + // ); + // } + + // // #[test] + // // TODO FIXME this should pass, but instead fails to canonicalize + // // fn use_flip() { + // // infer_eq( + // // indoc!( + // // r" + // // flip = \f -> (\a b -> f b a) + // // neverendingInt = \f int -> f int + // // x = neverendingInt (\a -> a) 5 + + // // flip neverendingInt + // // " + // // ), + // // "(Num [], (a -> a)) -> Num []", + // // ); + // // } + + // #[test] + // fn flip_function() { + // specializes_to( + // indoc!( + // r" + // \f -> (\a, b -> f b a) + // " + // ), + // "([], [] -> []) -> ([], [] -> [])", + // ); + // } + + // #[test] + // fn always_function() { + // specializes_to( + // indoc!( + // r" + // \val -> \_ -> val + // " + // ), + // "[] -> ([] -> [])", + // ); + // } + + // #[test] + // fn pass_a_function() { + // specializes_to( + // indoc!( + // r" + // \f -> f {} + // " + // ), + // "({} -> []) -> []", + // ); + // } + + // // OPERATORS + + // // #[test] + // // fn div_operator() { + // // infer_eq( + // // indoc!( + // // r" + // // \l r -> l / r + // // " + // // ), + // // "F64, F64 -> F64", + // // ); + // // } + + // // #[test] + // // fn basic_float_division() { + // // infer_eq( + // // indoc!( + // // r" + // // 1 / 2 + // // " + // // ), + // // "F64", + // // ); + // // } + + // // #[test] + // // fn basic_int_division() { + // // infer_eq( + // // indoc!( + // // r" + // // 1 // 2 + // // " + // // ), + // // "Num []", + // // ); + // // } + + // // #[test] + // // fn basic_addition() { + // // infer_eq( + // // indoc!( + // // r" + // // 1 + 2 + // // " + // // ), + // // "Num []", + // // ); + // // } + + // // #[test] + // // fn basic_circular_type() { + // // infer_eq( + // // indoc!( + // // r" + // // \x -> x x + // // " + // // ), + // // "", + // // ); + // // } + + // // #[test] + // // fn y_combinator_has_circular_type() { + // // assert_eq!( + // // infer(indoc!(r" + // // \f -> (\x -> f x x) (\x -> f x x) + // // ")), + // // Erroneous(Problem::CircularType) + // // ); + // // } + + // // #[test] + // // fn no_higher_ranked_types() { + // // // This should error because it can't type of alwaysFive + // // infer_eq( + // // indoc!( + // // r#" + // // \always -> [always [], always ""] + // // "# + // // ), + // // "", + // // ); + // // } + + // #[test] + // fn always_with_list() { + // specializes_to( + // indoc!( + // r#" + // alwaysFive = \_ -> 5 + + // [alwaysFive "foo", alwaysFive []] + // "# + // ), + // "List (Num [])", + // ); + // } + + // #[test] + // fn if_with_int_literals() { + // specializes_to( + // indoc!( + // r" + // if Bool.true then + // 42 + // else + // 24 + // " + // ), + // "Num []", + // ); + // } + + // #[test] + // fn when_with_int_literals() { + // specializes_to( + // indoc!( + // r" + // when 1 is + // 1 -> 2 + // 3 -> 4 + // " + // ), + // "Num []", + // ); + // } + + // // RECORDS + + // #[test] + // fn empty_record() { + // specializes_to("{}", "{}"); + // } + + // #[test] + // fn one_field_record() { + // specializes_to("{ x: 5 }", "{ x : Num [] }"); + // } + + // #[test] + // fn two_field_record() { + // specializes_to("{ x: 5, y : 3.14 }", "{ x : Num [], y : Frac [] }"); + // } + + // #[test] + // fn record_literal_accessor() { + // specializes_to("{ x: 5, y : 3.14 }.x", "Num []"); + // } + + // #[test] + // fn record_literal_accessor_function() { + // specializes_to(".x { x: 5, y : 3.14 }", "Num []"); + // } + + // #[test] + // fn tuple_literal_accessor() { + // specializes_to("(5, 3.14 ).0", "Num []"); + // } + + // #[test] + // fn tuple_literal_accessor_function() { + // specializes_to(".0 (5, 3.14 )", "Num []"); + // } + + // // #[test] + // // fn tuple_literal_ty() { + // // specializes_to("(5, 3.14 )", "( Num [], Frac [] )"); + // // } + + // // #[test] + // // fn tuple_literal_accessor_ty() { + // // specializes_to(".0", "( [] ) -> []"); + // // specializes_to(".4", "( _, _, _, _, [] ) -> []"); + // // specializes_to(".5", "( ... 5 omitted, [] ) -> []"); + // // specializes_to(".200", "( ... 200 omitted, [] ) -> []"); + // // } + + // #[test] + // fn tuple_accessor_generalization() { + // specializes_to( + // indoc!( + // r#" + // get0 = .0 + + // { a: get0 (1, 2), b: get0 ("a", "b", "c") } + // "# + // ), + // "{ a : Num [], b : Str }", + // ); + // } + + // #[test] + // fn record_arg() { + // specializes_to("\\rec -> rec.x", "{ x : [] } -> []"); + // } + + // #[test] + // fn record_with_bound_var() { + // specializes_to( + // indoc!( + // r" + // fn = \rec -> + // x = rec.x + + // rec + + // fn + // " + // ), + // "{ x : [] } -> { x : [] }", + // ); + // } + + // #[test] + // fn using_type_signature() { + // specializes_to( + // indoc!( + // r" + // bar : custom -> custom + // bar = \x -> x + + // bar + // " + // ), + // "[] -> []", + // ); + // } + + // #[test] + // fn type_signature_without_body() { + // specializes_to( + // indoc!( + // r#" + // foo: Str -> {} + + // foo "hi" + // "# + // ), + // "{}", + // ); + // } + + // #[test] + // fn type_signature_without_body_rigid() { + // specializes_to( + // indoc!( + // r" + // foo : Num * -> custom + + // foo 2 + // " + // ), + // "[]", + // ); + // } + + // #[test] + // fn accessor_function() { + // specializes_to(".foo", "{ foo : [] } -> []"); + // } + + // #[test] + // fn type_signature_without_body_record() { + // specializes_to( + // indoc!( + // r" + // { x, y } : { x : ({} -> custom), y : {} } + + // x + // " + // ), + // "{} -> []", + // ); + // } + + // #[test] + // fn empty_record_pattern() { + // specializes_to( + // indoc!( + // r" + // # technically, an empty record can be destructured + // thunk = \{} -> 42 + + // xEmpty = if thunk {} == 42 then { x: {} } else { x: {} } + + // when xEmpty is + // { x: {} } -> {} + // " + // ), + // "{}", + // ); + // } + + // #[test] + // fn record_type_annotation() { + // // check that a closed record remains closed + // specializes_to( + // indoc!( + // r" + // foo : { x : custom } -> custom + // foo = \{ x } -> x + + // foo + // " + // ), + // "{ x : [] } -> []", + // ); + // } + + // #[test] + // fn record_update() { + // specializes_to( + // indoc!( + // r#" + // user = { year: "foo", name: "Sam" } + + // { user & year: "foo" } + // "# + // ), + // "{ name : Str, year : Str }", + // ); + // } + + // #[test] + // fn bare_tag() { + // specializes_to( + // indoc!( + // r" + // Foo + // " + // ), + // "[Foo]", + // ); + // } + + // #[test] + // fn single_tag_pattern() { + // specializes_to( + // indoc!( + // r" + // \Foo -> 42 + // " + // ), + // "[Foo] -> Num []", + // ); + // } + + // #[test] + // fn two_tag_pattern() { + // specializes_to( + // indoc!( + // r" + // \x -> + // when x is + // True -> 1 + // False -> 0 + // " + // ), + // "[False, True] -> Num []", + // ); + // } + + // #[test] + // fn tag_application() { + // specializes_to( + // indoc!( + // r#" + // Foo "happy" 12 + // "# + // ), + // "[Foo Str (Num [])]", + // ); + // } + + // #[test] + // fn record_extraction() { + // specializes_to( + // indoc!( + // r" + // f = \x -> + // when x is + // { a, b: _ } -> a + + // f + // " + // ), + // "{ a : [], b : [] } -> []", + // ); + // } + + // #[test] + // fn record_field_pattern_match_with_guard() { + // specializes_to( + // indoc!( + // r" + // when { x: 5 } is + // { x: 4 } -> 4 + // " + // ), + // "Num []", + // ); + // } + + // #[test] + // fn tag_union_pattern_match() { + // specializes_to( + // indoc!( + // r" + // \Foo x -> Foo x + // " + // ), + // "[Foo []] -> [Foo []]", + // ); + // } + + // #[test] + // fn tag_union_pattern_match_ignored_field() { + // specializes_to( + // indoc!( + // r#" + // \Foo x _ -> Foo x "y" + // "# + // ), + // "[Foo [] []] -> [Foo [] Str]", + // ); + // } + + // #[test] + // fn tag_with_field() { + // specializes_to( + // indoc!( + // r#" + // when Foo "blah" is + // Foo x -> x + // "# + // ), + // "Str", + // ); + // } + + // #[test] + // fn qualified_annotation_num_integer() { + // specializes_to( + // indoc!( + // r" + // int : Num.Num (Num.Integer Num.Signed64) + + // int + // " + // ), + // "I64", + // ); + // } + // #[test] + // fn qualified_annotated_num_integer() { + // specializes_to( + // indoc!( + // r" + // int : Num.Num (Num.Integer Num.Signed64) + // int = 5 + + // int + // " + // ), + // "I64", + // ); + // } + // #[test] + // fn annotation_num_integer() { + // specializes_to( + // indoc!( + // r" + // int : Num (Integer Signed64) + + // int + // " + // ), + // "I64", + // ); + // } + // #[test] + // fn annotated_num_integer() { + // specializes_to( + // indoc!( + // r" + // int : Num (Integer Signed64) + // int = 5 + + // int + // " + // ), + // "I64", + // ); + // } + + // #[test] + // fn qualified_annotation_using_i128() { + // specializes_to( + // indoc!( + // r" + // int : Num.I128 + + // int + // " + // ), + // "I128", + // ); + // } + // #[test] + // fn qualified_annotated_using_i128() { + // specializes_to( + // indoc!( + // r" + // int : Num.I128 + // int = 5 + + // int + // " + // ), + // "I128", + // ); + // } + // #[test] + // fn annotation_using_i128() { + // specializes_to( + // indoc!( + // r" + // int : I128 + + // int + // " + // ), + // "I128", + // ); + // } + // #[test] + // fn annotated_using_i128() { + // specializes_to( + // indoc!( + // r" + // int : I128 + // int = 5 + + // int + // " + // ), + // "I128", + // ); + // } + + // #[test] + // fn qualified_annotation_using_u128() { + // specializes_to( + // indoc!( + // r" + // int : Num.U128 + + // int + // " + // ), + // "U128", + // ); + // } + // #[test] + // fn qualified_annotated_using_u128() { + // specializes_to( + // indoc!( + // r" + // int : Num.U128 + // int = 5 + + // int + // " + // ), + // "U128", + // ); + // } + // #[test] + // fn annotation_using_u128() { + // specializes_to( + // indoc!( + // r" + // int : U128 + + // int + // " + // ), + // "U128", + // ); + // } + // #[test] + // fn annotated_using_u128() { + // specializes_to( + // indoc!( + // r" + // int : U128 + // int = 5 + + // int + // " + // ), + // "U128", + // ); + // } + + // #[test] + // fn qualified_annotation_using_i64() { + // specializes_to( + // indoc!( + // r" + // int : Num.I64 + + // int + // " + // ), + // "I64", + // ); + // } + // #[test] + // fn qualified_annotated_using_i64() { + // specializes_to( + // indoc!( + // r" + // int : Num.I64 + // int = 5 + + // int + // " + // ), + // "I64", + // ); + // } + // #[test] + // fn annotation_using_i64() { + // specializes_to( + // indoc!( + // r" + // int : I64 + + // int + // " + // ), + // "I64", + // ); + // } + // #[test] + // fn annotated_using_i64() { + // specializes_to( + // indoc!( + // r" + // int : I64 + // int = 5 + + // int + // " + // ), + // "I64", + // ); + // } + + // #[test] + // fn qualified_annotation_using_u64() { + // specializes_to( + // indoc!( + // r" + // int : Num.U64 + + // int + // " + // ), + // "U64", + // ); + // } + // #[test] + // fn qualified_annotated_using_u64() { + // specializes_to( + // indoc!( + // r" + // int : Num.U64 + // int = 5 + + // int + // " + // ), + // "U64", + // ); + // } + // #[test] + // fn annotation_using_u64() { + // specializes_to( + // indoc!( + // r" + // int : U64 + + // int + // " + // ), + // "U64", + // ); + // } + // #[test] + // fn annotated_using_u64() { + // specializes_to( + // indoc!( + // r" + // int : U64 + // int = 5 + + // int + // " + // ), + // "U64", + // ); + // } + + // #[test] + // fn qualified_annotation_using_i32() { + // specializes_to( + // indoc!( + // r" + // int : Num.I32 + + // int + // " + // ), + // "I32", + // ); + // } + // #[test] + // fn qualified_annotated_using_i32() { + // specializes_to( + // indoc!( + // r" + // int : Num.I32 + // int = 5 + + // int + // " + // ), + // "I32", + // ); + // } + // #[test] + // fn annotation_using_i32() { + // specializes_to( + // indoc!( + // r" + // int : I32 + + // int + // " + // ), + // "I32", + // ); + // } + // #[test] + // fn annotated_using_i32() { + // specializes_to( + // indoc!( + // r" + // int : I32 + // int = 5 + + // int + // " + // ), + // "I32", + // ); + // } + + // #[test] + // fn qualified_annotation_using_u32() { + // specializes_to( + // indoc!( + // r" + // int : Num.U32 + + // int + // " + // ), + // "U32", + // ); + // } + // #[test] + // fn qualified_annotated_using_u32() { + // specializes_to( + // indoc!( + // r" + // int : Num.U32 + // int = 5 + + // int + // " + // ), + // "U32", + // ); + // } + // #[test] + // fn annotation_using_u32() { + // specializes_to( + // indoc!( + // r" + // int : U32 + + // int + // " + // ), + // "U32", + // ); + // } + // #[test] + // fn annotated_using_u32() { + // specializes_to( + // indoc!( + // r" + // int : U32 + // int = 5 + + // int + // " + // ), + // "U32", + // ); + // } + + // #[test] + // fn qualified_annotation_using_i16() { + // specializes_to( + // indoc!( + // r" + // int : Num.I16 + + // int + // " + // ), + // "I16", + // ); + // } + // #[test] + // fn qualified_annotated_using_i16() { + // specializes_to( + // indoc!( + // r" + // int : Num.I16 + // int = 5 + + // int + // " + // ), + // "I16", + // ); + // } + // #[test] + // fn annotation_using_i16() { + // specializes_to( + // indoc!( + // r" + // int : I16 + + // int + // " + // ), + // "I16", + // ); + // } + // #[test] + // fn annotated_using_i16() { + // specializes_to( + // indoc!( + // r" + // int : I16 + // int = 5 + + // int + // " + // ), + // "I16", + // ); + // } + + // #[test] + // fn qualified_annotation_using_u16() { + // specializes_to( + // indoc!( + // r" + // int : Num.U16 + + // int + // " + // ), + // "U16", + // ); + // } + // #[test] + // fn qualified_annotated_using_u16() { + // specializes_to( + // indoc!( + // r" + // int : Num.U16 + // int = 5 + + // int + // " + // ), + // "U16", + // ); + // } + // #[test] + // fn annotation_using_u16() { + // specializes_to( + // indoc!( + // r" + // int : U16 + + // int + // " + // ), + // "U16", + // ); + // } + // #[test] + // fn annotated_using_u16() { + // specializes_to( + // indoc!( + // r" + // int : U16 + // int = 5 + + // int + // " + // ), + // "U16", + // ); + // } + + // #[test] + // fn qualified_annotation_using_i8() { + // specializes_to( + // indoc!( + // r" + // int : Num.I8 + + // int + // " + // ), + // "I8", + // ); + // } + // #[test] + // fn qualified_annotated_using_i8() { + // specializes_to( + // indoc!( + // r" + // int : Num.I8 + // int = 5 + + // int + // " + // ), + // "I8", + // ); + // } + // #[test] + // fn annotation_using_i8() { + // specializes_to( + // indoc!( + // r" + // int : I8 + + // int + // " + // ), + // "I8", + // ); + // } + // #[test] + // fn annotated_using_i8() { + // specializes_to( + // indoc!( + // r" + // int : I8 + // int = 5 + + // int + // " + // ), + // "I8", + // ); + // } + + // #[test] + // fn qualified_annotation_using_u8() { + // specializes_to( + // indoc!( + // r" + // int : Num.U8 + + // int + // " + // ), + // "U8", + // ); + // } + // #[test] + // fn qualified_annotated_using_u8() { + // specializes_to( + // indoc!( + // r" + // int : Num.U8 + // int = 5 + + // int + // " + // ), + // "U8", + // ); + // } + // #[test] + // fn annotation_using_u8() { + // specializes_to( + // indoc!( + // r" + // int : U8 + + // int + // " + // ), + // "U8", + // ); + // } + // #[test] + // fn annotated_using_u8() { + // specializes_to( + // indoc!( + // r" + // int : U8 + // int = 5 + + // int + // " + // ), + // "U8", + // ); + // } + + // #[test] + // fn qualified_annotation_num_floatingpoint() { + // specializes_to( + // indoc!( + // r" + // float : Num.Num (Num.FloatingPoint Num.Binary64) + + // float + // " + // ), + // "F64", + // ); + // } + // #[test] + // fn qualified_annotated_num_floatingpoint() { + // specializes_to( + // indoc!( + // r" + // float : Num.Num (Num.FloatingPoint Num.Binary64) + // float = 5.5 + + // float + // " + // ), + // "F64", + // ); + // } + // #[test] + // fn annotation_num_floatingpoint() { + // specializes_to( + // indoc!( + // r" + // float : Num (FloatingPoint Binary64) + + // float + // " + // ), + // "F64", + // ); + // } + // #[test] + // fn annotated_num_floatingpoint() { + // specializes_to( + // indoc!( + // r" + // float : Num (FloatingPoint Binary64) + // float = 5.5 + + // float + // " + // ), + // "F64", + // ); + // } + + // #[test] + // fn qualified_annotation_f64() { + // specializes_to( + // indoc!( + // r" + // float : Num.F64 + + // float + // " + // ), + // "F64", + // ); + // } + // #[test] + // fn qualified_annotated_f64() { + // specializes_to( + // indoc!( + // r" + // float : Num.F64 + // float = 5.5 + + // float + // " + // ), + // "F64", + // ); + // } + // #[test] + // fn annotation_f64() { + // specializes_to( + // indoc!( + // r" + // float : F64 + + // float + // " + // ), + // "F64", + // ); + // } + // #[test] + // fn annotated_f64() { + // specializes_to( + // indoc!( + // r" + // float : F64 + // float = 5.5 + + // float + // " + // ), + // "F64", + // ); + // } + + // #[test] + // fn qualified_annotation_f32() { + // specializes_to( + // indoc!( + // r" + // float : Num.F32 + + // float + // " + // ), + // "F32", + // ); + // } + // #[test] + // fn qualified_annotated_f32() { + // specializes_to( + // indoc!( + // r" + // float : Num.F32 + // float = 5.5 + + // float + // " + // ), + // "F32", + // ); + // } + // #[test] + // fn annotation_f32() { + // specializes_to( + // indoc!( + // r" + // float : F32 + + // float + // " + // ), + // "F32", + // ); + // } + // #[test] + // fn annotated_f32() { + // specializes_to( + // indoc!( + // r" + // float : F32 + // float = 5.5 + + // float + // " + // ), + // "F32", + // ); + // } + + // #[test] + // fn fake_result_ok() { + // specializes_to( + // indoc!( + // r" + // Res a e : [Okay a, Error e] + + // ok : Res I64 * + // ok = Okay 5 + + // ok + // " + // ), + // "Res I64 []", + // ); + // } + + // #[test] + // fn fake_result_err() { + // specializes_to( + // indoc!( + // r#" + // Res a e : [Okay a, Error e] + + // err : Res * Str + // err = Error "blah" + + // err + // "# + // ), + // "Res [] Str", + // ); + // } + + // #[test] + // fn basic_result_ok() { + // specializes_to( + // indoc!( + // r" + // ok : Result I64 * + // ok = Ok 5 + + // ok + // " + // ), + // "Result I64 []", + // ); + // } + + // #[test] + // fn basic_result_err() { + // specializes_to( + // indoc!( + // r#" + // err : Result * Str + // err = Err "blah" + + // err + // "# + // ), + // "Result [] Str", + // ); + // } + + // #[test] + // fn basic_result_conditional() { + // specializes_to( + // indoc!( + // r#" + // ok : Result I64 _ + // ok = Ok 5 + + // err : Result _ Str + // err = Err "blah" + + // if 1 > 0 then + // ok + // else + // err + // "# + // ), + // "Result I64 Str", + // ); + // } + + // // #[test] + // // fn annotation_using_num_used() { + // // // There was a problem where `I64`, because it is only an annotation + // // // wasn't added to the vars_by_symbol. + // // infer_eq_without_problem( + // // indoc!( + // // r" + // // int : I64 + + // // p = (\x -> x) int + + // // p + // // " + // // ), + // // "I64", + // // ); + // // } + + // #[test] + // fn num_identity() { + // infer_eq_without_problem( + // indoc!( + // r" + // numIdentity : Num.Num a -> Num.Num a + // numIdentity = \x -> x + + // y = numIdentity 3.14 + + // { numIdentity, x : numIdentity 42, y } + // " + // ), + // "{ numIdentity : Num [] -> Num [], x : Num [], y : Frac [] }", + // ); + // } + + // #[test] + // fn when_with_annotation() { + // infer_eq_without_problem( + // indoc!( + // r" + // x : Num.Num (Num.Integer Num.Signed64) + // x = + // when 2 is + // 3 -> 4 + // _ -> 5 + + // x + // " + // ), + // "I64", + // ); + // } + + // // TODO add more realistic function when able + // #[test] + // fn integer_sum() { + // infer_eq_without_problem( + // indoc!( + // r" + // f = \n -> + // when n is + // 0 -> 0 + // _ -> f n + + // f + // " + // ), + // "Num [] -> Num []", + // ); + // } + + // #[test] + // fn identity_map() { + // 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 + // " + // ), + // "([] -> []), [Identity []] -> [Identity []]", + // ); + // } + + // #[test] + // fn to_bit() { + // infer_eq_without_problem( + // indoc!( + // r" + // toBit = \bool -> + // when bool is + // True -> 1 + // False -> 0 + + // toBit + // " + // ), + // "[False, True] -> Num []", + // ); + // } + + // // this test is related to a bug where ext_var would have an incorrect rank. + // // This match has duplicate cases, but we ignore that. + // #[test] + // fn to_bit_record() { + // specializes_to( + // indoc!( + // r#" + // foo = \rec -> + // when rec is + // { x: _ } -> "1" + // { y: _ } -> "2" + + // foo + // "# + // ), + // "{ x : [], y : [] } -> Str", + // ); + // } + + // #[test] + // fn from_bit() { + // infer_eq_without_problem( + // indoc!( + // r" + // fromBit = \int -> + // when int is + // 0 -> False + // _ -> True + + // fromBit + // " + // ), + // "Num [] -> [False, True]", + // ); + // } + + // #[test] + // fn result_map_explicit() { + // infer_eq_without_problem( + // indoc!( + // r" + // map : (a -> b), [Err e, Ok a] -> [Err e, Ok b] + // map = \f, result -> + // when result is + // Ok v -> Ok (f v) + // Err e -> Err e + + // map + // " + // ), + // "([] -> []), [Err [], Ok []] -> [Err [], Ok []]", + // ); + // } + + // #[test] + // fn result_map_alias() { + // infer_eq_without_problem( + // indoc!( + // r" + // Res e a : [Ok a, Err e] + + // map : (a -> b), Res e a -> Res e b + // map = \f, result -> + // when result is + // Ok v -> Ok (f v) + // Err e -> Err e + + // map + // " + // ), + // "([] -> []), Res [] [] -> Res [] []", + // ); + // } + + // #[test] + // fn record_from_load() { + // infer_eq_without_problem( + // indoc!( + // r" + // foo = \{ x } -> x + + // foo { x: 5 } + // " + // ), + // "Num []", + // ); + // } + + // #[test] + // fn defs_from_load() { + // infer_eq_without_problem( + // indoc!( + // r" + // alwaysThreePointZero = \_ -> 3.0 + + // answer = 42 + + // identity = \a -> a + + // threePointZero = identity (alwaysThreePointZero {}) + + // threePointZero + // " + // ), + // "Frac []", + // ); + // } + + // #[test] + // fn use_as_in_signature() { + // infer_eq_without_problem( + // indoc!( + // r#" + // foo : Str.Str as Foo -> Foo + // foo = \_ -> "foo" + + // foo + // "# + // ), + // "Foo -> Foo", + // ); + // } + + // #[test] + // fn use_alias_in_let() { + // infer_eq_without_problem( + // indoc!( + // r#" + // Foo : Str.Str + + // foo : Foo -> Foo + // foo = \_ -> "foo" + + // foo + // "# + // ), + // "Foo -> Foo", + // ); + // } + + // #[test] + // fn use_alias_with_argument_in_let() { + // infer_eq_without_problem( + // indoc!( + // r" + // Foo a : { foo : a } + + // v : Foo (Num.Num (Num.Integer Num.Signed64)) + // v = { foo: 42 } + + // v + // " + // ), + // "Foo I64", + // ); + // } + + // #[test] + // fn identity_alias() { + // infer_eq_without_problem( + // indoc!( + // r" + // Foo a : { foo : a } + + // id : Foo a -> Foo a + // id = \x -> x + + // id + // " + // ), + // "Foo [] -> Foo []", + // ); + // } + + // #[test] + // fn linked_list_empty() { + // infer_eq_without_problem( + // indoc!( + // r" + // empty : [Cons a (ConsList a), Nil] as ConsList a + // empty = Nil + + // empty + // " + // ), + // "ConsList []", + // ); + // } + + // #[test] + // fn linked_list_singleton() { + // infer_eq_without_problem( + // indoc!( + // r" + // singleton : a -> [Cons a (ConsList a), Nil] as ConsList a + // singleton = \x -> Cons x Nil + + // singleton + // " + // ), + // "[] -> ConsList []", + // ); + // } + + // #[test] + // fn peano_length() { + // infer_eq_without_problem( + // indoc!( + // r" + // Peano : [S Peano, Z] + + // length : Peano -> Num.Num (Num.Integer Num.Signed64) + // length = \peano -> + // when peano is + // Z -> 0 + // S v -> length v + + // length + // " + // ), + // "Peano -> I64", + // ); + // } + + // #[test] + // fn peano_map() { + // infer_eq_without_problem( + // indoc!( + // r" + // map : [S Peano, Z] as Peano -> Peano + // map = \peano -> + // when peano is + // Z -> Z + // S v -> S (map v) + + // map + // " + // ), + // "Peano -> Peano", + // ); + // } + + // // #[test] + // // fn infer_linked_list_map() { + // // infer_eq_without_problem( + // // indoc!( + // // r" + // // map = \f, list -> + // // when list is + // // Nil -> Nil + // // Cons x xs -> + // // a = f x + // // b = map f xs + + // // Cons a b + + // // map + // // " + // // ), + // // "([] -> []), [Cons [] c, Nil] as c -> [Cons [] d, Nil] as d", + // // ); + // // } + + // // #[test] + // // fn typecheck_linked_list_map() { + // // infer_eq_without_problem( + // // indoc!( + // // r" + // // ConsList a := [Cons a (ConsList a), Nil] + + // // map : (a -> b), ConsList a -> ConsList b + // // map = \f, list -> + // // when list is + // // Nil -> Nil + // // Cons x xs -> + // // Cons (f x) (map f xs) + + // // map + // // " + // // ), + // // "([] -> []), ConsList [] -> ConsList []", + // // ); + // // } + + // #[test] + // fn mismatch_in_alias_args_gets_reported() { + // specializes_to( + // indoc!( + // r#" + // Foo a : a + + // r : Foo {} + // r = {} + + // s : Foo Str.Str + // s = "bar" + + // when {} is + // _ -> s + // _ -> r + // "# + // ), + // "", + // ); + // } + + // #[test] + // fn mismatch_in_apply_gets_reported() { + // specializes_to( + // indoc!( + // r" + // r : { x : (Num.Num (Num.Integer Signed64)) } + // r = { x : 1 } + + // s : { left : { x : Num.Num (Num.FloatingPoint Num.Binary64) } } + // s = { left: { x : 3.14 } } + + // when 0 is + // 1 -> s.left + // 0 -> r + // " + // ), + // "", + // ); + // } + + // #[test] + // fn mismatch_in_tag_gets_reported() { + // specializes_to( + // indoc!( + // r" + // r : [Ok Str.Str] + // r = Ok 1 + + // s : { left: [Ok {}] } + // s = { left: Ok 3.14 } + + // when 0 is + // 1 -> s.left + // 0 -> r + // " + // ), + // "", + // ); + // } + + // // TODO As intended, this fails, but it fails with the wrong error! + // // + // // #[test] + // // fn nums() { + // // infer_eq_without_problem( + // // indoc!( + // // r" + // // s : Num * + // // s = 3.1 + + // // s + // // " + // // ), + // // "", + // // ); + // // } + + // #[test] + // fn peano_map_alias() { + // specializes_to( + // indoc!( + // r#" + // app "test" provides [main] to "./platform" + + // Peano : [S Peano, Z] + + // map : Peano -> Peano + // map = \peano -> + // when peano is + // Z -> Z + // S rest -> S (map rest) + + // main = + // map + // "# + // ), + // "Peano -> Peano", + // ); + // } + + // #[test] + // fn unit_alias() { + // specializes_to( + // indoc!( + // r" + // Unit : [Unit] + + // unit : Unit + // unit = Unit + + // unit + // " + // ), + // "Unit", + // ); + // } + + // #[test] + // fn rigid_in_letnonrec() { + // infer_eq_without_problem( + // indoc!( + // r" + // ConsList a : [Cons a (ConsList a), Nil] + + // toEmpty : ConsList a -> ConsList a + // toEmpty = \_ -> + // result : ConsList a + // result = Nil + + // result + + // toEmpty + // " + // ), + // "ConsList [] -> ConsList []", + // ); + // } + + // #[test] + // fn rigid_in_letrec_ignored() { + // infer_eq_without_problem( + // indoc!( + // r" + // ConsList a : [Cons a (ConsList a), Nil] + + // toEmpty : ConsList a -> ConsList a + // toEmpty = \_ -> + // result : ConsList _ # TODO to enable using `a` we need scoped variables + // result = Nil + + // toEmpty result + + // toEmpty + // " + // ), + // "ConsList [] -> ConsList []", + // ); + // } + + // #[test] + // fn rigid_in_letrec() { + // infer_eq_without_problem( + // indoc!( + // r#" + // app "test" provides [main] to "./platform" + + // ConsList a : [Cons a (ConsList a), Nil] + + // toEmpty : ConsList a -> ConsList a + // toEmpty = \_ -> + // result : ConsList _ # TODO to enable using `a` we need scoped variables + // result = Nil + + // toEmpty result + + // main = + // toEmpty + // "# + // ), + // "ConsList [] -> ConsList []", + // ); + // } + + // #[test] + // fn let_record_pattern_with_annotation() { + // infer_eq_without_problem( + // indoc!( + // r#" + // { x, y } : { x : Str.Str, y : Num.Num (Num.FloatingPoint Num.Binary64) } + // { x, y } = { x : "foo", y : 3.14 } + + // x + // "# + // ), + // "Str", + // ); + // } + + // #[test] + // fn let_record_pattern_with_annotation_alias() { + // specializes_to( + // indoc!( + // r#" + // Foo : { x : Str.Str, y : Num.Num (Num.FloatingPoint Num.Binary64) } + + // { x, y } : Foo + // { x, y } = { x : "foo", y : 3.14 } + + // x + // "# + // ), + // "Str", + // ); + // } + + // // #[test] + // // fn peano_map_infer() { + // // specializes_to( + // // indoc!( + // // r#" + // // app "test" provides [main] to "./platform" + + // // map = + // // \peano -> + // // when peano is + // // Z -> Z + // // S rest -> map rest |> S + + // // main = + // // map + // // "# + // // ), + // // "[S a, Z] as a -> [S b, Z] as b", + // // ); + // // } + + // // #[test] + // // fn peano_map_infer_nested() { + // // specializes_to( + // // indoc!( + // // r" + // // map = \peano -> + // // when peano is + // // Z -> Z + // // S rest -> + // // map rest |> S + + // // map + // // " + // // ), + // // "[S a, Z] as a -> [S b, Z] as b", + // // ); + // // } + + // #[test] + // fn let_record_pattern_with_alias_annotation() { + // infer_eq_without_problem( + // indoc!( + // r#" + // Foo : { x : Str.Str, y : Num.Num (Num.FloatingPoint Num.Binary64) } + + // { x, y } : Foo + // { x, y } = { x : "foo", y : 3.14 } + + // x + // "# + // ), + // "Str", + // ); + // } + + // #[test] + // fn let_tag_pattern_with_annotation() { + // infer_eq_without_problem( + // indoc!( + // r" + // UserId x : [UserId I64] + // UserId x = UserId 42 + + // x + // " + // ), + // "I64", + // ); + // } + + // #[test] + // fn typecheck_record_linked_list_map() { + // infer_eq_without_problem( + // indoc!( + // r" + // ConsList q : [Cons { x: q, xs: ConsList q }, Nil] + + // map : (a -> b), ConsList a -> ConsList b + // map = \f, list -> + // when list is + // Nil -> Nil + // Cons { x, xs } -> + // Cons { x: f x, xs : map f xs } + + // map + // " + // ), + // "([] -> []), ConsList [] -> ConsList []", + // ); + // } + + // // #[test] + // // fn infer_record_linked_list_map() { + // // infer_eq_without_problem( + // // indoc!( + // // r" + // // map = \f, list -> + // // when list is + // // Nil -> Nil + // // Cons { x, xs } -> + // // Cons { x: f x, xs : map f xs } + + // // map + // // " + // // ), + // // "(a -> b), [Cons { x : a, xs : c }, Nil] as c -> [Cons { x : b, xs : d }, Nil] as d", + // // ); + // // } + + // // #[test] + // // fn typecheck_mutually_recursive_tag_union_2() { + // // infer_eq_without_problem( + // // indoc!( + // // r" + // // ListA a b := [Cons a (ListB b a), Nil] + // // ListB a b := [Cons a (ListA b a), Nil] + + // // ConsList q : [Cons q (ConsList q), Nil] + + // // toAs : (b -> a), ListA a b -> ConsList a + // // toAs = \f, @ListA lista -> + // // when lista is + // // Nil -> Nil + // // Cons a (@ListB listb) -> + // // when listb is + // // Nil -> Nil + // // Cons b (@ListA newLista) -> + // // Cons a (Cons (f b) (toAs f newLista)) + + // // toAs + // // " + // // ), + // // "([] -> []), ListA [] [] -> ConsList []", + // // ); + // // } + + // #[test] + // fn typecheck_mutually_recursive_tag_union_listabc() { + // infer_eq_without_problem( + // indoc!( + // r" + // ListA a : [Cons a (ListB a)] + // ListB a : [Cons a (ListC a)] + // ListC a : [Cons a (ListA a), Nil] + + // val : ListC Num.I64 + // val = Cons 1 (Cons 2 (Cons 3 Nil)) + + // val + // " + // ), + // "ListC I64", + // ); + // } + + // // #[test] + // // fn infer_mutually_recursive_tag_union() { + // // infer_eq_without_problem( + // // indoc!( + // // r" + // // toAs = \f, lista -> + // // when lista is + // // Nil -> Nil + // // Cons a listb -> + // // when listb is + // // Nil -> Nil + // // Cons b newLista -> + // // Cons a (Cons (f b) (toAs f newLista)) + + // // toAs + // // " + // // ), + // // "(a -> b), [Cons c [Cons a d, Nil], Nil] as d -> [Cons c [Cons b e], Nil] as e", + // // ); + // // } + + // #[test] + // fn solve_list_get() { + // infer_eq_without_problem( + // indoc!( + // r#" + // List.get ["a"] 0 + // "# + // ), + // "Result Str [OutOfBounds]", + // ); + // } + + // #[test] + // fn type_more_general_than_signature() { + // infer_eq_without_problem( + // indoc!( + // r" + // partition : U64, U64, List (Int a) -> [Pair U64 (List (Int a))] + // partition = \low, high, initialList -> + // when List.get initialList high is + // Ok _ -> + // Pair 0 [] + + // Err _ -> + // Pair (low - 1) initialList + + // partition + // " + // ), + // "U64, U64, List (Int []) -> [Pair U64 (List (Int []))]", + // ); + // } + + // #[test] + // fn quicksort_partition() { + // infer_eq_without_problem( + // indoc!( + // r" + // swap : U64, U64, List a -> List a + // swap = \i, j, list -> + // when Pair (List.get list i) (List.get list j) is + // Pair (Ok atI) (Ok atJ) -> + // list + // |> List.set i atJ + // |> List.set j atI + + // _ -> + // list + + // partition : U64, U64, List (Int a) -> [Pair U64 (List (Int a))] + // partition = \low, high, initialList -> + // when List.get initialList high is + // Ok pivot -> + // go = \i, j, list -> + // if j < high then + // when List.get list j is + // Ok value -> + // if value <= pivot then + // go (i + 1) (j + 1) (swap (i + 1) j list) + // else + // go i (j + 1) list + + // Err _ -> + // Pair i list + // else + // Pair i list + + // when go (low - 1) low initialList is + // Pair newI newList -> + // Pair (newI + 1) (swap (newI + 1) high newList) + + // Err _ -> + // Pair (low - 1) initialList + + // partition + // " + // ), + // "U64, U64, List (Int []) -> [Pair U64 (List (Int []))]", + // ); + // } + + // #[test] + // fn identity_list() { + // infer_eq_without_problem( + // indoc!( + // r" + // idList : List a -> List a + // idList = \list -> list + + // foo : List I64 -> List I64 + // foo = \initialList -> idList initialList + + // foo + // " + // ), + // "List I64 -> List I64", + // ); + // } + + // #[test] + // fn list_get() { + // infer_eq_without_problem( + // indoc!( + // r" + // List.get [10, 9, 8, 7] 1 + // " + // ), + // "Result (Num []) [OutOfBounds]", + // ); + + // infer_eq_without_problem( + // indoc!( + // r" + // List.get + // " + // ), + // "List [], U64 -> Result [] [OutOfBounds]", + // ); + // } + + // #[test] + // fn use_rigid_twice() { + // infer_eq_without_problem( + // indoc!( + // r" + // id1 : q -> q + // id1 = \x -> x + + // id2 : q -> q + // id2 = \x -> x + + // { id1, id2 } + // " + // ), + // "{ id1 : [] -> [], id2 : [] -> [] }", + // ); + // } + + // #[test] + // fn map_insert() { + // infer_eq_without_problem("Dict.insert", "Dict [] [], [], [] -> Dict [] []"); + // } + + // #[test] + // fn num_to_frac() { + // infer_eq_without_problem("Num.toFrac", "Num [] -> Frac []"); + // } + + // #[test] + // fn pow() { + // infer_eq_without_problem("Num.pow", "Frac [], Frac [] -> Frac []"); + // } + + // #[test] + // fn ceiling() { + // infer_eq_without_problem("Num.ceiling", "Frac [] -> Int []"); + // } + + // #[test] + // fn floor() { + // infer_eq_without_problem("Num.floor", "Frac [] -> Int []"); + // } + + // #[test] + // fn div() { + // infer_eq_without_problem("Num.div", "Frac [], Frac [] -> Frac []") + // } + + // #[test] + // fn div_checked() { + // infer_eq_without_problem( + // "Num.divChecked", + // "Frac [], Frac [] -> Result (Frac []) [DivByZero]", + // ) + // } + + // #[test] + // fn div_ceil() { + // infer_eq_without_problem("Num.divCeil", "Int [], Int [] -> Int []"); + // } + + // #[test] + // fn div_ceil_checked() { + // infer_eq_without_problem( + // "Num.divCeilChecked", + // "Int [], Int [] -> Result (Int []) [DivByZero]", + // ); + // } + + // #[test] + // fn div_trunc() { + // infer_eq_without_problem("Num.divTrunc", "Int [], Int [] -> Int []"); + // } + + // #[test] + // fn div_trunc_checked() { + // infer_eq_without_problem( + // "Num.divTruncChecked", + // "Int [], Int [] -> Result (Int []) [DivByZero]", + // ); + // } + + // #[test] + // fn atan() { + // infer_eq_without_problem("Num.atan", "Frac [] -> Frac []"); + // } + + // #[test] + // fn min_i128() { + // infer_eq_without_problem("Num.minI128", "I128"); + // } + + // #[test] + // fn max_i128() { + // infer_eq_without_problem("Num.maxI128", "I128"); + // } + + // #[test] + // fn min_i64() { + // infer_eq_without_problem("Num.minI64", "I64"); + // } + + // #[test] + // fn max_i64() { + // infer_eq_without_problem("Num.maxI64", "I64"); + // } + + // #[test] + // fn min_u64() { + // infer_eq_without_problem("Num.minU64", "U64"); + // } + + // #[test] + // fn max_u64() { + // infer_eq_without_problem("Num.maxU64", "U64"); + // } + + // #[test] + // fn min_i32() { + // infer_eq_without_problem("Num.minI32", "I32"); + // } + + // #[test] + // fn max_i32() { + // infer_eq_without_problem("Num.maxI32", "I32"); + // } + + // #[test] + // fn min_u32() { + // infer_eq_without_problem("Num.minU32", "U32"); + // } + + // #[test] + // fn max_u32() { + // infer_eq_without_problem("Num.maxU32", "U32"); + // } + + // // #[test] + // // fn reconstruct_path() { + // // infer_eq_without_problem( + // // indoc!( + // // r" + // // reconstructPath : Dict position position, position -> List position where position implements Hash & Eq + // // reconstructPath = \cameFrom, goal -> + // // when Dict.get cameFrom goal is + // // Err KeyNotFound -> + // // [] + + // // Ok next -> + // // List.append (reconstructPath cameFrom next) goal + + // // reconstructPath + // // " + // // ), + // // "Dict position position, position -> List position where position implements Hash & Eq", + // // ); + // // } + + // #[test] + // fn use_correct_ext_record() { + // // Related to a bug solved in 81fbab0b3fe4765bc6948727e603fc2d49590b1c + // infer_eq_without_problem( + // indoc!( + // r" + // f = \r -> + // g = r.q + // h = r.p + + // 42 + + // f + // " + // ), + // "{ p : [], q : [] } -> Num []", + // ); + // } + + // // #[test] + // // fn use_correct_ext_tag_union() { + // // // related to a bug solved in 08c82bf151a85e62bce02beeed1e14444381069f + // // infer_eq_without_problem( + // // indoc!( + // // r#" + // // app "test" imports [] provides [main] to "./platform" + + // // boom = \_ -> boom {} + + // // Model position : { openSet : Set position } + + // // cheapestOpen : Model position -> Result position [KeyNotFound] where position implements Hash & Eq + // // cheapestOpen = \model -> + + // // folder = \resSmallestSoFar, position -> + // // when resSmallestSoFar is + // // Err _ -> resSmallestSoFar + // // Ok smallestSoFar -> + // // if position == smallestSoFar.position then resSmallestSoFar + + // // else + // // Ok { position, cost: 0.0 } + + // // Set.walk model.openSet (Ok { position: boom {}, cost: 0.0 }) folder + // // |> Result.map (\x -> x.position) + + // // astar : Model position -> Result position [KeyNotFound] where position implements Hash & Eq + // // astar = \model -> cheapestOpen model + + // // main = + // // astar + // // "# + // // ), + // // "Model position -> Result position [KeyNotFound] where position implements Hash & Eq", + // // ); + // // } + + // #[test] + // fn when_with_or_pattern_and_guard() { + // infer_eq_without_problem( + // indoc!( + // r" + // \x -> + // when x is + // 2 | 3 -> 0 + // a if a < 20 -> 1 + // 3 | 4 if Bool.false -> 2 + // _ -> 3 + // " + // ), + // "Num [] -> Num []", + // ); + // } + + // #[test] + // fn sorting() { + // // based on https://github.com/elm/compiler/issues/2057 + // // Roc seems to do this correctly, tracking to make sure it stays that way + // infer_eq_without_problem( + // indoc!( + // r" + // sort : ConsList cm -> ConsList cm + // sort = + // \xs -> + // f : cm, cm -> Order + // f = \_, _ -> LT + + // sortWith f xs + + // sortBy : (x -> cmpl), ConsList x -> ConsList x + // sortBy = + // \_, list -> + // cmp : x, x -> Order + // cmp = \_, _ -> LT + + // sortWith cmp list + + // always = \x, _ -> x + + // sortWith : (foobar, foobar -> Order), ConsList foobar -> ConsList foobar + // sortWith = + // \_, list -> + // f = \arg -> + // g arg + + // g = \bs -> + // when bs is + // bx -> f bx + + // always Nil (f list) + + // Order : [LT, GT, EQ] + // ConsList a : [Nil, Cons a (ConsList a)] + + // { x: sortWith, y: sort, z: sortBy } + // " + // ), + // "{ x : ([], [] -> Order), ConsList [] -> ConsList [], y : ConsList [] -> ConsList [], z : ([] -> []), ConsList [] -> ConsList [] }" + // ); + // } + + // // Like in elm, this test now fails. Polymorphic recursion (even with an explicit signature) + // // yields a type error. + // // + // // We should at some point investigate why that is. Elm did support polymorphic recursion in + // // earlier versions. + // // + // // #[test] + // // fn wrapper() { + // // // based on https://github.com/elm/compiler/issues/1964 + // // // Roc seems to do this correctly, tracking to make sure it stays that way + // // infer_eq_without_problem( + // // indoc!( + // // r" + // // Type a : [TypeCtor (Type (Wrapper a))] + // // + // // Wrapper a : [Wrapper a] + // // + // // Opaque : [Opaque] + // // + // // encodeType1 : Type a -> Opaque + // // encodeType1 = \thing -> + // // when thing is + // // TypeCtor v0 -> + // // encodeType1 v0 + // // + // // encodeType1 + // // " + // // ), + // // "Type a -> Opaque", + // // ); + // // } + + // #[test] + // fn rigids() { + // infer_eq_without_problem( + // indoc!( + // r" + // f : List a -> List a + // f = \input -> + // # let-polymorphism at work + // x : {} -> List b + // x = \{} -> [] + + // when List.get input 0 is + // Ok val -> List.append (x {}) val + // Err _ -> input + // f + // " + // ), + // "List [] -> List []", + // ); + // } + + // #[cfg(debug_assertions)] + // #[test] + // #[should_panic] + // fn rigid_record_quantification() { + // // the ext here is qualified on the outside (because we have rank 1 types, not rank 2). + // // That means e.g. `f : { bar : String, foo : I64 } -> Bool }` is a valid argument, but + // // that function could not be applied to the `{ foo : I64 }` list. Therefore, this function + // // is not allowed. + // // + // // should hit a debug_assert! in debug mode, and produce a type error in release mode + // infer_eq_without_problem( + // indoc!( + // r" + // test : ({ foo : I64 }ext -> Bool), { foo : I64 } -> Bool + // test = \fn, a -> fn a + + // test + // " + // ), + // "should fail", + // ); + // } + + // // OPTIONAL RECORD FIELDS + + // #[test] + // fn optional_field_unifies_with_missing() { + // infer_eq_without_problem( + // indoc!( + // r" + // negatePoint : { x : I64, y : I64, z ? Num c } -> { x : I64, y : I64, z : Num c } + + // negatePoint { x: 1, y: 2 } + // " + // ), + // "{ x : I64, y : I64, z : Num [] }", + // ); + // } + + // // #[test] + // // fn open_optional_field_unifies_with_missing() { + // // infer_eq_without_problem( + // // indoc!( + // // r#" + // // negatePoint : { x : I64, y : I64, z ? Num c }r -> { x : I64, y : I64, z : Num c }r + + // // a = negatePoint { x: 1, y: 2 } + // // b = negatePoint { x: 1, y: 2, blah : "hi" } + + // // { a, b } + // // "# + // // ), + // // "{ a : { x : I64, y : I64, z : Num [] }, b : { blah : Str, x : I64, y : I64, z : Num [] } }", + // // ); + // // } + + // #[test] + // fn optional_field_unifies_with_present() { + // infer_eq_without_problem( + // indoc!( + // r" + // negatePoint : { x : Num a, y : Num b, z ? c } -> { x : Num a, y : Num b, z : c } + + // negatePoint { x: 1, y: 2.1, z: 0x3 } + // " + // ), + // "{ x : Num [], y : Frac [], z : Int [] }", + // ); + // } + + // // #[test] + // // fn open_optional_field_unifies_with_present() { + // // infer_eq_without_problem( + // // indoc!( + // // r#" + // // negatePoint : { x : Num a, y : Num b, z ? c }r -> { x : Num a, y : Num b, z : c }r + + // // a = negatePoint { x: 1, y: 2.1 } + // // b = negatePoint { x: 1, y: 2.1, blah : "hi" } + + // // { a, b } + // // "# + // // ), + // // "{ a : { x : Num [], y : Frac [], z : c }, b : { blah : Str, x : Num [], y : Frac [], z : c1 } }", + // // ); + // // } + + // #[test] + // fn optional_field_function() { + // infer_eq_without_problem( + // indoc!( + // r" + // \{ x, y ? 0 } -> x + y + // " + // ), + // "{ x : Num [], y ? Num [] } -> Num []", + // ); + // } + + // #[test] + // fn optional_field_let() { + // infer_eq_without_problem( + // indoc!( + // r" + // { x, y ? 0 } = { x: 32 } + + // x + y + // " + // ), + // "Num []", + // ); + // } + + // // #[test] + // // fn optional_field_when() { + // // infer_eq_without_problem( + // // indoc!( + // // r" + // // \r -> + // // when r is + // // { x, y ? 0 } -> x + y + // // " + // // ), + // // "{ x : Num a, y ? Num a } -> Num a", + // // ); + // // } + + // // #[test] + // // fn optional_field_let_with_signature() { + // // infer_eq_without_problem( + // // indoc!( + // // r" + // // \rec -> + // // { x, y } : { x : I64, y ? Bool }* + // // { x, y ? Bool.false } = rec + + // // { x, y } + // // " + // // ), + // // "{ x : I64, y ? Bool }-> { x : I64, y : Bool }", + // // ); + // // } + + // #[test] + // fn list_walk_backwards() { + // infer_eq_without_problem( + // indoc!( + // r" + // List.walkBackwards + // " + // ), + // "List [], [], ([], [] -> []) -> []", + // ); + // } + + // #[test] + // fn list_walk_backwards_example() { + // infer_eq_without_problem( + // indoc!( + // r" + // empty : List I64 + // empty = + // [] + + // List.walkBackwards empty 0 (\a, b -> a + b) + // " + // ), + // "I64", + // ); + // } + + // #[test] + // fn list_walk_with_index_until() { + // infer_eq_without_problem( + // indoc!(r"List.walkWithIndexUntil"), + // "List [], [], ([], [], U64 -> [Break [], Continue []]) -> []", + // ); + // } + + // #[test] + // fn list_drop_at() { + // infer_eq_without_problem( + // indoc!( + // r" + // List.dropAt + // " + // ), + // "List [], U64 -> List []", + // ); + // } + + // #[test] + // fn str_trim() { + // infer_eq_without_problem( + // indoc!( + // r" + // Str.trim + // " + // ), + // "Str -> Str", + // ); + // } + + // #[test] + // fn str_trim_start() { + // infer_eq_without_problem( + // indoc!( + // r" + // Str.trimStart + // " + // ), + // "Str -> Str", + // ); + // } + + // #[test] + // fn list_take_first() { + // infer_eq_without_problem( + // indoc!( + // r" + // List.takeFirst + // " + // ), + // "List [], U64 -> List []", + // ); + // } + + // #[test] + // fn list_take_last() { + // infer_eq_without_problem( + // indoc!( + // r" + // List.takeLast + // " + // ), + // "List [], U64 -> List []", + // ); + // } + + // #[test] + // fn list_sublist() { + // infer_eq_without_problem( + // indoc!( + // r" + // List.sublist + // " + // ), + // "List [], { len : U64, start : U64 } -> List []", + // ); + // } + + // #[test] + // fn list_split() { + // infer_eq_without_problem( + // indoc!("List.split"), + // "List [], U64 -> { before : List [], others : List [] }", + // ); + // } + + // #[test] + // fn list_drop_last() { + // infer_eq_without_problem( + // indoc!( + // r" + // List.dropLast + // " + // ), + // "List [], U64 -> List []", + // ); + // } + + // #[test] + // fn list_intersperse() { + // infer_eq_without_problem( + // indoc!( + // r" + // List.intersperse + // " + // ), + // "List [], [] -> List []", + // ); + // } + // #[test] + // fn function_that_captures_nothing_is_not_captured() { + // // we should make sure that a function that doesn't capture anything it not itself captured + // // such functions will be lifted to the top-level, and are thus globally available! + // infer_eq_without_problem( + // indoc!( + // r" + // f = \x -> x + 1 + + // g = \y -> f y + + // g + // " + // ), + // "Num [] -> Num []", + // ); + // } + + // #[test] + // fn double_named_rigids() { + // infer_eq_without_problem( + // indoc!( + // r#" + // app "test" provides [main] to "./platform" + + // main : List x + // main = + // empty : List x + // empty = [] + + // empty + // "# + // ), + // "List []", + // ); + // } + + // #[test] + // fn double_tag_application() { + // infer_eq_without_problem( + // indoc!( + // r#" + // app "test" provides [main] to "./platform" + + // main = + // if 1 == 1 then + // Foo (Bar) 1 + // else + // Foo Bar 1 + // "# + // ), + // "[Foo [Bar] (Num [])]", + // ); + + // infer_eq_without_problem("Foo Bar 1", "[Foo [Bar] (Num [])]"); + // } + + // #[test] + // fn double_tag_application_pattern() { + // infer_eq_without_problem( + // indoc!( + // r#" + // app "test" provides [main] to "./platform" + + // Bar : [Bar] + // Foo : [Foo Bar I64, Empty] + + // foo : Foo + // foo = Foo Bar 1 + + // main = + // when foo is + // Foo Bar 1 -> + // Foo Bar 2 + + // x -> + // x + // "# + // ), + // "[Empty, Foo [Bar] I64]", + // ); + // } + + // #[test] + // fn recursive_function_with_rigid() { + // infer_eq_without_problem( + // indoc!( + // r#" + // app "test" provides [main] to "./platform" + + // State a : { count : I64, x : a } + + // foo : State a -> I64 + // foo = \state -> + // if state.count == 0 then + // 0 + // else + // 1 + foo { count: state.count - 1, x: state.x } + + // main : I64 + // main = + // foo { count: 3, x: {} } + // "# + // ), + // "I64", + // ); + // } + + // #[test] + // fn rbtree_empty() { + // infer_eq_without_problem( + // indoc!( + // r#" + // app "test" provides [main] to "./platform" + + // # The color of a node. Leaves are considered Black. + // NodeColor : [Red, Black] + + // RBTree k v : [Node NodeColor k v (RBTree k v) (RBTree k v), Empty] + + // # Create an empty dictionary. + // empty : {} -> RBTree k v + // empty = \{} -> + // Empty + + // foo : RBTree I64 I64 + // foo = empty {} + + // main : RBTree I64 I64 + // main = + // foo + // "# + // ), + // "RBTree I64 I64", + // ); + // } + + // #[test] + // fn rbtree_insert() { + // // exposed an issue where pattern variables were not introduced + // // at the correct level in the constraint + // // + // // see 22592eff805511fbe1da63849771ee5f367a6a16 + // infer_eq_without_problem( + // indoc!( + // r#" + // app "test" provides [main] to "./platform" + + // RBTree k : [Node k (RBTree k), Empty] + + // balance : RBTree k -> RBTree k + // balance = \left -> + // when left is + // Node _ Empty -> Empty + + // _ -> Empty + + // main : RBTree {} + // main = + // balance Empty + // "# + // ), + // "RBTree {}", + // ); + // } + + // #[test] + // fn rbtree_full_remove_min() { + // infer_eq_without_problem( + // indoc!( + // r#" + // app "test" provides [main] to "./platform" + + // NodeColor : [Red, Black] + + // RBTree k v : [Node NodeColor k v (RBTree k v) (RBTree k v), Empty] + + // moveRedLeft : RBTree k v -> RBTree k v + // moveRedLeft = \dict -> + // when dict is + // # Node clr k v (Node lClr lK lV lLeft lRight) (Node rClr rK rV ((Node Red rlK rlV rlL rlR) as rLeft) rRight) -> + // # Node clr k v (Node lClr lK lV lLeft lRight) (Node rClr rK rV rLeft rRight) -> + // Node clr k v (Node _ lK lV lLeft lRight) (Node _ rK rV rLeft rRight) -> + // when rLeft is + // Node Red rlK rlV rlL rlR -> + // Node + // Red + // rlK + // rlV + // (Node Black k v (Node Red lK lV lLeft lRight) rlL) + // (Node Black rK rV rlR rRight) + + // _ -> + // when clr is + // Black -> + // Node + // Black + // k + // v + // (Node Red lK lV lLeft lRight) + // (Node Red rK rV rLeft rRight) + + // Red -> + // Node + // Black + // k + // v + // (Node Red lK lV lLeft lRight) + // (Node Red rK rV rLeft rRight) + + // _ -> + // dict + + // balance : NodeColor, k, v, RBTree k v, RBTree k v -> RBTree k v + // balance = \color, key, value, left, right -> + // when right is + // Node Red rK rV rLeft rRight -> + // when left is + // Node Red lK lV lLeft lRight -> + // Node + // Red + // key + // value + // (Node Black lK lV lLeft lRight) + // (Node Black rK rV rLeft rRight) + + // _ -> + // Node color rK rV (Node Red key value left rLeft) rRight + + // _ -> + // when left is + // Node Red lK lV (Node Red llK llV llLeft llRight) lRight -> + // Node + // Red + // lK + // lV + // (Node Black llK llV llLeft llRight) + // (Node Black key value lRight right) + + // _ -> + // Node color key value left right + + // Key k : Num k + + // removeHelpEQGT : Key k, RBTree (Key k) v -> RBTree (Key k) v where k implements Hash & Eq + // removeHelpEQGT = \targetKey, dict -> + // when dict is + // Node color key value left right -> + // if targetKey == key then + // when getMin right is + // Node _ minKey minValue _ _ -> + // balance color minKey minValue left (removeMin right) + + // Empty -> + // Empty + // else + // balance color key value left (removeHelp targetKey right) + + // Empty -> + // Empty + + // getMin : RBTree k v -> RBTree k v + // getMin = \dict -> + // when dict is + // # Node _ _ _ ((Node _ _ _ _ _) as left) _ -> + // Node _ _ _ left _ -> + // when left is + // Node _ _ _ _ _ -> getMin left + // _ -> dict + + // _ -> + // dict + + // moveRedRight : RBTree k v -> RBTree k v + // moveRedRight = \dict -> + // when dict is + // Node clr k v (Node lClr lK lV (Node Red llK llV llLeft llRight) lRight) (Node rClr rK rV rLeft rRight) -> + // Node + // Red + // lK + // lV + // (Node Black llK llV llLeft llRight) + // (Node Black k v lRight (Node Red rK rV rLeft rRight)) + + // Node clr k v (Node lClr lK lV lLeft lRight) (Node rClr rK rV rLeft rRight) -> + // when clr is + // Black -> + // Node + // Black + // k + // v + // (Node Red lK lV lLeft lRight) + // (Node Red rK rV rLeft rRight) + + // Red -> + // Node + // Black + // k + // v + // (Node Red lK lV lLeft lRight) + // (Node Red rK rV rLeft rRight) + + // _ -> + // dict + + // removeHelpPrepEQGT : Key k, RBTree (Key k) v, NodeColor, (Key k), v, RBTree (Key k) v, RBTree (Key k) v -> RBTree (Key k) v + // removeHelpPrepEQGT = \_, dict, color, key, value, left, right -> + // when left is + // Node Red lK lV lLeft lRight -> + // Node + // color + // lK + // lV + // lLeft + // (Node Red key value lRight right) + + // _ -> + // when right is + // Node Black _ _ (Node Black _ _ _ _) _ -> + // moveRedRight dict + + // Node Black _ _ Empty _ -> + // moveRedRight dict + + // _ -> + // dict + + // removeMin : RBTree k v -> RBTree k v + // removeMin = \dict -> + // when dict is + // Node color key value left right -> + // when left is + // Node lColor _ _ lLeft _ -> + // when lColor is + // Black -> + // when lLeft is + // Node Red _ _ _ _ -> + // Node color key value (removeMin left) right + + // _ -> + // when moveRedLeft dict is # here 1 + // Node nColor nKey nValue nLeft nRight -> + // balance nColor nKey nValue (removeMin nLeft) nRight + + // Empty -> + // Empty + + // _ -> + // Node color key value (removeMin left) right + + // _ -> + // Empty + // _ -> + // Empty + + // removeHelp : Key k, RBTree (Key k) v -> RBTree (Key k) v where k implements Hash & Eq + // removeHelp = \targetKey, dict -> + // when dict is + // Empty -> + // Empty + + // Node color key value left right -> + // if targetKey < key then + // when left is + // Node Black _ _ lLeft _ -> + // when lLeft is + // Node Red _ _ _ _ -> + // Node color key value (removeHelp targetKey left) right + + // _ -> + // when moveRedLeft dict is # here 2 + // Node nColor nKey nValue nLeft nRight -> + // balance nColor nKey nValue (removeHelp targetKey nLeft) nRight + + // Empty -> + // Empty + + // _ -> + // Node color key value (removeHelp targetKey left) right + // else + // removeHelpEQGT targetKey (removeHelpPrepEQGT targetKey dict color key value left right) + + // main : RBTree I64 I64 + // main = + // removeHelp 1i64 Empty + // "# + // ), + // "RBTree (Key (Integer Signed64)) I64", + // ); + // } + + // #[test] + // fn rbtree_remove_min_1() { + // infer_eq_without_problem( + // indoc!( + // r#" + // app "test" provides [main] to "./platform" + + // RBTree k : [Node k (RBTree k) (RBTree k), Empty] + + // removeHelp : Num k, RBTree (Num k) -> RBTree (Num k) + // removeHelp = \targetKey, dict -> + // when dict is + // Empty -> + // Empty + + // Node key left right -> + // if targetKey < key then + // when left is + // Node _ lLeft _ -> + // when lLeft is + // Node _ _ _ -> + // Empty + + // _ -> Empty + + // _ -> + // Node key (removeHelp targetKey left) right + // else + // Empty + + // main : RBTree I64 + // main = + // removeHelp 1 Empty + // "# + // ), + // "RBTree I64", + // ); + // } + + // #[test] + // fn rbtree_foobar() { + // infer_eq_without_problem( + // indoc!( + // r#" + // app "test" provides [main] to "./platform" + + // NodeColor : [Red, Black] + + // RBTree k v : [Node NodeColor k v (RBTree k v) (RBTree k v), Empty] + + // removeHelp : Num k, RBTree (Num k) v -> RBTree (Num k) v where k implements Hash & Eq + // removeHelp = \targetKey, dict -> + // when dict is + // Empty -> + // Empty + + // Node color key value left right -> + // if targetKey < key then + // when left is + // Node Black _ _ lLeft _ -> + // when lLeft is + // Node Red _ _ _ _ -> + // Node color key value (removeHelp targetKey left) right + + // _ -> + // when moveRedLeft dict is # here 2 + // Node nColor nKey nValue nLeft nRight -> + // balance nColor nKey nValue (removeHelp targetKey nLeft) nRight + + // Empty -> + // Empty + + // _ -> + // Node color key value (removeHelp targetKey left) right + // else + // removeHelpEQGT targetKey (removeHelpPrepEQGT targetKey dict color key value left right) + + // Key k : Num k + + // balance : NodeColor, k, v, RBTree k v, RBTree k v -> RBTree k v + + // moveRedLeft : RBTree k v -> RBTree k v + + // removeHelpPrepEQGT : Key k, RBTree (Key k) v, NodeColor, (Key k), v, RBTree (Key k) v, RBTree (Key k) v -> RBTree (Key k) v + + // removeHelpEQGT : Key k, RBTree (Key k) v -> RBTree (Key k) v where k implements Hash & Eq + // removeHelpEQGT = \targetKey, dict -> + // when dict is + // Node color key value left right -> + // if targetKey == key then + // when getMin right is + // Node _ minKey minValue _ _ -> + // balance color minKey minValue left (removeMin right) + + // Empty -> + // Empty + // else + // balance color key value left (removeHelp targetKey right) + + // Empty -> + // Empty + + // getMin : RBTree k v -> RBTree k v + + // removeMin : RBTree k v -> RBTree k v + + // main : RBTree I64 I64 + // main = + // removeHelp 1i64 Empty + // "# + // ), + // "RBTree I64 I64", + // ); + // } + + // #[test] + // fn quicksort_partition_help() { + // infer_eq_without_problem( + // indoc!( + // r#" + // app "test" provides [partitionHelp] to "./platform" + + // swap : U64, U64, List a -> List a + // swap = \i, j, list -> + // when Pair (List.get list i) (List.get list j) is + // Pair (Ok atI) (Ok atJ) -> + // list + // |> List.set i atJ + // |> List.set j atI + + // _ -> + // [] + + // partitionHelp : U64, U64, List (Num a), U64, (Num a) -> [Pair U64 (List (Num a))] + // partitionHelp = \i, j, list, high, pivot -> + // if j < high then + // when List.get list j is + // Ok value -> + // if value <= pivot then + // partitionHelp (i + 1) (j + 1) (swap (i + 1) j list) high pivot + // else + // partitionHelp i (j + 1) list high pivot + + // Err _ -> + // Pair i list + // else + // Pair i list + // "# + // ), + // "U64, U64, List (Num []), U64, Num [] -> [Pair U64 (List (Num []))]", + // ); + // } + + // #[test] + // fn rbtree_old_balance_simplified() { + // infer_eq_without_problem( + // indoc!( + // r#" + // app "test" provides [main] to "./platform" + + // RBTree k : [Node k (RBTree k) (RBTree k), Empty] + + // balance : k, RBTree k -> RBTree k + // balance = \key, left -> + // Node key left Empty + + // main : RBTree I64 + // main = + // balance 0 Empty + // "# + // ), + // "RBTree I64", + // ); + // } + + // #[test] + // fn rbtree_balance_simplified() { + // infer_eq_without_problem( + // indoc!( + // r#" + // app "test" provides [main] to "./platform" + + // RBTree k : [Node k (RBTree k) (RBTree k), Empty] + + // node = \x,y,z -> Node x y z + + // balance : k, RBTree k -> RBTree k + // balance = \key, left -> + // node key left Empty + + // main : RBTree I64 + // main = + // balance 0 Empty + // "# + // ), + // "RBTree I64", + // ); + // } + + // #[test] + // fn rbtree_balance() { + // infer_eq_without_problem( + // indoc!( + // r#" + // app "test" provides [main] to "./platform" + + // NodeColor : [Red, Black] + + // RBTree k v : [Node NodeColor k v (RBTree k v) (RBTree k v), Empty] + + // balance : NodeColor, k, v, RBTree k v, RBTree k v -> RBTree k v + // balance = \color, key, value, left, right -> + // when right is + // Node Red rK rV rLeft rRight -> + // when left is + // Node Red lK lV lLeft lRight -> + // Node + // Red + // key + // value + // (Node Black lK lV lLeft lRight) + // (Node Black rK rV rLeft rRight) + + // _ -> + // Node color rK rV (Node Red key value left rLeft) rRight + + // _ -> + // when left is + // Node Red lK lV (Node Red llK llV llLeft llRight) lRight -> + // Node + // Red + // lK + // lV + // (Node Black llK llV llLeft llRight) + // (Node Black key value lRight right) + + // _ -> + // Node color key value left right + + // main : RBTree I64 I64 + // main = + // balance Red 0 0 Empty Empty + // "# + // ), + // "RBTree I64 I64", + // ); + // } + + // #[test] + // fn pattern_rigid_problem() { + // infer_eq_without_problem( + // indoc!( + // r#" + // app "test" provides [main] to "./platform" + + // RBTree k : [Node k (RBTree k) (RBTree k), Empty] + + // balance : k, RBTree k -> RBTree k + // balance = \key, left -> + // when left is + // Node _ _ lRight -> + // Node key lRight Empty + + // _ -> + // Empty + + // main : RBTree I64 + // main = + // balance 0 Empty + // "# + // ), + // "RBTree I64", + // ); + // } + + // #[test] + // fn expr_to_str() { + // infer_eq_without_problem( + // indoc!( + // r#" + // app "test" provides [main] to "./platform" + + // Expr : [Add Expr Expr, Val I64, Var I64] + + // printExpr : Expr -> Str + // printExpr = \e -> + // when e is + // Add a b -> + // "Add (" + // |> Str.concat (printExpr a) + // |> Str.concat ") (" + // |> Str.concat (printExpr b) + // |> Str.concat ")" + // Val v -> Num.toStr v + // Var v -> "Var " |> Str.concat (Num.toStr v) + + // main : Str + // main = printExpr (Var 3) + // "# + // ), + // "Str", + // ); + // } + + // #[test] + // fn int_type_let_polymorphism() { + // infer_eq_without_problem( + // indoc!( + // r#" + // app "test" provides [main] to "./platform" + + // x = 4 + + // f : U8 -> U32 + // f = \z -> Num.intCast z + + // y = f x + + // main = + // x + // "# + // ), + // "Num []", + // ); + // } + + // #[test] + // fn rigid_type_variable_problem() { + // // see https://github.com/roc-lang/roc/issues/1162 + // infer_eq_without_problem( + // indoc!( + // r#" + // app "test" provides [main] to "./platform" + + // RBTree k : [Node k (RBTree k) (RBTree k), Empty] + + // balance : a, RBTree a -> RBTree a + // balance = \key, left -> + // when left is + // Node _ _ lRight -> + // Node key lRight Empty + + // _ -> + // Empty + + // main : RBTree {} + // main = + // balance {} Empty + // "# + // ), + // "RBTree {}", + // ); + // } + + // #[test] + // fn inference_var_inside_arrow() { + // infer_eq_without_problem( + // indoc!( + // r" + // id : _ -> _ + // id = \x -> x + // id + // " + // ), + // "[] -> []", + // ) + // } + + // #[test] + // fn inference_var_inside_ctor() { + // infer_eq_without_problem( + // indoc!( + // r#" + // canIGo : _ -> Result.Result _ _ + // canIGo = \color -> + // when color is + // "green" -> Ok "go!" + // "yellow" -> Err (SlowIt "whoa, let's slow down!") + // "red" -> Err (StopIt "absolutely not") + // _ -> Err (UnknownColor "this is a weird stoplight") + // canIGo + // "# + // ), + // "Str -> Result Str [SlowIt Str, StopIt Str, UnknownColor Str]", + // ) + // } + + // #[test] + // fn inference_var_inside_ctor_linked() { + // infer_eq_without_problem( + // indoc!( + // r" + // swapRcd: {x: _, y: _} -> {x: _, y: _} + // swapRcd = \{x, y} -> {x: y, y: x} + // swapRcd + // " + // ), + // "{ x : [], y : [] } -> { x : [], y : [] }", + // ) + // } + + // #[test] + // fn inference_var_link_with_rigid() { + // infer_eq_without_problem( + // indoc!( + // r" + // swapRcd: {x: tx, y: ty} -> {x: _, y: _} + // swapRcd = \{x, y} -> {x: y, y: x} + // swapRcd + // " + // ), + // "{ x : [], y : [] } -> { x : [], y : [] }", + // ) + // } + + // #[test] + // fn inference_var_inside_tag_ctor() { + // infer_eq_without_problem( + // indoc!( + // r#" + // badComics: [True, False] -> [CowTools _, Thagomizer _] + // badComics = \c -> + // when c is + // True -> CowTools "The Far Side" + // False -> Thagomizer "The Far Side" + // badComics + // "# + // ), + // "[False, True] -> [CowTools Str, Thagomizer Str]", + // ) + // } + + // #[test] + // fn inference_var_tag_union_ext() { + // // TODO: we should really be inferring [Blue, Orange]a -> [Lavender, Peach]a here. + // // See https://github.com/roc-lang/roc/issues/2053 + // infer_eq_without_problem( + // indoc!( + // r" + // pastelize: _ -> [Lavender, Peach]_ + // pastelize = \color -> + // when color is + // Blue -> Lavender + // Orange -> Peach + // col -> col + // pastelize + // " + // ), + // "[Blue, Lavender, Orange, Peach] -> [Blue, Lavender, Orange, Peach]", + // ) + // } + + // #[test] + // fn inference_var_rcd_union_ext() { + // infer_eq_without_problem( + // indoc!( + // r#" + // setRocEmail : _ -> { name: Str, email: Str }_ + // setRocEmail = \person -> + // { person & email: "$(person.name)@roclang.com" } + // setRocEmail + // "# + // ), + // "{ email : Str, name : Str } -> { email : Str, name : Str }", + // ) + // } + + // #[test] + // fn issue_2217() { + // infer_eq_without_problem( + // indoc!( + // r" + // LinkedList elem : [Empty, Prepend (LinkedList elem) elem] + + // fromList : List elem -> LinkedList elem + // fromList = \elems -> List.walk elems Empty Prepend + + // fromList + // " + // ), + // "List [] -> LinkedList []", + // ) + // } + + // #[test] + // fn issue_2217_inlined() { + // infer_eq_without_problem( + // indoc!( + // r" + // fromList : List elem -> [Empty, Prepend (LinkedList elem) elem] as LinkedList elem + // fromList = \elems -> List.walk elems Empty Prepend + + // fromList + // " + // ), + // "List [] -> LinkedList []", + // ) + // } + + // #[test] + // fn infer_union_input_position1() { + // infer_eq_without_problem( + // indoc!( + // r" + // \tag -> + // when tag is + // A -> X + // B -> Y + // " + // ), + // "[A, B] -> [X, Y]", + // ) + // } + + // #[test] + // fn infer_union_input_position2() { + // infer_eq_without_problem( + // indoc!( + // r" + // \tag -> + // when tag is + // A -> X + // B -> Y + // _ -> Z + // " + // ), + // "[A, B] -> [X, Y, Z]", + // ) + // } + + // #[test] + // fn infer_union_input_position3() { + // infer_eq_without_problem( + // indoc!( + // r" + // \tag -> + // when tag is + // A M -> X + // A N -> Y + // " + // ), + // "[A [M, N]] -> [X, Y]", + // ) + // } + + // #[test] + // fn infer_union_input_position4() { + // infer_eq_without_problem( + // indoc!( + // r" + // \tag -> + // when tag is + // A M -> X + // A N -> Y + // A _ -> Z + // " + // ), + // "[A [M, N]] -> [X, Y, Z]", + // ) + // } + + // #[test] + // fn infer_union_input_position5() { + // infer_eq_without_problem( + // indoc!( + // r" + // \tag -> + // when tag is + // A (M J) -> X + // A (N K) -> X + // " + // ), + // "[A [M [J], N [K]]] -> [X]", + // ) + // } + + // #[test] + // fn infer_union_input_position6() { + // infer_eq_without_problem( + // indoc!( + // r" + // \tag -> + // when tag is + // A M -> X + // B -> X + // A N -> X + // " + // ), + // "[A [M, N], B] -> [X]", + // ) + // } + + // #[test] + // fn infer_union_input_position7() { + // infer_eq_without_problem( + // indoc!( + // r" + // \tag -> + // when tag is + // A -> X + // t -> t + // " + // ), + // "[A, X] -> [A, X]", + // ) + // } + + // #[test] + // fn infer_union_input_position8() { + // infer_eq_without_problem( + // indoc!( + // r" + // \opt -> + // when opt is + // Some ({tag: A}) -> 1 + // Some ({tag: B}) -> 1 + // None -> 0 + // " + // ), + // "[None, Some { tag : [A, B] }] -> Num []", + // ) + // } + + // #[test] + // fn infer_union_input_position9() { + // infer_eq_without_problem( + // indoc!( + // r#" + // opt : [Some Str, None] + // opt = Some "" + // rcd = { opt } + + // when rcd is + // { opt: Some s } -> s + // { opt: None } -> "?" + // "# + // ), + // "Str", + // ) + // } + + // #[test] + // fn infer_union_input_position10() { + // infer_eq_without_problem( + // indoc!( + // r" + // \r -> + // when r is + // { x: Blue, y ? 3 } -> y + // { x: Red, y ? 5 } -> y + // " + // ), + // "{ x : [Blue, Red], y ? Num [] } -> Num []", + // ) + // } + + // #[test] + // // Issue #2299 + // fn infer_union_argument_position() { + // infer_eq_without_problem( + // indoc!( + // r" + // \UserId id -> id + 1 + // " + // ), + // "[UserId (Num [])] -> Num []", + // ) + // } + + // #[test] + // fn infer_union_def_position() { + // infer_eq_without_problem( + // indoc!( + // r" + // \email -> + // Email str = email + // Str.isEmpty str + // " + // ), + // "[Email Str] -> Bool", + // ) + // } + + // #[test] + // fn numeric_literal_suffixes() { + // infer_eq_without_problem( + // indoc!( + // r" + // { + // u8: 123u8, + // u16: 123u16, + // u32: 123u32, + // u64: 123u64, + // u128: 123u128, + + // i8: 123i8, + // i16: 123i16, + // i32: 123i32, + // i64: 123i64, + // i128: 123i128, + + // bu8: 0b11u8, + // bu16: 0b11u16, + // bu32: 0b11u32, + // bu64: 0b11u64, + // bu128: 0b11u128, + + // bi8: 0b11i8, + // bi16: 0b11i16, + // bi32: 0b11i32, + // bi64: 0b11i64, + // bi128: 0b11i128, + + // dec: 123.0dec, + // f32: 123.0f32, + // f64: 123.0f64, + + // fdec: 123dec, + // ff32: 123f32, + // ff64: 123f64, + // } + // " + // ), + // r"{ bi128 : I128, bi16 : I16, bi32 : I32, bi64 : I64, bi8 : I8, bu128 : U128, bu16 : U16, bu32 : U32, bu64 : U64, bu8 : U8, dec : Dec, f32 : F32, f64 : F64, fdec : Dec, ff32 : F32, ff64 : F64, i128 : I128, i16 : I16, i32 : I32, i64 : I64, i8 : I8, u128 : U128, u16 : U16, u32 : U32, u64 : U64, u8 : U8 }", + // ) + // } + + // #[test] + // fn numeric_literal_suffixes_in_pattern() { + // infer_eq_without_problem( + // indoc!( + // r" + // { + // u8: (\n -> + // when n is + // 123u8 -> n + // _ -> n), + // u16: (\n -> + // when n is + // 123u16 -> n + // _ -> n), + // u32: (\n -> + // when n is + // 123u32 -> n + // _ -> n), + // u64: (\n -> + // when n is + // 123u64 -> n + // _ -> n), + // u128: (\n -> + // when n is + // 123u128 -> n + // _ -> n), + + // i8: (\n -> + // when n is + // 123i8 -> n + // _ -> n), + // i16: (\n -> + // when n is + // 123i16 -> n + // _ -> n), + // i32: (\n -> + // when n is + // 123i32 -> n + // _ -> n), + // i64: (\n -> + // when n is + // 123i64 -> n + // _ -> n), + // i128: (\n -> + // when n is + // 123i128 -> n + // _ -> n), + + // bu8: (\n -> + // when n is + // 0b11u8 -> n + // _ -> n), + // bu16: (\n -> + // when n is + // 0b11u16 -> n + // _ -> n), + // bu32: (\n -> + // when n is + // 0b11u32 -> n + // _ -> n), + // bu64: (\n -> + // when n is + // 0b11u64 -> n + // _ -> n), + // bu128: (\n -> + // when n is + // 0b11u128 -> n + // _ -> n), + + // bi8: (\n -> + // when n is + // 0b11i8 -> n + // _ -> n), + // bi16: (\n -> + // when n is + // 0b11i16 -> n + // _ -> n), + // bi32: (\n -> + // when n is + // 0b11i32 -> n + // _ -> n), + // bi64: (\n -> + // when n is + // 0b11i64 -> n + // _ -> n), + // bi128: (\n -> + // when n is + // 0b11i128 -> n + // _ -> n), + + // dec: (\n -> + // when n is + // 123.0dec -> n + // _ -> n), + // f32: (\n -> + // when n is + // 123.0f32 -> n + // _ -> n), + // f64: (\n -> + // when n is + // 123.0f64 -> n + // _ -> n), + + // fdec: (\n -> + // when n is + // 123dec -> n + // _ -> n), + // ff32: (\n -> + // when n is + // 123f32 -> n + // _ -> n), + // ff64: (\n -> + // when n is + // 123f64 -> n + // _ -> n), + // } + // " + // ), + // r"{ bi128 : I128 -> I128, bi16 : I16 -> I16, bi32 : I32 -> I32, bi64 : I64 -> I64, bi8 : I8 -> I8, bu128 : U128 -> U128, bu16 : U16 -> U16, bu32 : U32 -> U32, bu64 : U64 -> U64, bu8 : U8 -> U8, dec : Dec -> Dec, f32 : F32 -> F32, f64 : F64 -> F64, fdec : Dec -> Dec, ff32 : F32 -> F32, ff64 : F64 -> F64, i128 : I128 -> I128, i16 : I16 -> I16, i32 : I32 -> I32, i64 : I64 -> I64, i8 : I8 -> I8, u128 : U128 -> U128, u16 : U16 -> U16, u32 : U32 -> U32, u64 : U64 -> U64, u8 : U8 -> U8 }", + // ) + // } +} diff --git a/crates/compiler/specialize_types/tests/test_specialize_types.rs b/crates/compiler/specialize_types/tests/test_specialize_types.rs index de8cead601..34f44e50b8 100644 --- a/crates/compiler/specialize_types/tests/test_specialize_types.rs +++ b/crates/compiler/specialize_types/tests/test_specialize_types.rs @@ -9,7 +9,7 @@ extern crate bumpalo; mod specialize_types { use roc_load::LoadedModule; use roc_solve::FunctionKind; - use roc_specialize_types::MonoCache; + use roc_specialize_types::{DebugInfo, MonoCache, MonoTypes, RecordFieldIds, TupleElemIds}; use test_solve_helpers::{format_problems, run_load_and_infer}; use roc_types::pretty_print::{name_and_print_var, DebugPrint}; @@ -54,9 +54,21 @@ mod specialize_types { debug_assert!(exposed_to_host.len() == 1, "{exposed_to_host:?}"); let (_symbol, variable) = exposed_to_host.into_iter().next().unwrap(); let mut mono_cache = MonoCache::from_subs(subs); + let mut mono_types = MonoTypes::new(); + let debug_info = DebugInfo::new(); + let mut record_field_ids = RecordFieldIds::new(); + let mut tuple_elem_ids = TupleElemIds::new(); let mut problems = Vec::new(); - mono_cache.monomorphize_var(subs, &mut problems, variable); + mono_cache.monomorphize_var( + subs, + &mut mono_types, + &mut record_field_ids, + &mut tuple_elem_ids, + &mut problems, + &mut Some(debug_info), + variable, + ); assert_eq!(problems, Vec::new()); diff --git a/crates/compiler/test_gen/src/helpers/llvm.rs b/crates/compiler/test_gen/src/helpers/llvm.rs index c65af4c149..880a88b0ea 100644 --- a/crates/compiler/test_gen/src/helpers/llvm.rs +++ b/crates/compiler/test_gen/src/helpers/llvm.rs @@ -537,6 +537,7 @@ pub fn try_run_lib_function( } // only used in tests +#[allow(dead_code)] pub(crate) fn llvm_evals_to( src: &str, expected: U, @@ -585,6 +586,7 @@ pub(crate) fn llvm_evals_to( } } +#[allow(unused_macros)] macro_rules! assert_llvm_evals_to { ($src:expr, $expected:expr, $ty:ty, $transform:expr, $ignore_problems:expr) => { crate::helpers::llvm::llvm_evals_to::<$ty, _, _>( @@ -649,6 +651,7 @@ macro_rules! assert_evals_to { }}; } +#[allow(unused_macros)] macro_rules! assert_evals_to_erased { ($src:expr, $expected:expr, $ty:ty) => {{ crate::helpers::llvm::llvm_evals_to::<$ty, _, _>( @@ -661,12 +664,16 @@ macro_rules! assert_evals_to_erased { }}; } +#[allow(dead_code)] pub fn identity(value: T) -> T { value } +#[allow(unused_imports)] pub(crate) use assert_evals_to; +#[allow(unused_imports)] pub(crate) use assert_evals_to_erased; +#[allow(unused_imports)] pub(crate) use assert_llvm_evals_to; #[cfg(feature = "gen-llvm-wasm")] pub(crate) use assert_wasm_evals_to; diff --git a/crates/limits/Cargo.toml b/crates/limits/Cargo.toml new file mode 100644 index 0000000000..b31d24f7d4 --- /dev/null +++ b/crates/limits/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "limits" +description = "Things like maximum number of arguments supported per function, etc." +path = "src/limits.rs" + +authors.workspace = true +edition.workspace = true +license.workspace = true +version.workspace = true + +[dependencies] + +[dev-dependencies] diff --git a/crates/limits/src/limits.rs b/crates/limits/src/limits.rs new file mode 100644 index 0000000000..3107a99ea7 --- /dev/null +++ b/crates/limits/src/limits.rs @@ -0,0 +1,7 @@ +/// See specialize_type.rs for where these numbers comes from. +pub const MAX_ARGS_PER_FUNCTION: usize = (i16::MAX - 1) as usize; +pub const MAX_FIELDS_PER_RECORD: usize = u16::MAX as usize; +pub const MAX_ELEMS_PER_TUPLE: usize = u16::MAX as usize; +pub const MAX_TAG_UNION_VARIANTS: usize = u16::MAX as usize; // We don't support discriminants bigger than u16. +pub const MAX_DEC_INTEGER_COMPONENT: usize = (); // TODO figure out what this is +pub const MAX_DEC_DECIMAL_COMPONENT: usize = (); // TODO figure out what this is diff --git a/crates/soa/src/lib.rs b/crates/soa/src/lib.rs index bace9f156b..5e95f574cb 100644 --- a/crates/soa/src/lib.rs +++ b/crates/soa/src/lib.rs @@ -2,8 +2,10 @@ mod either_index; mod soa_index; mod soa_slice; mod soa_slice2; +mod soa_slice3; pub use either_index::*; pub use soa_index::*; -pub use soa_slice::*; -pub use soa_slice2::*; +pub use soa_slice::{NonEmptySlice, Slice}; +pub use soa_slice2::Slice2; +pub use soa_slice3::Slice3; diff --git a/crates/soa/src/soa_slice.rs b/crates/soa/src/soa_slice.rs index f6088086c2..4edd9f115b 100644 --- a/crates/soa/src/soa_slice.rs +++ b/crates/soa/src/soa_slice.rs @@ -1,7 +1,31 @@ -use core::{fmt, marker::PhantomData, ops::Range}; +use core::{ + fmt, + marker::PhantomData, + num::{NonZeroU16, NonZeroUsize}, + ops::Range, +}; use crate::soa_index::Index; +#[derive(PartialEq, Eq, PartialOrd, Ord)] +pub struct NonEmptySlice { + inner: Slice, +} + +impl fmt::Debug for NonEmptySlice { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.inner.fmt(f) + } +} + +impl Copy for NonEmptySlice {} + +impl Clone for NonEmptySlice { + fn clone(&self) -> Self { + *self + } +} + /// A slice into an array of values, based /// on an offset into the array rather than a pointer. /// @@ -87,13 +111,6 @@ impl Slice { } } - pub fn at(&self, i: usize) -> Index { - Index { - index: self.start + i as u32, - _marker: PhantomData, - } - } - pub const fn new(start: u32, length: u16) -> Self { Self { start, @@ -149,3 +166,72 @@ impl ExactSizeIterator for SliceIterator {} pub trait GetSlice { fn get_slice(&self, slice: Slice) -> &[T]; } + +impl NonEmptySlice { + pub const fn start(self) -> u32 { + self.inner.start() + } + + pub fn advance(&mut self, amount: u32) { + self.inner.advance(amount); + } + + pub fn get_slice<'a>(&self, elems: &'a [T]) -> &'a [T] { + self.inner.get_slice(elems) + } + + pub fn get_slice_mut<'a>(&self, elems: &'a mut [T]) -> &'a mut [T] { + self.inner.get_slice_mut(elems) + } + + #[inline(always)] + pub const fn indices(&self) -> Range { + self.inner.indices() + } + + pub const fn len(&self) -> NonZeroUsize { + // Safety: we only accept a nonzero length on construction + unsafe { NonZeroUsize::new_unchecked(self.inner.len()) } + } + + pub const fn new(start: u32, length: NonZeroU16) -> Self { + Self { + inner: Slice { + start, + length: length.get(), + _marker: PhantomData, + }, + } + } + + pub const unsafe fn new_unchecked(start: u32, length: u16) -> Self { + Self { + inner: Slice { + start, + length, + _marker: PhantomData, + }, + } + } + + pub const fn from_slice(slice: Slice) -> Option { + // Using a match here because Option::map is not const + match NonZeroU16::new(slice.length) { + Some(len) => Some(Self::new(slice.start, len)), + None => None, + } + } + + pub const unsafe fn from_slice_unchecked(slice: Slice) -> Self { + Self::new(slice.start, NonZeroU16::new_unchecked(slice.length)) + } +} + +impl IntoIterator for NonEmptySlice { + type Item = Index; + type IntoIter = SliceIterator; + + fn into_iter(self) -> Self::IntoIter { + self.inner.into_iter() + } +} diff --git a/crates/soa/src/soa_slice2.rs b/crates/soa/src/soa_slice2.rs index 821953b331..af34b1d273 100644 --- a/crates/soa/src/soa_slice2.rs +++ b/crates/soa/src/soa_slice2.rs @@ -1,4 +1,4 @@ -use core::{fmt, marker::PhantomData, ops::Range}; +use core::{fmt, marker::PhantomData}; use crate::{soa_index::Index, soa_slice::Slice}; diff --git a/crates/soa/src/soa_slice3.rs b/crates/soa/src/soa_slice3.rs new file mode 100644 index 0000000000..75fda00d4e --- /dev/null +++ b/crates/soa/src/soa_slice3.rs @@ -0,0 +1,152 @@ +use core::{fmt, marker::PhantomData}; + +use crate::{soa_index::Index, soa_slice::Slice}; + +/// Three slices of the same length, each based on a different +/// offset into the same array (rather than a pointer). +/// +/// Unlike a Rust slice, this is a u32 offset +/// rather than a pointer, and the length is u16. +#[derive(PartialEq, Eq, PartialOrd, Ord)] +pub struct Slice3 { + pub start1: u32, + pub start2: u32, + pub start3: u32, + pub length: u16, + pub _marker: core::marker::PhantomData<(T, U, V)>, +} + +impl fmt::Debug for Slice3 { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "Slice3<{}, {}, {}> {{ start1: {}, start2: {}, start3: {}, length: {} }}", + core::any::type_name::(), + core::any::type_name::(), + core::any::type_name::(), + self.start1, + self.start2, + self.start3, + self.length + ) + } +} + +// derive of copy and clone does not play well with PhantomData + +impl Copy for Slice3 {} + +impl Clone for Slice3 { + fn clone(&self) -> Self { + *self + } +} + +impl Default for Slice3 { + fn default() -> Self { + Self::empty() + } +} + +impl Slice3 { + pub const fn empty() -> Self { + Self { + start1: 0, + start2: 0, + start3: 0, + length: 0, + _marker: PhantomData, + } + } + + pub const fn slice_first(self) -> Slice { + Slice { + start: self.start1, + length: self.length, + _marker: PhantomData, + } + } + + pub const fn slice_second(self) -> Slice { + Slice { + start: self.start2, + length: self.length, + _marker: PhantomData, + } + } + + pub const fn slice_third(self) -> Slice { + Slice { + start: self.start3, + length: self.length, + _marker: PhantomData, + } + } + + pub const fn len(&self) -> usize { + self.length as usize + } + + pub const fn is_empty(&self) -> bool { + self.len() == 0 + } + + pub const fn new(start1: u32, start2: u32, start3: u32, length: u16) -> Self { + Self { + start1, + start2, + start3, + length, + _marker: PhantomData, + } + } +} + +impl IntoIterator for Slice3 { + type Item = (Index, Index); + type IntoIter = SliceIterator; + + fn into_iter(self) -> Self::IntoIter { + SliceIterator { + slice: self, + offset: 0, + } + } +} + +pub struct SliceIterator { + slice: Slice3, + offset: u32, +} + +impl Iterator for SliceIterator { + type Item = (Index, Index); + + fn next(&mut self) -> Option { + let offset = self.offset; + + if offset < self.slice.length as u32 { + let index1 = Index { + index: self.slice.start1 + offset, + _marker: PhantomData, + }; + let index2 = Index { + index: self.slice.start2 + offset, + _marker: PhantomData, + }; + + self.offset += 1; + + Some((index1, index2)) + } else { + None + } + } + + fn size_hint(&self) -> (usize, Option) { + let remaining = (self.slice.length as u32 - self.offset) as usize; + (remaining, Some(remaining)) + } +} + +impl ExactSizeIterator for SliceIterator {} diff --git a/crates/test_compile/Cargo.toml b/crates/test_compile/Cargo.toml new file mode 100644 index 0000000000..1a5c3b259b --- /dev/null +++ b/crates/test_compile/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "test_compile" +description = "Utility functions for starting from strings of code and compiling various IRs." + +authors.workspace = true +edition.workspace = true +license.workspace = true +version.workspace = true + +[dev-dependencies] +roc_builtins = { path = "../builtins" } +roc_derive = { path = "../derive", features = ["debug-derived-symbols"] } +roc_load = { path = "../load" } +roc_parse = { path = "../parse" } +roc_problem = { path = "../problem" } +roc_reporting = { path = "../../reporting" } +roc_target = { path = "../roc_target" } +roc_solve = { path = "../solve" } +test_solve_helpers = { path = "../test_solve_helpers" } +pretty_assertions.workspace = true diff --git a/crates/test_compile/src/help_parse.rs b/crates/test_compile/src/help_parse.rs new file mode 100644 index 0000000000..ffa71d207c --- /dev/null +++ b/crates/test_compile/src/help_parse.rs @@ -0,0 +1,20 @@ +pub struct ParseExpr { + arena: Box, + ast: Box, +} + +impl ParseExpr { + pub fn parse(&str) -> Self { + let mut arena = Bump::new(); + let ast = parse(arena, without_indent(str)); + + Self { + arena, + ast, + } + } + + pub fn ast(&self) -> &Ast { + self.ast + } +} diff --git a/crates/test_compile/src/lib.rs b/crates/test_compile/src/lib.rs new file mode 100644 index 0000000000..18e9de9377 --- /dev/null +++ b/crates/test_compile/src/lib.rs @@ -0,0 +1,2 @@ +mod help_parse; +mod without_indent; diff --git a/crates/test_compile/src/without_indent.rs b/crates/test_compile/src/without_indent.rs new file mode 100644 index 0000000000..ee2a9a0a92 --- /dev/null +++ b/crates/test_compile/src/without_indent.rs @@ -0,0 +1,100 @@ +/// The purpose of this function is to let us run tests like this: +/// +/// run_some_test(r#" +/// x = 1 +/// +/// x +/// ") +/// +/// ...without needing to call a macro like `indoc!` to deal with the fact that +/// multiline Rust string literals preserve all the indented spaces. This takes out +/// the indentation as well as the leading newline in examples like the above, and it's +/// a no-op on single-line strings. +pub fn without_indent(input: &str) -> &str { + // Ignore any leading newlines, which we expect because the opening line will be `(r#"` + let input = input.trim_start_matches('\n'); + let leading_spaces = input.chars().take_while(|&ch| ch == ' ').count(); + + input + .lines() + .map(|line| { + if line.starts_with(" ") { + line.get(leading_spaces..).unwrap_or("") + } else { + line + } + }) + .collect::>() + .join("\n") +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_single_line_input() { + let input = "single line"; + assert_eq!(without_indent(input), "single line"); + } + + #[test] + fn test_multiline_with_indentation() { + let input = r#" + x = 1 + + x + "#; + let expected = "x = 1\n\nx"; + assert_eq!(without_indent(input), expected); + } + + #[test] + fn test_multiline_with_varying_indentation() { + let input = r#" + x = 1 + y = 2 + z = 3 + "#; + let expected = "x = 1\n y = 2\nz = 3"; + assert_eq!(without_indent(input), expected); + } + + #[test] + fn test_multiline_with_empty_lines() { + let input = r#" + x = 1 + + y = 2 + + z = 3 + "#; + let expected = "x = 1\n\ny = 2\n\nz = 3"; + assert_eq!(without_indent(input), expected); + } + + #[test] + fn test_input_without_leading_newline() { + let input = " x = 1\n y = 2"; + let expected = "x = 1\ny = 2"; + assert_eq!(without_indent(input), expected); + } + + #[test] + fn test_input_with_multiple_leading_newlines() { + let input = "\n\n\n x = 1\n y = 2"; + let expected = "x = 1\ny = 2"; + assert_eq!(without_indent(input), expected); + } + + #[test] + fn test_input_with_mixed_indentation() { + let input = r#" + x = 1 + y = 2 + z = 3 + "#; + let expected = "x = 1\ny = 2\n z = 3"; + assert_eq!(without_indent(input), expected); + } +} From 67bca8092108c20164a686be42253f42fcc44e86 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sun, 20 Oct 2024 11:52:46 -0400 Subject: [PATCH 18/65] Add test_compile crate --- Cargo.lock | 19 ++ Cargo.toml | 1 + crates/compiler/parse/src/expr.rs | 2 +- crates/test_compile/Cargo.toml | 26 ++- crates/test_compile/src/deindent.rs | 222 ++++++++++++++++++++++ crates/test_compile/src/help_can.rs | 106 +++++++++++ crates/test_compile/src/help_parse.rs | 60 +++++- crates/test_compile/src/lib.rs | 3 +- crates/test_compile/src/without_indent.rs | 100 ---------- 9 files changed, 418 insertions(+), 121 deletions(-) create mode 100644 crates/test_compile/src/deindent.rs create mode 100644 crates/test_compile/src/help_can.rs delete mode 100644 crates/test_compile/src/without_indent.rs diff --git a/Cargo.lock b/Cargo.lock index 2427583a2b..b22c838709 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3803,6 +3803,25 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "test_compile" +version = "0.0.1" +dependencies = [ + "bumpalo", + "pretty_assertions", + "roc_builtins", + "roc_can", + "roc_derive", + "roc_load", + "roc_parse", + "roc_problem", + "roc_region", + "roc_reporting", + "roc_solve", + "roc_target", + "test_solve_helpers", +] + [[package]] name = "test_derive" version = "0.0.1" diff --git a/Cargo.toml b/Cargo.toml index 3b16d84ff5..e7055d7b2d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ members = [ "crates/repl_wasm", "crates/repl_expect", "crates/roc_std", + "crates/test_compile", "crates/test_utils", "crates/test_utils_dir", "crates/valgrind", diff --git a/crates/compiler/parse/src/expr.rs b/crates/compiler/parse/src/expr.rs index 20a00f3daa..c295b4f0d6 100644 --- a/crates/compiler/parse/src/expr.rs +++ b/crates/compiler/parse/src/expr.rs @@ -35,7 +35,7 @@ use roc_region::all::{Loc, Position, Region}; use crate::parser::Progress::{self, *}; -fn expr_end<'a>() -> impl Parser<'a, (), EExpr<'a>> { +pub fn expr_end<'a>() -> impl Parser<'a, (), EExpr<'a>> { |_arena, state: State<'a>, _min_indent: u32| { if state.has_reached_end() { Ok((NoProgress, (), state)) diff --git a/crates/test_compile/Cargo.toml b/crates/test_compile/Cargo.toml index 1a5c3b259b..8cbb13ad46 100644 --- a/crates/test_compile/Cargo.toml +++ b/crates/test_compile/Cargo.toml @@ -7,14 +7,22 @@ edition.workspace = true license.workspace = true version.workspace = true +[dependencies] +roc_builtins = { path = "../compiler/builtins" } +roc_derive = { path = "../compiler/derive", features = [ + "debug-derived-symbols", +] } +roc_region = { path = "../compiler/region" } +roc_load = { path = "../compiler/load" } +roc_parse = { path = "../compiler/parse" } +roc_can = { path = "../compiler/can" } +roc_problem = { path = "../compiler/problem" } +roc_reporting = { path = "../reporting" } +roc_target = { path = "../compiler/roc_target" } +roc_solve = { path = "../compiler/solve" } +test_solve_helpers = { path = "../compiler/test_solve_helpers" } + +bumpalo.workspace = true + [dev-dependencies] -roc_builtins = { path = "../builtins" } -roc_derive = { path = "../derive", features = ["debug-derived-symbols"] } -roc_load = { path = "../load" } -roc_parse = { path = "../parse" } -roc_problem = { path = "../problem" } -roc_reporting = { path = "../../reporting" } -roc_target = { path = "../roc_target" } -roc_solve = { path = "../solve" } -test_solve_helpers = { path = "../test_solve_helpers" } pretty_assertions.workspace = true diff --git a/crates/test_compile/src/deindent.rs b/crates/test_compile/src/deindent.rs new file mode 100644 index 0000000000..501a770e80 --- /dev/null +++ b/crates/test_compile/src/deindent.rs @@ -0,0 +1,222 @@ +use bumpalo::Bump; +use std::borrow::Cow; + +/// The purpose of this function is to let us run tests like this: +/// +/// run_some_test(r#" +/// x = 1 +/// +/// x +/// ") +/// +/// ...without needing to call a macro like `indoc!` to deal with the fact that +/// multiline Rust string literals preserve all the indented spaces. +/// +/// This function removes the indentation by removing leading newlines (e.g. after +/// the `(r#"` opening) and then counting how many spaces precede the first line +/// (e.g. `" x = 1"` here) and trimming that many spaces from the beginning +/// of each subsequent line. The end of the string is then trimmed normally, and +/// any remaining empty lines are left empty. +/// +/// This function is a no-op on single-line strings. +pub fn trim_and_deindent<'a>(arena: &'a Bump, input: &'a str) -> &'a str { + let newline_count = input.chars().filter(|&ch| ch == '\n').count(); + + // If it's a single-line string, return it without allocating anything. + if newline_count == 0 { + return input.trim(); // Trim to remove spaces + } + + // Trim leading blank lines - we expect at least one, because the opening line will be `(r#"` + // (Also, there may be stray blank lines at the start, which this will trim off too.) + let mut lines = bumpalo::collections::Vec::with_capacity_in(newline_count + 1, arena); + + for line in input + .lines() + // Keep skipping until we hit a line that is neither empty nor all spaces. + .skip_while(|line| line.chars().all(|ch| ch == ' ')) + { + lines.push(line); + } + + // Drop trailing blank lines + while lines + .last() + .map_or(false, |line| line.chars().all(|ch| ch == ' ')) + { + lines.pop(); + } + + // Now that we've trimmed leading and trailing blank lines, + // Find the smallest indent of the remaining lines. That's our indentation amount. + let smallest_indent = lines + .iter() + .filter(|line| !line.is_empty()) + .map(|line| line.chars().take_while(|&ch| ch == ' ').count()) + .min() + .unwrap_or(0); + + // Remove this amount of indentation from each line. + let mut final_str_len = 0; + + lines.iter_mut().for_each(|line| { + if line.starts_with(" ") { + *line = line.get(smallest_indent..).unwrap_or(""); + } + final_str_len += line.len() + 1; // +1 for the newline that will be added to the end of this line. + }); + + // Convert lines into a bumpalo::String + let mut answer = bumpalo::collections::String::with_capacity_in(final_str_len, arena); + + // Unconditionally push a newline after each line we add. We'll trim off the last one before we return. + for line in lines { + answer.push_str(line); + answer.push('\n'); + } + + // Trim off the extra newline we added at the end. (Saturate to 0 if we ended up with no lines.) + &answer.into_bump_str()[..final_str_len.saturating_sub(1)] +} + +#[cfg(test)] +mod tests { + use super::*; + use pretty_assertions::assert_eq; + + #[test] + fn test_single_line_input() { + let input = "single line"; + assert_eq!(trim_and_deindent(&Bump::new(), input), "single line"); + } + + #[test] + fn test_multiline_with_indentation() { + let input = r#" + x = 1 + + x + "#; + let expected = "x = 1\n\nx"; + assert_eq!(trim_and_deindent(&Bump::new(), input), expected); + } + + #[test] + fn test_multiline_with_varying_indentation() { + let input = r#" + x = 1 + y = 2 + z = 3 + "#; + let expected = "x = 1\n y = 2\nz = 3"; + assert_eq!(trim_and_deindent(&Bump::new(), input), expected); + } + + #[test] + fn test_multiline_with_empty_lines() { + let input = r#" + x = 1 + + y = 2 + + z = 3 + "#; + let expected = "x = 1\n\ny = 2\n\nz = 3"; + assert_eq!(trim_and_deindent(&Bump::new(), input), expected); + } + + #[test] + fn test_input_without_leading_newline() { + let input = " x = 1\n y = 2"; + let expected = "x = 1\ny = 2"; + assert_eq!(trim_and_deindent(&Bump::new(), input), expected); + } + + #[test] + fn test_input_with_multiple_leading_newlines() { + let input = "\n\n\n x = 1\n y = 2"; + let expected = "x = 1\ny = 2"; + assert_eq!(trim_and_deindent(&Bump::new(), input), expected); + } + + #[test] + fn test_input_with_mixed_indentation() { + let input = r#" + x = 1 + y = 2 + z = 3 + "#; + let expected = " x = 1\ny = 2\n z = 3"; + assert_eq!(trim_and_deindent(&Bump::new(), input), expected); + } + + #[test] + fn test_input_with_only_spaces() { + let input = " "; + let expected = ""; + assert_eq!(trim_and_deindent(&Bump::new(), input), expected); + } + + #[test] + fn test_input_with_only_newlines() { + let input = "\n\n\n"; + let expected = ""; + assert_eq!(trim_and_deindent(&Bump::new(), input), expected); + } + + #[test] + fn test_input_with_tabs() { + let input = "\t\tx = 1\n\t\ty = 2"; + let expected = "\t\tx = 1\n\t\ty = 2"; + assert_eq!(trim_and_deindent(&Bump::new(), input), expected); + } + + #[test] + fn test_input_with_mixed_spaces_and_tabs() { + let input = " \tx = 1\n \ty = 2"; + let expected = "\tx = 1\n\ty = 2"; + assert_eq!(trim_and_deindent(&Bump::new(), input), expected); + } + + #[test] + fn test_input_with_trailing_spaces() { + let input = " x = 1 \n y = 2 "; + let expected = "x = 1 \ny = 2 "; + assert_eq!(trim_and_deindent(&Bump::new(), input), expected); + } + + #[test] + fn test_input_with_empty_lines_and_spaces() { + let input = " x = 1\n \n y = 2"; + let expected = "x = 1\n\ny = 2"; + assert_eq!(trim_and_deindent(&Bump::new(), input), expected); + } + + #[test] + fn test_input_with_different_indentation_levels() { + let input = " x = 1\n y = 2\n z = 3"; + let expected = " x = 1\n y = 2\nz = 3"; + assert_eq!(trim_and_deindent(&Bump::new(), input), expected); + } + + #[test] + fn test_input_with_non_space_characters_at_start() { + let input = "x = 1\n y = 2\n z = 3"; + let expected = "x = 1\n y = 2\n z = 3"; + assert_eq!(trim_and_deindent(&Bump::new(), input), expected); + } + + #[test] + fn test_empty_input() { + let input = ""; + let expected = ""; + assert_eq!(trim_and_deindent(&Bump::new(), input), expected); + } + + #[test] + fn test_input_with_only_one_indented_line() { + let input = " x = 1"; + let expected = "x = 1"; + assert_eq!(trim_and_deindent(&Bump::new(), input), expected); + } +} diff --git a/crates/test_compile/src/help_can.rs b/crates/test_compile/src/help_can.rs new file mode 100644 index 0000000000..a9336445b4 --- /dev/null +++ b/crates/test_compile/src/help_can.rs @@ -0,0 +1,106 @@ +use crate::help_parse::ParseExpr; +use bumpalo::Bump; +use roc_can::expr::Expr; + +pub struct CanExpr { + parse_expr: ParseExpr, +} + +impl Default for CanExpr { + fn default() -> Self { + Self { + parse_expr: ParseExpr::default(), + } + } +} + +impl CanExpr { + pub fn can_expr<'a>(&'a self, input: &'a str) -> Result { + match self.parse_expr.parse_expr(input) { + Ok(ast) => { + // todo canonicalize AST and return that result. + let loc_expr = roc_parse::test_helpers::parse_loc_with(arena, expr_str).unwrap_or_else(|e| { + panic!( + "can_expr_with() got a parse error when attempting to canonicalize:\n\n{expr_str:?} {e:?}" + ) + }); + + let mut var_store = VarStore::default(); + let var = var_store.fresh(); + let qualified_module_ids = PackageModuleIds::default(); + + let mut scope = Scope::new( + home, + "TestPath".into(), + IdentIds::default(), + Default::default(), + ); + + let dep_idents = IdentIds::exposed_builtins(0); + let mut env = Env::new( + arena, + expr_str, + home, + Path::new("Test.roc"), + &dep_idents, + &qualified_module_ids, + None, + ); + + // 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 = desugar::desugar_expr(&mut env, &mut scope, &loc_expr); + + scope.add_alias( + Symbol::NUM_INT, + Region::zero(), + vec![Loc::at_zero(AliasVar::unbound( + "a".into(), + Variable::EMPTY_RECORD, + ))], + vec![], + Type::EmptyRec, + roc_types::types::AliasKind::Structural, + ); + + let (loc_expr, output) = canonicalize_expr( + &mut env, + &mut var_store, + &mut scope, + Region::zero(), + &loc_expr.value, + ); + + let mut all_ident_ids = IdentIds::exposed_builtins(1); + all_ident_ids.insert(home, scope.locals.ident_ids); + + let interns = Interns { + module_ids: env.qualified_module_ids.clone().into_module_ids(), + all_ident_ids, + }; + + CanExprOut { + loc_expr, + output, + problems: env.problems, + home: env.home, + var_store, + interns, + var, + } + } + Err(syntax_error) => { + // todo panic due to unexpected syntax error + } + } + } + + pub fn into_arena(self) -> Bump { + self.parse_expr.into_arena() + } +} diff --git a/crates/test_compile/src/help_parse.rs b/crates/test_compile/src/help_parse.rs index ffa71d207c..a7cb083370 100644 --- a/crates/test_compile/src/help_parse.rs +++ b/crates/test_compile/src/help_parse.rs @@ -1,20 +1,60 @@ +use bumpalo::Bump; +use roc_parse::{ + ast, + blankspace::space0_before_optional_after, + expr::{expr_end, loc_expr_block}, + parser::{skip_second, EExpr, Parser, SourceError, SyntaxError}, + state::State, +}; +use roc_region::all::{Loc, Position}; + +use crate::deindent::trim_and_deindent; + pub struct ParseExpr { - arena: Box, - ast: Box, + arena: Bump, +} + +impl Default for ParseExpr { + fn default() -> Self { + Self { + arena: Bump::with_capacity(4096), + } + } } impl ParseExpr { - pub fn parse(&str) -> Self { - let mut arena = Bump::new(); - let ast = parse(arena, without_indent(str)); + pub fn parse_expr<'a>(&'a self, input: &'a str) -> Result, SyntaxError<'a>> { + self.parse_loc_expr(input) + .map(|loc_expr| loc_expr.value) + .map_err(|e| e.problem) + } - Self { - arena, - ast, + pub fn parse_loc_expr<'a>( + &'a self, + input: &'a str, + ) -> Result>, SourceError<'a, SyntaxError<'a>>> { + let original_bytes = trim_and_deindent(&self.arena, input).as_bytes(); + let state = State::new(original_bytes); + + let parser = skip_second( + space0_before_optional_after( + loc_expr_block(true), + EExpr::IndentStart, + EExpr::IndentEnd, + ), + expr_end(), + ); + + match parser.parse(&self.arena, state, 0) { + Ok((_, loc_expr, _)) => Ok(loc_expr), + Err((_, fail)) => Err(SourceError { + problem: SyntaxError::Expr(fail, Position::default()), + bytes: original_bytes, + }), } } - pub fn ast(&self) -> &Ast { - self.ast + pub fn into_arena(self) -> Bump { + self.arena } } diff --git a/crates/test_compile/src/lib.rs b/crates/test_compile/src/lib.rs index 18e9de9377..99b86a283d 100644 --- a/crates/test_compile/src/lib.rs +++ b/crates/test_compile/src/lib.rs @@ -1,2 +1,3 @@ +mod deindent; +mod help_can; mod help_parse; -mod without_indent; diff --git a/crates/test_compile/src/without_indent.rs b/crates/test_compile/src/without_indent.rs deleted file mode 100644 index ee2a9a0a92..0000000000 --- a/crates/test_compile/src/without_indent.rs +++ /dev/null @@ -1,100 +0,0 @@ -/// The purpose of this function is to let us run tests like this: -/// -/// run_some_test(r#" -/// x = 1 -/// -/// x -/// ") -/// -/// ...without needing to call a macro like `indoc!` to deal with the fact that -/// multiline Rust string literals preserve all the indented spaces. This takes out -/// the indentation as well as the leading newline in examples like the above, and it's -/// a no-op on single-line strings. -pub fn without_indent(input: &str) -> &str { - // Ignore any leading newlines, which we expect because the opening line will be `(r#"` - let input = input.trim_start_matches('\n'); - let leading_spaces = input.chars().take_while(|&ch| ch == ' ').count(); - - input - .lines() - .map(|line| { - if line.starts_with(" ") { - line.get(leading_spaces..).unwrap_or("") - } else { - line - } - }) - .collect::>() - .join("\n") -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_single_line_input() { - let input = "single line"; - assert_eq!(without_indent(input), "single line"); - } - - #[test] - fn test_multiline_with_indentation() { - let input = r#" - x = 1 - - x - "#; - let expected = "x = 1\n\nx"; - assert_eq!(without_indent(input), expected); - } - - #[test] - fn test_multiline_with_varying_indentation() { - let input = r#" - x = 1 - y = 2 - z = 3 - "#; - let expected = "x = 1\n y = 2\nz = 3"; - assert_eq!(without_indent(input), expected); - } - - #[test] - fn test_multiline_with_empty_lines() { - let input = r#" - x = 1 - - y = 2 - - z = 3 - "#; - let expected = "x = 1\n\ny = 2\n\nz = 3"; - assert_eq!(without_indent(input), expected); - } - - #[test] - fn test_input_without_leading_newline() { - let input = " x = 1\n y = 2"; - let expected = "x = 1\ny = 2"; - assert_eq!(without_indent(input), expected); - } - - #[test] - fn test_input_with_multiple_leading_newlines() { - let input = "\n\n\n x = 1\n y = 2"; - let expected = "x = 1\ny = 2"; - assert_eq!(without_indent(input), expected); - } - - #[test] - fn test_input_with_mixed_indentation() { - let input = r#" - x = 1 - y = 2 - z = 3 - "#; - let expected = "x = 1\ny = 2\n z = 3"; - assert_eq!(without_indent(input), expected); - } -} From 78ceb8cdbf9ca7a1b64be7b920928ee52d6e07ab Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Fri, 1 Nov 2024 21:09:27 -0400 Subject: [PATCH 19/65] Fix help_can and help_parse --- Cargo.lock | 2 + crates/test_compile/Cargo.toml | 2 + crates/test_compile/src/deindent.rs | 10 ++--- crates/test_compile/src/help_can.rs | 55 ++++++++++++++++++++------- crates/test_compile/src/help_parse.rs | 4 ++ 5 files changed, 54 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b22c838709..a1b75ae205 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3813,12 +3813,14 @@ dependencies = [ "roc_can", "roc_derive", "roc_load", + "roc_module", "roc_parse", "roc_problem", "roc_region", "roc_reporting", "roc_solve", "roc_target", + "roc_types", "test_solve_helpers", ] diff --git a/crates/test_compile/Cargo.toml b/crates/test_compile/Cargo.toml index 8cbb13ad46..977a607814 100644 --- a/crates/test_compile/Cargo.toml +++ b/crates/test_compile/Cargo.toml @@ -16,6 +16,8 @@ roc_region = { path = "../compiler/region" } roc_load = { path = "../compiler/load" } roc_parse = { path = "../compiler/parse" } roc_can = { path = "../compiler/can" } +roc_module = { path = "../compiler/module" } +roc_types = { path = "../compiler/types" } roc_problem = { path = "../compiler/problem" } roc_reporting = { path = "../reporting" } roc_target = { path = "../compiler/roc_target" } diff --git a/crates/test_compile/src/deindent.rs b/crates/test_compile/src/deindent.rs index 501a770e80..434d63e413 100644 --- a/crates/test_compile/src/deindent.rs +++ b/crates/test_compile/src/deindent.rs @@ -1,13 +1,13 @@ use bumpalo::Bump; -use std::borrow::Cow; /// The purpose of this function is to let us run tests like this: /// -/// run_some_test(r#" -/// x = 1 +/// ```rust,ignore +/// run_some_test(r#" +/// x = 1 /// -/// x -/// ") +/// x +/// "#) /// /// ...without needing to call a macro like `indoc!` to deal with the fact that /// multiline Rust string literals preserve all the indented spaces. diff --git a/crates/test_compile/src/help_can.rs b/crates/test_compile/src/help_can.rs index a9336445b4..a36ee48d36 100644 --- a/crates/test_compile/src/help_can.rs +++ b/crates/test_compile/src/help_can.rs @@ -1,6 +1,31 @@ +use std::path::Path; + use crate::help_parse::ParseExpr; use bumpalo::Bump; -use roc_can::expr::Expr; +use roc_can::{ + desugar, + env::Env, + expr::{canonicalize_expr, Expr, Output}, + scope::Scope, +}; +use roc_module::symbol::{IdentIds, Interns, ModuleId, ModuleIds, PackageModuleIds, Symbol}; +use roc_problem::can::Problem; +use roc_region::all::{Loc, Region}; +use roc_types::{ + subs::{VarStore, Variable}, + types::{AliasVar, Type}, +}; + +#[derive(Debug)] +pub struct CanExprOut { + pub loc_expr: Loc, + pub output: Output, + pub problems: Vec, + pub home: ModuleId, + pub interns: Interns, + pub var_store: VarStore, + pub var: Variable, +} pub struct CanExpr { parse_expr: ParseExpr, @@ -14,20 +39,18 @@ impl Default for CanExpr { } } -impl CanExpr { - pub fn can_expr<'a>(&'a self, input: &'a str) -> Result { - match self.parse_expr.parse_expr(input) { - Ok(ast) => { - // todo canonicalize AST and return that result. - let loc_expr = roc_parse::test_helpers::parse_loc_with(arena, expr_str).unwrap_or_else(|e| { - panic!( - "can_expr_with() got a parse error when attempting to canonicalize:\n\n{expr_str:?} {e:?}" - ) - }); +fn test_home() -> ModuleId { + ModuleIds::default().get_or_insert(&"Test".into()) +} +impl CanExpr { + pub fn can_expr<'a>(&'a self, input: &'a str) -> CanExprOut { + match self.parse_expr.parse_loc_expr(input) { + Ok(loc_expr) => { let mut var_store = VarStore::default(); let var = var_store.fresh(); let qualified_module_ids = PackageModuleIds::default(); + let home = test_home(); let mut scope = Scope::new( home, @@ -38,8 +61,8 @@ impl CanExpr { let dep_idents = IdentIds::exposed_builtins(0); let mut env = Env::new( - arena, - expr_str, + &self.arena(), + input, home, Path::new("Test.roc"), &dep_idents, @@ -95,7 +118,7 @@ impl CanExpr { } } Err(syntax_error) => { - // todo panic due to unexpected syntax error + panic!("Unexpected syntax error: {:?}", syntax_error); } } } @@ -103,4 +126,8 @@ impl CanExpr { pub fn into_arena(self) -> Bump { self.parse_expr.into_arena() } + + pub fn arena(&self) -> &Bump { + &self.parse_expr.arena() + } } diff --git a/crates/test_compile/src/help_parse.rs b/crates/test_compile/src/help_parse.rs index a7cb083370..081807b217 100644 --- a/crates/test_compile/src/help_parse.rs +++ b/crates/test_compile/src/help_parse.rs @@ -57,4 +57,8 @@ impl ParseExpr { pub fn into_arena(self) -> Bump { self.arena } + + pub fn arena(&self) -> &Bump { + &self.arena + } } From 2af00caa9c44065adb3fffff434457e46fb77715 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Fri, 1 Nov 2024 21:16:45 -0400 Subject: [PATCH 20/65] Add help_constrain, help_solve, help_specialize --- crates/test_compile/src/help_constrain.rs | 0 crates/test_compile/src/help_solve.rs | 0 crates/test_compile/src/help_specialize.rs | 0 crates/test_compile/src/lib.rs | 3 +++ 4 files changed, 3 insertions(+) create mode 100644 crates/test_compile/src/help_constrain.rs create mode 100644 crates/test_compile/src/help_solve.rs create mode 100644 crates/test_compile/src/help_specialize.rs diff --git a/crates/test_compile/src/help_constrain.rs b/crates/test_compile/src/help_constrain.rs new file mode 100644 index 0000000000..e69de29bb2 diff --git a/crates/test_compile/src/help_solve.rs b/crates/test_compile/src/help_solve.rs new file mode 100644 index 0000000000..e69de29bb2 diff --git a/crates/test_compile/src/help_specialize.rs b/crates/test_compile/src/help_specialize.rs new file mode 100644 index 0000000000..e69de29bb2 diff --git a/crates/test_compile/src/lib.rs b/crates/test_compile/src/lib.rs index 99b86a283d..e48558f97b 100644 --- a/crates/test_compile/src/lib.rs +++ b/crates/test_compile/src/lib.rs @@ -1,3 +1,6 @@ mod deindent; mod help_can; +mod help_constrain; mod help_parse; +mod help_solve; +mod help_specialize; From ed6ad1bc820545724349fe30ef179b87a0a2a6e4 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Thu, 7 Nov 2024 23:39:58 -0500 Subject: [PATCH 21/65] Get a failing specialize_expr test --- Cargo.lock | 7 + crates/compiler/can/Cargo.toml | 1 + crates/compiler/can/src/annotation.rs | 2 +- crates/compiler/can/src/def.rs | 4 +- crates/compiler/can/src/expected.rs | 4 +- crates/compiler/can/src/expr.rs | 16 +- crates/compiler/can/src/pattern.rs | 10 +- crates/compiler/can/tests/test_can_expr.rs | 13 + crates/compiler/collections/src/push.rs | 5 + crates/compiler/constrain/src/expr.rs | 10 + crates/compiler/load/tests/helpers/mod.rs | 41 +- crates/compiler/solve/Cargo.toml | 2 + crates/compiler/solve_problem/src/lib.rs | 2 +- crates/compiler/specialize_types/Cargo.toml | 2 + crates/compiler/specialize_types/src/lib.rs | 4 +- .../specialize_types/src/mono_expr.rs | 33 +- .../compiler/specialize_types/src/mono_ir.rs | 22 +- .../specialize_types/src/mono_module.rs | 5 +- .../compiler/specialize_types/src/mono_num.rs | 2 +- .../specialize_types/src/mono_type.rs | 1 + .../specialize_types/src/specialize_type.rs | 5 +- .../tests/test_specialize_expr.rs | 4894 +---------------- .../tests/test_specialize_types.rs | 14 +- crates/compiler/types/src/subs.rs | 30 +- crates/compiler/types/src/types.rs | 4 +- crates/test_compile/Cargo.toml | 4 + crates/test_compile/src/help_can.rs | 16 +- crates/test_compile/src/help_constrain.rs | 77 + crates/test_compile/src/help_solve.rs | 81 + crates/test_compile/src/help_specialize.rs | 58 + crates/test_compile/src/lib.rs | 17 + 31 files changed, 437 insertions(+), 4949 deletions(-) create mode 100644 crates/compiler/can/tests/test_can_expr.rs diff --git a/Cargo.lock b/Cargo.lock index a1b75ae205..76cff0437a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2345,6 +2345,7 @@ dependencies = [ "roc_serialize", "roc_types", "static_assertions", + "test_compile", "ven_pretty", ] @@ -3105,6 +3106,7 @@ dependencies = [ "roc_types", "roc_unify", "tempfile", + "test_compile", "test_solve_helpers", ] @@ -3154,6 +3156,7 @@ dependencies = [ "roc_types", "soa", "static_assertions", + "test_compile", "test_solve_helpers", ] @@ -3811,6 +3814,8 @@ dependencies = [ "pretty_assertions", "roc_builtins", "roc_can", + "roc_collections", + "roc_constrain", "roc_derive", "roc_load", "roc_module", @@ -3819,6 +3824,8 @@ dependencies = [ "roc_region", "roc_reporting", "roc_solve", + "roc_solve_problem", + "roc_specialize_types", "roc_target", "roc_types", "test_solve_helpers", diff --git a/crates/compiler/can/Cargo.toml b/crates/compiler/can/Cargo.toml index 76b115bd5a..cd05e59d1d 100644 --- a/crates/compiler/can/Cargo.toml +++ b/crates/compiler/can/Cargo.toml @@ -28,3 +28,4 @@ static_assertions.workspace = true indoc.workspace = true insta.workspace = true pretty_assertions.workspace = true +test_compile.workspace = true diff --git a/crates/compiler/can/src/annotation.rs b/crates/compiler/can/src/annotation.rs index 5279f9f7a9..61e51403c5 100644 --- a/crates/compiler/can/src/annotation.rs +++ b/crates/compiler/can/src/annotation.rs @@ -130,7 +130,7 @@ pub struct AbleVariable { pub first_seen: Region, } -#[derive(Clone, Debug, Default)] +#[derive(Clone, Debug, Default, PartialEq)] pub struct IntroducedVariables { pub wildcards: Vec>, pub lambda_sets: Vec, diff --git a/crates/compiler/can/src/def.rs b/crates/compiler/can/src/def.rs index c95b62bf8d..a08a28be8f 100644 --- a/crates/compiler/can/src/def.rs +++ b/crates/compiler/can/src/def.rs @@ -62,7 +62,7 @@ use std::io::Read; use std::path::PathBuf; use std::sync::Arc; -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub struct Def { pub loc_pattern: Loc, pub loc_expr: Loc, @@ -89,7 +89,7 @@ impl Def { } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub struct Annotation { pub signature: Type, pub introduced_variables: IntroducedVariables, diff --git a/crates/compiler/can/src/expected.rs b/crates/compiler/can/src/expected.rs index d1fae08746..1b6facae9e 100644 --- a/crates/compiler/can/src/expected.rs +++ b/crates/compiler/can/src/expected.rs @@ -2,7 +2,7 @@ use crate::pattern::Pattern; use roc_region::all::{Loc, Region}; use roc_types::types::{AnnotationSource, PReason, Reason}; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub enum Expected { NoExpectation(T), FromAnnotation(Loc, usize, AnnotationSource, T), @@ -10,7 +10,7 @@ pub enum Expected { } /// Like Expected, but for Patterns. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub enum PExpected { NoExpectation(T), ForReason(PReason, T, Region), diff --git a/crates/compiler/can/src/expr.rs b/crates/compiler/can/src/expr.rs index 219e610c4b..4106df908f 100644 --- a/crates/compiler/can/src/expr.rs +++ b/crates/compiler/can/src/expr.rs @@ -132,7 +132,7 @@ impl IntValue { } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub enum Expr { // Literals @@ -341,7 +341,7 @@ pub enum Expr { RuntimeError(RuntimeError), } -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] pub struct ExpectLookup { pub symbol: Symbol, pub var: Variable, @@ -419,7 +419,7 @@ impl Expr { /// Stores exhaustiveness-checking metadata for a closure argument that may /// have an annotated type. -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] pub struct AnnotatedMark { pub annotation_var: Variable, pub exhaustive: ExhaustiveMark, @@ -443,7 +443,7 @@ impl AnnotatedMark { } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub struct ClosureData { pub function_type: Variable, pub closure_type: Variable, @@ -536,7 +536,7 @@ impl StructAccessorData { /// An opaque wrapper like `@Foo`, which is equivalent to `\p -> @Foo p` /// These are desugared to closures, but we distinguish them so we can have /// better error messages during constraint generation. -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub struct OpaqueWrapFunctionData { pub opaque_name: Symbol, pub opaque_var: Variable, @@ -606,7 +606,7 @@ impl OpaqueWrapFunctionData { } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub struct Field { pub var: Variable, // The region of the full `foo: f bar`, rather than just `f bar` @@ -630,7 +630,7 @@ impl Recursive { } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub struct WhenBranchPattern { pub pattern: Loc, /// Degenerate branch patterns are those that don't fully bind symbols that the branch body @@ -639,7 +639,7 @@ pub struct WhenBranchPattern { pub degenerate: bool, } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub struct WhenBranch { pub patterns: Vec, pub value: Loc, diff --git a/crates/compiler/can/src/pattern.rs b/crates/compiler/can/src/pattern.rs index f1b59ef10a..b2c2ae1d3c 100644 --- a/crates/compiler/can/src/pattern.rs +++ b/crates/compiler/can/src/pattern.rs @@ -19,7 +19,7 @@ use roc_types::types::{LambdaSet, OptAbleVar, PatternCategory, Type}; /// A pattern, including possible problems (e.g. shadowing) so that /// codegen can generate a runtime error if this pattern is reached. -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub enum Pattern { Identifier(Symbol), As(Box>, Symbol), @@ -198,7 +198,7 @@ impl Pattern { } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub struct ListPatterns { pub patterns: Vec>, /// Where a rest pattern splits patterns before and after it, if it does at all. @@ -229,7 +229,7 @@ impl ListPatterns { } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub struct RecordDestruct { pub var: Variable, pub label: Lowercase, @@ -237,14 +237,14 @@ pub struct RecordDestruct { pub typ: DestructType, } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub struct TupleDestruct { pub var: Variable, pub destruct_index: usize, pub typ: (Variable, Loc), } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub enum DestructType { Required, Optional(Variable, Loc), diff --git a/crates/compiler/can/tests/test_can_expr.rs b/crates/compiler/can/tests/test_can_expr.rs new file mode 100644 index 0000000000..76d4a23078 --- /dev/null +++ b/crates/compiler/can/tests/test_can_expr.rs @@ -0,0 +1,13 @@ +#[cfg(test)] +mod test_can_expr { + use roc_can::expr::Expr; + use test_compile::can_expr; + + #[test] + fn test_can_unit() { + let output = can_expr("{}"); + + assert_eq!(output.problems, Vec::new()); + assert!(matches!(output.expr, Expr::EmptyRecord)); + } +} diff --git a/crates/compiler/collections/src/push.rs b/crates/compiler/collections/src/push.rs index 9ebd4d6ccc..1a05826416 100644 --- a/crates/compiler/collections/src/push.rs +++ b/crates/compiler/collections/src/push.rs @@ -7,3 +7,8 @@ impl Push for Vec { self.push(entry); } } +impl Push for &mut Vec { + fn push(&mut self, entry: T) { + (*self).push(entry); + } +} diff --git a/crates/compiler/constrain/src/expr.rs b/crates/compiler/constrain/src/expr.rs index 4961cb43db..c711592e84 100644 --- a/crates/compiler/constrain/src/expr.rs +++ b/crates/compiler/constrain/src/expr.rs @@ -60,6 +60,16 @@ pub struct Env { pub home: ModuleId, } +impl Env { + pub fn new(home: ModuleId) -> Self { + Self { + rigids: MutMap::default(), + resolutions_to_make: Vec::new(), + home, + } + } +} + fn constrain_untyped_args( types: &mut Types, constraints: &mut Constraints, diff --git a/crates/compiler/load/tests/helpers/mod.rs b/crates/compiler/load/tests/helpers/mod.rs index c3a6fff90b..5db6fce76c 100644 --- a/crates/compiler/load/tests/helpers/mod.rs +++ b/crates/compiler/load/tests/helpers/mod.rs @@ -3,7 +3,6 @@ extern crate bumpalo; use self::bumpalo::Bump; use roc_can::abilities::AbilitiesStore; use roc_can::constraint::{Constraint, Constraints}; -use roc_can::desugar; use roc_can::env::Env; use roc_can::expected::Expected; use roc_can::expr::{canonicalize_expr, Expr, Output, PendingDerives}; @@ -11,7 +10,7 @@ use roc_can::scope::Scope; use roc_collections::all::{ImMap, MutMap, SendSet}; use roc_constrain::expr::constrain_expr; use roc_derive::SharedDerivedModule; -use roc_module::symbol::{IdentIds, Interns, ModuleId, ModuleIds, PQModuleName, PackageModuleIds}; +use roc_module::symbol::{IdentIds, Interns, ModuleId, ModuleIds}; use roc_parse::parser::{SourceError, SyntaxError}; use roc_problem::can::Problem; use roc_region::all::Loc; @@ -51,8 +50,6 @@ pub fn infer_expr( exposed_by_module: &Default::default(), derived_module, function_kind: FunctionKind::LambdaSet, - module_params: None, - module_params_vars: Default::default(), #[cfg(debug_assertions)] checkmate: None, }; @@ -156,28 +153,10 @@ pub fn can_expr_with<'a>( let var = var_store.fresh(); let var_index = constraints.push_variable(var); let expected = constraints.push_expected_type(Expected::NoExpectation(var_index)); - let mut module_ids = PackageModuleIds::default(); + let mut module_ids = ModuleIds::default(); // ensure the Test module is accessible in our tests - module_ids.get_or_insert(&PQModuleName::Unqualified("Test".into())); - - let mut scope = Scope::new( - home, - "TestPath".into(), - IdentIds::default(), - Default::default(), - ); - - let dep_idents = IdentIds::exposed_builtins(0); - let mut env = Env::new( - arena, - expr_str, - home, - Path::new("Test.roc"), - &dep_idents, - &module_ids, - None, - ); + module_ids.get_or_insert(&"Test".into()); // Desugar operators (convert them to Apply calls, taking into account // operator precedence and associativity rules), before doing other canonicalization. @@ -186,8 +165,18 @@ pub fn can_expr_with<'a>( // 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 = desugar::desugar_expr(&mut env, &mut scope, &loc_expr); + let loc_expr = operator::desugar_expr( + arena, + &loc_expr, + expr_str, + &mut None, + arena.alloc("TestPath"), + ); + let mut scope = Scope::new(home, IdentIds::default(), Default::default()); + + let dep_idents = IdentIds::exposed_builtins(0); + let mut env = Env::new(arena, home, &dep_idents, &module_ids); let (loc_expr, output) = canonicalize_expr( &mut env, &mut var_store, @@ -213,7 +202,7 @@ pub fn can_expr_with<'a>( all_ident_ids.insert(home, scope.locals.ident_ids); let interns = Interns { - module_ids: env.qualified_module_ids.clone().into_module_ids(), + module_ids: env.module_ids.clone(), all_ident_ids, }; diff --git a/crates/compiler/solve/Cargo.toml b/crates/compiler/solve/Cargo.toml index 0ae89a53e1..4d368007db 100644 --- a/crates/compiler/solve/Cargo.toml +++ b/crates/compiler/solve/Cargo.toml @@ -43,3 +43,5 @@ lazy_static.workspace = true pretty_assertions.workspace = true regex.workspace = true tempfile.workspace = true + +test_compile.workspace = true diff --git a/crates/compiler/solve_problem/src/lib.rs b/crates/compiler/solve_problem/src/lib.rs index 9eabe601ba..22af38dd4b 100644 --- a/crates/compiler/solve_problem/src/lib.rs +++ b/crates/compiler/solve_problem/src/lib.rs @@ -11,7 +11,7 @@ use roc_region::all::Region; use roc_types::types::{Category, ErrorType, PatternCategory}; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub enum TypeError { BadExpr(Region, Category, ErrorType, Expected), BadPattern(Region, PatternCategory, ErrorType, PExpected), diff --git a/crates/compiler/specialize_types/Cargo.toml b/crates/compiler/specialize_types/Cargo.toml index eb076bf4cf..93c306122c 100644 --- a/crates/compiler/specialize_types/Cargo.toml +++ b/crates/compiler/specialize_types/Cargo.toml @@ -13,6 +13,7 @@ roc_region = { path = "../region" } roc_types = { path = "../types" } roc_collections = { path = "../collections" } roc_module = { path = "../module" } +roc_solve = { path = "../solve" } bitvec.workspace = true arrayvec.workspace = true @@ -35,4 +36,5 @@ roc_target = { path = "../roc_target" } roc_solve = { path = "../solve" } test_solve_helpers = { path = "../test_solve_helpers" } +test_compile.workspace = true pretty_assertions.workspace = true diff --git a/crates/compiler/specialize_types/src/lib.rs b/crates/compiler/specialize_types/src/lib.rs index 4439b342aa..03df00459b 100644 --- a/crates/compiler/specialize_types/src/lib.rs +++ b/crates/compiler/specialize_types/src/lib.rs @@ -12,8 +12,8 @@ mod specialize_type; pub use debug_info::DebugInfo; pub use foreign_symbol::{ForeignSymbolId, ForeignSymbols}; pub use mono_expr::Env; -pub use mono_ir::MonoExpr; +pub use mono_ir::{MonoExpr, MonoExprId, MonoExprs}; pub use mono_num::Number; pub use mono_struct::MonoFieldId; pub use mono_type::{MonoType, MonoTypeId, MonoTypes}; -pub use specialize_type::{MonoCache, RecordFieldIds, TupleElemIds}; +pub use specialize_type::{MonoCache, Problem, RecordFieldIds, TupleElemIds}; diff --git a/crates/compiler/specialize_types/src/mono_expr.rs b/crates/compiler/specialize_types/src/mono_expr.rs index 90c3b88606..4c8987b8d6 100644 --- a/crates/compiler/specialize_types/src/mono_expr.rs +++ b/crates/compiler/specialize_types/src/mono_expr.rs @@ -7,6 +7,7 @@ use crate::{ }; use roc_can::expr::{Expr, IntValue}; use roc_collections::Push; +use roc_solve::module::Solved; use roc_types::subs::Subs; pub struct Env<'c, 'd, 's, 't, P> { @@ -21,6 +22,28 @@ pub struct Env<'c, 'd, 's, 't, P> { } impl<'c, 'd, 's, 't, P: Push> Env<'c, 'd, 's, 't, P> { + pub fn new( + subs: &'s mut Solved, + types_cache: &'c mut MonoCache, + mono_types: &'t mut MonoTypes, + mono_exprs: &'t mut MonoExprs, + record_field_ids: RecordFieldIds, + tuple_elem_ids: TupleElemIds, + debug_info: &'d mut Option, + problems: P, + ) -> Self { + Env { + subs: subs.inner_mut(), + types_cache, + mono_types, + mono_exprs, + record_field_ids, + tuple_elem_ids, + debug_info, + problems, + } + } + pub fn to_mono_expr(&mut self, can_expr: Expr) -> Option { let problems = &mut self.problems; let mono_types = &mut self.mono_types; @@ -67,6 +90,10 @@ impl<'c, 'd, 's, 't, P: Push> Env<'c, 'd, 's, 't, P> { return compiler_bug!(Problem::NumSpecializedToWrongType(None)); } }, + Expr::EmptyRecord => { + // Empty records are zero-sized and should be discarded. + return None; + } _ => todo!(), // Expr::Float(var, _precision_var, _str, val, _bound) => { // match mono_from_var(var) { @@ -129,8 +156,10 @@ impl<'c, 'd, 's, 't, P: Push> Env<'c, 'd, 's, 't, P> { // ret_var, // } => todo!(), // Expr::Closure(closure_data) => todo!(), - // Expr::Record { record_var, fields } => todo!(), - // Expr::EmptyRecord => todo!(), + // Expr::Record { record_var, fields } => { + // // TODO *after* having converted to mono (dropping zero-sized fields), if no fields remain, then return None. + // todo!() + // } // Expr::Tuple { tuple_var, elems } => todo!(), // Expr::ImportParams(module_id, region, _) => todo!(), // Expr::Crash { msg, ret_var } => todo!(), diff --git a/crates/compiler/specialize_types/src/mono_ir.rs b/crates/compiler/specialize_types/src/mono_ir.rs index f86f71f2cd..0211f43ca3 100644 --- a/crates/compiler/specialize_types/src/mono_ir.rs +++ b/crates/compiler/specialize_types/src/mono_ir.rs @@ -8,14 +8,14 @@ use roc_module::low_level::LowLevel; use roc_module::symbol::Symbol; use soa::{Id, NonEmptySlice, Slice2, Slice3}; -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] pub struct MonoPatternId { inner: u32, } pub type IdentId = Symbol; // TODO make this an Index into an array local to this module -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] pub struct Def { pub pattern: MonoPatternId, /// Named variables in the pattern, e.g. `a` in `Ok a ->` @@ -30,6 +30,10 @@ pub struct MonoExprs { } impl MonoExprs { + pub fn new() -> Self { + Self { exprs: Vec::new() } + } + pub fn add(&mut self, expr: MonoExpr) -> MonoExprId { let index = self.exprs.len() as u32; self.exprs.push(expr); @@ -38,6 +42,16 @@ impl MonoExprs { inner: Id::new(index), } } + + pub fn get(&self, id: MonoExprId) -> &MonoExpr { + debug_assert!( + self.exprs.get(id.inner.index()).is_some(), + "A MonoExprId was not found in MonoExprs. This should never happen!" + ); + + // Safety: we should only ever hand out MonoExprIds that are valid indices into here. + unsafe { self.exprs.get_unchecked(id.inner.index() as usize) } + } } #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -45,7 +59,7 @@ pub struct MonoExprId { inner: Id, } -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] pub enum MonoExpr { Str, Number(Number), @@ -189,7 +203,7 @@ pub enum MonoExpr { CompilerBug(Problem), } -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] pub struct WhenBranch { pub patterns: Slice, pub body: MonoExprId, diff --git a/crates/compiler/specialize_types/src/mono_module.rs b/crates/compiler/specialize_types/src/mono_module.rs index e82454e691..bf7f4de1a9 100644 --- a/crates/compiler/specialize_types/src/mono_module.rs +++ b/crates/compiler/specialize_types/src/mono_module.rs @@ -1,3 +1,4 @@ +use roc_solve::module::Solved; use roc_types::subs::Subs; use crate::{foreign_symbol::ForeignSymbols, mono_type::MonoTypes, DebugInfo}; @@ -10,7 +11,7 @@ pub struct MonoModule { } impl MonoModule { - pub fn from_typed_can_module(subs: &Subs) -> Self { + pub fn from_typed_can_module(subs: &Solved) -> Self { Self { mono_types: MonoTypes::new(), foreign_symbols: ForeignSymbols::new(), @@ -20,5 +21,5 @@ impl MonoModule { } } -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct InternedStrId(u32); diff --git a/crates/compiler/specialize_types/src/mono_num.rs b/crates/compiler/specialize_types/src/mono_num.rs index e435f91a7a..70fa9150a9 100644 --- a/crates/compiler/specialize_types/src/mono_num.rs +++ b/crates/compiler/specialize_types/src/mono_num.rs @@ -1,6 +1,6 @@ use roc_can::expr::IntValue; -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] pub enum Number { I8(i8), U8(u8), diff --git a/crates/compiler/specialize_types/src/mono_type.rs b/crates/compiler/specialize_types/src/mono_type.rs index e7706109e7..0c9bbefe81 100644 --- a/crates/compiler/specialize_types/src/mono_type.rs +++ b/crates/compiler/specialize_types/src/mono_type.rs @@ -12,6 +12,7 @@ impl MonoTypeId { } } +#[derive(Debug)] pub struct MonoTypes { entries: Vec, ids: Vec, diff --git a/crates/compiler/specialize_types/src/specialize_type.rs b/crates/compiler/specialize_types/src/specialize_type.rs index 7503a51ba3..0622b1e495 100644 --- a/crates/compiler/specialize_types/src/specialize_type.rs +++ b/crates/compiler/specialize_types/src/specialize_type.rs @@ -10,6 +10,7 @@ use crate::{ }; use roc_collections::{Push, VecMap}; use roc_module::ident::{Lowercase, TagName}; +use roc_solve::module::Solved; use roc_types::subs::{ Content, FlatType, RecordFields, Subs, TagExt, TupleElems, UnionLabels, UnionTags, Variable, }; @@ -44,9 +45,9 @@ pub struct MonoCache { } impl MonoCache { - pub fn from_subs(subs: &Subs) -> Self { + pub fn from_subs(subs: &Solved) -> Self { Self { - inner: VecMap::with_capacity(subs.len()), + inner: VecMap::with_capacity(subs.inner().len()), } } diff --git a/crates/compiler/specialize_types/tests/test_specialize_expr.rs b/crates/compiler/specialize_types/tests/test_specialize_expr.rs index 4e1941cc88..040a87b5b2 100644 --- a/crates/compiler/specialize_types/tests/test_specialize_expr.rs +++ b/crates/compiler/specialize_types/tests/test_specialize_expr.rs @@ -1,4889 +1,41 @@ #[macro_use] extern crate pretty_assertions; -#[macro_use] -extern crate indoc; extern crate bumpalo; #[cfg(test)] mod specialize_types { - use roc_load::LoadedModule; - use roc_solve::FunctionKind; - use roc_specialize_types::{ - DebugInfo, MonoCache, MonoExpr, MonoTypes, RecordFieldIds, TupleElemIds, - }; - use test_solve_helpers::{format_problems, run_load_and_infer}; + use roc_specialize_types::{MonoExpr, Number}; + use test_compile::specialize_expr; - use roc_types::pretty_print::{name_and_print_var, DebugPrint}; + fn expect_no_expr(input: impl AsRef) { + let out = specialize_expr(input.as_ref()); + let actual = out.mono_expr_id.map(|id| out.mono_exprs.get(id)); - // HELPERS - - fn infer_eq_help(src: &str) -> Result<(String, String, MonoExpr, String), std::io::Error> { - let ( - LoadedModule { - module_id: home, - mut can_problems, - mut type_problems, - interns, - mut solved, - mut exposed_to_host, - abilities_store, - .. - }, - src, - ) = run_load_and_infer(src, [], false, FunctionKind::LambdaSet)?; - - let mut can_problems = can_problems.remove(&home).unwrap_or_default(); - let type_problems = type_problems.remove(&home).unwrap_or_default(); - - // Disregard UnusedDef problems, because those are unavoidable when - // returning a function from the test expression. - can_problems.retain(|prob| { - !matches!( - prob, - roc_problem::can::Problem::UnusedDef(_, _) - | roc_problem::can::Problem::UnusedBranchDef(..) - ) - }); - - let (can_problems, type_problems) = - format_problems(&src, home, &interns, can_problems, type_problems); - - let subs = solved.inner_mut(); - - exposed_to_host.retain(|s, _| !abilities_store.is_specialization_name(*s)); - - debug_assert!(exposed_to_host.len() == 1, "{exposed_to_host:?}"); - let (symbol, variable) = exposed_to_host.into_iter().next().unwrap(); - let mut mono_cache = MonoCache::from_subs(subs); - let mut mono_types = MonoTypes::new(); - let debug_info = DebugInfo::new(); - let mut record_field_ids = RecordFieldIds::new(); - let mut tuple_elem_ids = TupleElemIds::new(); - let mut problems = Vec::new(); - - mono_cache.monomorphize_var( - subs, - &mut mono_types, - &mut record_field_ids, - &mut tuple_elem_ids, - &mut problems, - &mut Some(debug_info), - variable, - ); - - assert_eq!(problems, Vec::new()); - - let type_str = name_and_print_var(variable, subs, home, &interns, DebugPrint::NOTHING); - - Ok((type_problems, can_problems, mono_expr, type_str)) + assert_eq!(None, actual, "This input expr should have specialized to being dicarded as zero-sized, but it didn't: {:?}", input.as_ref()); } - fn specializes_to(src: &str, expected_expr: MonoExpr, expected_type_str: &str) { - let (_, can_problems, actual_expr, actual_type_str) = infer_eq_help(src).unwrap(); + fn expect_mono_expr(input: impl AsRef, mono_expr: MonoExpr) { + let out = specialize_expr(input.as_ref()); + let mono_expr_id = out + .mono_expr_id + .expect("This input expr should not have been discarded as zero-sized, but it was discarded: {input:?}"); - assert!( - can_problems.is_empty(), - "Canonicalization problems: {can_problems}" - ); - - assert_eq!( - (expected_expr, expected_type_str), - (actual_expr, &actual_type_str) - ); - } - - fn infer_eq_without_problem(src: &str, expected: &str) { - let (type_problems, can_problems, actual) = infer_eq_help(src).unwrap(); - - assert!( - can_problems.is_empty(), - "Canonicalization problems: {can_problems}" - ); - - if !type_problems.is_empty() { - // fail with an assert, but print the problems normally so rust doesn't try to diff - // an empty vec with the problems. - panic!("expected:\n{expected:?}\ninferred:\n{actual:?}\nproblems:\n{type_problems}",); - } - assert_eq!(actual, expected.to_string()); + assert_eq!(&mono_expr, out.mono_exprs.get(mono_expr_id)); } #[test] - fn str_literal_solo() { - specializes_to("\"test\"", MonoExpr::Str); + fn empty_record() { + let todo = (); // TODO need to get earlier stage working, specifically constraint + solve, by replicating existing solve tests. + expect_no_expr("{}"); } - // #[test] - // fn int_literal_solo() { - // specializes_to("5", "Num []"); - // } - - // #[test] - // fn frac_literal_solo() { - // specializes_to("0.5", "Frac []"); - // } - - // #[test] - // fn dec_literal() { - // specializes_to( - // indoc!( - // r" - // val : Dec - // val = 1.2 - - // val - // " - // ), - // "Dec", - // ); - // } - - // #[test] - // fn str_starts_with() { - // specializes_to("Str.startsWith", "Str, Str -> Bool"); - // } - - // #[test] - // fn str_from_int() { - // infer_eq_without_problem("Num.toStr", "Num [] -> Str"); - // } - - // #[test] - // fn str_from_utf8() { - // infer_eq_without_problem( - // "Str.fromUtf8", - // "List U8 -> Result Str [BadUtf8 Utf8ByteProblem U64]", - // ); - // } - - // #[test] - // fn list_concat_utf8() { - // infer_eq_without_problem("List.concatUtf8", "List U8, Str -> List U8") - // } - - // // LIST - - // #[test] - // fn empty_list() { - // specializes_to("[]", "List []"); - // } - - // #[test] - // fn list_of_lists() { - // specializes_to("[[]]", "List (List [])"); - // } - - // #[test] - // fn triple_nested_list() { - // specializes_to("[[[]]]", "List (List (List []))"); - // } - - // #[test] - // fn nested_empty_list() { - // specializes_to("[[], [[]]]", "List (List (List []))"); - // } - - // #[test] - // fn list_of_one_int() { - // specializes_to("[42]", "List (Num [])"); - // } - - // #[test] - // fn triple_nested_int_list() { - // specializes_to("[[[5]]]", "List (List (List (Num [])))"); - // } - - // #[test] - // fn list_of_ints() { - // specializes_to("[1, 2, 3]", "List (Num [])"); - // } - - // #[test] - // fn nested_list_of_ints() { - // specializes_to("[[1], [2, 3]]", "List (List (Num []))"); - // } - - // #[test] - // fn list_of_one_string() { - // specializes_to(r#"["cowabunga"]"#, "List Str"); - // } - - // #[test] - // fn triple_nested_string_list() { - // specializes_to(r#"[[["foo"]]]"#, "List (List (List Str))"); - // } - - // #[test] - // fn list_of_strings() { - // specializes_to(r#"["foo", "bar"]"#, "List Str"); - // } - - // // INTERPOLATED STRING - - // #[test] - // fn infer_interpolated_string() { - // specializes_to( - // indoc!( - // r#" - // whatItIs = "great" - - // "type inference is $(whatItIs)!" - // "# - // ), - // "Str", - // ); - // } - - // #[test] - // fn infer_interpolated_var() { - // specializes_to( - // indoc!( - // r#" - // whatItIs = "great" - - // str = "type inference is $(whatItIs)!" - - // whatItIs - // "# - // ), - // "Str", - // ); - // } - - // #[test] - // fn infer_interpolated_field() { - // specializes_to( - // indoc!( - // r#" - // rec = { whatItIs: "great" } - - // str = "type inference is $(rec.whatItIs)!" - - // rec - // "# - // ), - // "{ whatItIs : Str }", - // ); - // } - - // // LIST MISMATCH - - // #[test] - // fn mismatch_heterogeneous_list() { - // specializes_to( - // indoc!( - // r#" - // ["foo", 5] - // "# - // ), - // "List ", - // ); - // } - - // #[test] - // fn mismatch_heterogeneous_nested_list() { - // specializes_to( - // indoc!( - // r#" - // [["foo", 5]] - // "# - // ), - // "List (List )", - // ); - // } - - // #[test] - // fn mismatch_heterogeneous_nested_empty_list() { - // specializes_to( - // indoc!( - // r" - // [[1], [[]]] - // " - // ), - // "List ", - // ); - // } - - // // CLOSURE - - // #[test] - // fn always_return_empty_record() { - // specializes_to( - // indoc!( - // r" - // \_ -> {} - // " - // ), - // "[] -> {}", - // ); - // } - - // #[test] - // fn two_arg_return_int() { - // specializes_to( - // indoc!( - // r" - // \_, _ -> 42 - // " - // ), - // "[], [] -> Num []", - // ); - // } - - // #[test] - // fn three_arg_return_string() { - // specializes_to( - // indoc!( - // r#" - // \_, _, _ -> "test!" - // "# - // ), - // "[], [], [] -> Str", - // ); - // } - - // // DEF - - // #[test] - // fn def_empty_record() { - // specializes_to( - // indoc!( - // r" - // foo = {} - - // foo - // " - // ), - // "{}", - // ); - // } - - // #[test] - // fn def_string() { - // specializes_to( - // indoc!( - // r#" - // str = "thing" - - // str - // "# - // ), - // "Str", - // ); - // } - - // #[test] - // fn def_1_arg_closure() { - // specializes_to( - // indoc!( - // r" - // fn = \_ -> {} - - // fn - // " - // ), - // "[] -> {}", - // ); - // } - - // #[test] - // fn applied_tag() { - // infer_eq_without_problem( - // indoc!( - // r#" - // List.map ["a", "b"] \elem -> Foo elem - // "# - // ), - // "List [Foo Str]", - // ) - // } - - // // Tests (TagUnion, Func) - // #[test] - // fn applied_tag_function() { - // infer_eq_without_problem( - // indoc!( - // r#" - // foo = Foo - - // foo "hi" - // "# - // ), - // "[Foo Str]", - // ) - // } - - // // Tests (TagUnion, Func) - // #[test] - // fn applied_tag_function_list_map() { - // infer_eq_without_problem( - // indoc!( - // r#" - // List.map ["a", "b"] Foo - // "# - // ), - // "List [Foo Str]", - // ) - // } - - // // Tests (TagUnion, Func) - // #[test] - // fn applied_tag_function_list() { - // infer_eq_without_problem( - // indoc!( - // r" - // [\x -> Bar x, Foo] - // " - // ), - // "List ([] -> [Bar [], Foo []])", - // ) - // } - - // // Tests (Func, TagUnion) - // #[test] - // fn applied_tag_function_list_other_way() { - // infer_eq_without_problem( - // indoc!( - // r" - // [Foo, \x -> Bar x] - // " - // ), - // "List ([] -> [Bar [], Foo []])", - // ) - // } - - // // Tests (Func, TagUnion) - // #[test] - // fn applied_tag_function_record() { - // infer_eq_without_problem( - // indoc!( - // r" - // foo0 = Foo - // foo1 = Foo - // foo2 = Foo - - // { - // x: [foo0, Foo], - // y: [foo1, \x -> Foo x], - // z: [foo2, \x,y -> Foo x y] - // } - // " - // ), - // "{ x : List [Foo], y : List ([] -> [Foo []]), z : List ([], [] -> [Foo [] []]) }", - // ) - // } - - // // Tests (TagUnion, Func) - // #[test] - // fn applied_tag_function_with_annotation() { - // infer_eq_without_problem( - // indoc!( - // r" - // x : List [Foo I64] - // x = List.map [1, 2] Foo - - // x - // " - // ), - // "List [Foo I64]", - // ) - // } - - // #[test] - // fn def_2_arg_closure() { - // specializes_to( - // indoc!( - // r" - // func = \_, _ -> 42 - - // func - // " - // ), - // "[], [] -> Num []", - // ); - // } - - // #[test] - // fn def_3_arg_closure() { - // specializes_to( - // indoc!( - // r#" - // f = \_, _, _ -> "test!" - - // f - // "# - // ), - // "[], [], [] -> Str", - // ); - // } - - // #[test] - // fn def_multiple_functions() { - // specializes_to( - // indoc!( - // r#" - // a = \_, _, _ -> "test!" - - // b = a - - // b - // "# - // ), - // "[], [], [] -> Str", - // ); - // } - - // #[test] - // fn def_multiple_strings() { - // specializes_to( - // indoc!( - // r#" - // a = "test!" - - // b = a - - // b - // "# - // ), - // "Str", - // ); - // } - - // #[test] - // fn def_multiple_ints() { - // specializes_to( - // indoc!( - // r" - // c = b - - // b = a - - // a = 42 - - // c - // " - // ), - // "Num []", - // ); - // } - - // #[test] - // fn def_returning_closure() { - // specializes_to( - // indoc!( - // r" - // f = \z -> z - // g = \z -> z - - // (\x -> - // a = f x - // b = g x - // x - // ) - // " - // ), - // "[] -> []", - // ); - // } - - // // CALLING FUNCTIONS - - // #[test] - // fn call_returns_int() { - // specializes_to( - // indoc!( - // r#" - // alwaysFive = \_ -> 5 - - // alwaysFive "stuff" - // "# - // ), - // "Num []", - // ); - // } - - // #[test] - // fn identity_returns_given_type() { - // specializes_to( - // indoc!( - // r#" - // identity = \a -> a - - // identity "hi" - // "# - // ), - // "Str", - // ); - // } - - // #[test] - // fn identity_infers_principal_type() { - // specializes_to( - // indoc!( - // r" - // identity = \x -> x - - // y = identity 5 - - // identity - // " - // ), - // "[] -> []", - // ); - // } - - // #[test] - // fn identity_works_on_incompatible_types() { - // specializes_to( - // indoc!( - // r#" - // identity = \a -> a - - // x = identity 5 - // y = identity "hi" - - // x - // "# - // ), - // "Num []", - // ); - // } - - // #[test] - // fn call_returns_list() { - // specializes_to( - // indoc!( - // r" - // enlist = \val -> [val] - - // enlist 5 - // " - // ), - // "List (Num [])", - // ); - // } - - // #[test] - // fn indirect_always() { - // specializes_to( - // indoc!( - // r#" - // always = \val -> (\_ -> val) - // alwaysFoo = always "foo" - - // alwaysFoo 42 - // "# - // ), - // "Str", - // ); - // } - - // #[test] - // fn pizza_desugar() { - // specializes_to( - // indoc!( - // r" - // 1 |> (\a -> a) - // " - // ), - // "Num []", - // ); - // } - - // #[test] - // fn pizza_desugar_two_arguments() { - // specializes_to( - // indoc!( - // r#" - // always2 = \a, _ -> a - - // 1 |> always2 "foo" - // "# - // ), - // "Num []", - // ); - // } - - // #[test] - // fn anonymous_identity() { - // specializes_to( - // indoc!( - // r" - // (\a -> a) 3.14 - // " - // ), - // "Frac []", - // ); - // } - - // #[test] - // fn identity_of_identity() { - // specializes_to( - // indoc!( - // r" - // (\val -> val) (\val -> val) - // " - // ), - // "[] -> []", - // ); - // } - - // #[test] - // fn recursive_identity() { - // specializes_to( - // indoc!( - // r" - // identity = \val -> val - - // identity identity - // " - // ), - // "[] -> []", - // ); - // } - - // #[test] - // fn identity_function() { - // specializes_to( - // indoc!( - // r" - // \val -> val - // " - // ), - // "[] -> []", - // ); - // } - - // #[test] - // fn use_apply() { - // specializes_to( - // indoc!( - // r" - // identity = \a -> a - // apply = \f, x -> f x - - // apply identity 5 - // " - // ), - // "Num []", - // ); - // } - - // #[test] - // fn apply_function() { - // specializes_to( - // indoc!( - // r" - // \f, x -> f x - // " - // ), - // "([] -> []), [] -> []", - // ); - // } - - // // #[test] - // // TODO FIXME this should pass, but instead fails to canonicalize - // // fn use_flip() { - // // infer_eq( - // // indoc!( - // // r" - // // flip = \f -> (\a b -> f b a) - // // neverendingInt = \f int -> f int - // // x = neverendingInt (\a -> a) 5 - - // // flip neverendingInt - // // " - // // ), - // // "(Num [], (a -> a)) -> Num []", - // // ); - // // } - - // #[test] - // fn flip_function() { - // specializes_to( - // indoc!( - // r" - // \f -> (\a, b -> f b a) - // " - // ), - // "([], [] -> []) -> ([], [] -> [])", - // ); - // } - - // #[test] - // fn always_function() { - // specializes_to( - // indoc!( - // r" - // \val -> \_ -> val - // " - // ), - // "[] -> ([] -> [])", - // ); - // } - - // #[test] - // fn pass_a_function() { - // specializes_to( - // indoc!( - // r" - // \f -> f {} - // " - // ), - // "({} -> []) -> []", - // ); - // } - - // // OPERATORS - - // // #[test] - // // fn div_operator() { - // // infer_eq( - // // indoc!( - // // r" - // // \l r -> l / r - // // " - // // ), - // // "F64, F64 -> F64", - // // ); - // // } - - // // #[test] - // // fn basic_float_division() { - // // infer_eq( - // // indoc!( - // // r" - // // 1 / 2 - // // " - // // ), - // // "F64", - // // ); - // // } - - // // #[test] - // // fn basic_int_division() { - // // infer_eq( - // // indoc!( - // // r" - // // 1 // 2 - // // " - // // ), - // // "Num []", - // // ); - // // } - - // // #[test] - // // fn basic_addition() { - // // infer_eq( - // // indoc!( - // // r" - // // 1 + 2 - // // " - // // ), - // // "Num []", - // // ); - // // } - - // // #[test] - // // fn basic_circular_type() { - // // infer_eq( - // // indoc!( - // // r" - // // \x -> x x - // // " - // // ), - // // "", - // // ); - // // } - - // // #[test] - // // fn y_combinator_has_circular_type() { - // // assert_eq!( - // // infer(indoc!(r" - // // \f -> (\x -> f x x) (\x -> f x x) - // // ")), - // // Erroneous(Problem::CircularType) - // // ); - // // } - - // // #[test] - // // fn no_higher_ranked_types() { - // // // This should error because it can't type of alwaysFive - // // infer_eq( - // // indoc!( - // // r#" - // // \always -> [always [], always ""] - // // "# - // // ), - // // "", - // // ); - // // } - - // #[test] - // fn always_with_list() { - // specializes_to( - // indoc!( - // r#" - // alwaysFive = \_ -> 5 - - // [alwaysFive "foo", alwaysFive []] - // "# - // ), - // "List (Num [])", - // ); - // } - - // #[test] - // fn if_with_int_literals() { - // specializes_to( - // indoc!( - // r" - // if Bool.true then - // 42 - // else - // 24 - // " - // ), - // "Num []", - // ); - // } - - // #[test] - // fn when_with_int_literals() { - // specializes_to( - // indoc!( - // r" - // when 1 is - // 1 -> 2 - // 3 -> 4 - // " - // ), - // "Num []", - // ); - // } - - // // RECORDS - - // #[test] - // fn empty_record() { - // specializes_to("{}", "{}"); - // } - - // #[test] - // fn one_field_record() { - // specializes_to("{ x: 5 }", "{ x : Num [] }"); - // } - - // #[test] - // fn two_field_record() { - // specializes_to("{ x: 5, y : 3.14 }", "{ x : Num [], y : Frac [] }"); - // } - - // #[test] - // fn record_literal_accessor() { - // specializes_to("{ x: 5, y : 3.14 }.x", "Num []"); - // } - - // #[test] - // fn record_literal_accessor_function() { - // specializes_to(".x { x: 5, y : 3.14 }", "Num []"); - // } - - // #[test] - // fn tuple_literal_accessor() { - // specializes_to("(5, 3.14 ).0", "Num []"); - // } - - // #[test] - // fn tuple_literal_accessor_function() { - // specializes_to(".0 (5, 3.14 )", "Num []"); - // } - - // // #[test] - // // fn tuple_literal_ty() { - // // specializes_to("(5, 3.14 )", "( Num [], Frac [] )"); - // // } - - // // #[test] - // // fn tuple_literal_accessor_ty() { - // // specializes_to(".0", "( [] ) -> []"); - // // specializes_to(".4", "( _, _, _, _, [] ) -> []"); - // // specializes_to(".5", "( ... 5 omitted, [] ) -> []"); - // // specializes_to(".200", "( ... 200 omitted, [] ) -> []"); - // // } - - // #[test] - // fn tuple_accessor_generalization() { - // specializes_to( - // indoc!( - // r#" - // get0 = .0 - - // { a: get0 (1, 2), b: get0 ("a", "b", "c") } - // "# - // ), - // "{ a : Num [], b : Str }", - // ); - // } - - // #[test] - // fn record_arg() { - // specializes_to("\\rec -> rec.x", "{ x : [] } -> []"); - // } - - // #[test] - // fn record_with_bound_var() { - // specializes_to( - // indoc!( - // r" - // fn = \rec -> - // x = rec.x - - // rec - - // fn - // " - // ), - // "{ x : [] } -> { x : [] }", - // ); - // } - - // #[test] - // fn using_type_signature() { - // specializes_to( - // indoc!( - // r" - // bar : custom -> custom - // bar = \x -> x - - // bar - // " - // ), - // "[] -> []", - // ); - // } - - // #[test] - // fn type_signature_without_body() { - // specializes_to( - // indoc!( - // r#" - // foo: Str -> {} - - // foo "hi" - // "# - // ), - // "{}", - // ); - // } - - // #[test] - // fn type_signature_without_body_rigid() { - // specializes_to( - // indoc!( - // r" - // foo : Num * -> custom - - // foo 2 - // " - // ), - // "[]", - // ); - // } - - // #[test] - // fn accessor_function() { - // specializes_to(".foo", "{ foo : [] } -> []"); - // } - - // #[test] - // fn type_signature_without_body_record() { - // specializes_to( - // indoc!( - // r" - // { x, y } : { x : ({} -> custom), y : {} } - - // x - // " - // ), - // "{} -> []", - // ); - // } - - // #[test] - // fn empty_record_pattern() { - // specializes_to( - // indoc!( - // r" - // # technically, an empty record can be destructured - // thunk = \{} -> 42 - - // xEmpty = if thunk {} == 42 then { x: {} } else { x: {} } - - // when xEmpty is - // { x: {} } -> {} - // " - // ), - // "{}", - // ); - // } - - // #[test] - // fn record_type_annotation() { - // // check that a closed record remains closed - // specializes_to( - // indoc!( - // r" - // foo : { x : custom } -> custom - // foo = \{ x } -> x - - // foo - // " - // ), - // "{ x : [] } -> []", - // ); - // } - - // #[test] - // fn record_update() { - // specializes_to( - // indoc!( - // r#" - // user = { year: "foo", name: "Sam" } - - // { user & year: "foo" } - // "# - // ), - // "{ name : Str, year : Str }", - // ); - // } - - // #[test] - // fn bare_tag() { - // specializes_to( - // indoc!( - // r" - // Foo - // " - // ), - // "[Foo]", - // ); - // } - - // #[test] - // fn single_tag_pattern() { - // specializes_to( - // indoc!( - // r" - // \Foo -> 42 - // " - // ), - // "[Foo] -> Num []", - // ); - // } - - // #[test] - // fn two_tag_pattern() { - // specializes_to( - // indoc!( - // r" - // \x -> - // when x is - // True -> 1 - // False -> 0 - // " - // ), - // "[False, True] -> Num []", - // ); - // } - - // #[test] - // fn tag_application() { - // specializes_to( - // indoc!( - // r#" - // Foo "happy" 12 - // "# - // ), - // "[Foo Str (Num [])]", - // ); - // } - - // #[test] - // fn record_extraction() { - // specializes_to( - // indoc!( - // r" - // f = \x -> - // when x is - // { a, b: _ } -> a - - // f - // " - // ), - // "{ a : [], b : [] } -> []", - // ); - // } - - // #[test] - // fn record_field_pattern_match_with_guard() { - // specializes_to( - // indoc!( - // r" - // when { x: 5 } is - // { x: 4 } -> 4 - // " - // ), - // "Num []", - // ); - // } - - // #[test] - // fn tag_union_pattern_match() { - // specializes_to( - // indoc!( - // r" - // \Foo x -> Foo x - // " - // ), - // "[Foo []] -> [Foo []]", - // ); - // } - - // #[test] - // fn tag_union_pattern_match_ignored_field() { - // specializes_to( - // indoc!( - // r#" - // \Foo x _ -> Foo x "y" - // "# - // ), - // "[Foo [] []] -> [Foo [] Str]", - // ); - // } - - // #[test] - // fn tag_with_field() { - // specializes_to( - // indoc!( - // r#" - // when Foo "blah" is - // Foo x -> x - // "# - // ), - // "Str", - // ); - // } - - // #[test] - // fn qualified_annotation_num_integer() { - // specializes_to( - // indoc!( - // r" - // int : Num.Num (Num.Integer Num.Signed64) - - // int - // " - // ), - // "I64", - // ); - // } - // #[test] - // fn qualified_annotated_num_integer() { - // specializes_to( - // indoc!( - // r" - // int : Num.Num (Num.Integer Num.Signed64) - // int = 5 - - // int - // " - // ), - // "I64", - // ); - // } - // #[test] - // fn annotation_num_integer() { - // specializes_to( - // indoc!( - // r" - // int : Num (Integer Signed64) - - // int - // " - // ), - // "I64", - // ); - // } - // #[test] - // fn annotated_num_integer() { - // specializes_to( - // indoc!( - // r" - // int : Num (Integer Signed64) - // int = 5 - - // int - // " - // ), - // "I64", - // ); - // } - - // #[test] - // fn qualified_annotation_using_i128() { - // specializes_to( - // indoc!( - // r" - // int : Num.I128 - - // int - // " - // ), - // "I128", - // ); - // } - // #[test] - // fn qualified_annotated_using_i128() { - // specializes_to( - // indoc!( - // r" - // int : Num.I128 - // int = 5 - - // int - // " - // ), - // "I128", - // ); - // } - // #[test] - // fn annotation_using_i128() { - // specializes_to( - // indoc!( - // r" - // int : I128 - - // int - // " - // ), - // "I128", - // ); - // } - // #[test] - // fn annotated_using_i128() { - // specializes_to( - // indoc!( - // r" - // int : I128 - // int = 5 - - // int - // " - // ), - // "I128", - // ); - // } - - // #[test] - // fn qualified_annotation_using_u128() { - // specializes_to( - // indoc!( - // r" - // int : Num.U128 - - // int - // " - // ), - // "U128", - // ); - // } - // #[test] - // fn qualified_annotated_using_u128() { - // specializes_to( - // indoc!( - // r" - // int : Num.U128 - // int = 5 - - // int - // " - // ), - // "U128", - // ); - // } - // #[test] - // fn annotation_using_u128() { - // specializes_to( - // indoc!( - // r" - // int : U128 - - // int - // " - // ), - // "U128", - // ); - // } - // #[test] - // fn annotated_using_u128() { - // specializes_to( - // indoc!( - // r" - // int : U128 - // int = 5 - - // int - // " - // ), - // "U128", - // ); - // } - - // #[test] - // fn qualified_annotation_using_i64() { - // specializes_to( - // indoc!( - // r" - // int : Num.I64 - - // int - // " - // ), - // "I64", - // ); - // } - // #[test] - // fn qualified_annotated_using_i64() { - // specializes_to( - // indoc!( - // r" - // int : Num.I64 - // int = 5 - - // int - // " - // ), - // "I64", - // ); - // } - // #[test] - // fn annotation_using_i64() { - // specializes_to( - // indoc!( - // r" - // int : I64 - - // int - // " - // ), - // "I64", - // ); - // } - // #[test] - // fn annotated_using_i64() { - // specializes_to( - // indoc!( - // r" - // int : I64 - // int = 5 - - // int - // " - // ), - // "I64", - // ); - // } - - // #[test] - // fn qualified_annotation_using_u64() { - // specializes_to( - // indoc!( - // r" - // int : Num.U64 - - // int - // " - // ), - // "U64", - // ); - // } - // #[test] - // fn qualified_annotated_using_u64() { - // specializes_to( - // indoc!( - // r" - // int : Num.U64 - // int = 5 - - // int - // " - // ), - // "U64", - // ); - // } - // #[test] - // fn annotation_using_u64() { - // specializes_to( - // indoc!( - // r" - // int : U64 - - // int - // " - // ), - // "U64", - // ); - // } - // #[test] - // fn annotated_using_u64() { - // specializes_to( - // indoc!( - // r" - // int : U64 - // int = 5 - - // int - // " - // ), - // "U64", - // ); - // } - - // #[test] - // fn qualified_annotation_using_i32() { - // specializes_to( - // indoc!( - // r" - // int : Num.I32 - - // int - // " - // ), - // "I32", - // ); - // } - // #[test] - // fn qualified_annotated_using_i32() { - // specializes_to( - // indoc!( - // r" - // int : Num.I32 - // int = 5 - - // int - // " - // ), - // "I32", - // ); - // } - // #[test] - // fn annotation_using_i32() { - // specializes_to( - // indoc!( - // r" - // int : I32 - - // int - // " - // ), - // "I32", - // ); - // } - // #[test] - // fn annotated_using_i32() { - // specializes_to( - // indoc!( - // r" - // int : I32 - // int = 5 - - // int - // " - // ), - // "I32", - // ); - // } - - // #[test] - // fn qualified_annotation_using_u32() { - // specializes_to( - // indoc!( - // r" - // int : Num.U32 - - // int - // " - // ), - // "U32", - // ); - // } - // #[test] - // fn qualified_annotated_using_u32() { - // specializes_to( - // indoc!( - // r" - // int : Num.U32 - // int = 5 - - // int - // " - // ), - // "U32", - // ); - // } - // #[test] - // fn annotation_using_u32() { - // specializes_to( - // indoc!( - // r" - // int : U32 - - // int - // " - // ), - // "U32", - // ); - // } - // #[test] - // fn annotated_using_u32() { - // specializes_to( - // indoc!( - // r" - // int : U32 - // int = 5 - - // int - // " - // ), - // "U32", - // ); - // } - - // #[test] - // fn qualified_annotation_using_i16() { - // specializes_to( - // indoc!( - // r" - // int : Num.I16 - - // int - // " - // ), - // "I16", - // ); - // } - // #[test] - // fn qualified_annotated_using_i16() { - // specializes_to( - // indoc!( - // r" - // int : Num.I16 - // int = 5 - - // int - // " - // ), - // "I16", - // ); - // } - // #[test] - // fn annotation_using_i16() { - // specializes_to( - // indoc!( - // r" - // int : I16 - - // int - // " - // ), - // "I16", - // ); - // } - // #[test] - // fn annotated_using_i16() { - // specializes_to( - // indoc!( - // r" - // int : I16 - // int = 5 - - // int - // " - // ), - // "I16", - // ); - // } - - // #[test] - // fn qualified_annotation_using_u16() { - // specializes_to( - // indoc!( - // r" - // int : Num.U16 - - // int - // " - // ), - // "U16", - // ); - // } - // #[test] - // fn qualified_annotated_using_u16() { - // specializes_to( - // indoc!( - // r" - // int : Num.U16 - // int = 5 - - // int - // " - // ), - // "U16", - // ); - // } - // #[test] - // fn annotation_using_u16() { - // specializes_to( - // indoc!( - // r" - // int : U16 - - // int - // " - // ), - // "U16", - // ); - // } - // #[test] - // fn annotated_using_u16() { - // specializes_to( - // indoc!( - // r" - // int : U16 - // int = 5 - - // int - // " - // ), - // "U16", - // ); - // } - - // #[test] - // fn qualified_annotation_using_i8() { - // specializes_to( - // indoc!( - // r" - // int : Num.I8 - - // int - // " - // ), - // "I8", - // ); - // } - // #[test] - // fn qualified_annotated_using_i8() { - // specializes_to( - // indoc!( - // r" - // int : Num.I8 - // int = 5 - - // int - // " - // ), - // "I8", - // ); - // } - // #[test] - // fn annotation_using_i8() { - // specializes_to( - // indoc!( - // r" - // int : I8 - - // int - // " - // ), - // "I8", - // ); - // } - // #[test] - // fn annotated_using_i8() { - // specializes_to( - // indoc!( - // r" - // int : I8 - // int = 5 - - // int - // " - // ), - // "I8", - // ); - // } - - // #[test] - // fn qualified_annotation_using_u8() { - // specializes_to( - // indoc!( - // r" - // int : Num.U8 - - // int - // " - // ), - // "U8", - // ); - // } - // #[test] - // fn qualified_annotated_using_u8() { - // specializes_to( - // indoc!( - // r" - // int : Num.U8 - // int = 5 - - // int - // " - // ), - // "U8", - // ); - // } - // #[test] - // fn annotation_using_u8() { - // specializes_to( - // indoc!( - // r" - // int : U8 - - // int - // " - // ), - // "U8", - // ); - // } - // #[test] - // fn annotated_using_u8() { - // specializes_to( - // indoc!( - // r" - // int : U8 - // int = 5 - - // int - // " - // ), - // "U8", - // ); - // } - - // #[test] - // fn qualified_annotation_num_floatingpoint() { - // specializes_to( - // indoc!( - // r" - // float : Num.Num (Num.FloatingPoint Num.Binary64) - - // float - // " - // ), - // "F64", - // ); - // } - // #[test] - // fn qualified_annotated_num_floatingpoint() { - // specializes_to( - // indoc!( - // r" - // float : Num.Num (Num.FloatingPoint Num.Binary64) - // float = 5.5 - - // float - // " - // ), - // "F64", - // ); - // } - // #[test] - // fn annotation_num_floatingpoint() { - // specializes_to( - // indoc!( - // r" - // float : Num (FloatingPoint Binary64) - - // float - // " - // ), - // "F64", - // ); - // } - // #[test] - // fn annotated_num_floatingpoint() { - // specializes_to( - // indoc!( - // r" - // float : Num (FloatingPoint Binary64) - // float = 5.5 - - // float - // " - // ), - // "F64", - // ); - // } - - // #[test] - // fn qualified_annotation_f64() { - // specializes_to( - // indoc!( - // r" - // float : Num.F64 - - // float - // " - // ), - // "F64", - // ); - // } - // #[test] - // fn qualified_annotated_f64() { - // specializes_to( - // indoc!( - // r" - // float : Num.F64 - // float = 5.5 - - // float - // " - // ), - // "F64", - // ); - // } - // #[test] - // fn annotation_f64() { - // specializes_to( - // indoc!( - // r" - // float : F64 - - // float - // " - // ), - // "F64", - // ); - // } - // #[test] - // fn annotated_f64() { - // specializes_to( - // indoc!( - // r" - // float : F64 - // float = 5.5 - - // float - // " - // ), - // "F64", - // ); - // } - - // #[test] - // fn qualified_annotation_f32() { - // specializes_to( - // indoc!( - // r" - // float : Num.F32 - - // float - // " - // ), - // "F32", - // ); - // } - // #[test] - // fn qualified_annotated_f32() { - // specializes_to( - // indoc!( - // r" - // float : Num.F32 - // float = 5.5 - - // float - // " - // ), - // "F32", - // ); - // } - // #[test] - // fn annotation_f32() { - // specializes_to( - // indoc!( - // r" - // float : F32 - - // float - // " - // ), - // "F32", - // ); - // } - // #[test] - // fn annotated_f32() { - // specializes_to( - // indoc!( - // r" - // float : F32 - // float = 5.5 - - // float - // " - // ), - // "F32", - // ); - // } - - // #[test] - // fn fake_result_ok() { - // specializes_to( - // indoc!( - // r" - // Res a e : [Okay a, Error e] - - // ok : Res I64 * - // ok = Okay 5 - - // ok - // " - // ), - // "Res I64 []", - // ); - // } - - // #[test] - // fn fake_result_err() { - // specializes_to( - // indoc!( - // r#" - // Res a e : [Okay a, Error e] - - // err : Res * Str - // err = Error "blah" - - // err - // "# - // ), - // "Res [] Str", - // ); - // } - - // #[test] - // fn basic_result_ok() { - // specializes_to( - // indoc!( - // r" - // ok : Result I64 * - // ok = Ok 5 - - // ok - // " - // ), - // "Result I64 []", - // ); - // } - - // #[test] - // fn basic_result_err() { - // specializes_to( - // indoc!( - // r#" - // err : Result * Str - // err = Err "blah" - - // err - // "# - // ), - // "Result [] Str", - // ); - // } - - // #[test] - // fn basic_result_conditional() { - // specializes_to( - // indoc!( - // r#" - // ok : Result I64 _ - // ok = Ok 5 - - // err : Result _ Str - // err = Err "blah" - - // if 1 > 0 then - // ok - // else - // err - // "# - // ), - // "Result I64 Str", - // ); - // } - - // // #[test] - // // fn annotation_using_num_used() { - // // // There was a problem where `I64`, because it is only an annotation - // // // wasn't added to the vars_by_symbol. - // // infer_eq_without_problem( - // // indoc!( - // // r" - // // int : I64 - - // // p = (\x -> x) int - - // // p - // // " - // // ), - // // "I64", - // // ); - // // } - - // #[test] - // fn num_identity() { - // infer_eq_without_problem( - // indoc!( - // r" - // numIdentity : Num.Num a -> Num.Num a - // numIdentity = \x -> x - - // y = numIdentity 3.14 - - // { numIdentity, x : numIdentity 42, y } - // " - // ), - // "{ numIdentity : Num [] -> Num [], x : Num [], y : Frac [] }", - // ); - // } - - // #[test] - // fn when_with_annotation() { - // infer_eq_without_problem( - // indoc!( - // r" - // x : Num.Num (Num.Integer Num.Signed64) - // x = - // when 2 is - // 3 -> 4 - // _ -> 5 - - // x - // " - // ), - // "I64", - // ); - // } - - // // TODO add more realistic function when able - // #[test] - // fn integer_sum() { - // infer_eq_without_problem( - // indoc!( - // r" - // f = \n -> - // when n is - // 0 -> 0 - // _ -> f n - - // f - // " - // ), - // "Num [] -> Num []", - // ); - // } - - // #[test] - // fn identity_map() { - // 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 - // " - // ), - // "([] -> []), [Identity []] -> [Identity []]", - // ); - // } - - // #[test] - // fn to_bit() { - // infer_eq_without_problem( - // indoc!( - // r" - // toBit = \bool -> - // when bool is - // True -> 1 - // False -> 0 - - // toBit - // " - // ), - // "[False, True] -> Num []", - // ); - // } - - // // this test is related to a bug where ext_var would have an incorrect rank. - // // This match has duplicate cases, but we ignore that. - // #[test] - // fn to_bit_record() { - // specializes_to( - // indoc!( - // r#" - // foo = \rec -> - // when rec is - // { x: _ } -> "1" - // { y: _ } -> "2" - - // foo - // "# - // ), - // "{ x : [], y : [] } -> Str", - // ); - // } - - // #[test] - // fn from_bit() { - // infer_eq_without_problem( - // indoc!( - // r" - // fromBit = \int -> - // when int is - // 0 -> False - // _ -> True - - // fromBit - // " - // ), - // "Num [] -> [False, True]", - // ); - // } - - // #[test] - // fn result_map_explicit() { - // infer_eq_without_problem( - // indoc!( - // r" - // map : (a -> b), [Err e, Ok a] -> [Err e, Ok b] - // map = \f, result -> - // when result is - // Ok v -> Ok (f v) - // Err e -> Err e - - // map - // " - // ), - // "([] -> []), [Err [], Ok []] -> [Err [], Ok []]", - // ); - // } - - // #[test] - // fn result_map_alias() { - // infer_eq_without_problem( - // indoc!( - // r" - // Res e a : [Ok a, Err e] - - // map : (a -> b), Res e a -> Res e b - // map = \f, result -> - // when result is - // Ok v -> Ok (f v) - // Err e -> Err e - - // map - // " - // ), - // "([] -> []), Res [] [] -> Res [] []", - // ); - // } - - // #[test] - // fn record_from_load() { - // infer_eq_without_problem( - // indoc!( - // r" - // foo = \{ x } -> x - - // foo { x: 5 } - // " - // ), - // "Num []", - // ); - // } - - // #[test] - // fn defs_from_load() { - // infer_eq_without_problem( - // indoc!( - // r" - // alwaysThreePointZero = \_ -> 3.0 - - // answer = 42 - - // identity = \a -> a - - // threePointZero = identity (alwaysThreePointZero {}) - - // threePointZero - // " - // ), - // "Frac []", - // ); - // } - - // #[test] - // fn use_as_in_signature() { - // infer_eq_without_problem( - // indoc!( - // r#" - // foo : Str.Str as Foo -> Foo - // foo = \_ -> "foo" - - // foo - // "# - // ), - // "Foo -> Foo", - // ); - // } - - // #[test] - // fn use_alias_in_let() { - // infer_eq_without_problem( - // indoc!( - // r#" - // Foo : Str.Str - - // foo : Foo -> Foo - // foo = \_ -> "foo" - - // foo - // "# - // ), - // "Foo -> Foo", - // ); - // } - - // #[test] - // fn use_alias_with_argument_in_let() { - // infer_eq_without_problem( - // indoc!( - // r" - // Foo a : { foo : a } - - // v : Foo (Num.Num (Num.Integer Num.Signed64)) - // v = { foo: 42 } - - // v - // " - // ), - // "Foo I64", - // ); - // } - - // #[test] - // fn identity_alias() { - // infer_eq_without_problem( - // indoc!( - // r" - // Foo a : { foo : a } - - // id : Foo a -> Foo a - // id = \x -> x - - // id - // " - // ), - // "Foo [] -> Foo []", - // ); - // } - - // #[test] - // fn linked_list_empty() { - // infer_eq_without_problem( - // indoc!( - // r" - // empty : [Cons a (ConsList a), Nil] as ConsList a - // empty = Nil - - // empty - // " - // ), - // "ConsList []", - // ); - // } - - // #[test] - // fn linked_list_singleton() { - // infer_eq_without_problem( - // indoc!( - // r" - // singleton : a -> [Cons a (ConsList a), Nil] as ConsList a - // singleton = \x -> Cons x Nil - - // singleton - // " - // ), - // "[] -> ConsList []", - // ); - // } - - // #[test] - // fn peano_length() { - // infer_eq_without_problem( - // indoc!( - // r" - // Peano : [S Peano, Z] - - // length : Peano -> Num.Num (Num.Integer Num.Signed64) - // length = \peano -> - // when peano is - // Z -> 0 - // S v -> length v - - // length - // " - // ), - // "Peano -> I64", - // ); - // } - - // #[test] - // fn peano_map() { - // infer_eq_without_problem( - // indoc!( - // r" - // map : [S Peano, Z] as Peano -> Peano - // map = \peano -> - // when peano is - // Z -> Z - // S v -> S (map v) - - // map - // " - // ), - // "Peano -> Peano", - // ); - // } - - // // #[test] - // // fn infer_linked_list_map() { - // // infer_eq_without_problem( - // // indoc!( - // // r" - // // map = \f, list -> - // // when list is - // // Nil -> Nil - // // Cons x xs -> - // // a = f x - // // b = map f xs - - // // Cons a b - - // // map - // // " - // // ), - // // "([] -> []), [Cons [] c, Nil] as c -> [Cons [] d, Nil] as d", - // // ); - // // } - - // // #[test] - // // fn typecheck_linked_list_map() { - // // infer_eq_without_problem( - // // indoc!( - // // r" - // // ConsList a := [Cons a (ConsList a), Nil] - - // // map : (a -> b), ConsList a -> ConsList b - // // map = \f, list -> - // // when list is - // // Nil -> Nil - // // Cons x xs -> - // // Cons (f x) (map f xs) - - // // map - // // " - // // ), - // // "([] -> []), ConsList [] -> ConsList []", - // // ); - // // } - - // #[test] - // fn mismatch_in_alias_args_gets_reported() { - // specializes_to( - // indoc!( - // r#" - // Foo a : a - - // r : Foo {} - // r = {} - - // s : Foo Str.Str - // s = "bar" - - // when {} is - // _ -> s - // _ -> r - // "# - // ), - // "", - // ); - // } - - // #[test] - // fn mismatch_in_apply_gets_reported() { - // specializes_to( - // indoc!( - // r" - // r : { x : (Num.Num (Num.Integer Signed64)) } - // r = { x : 1 } - - // s : { left : { x : Num.Num (Num.FloatingPoint Num.Binary64) } } - // s = { left: { x : 3.14 } } - - // when 0 is - // 1 -> s.left - // 0 -> r - // " - // ), - // "", - // ); - // } - - // #[test] - // fn mismatch_in_tag_gets_reported() { - // specializes_to( - // indoc!( - // r" - // r : [Ok Str.Str] - // r = Ok 1 - - // s : { left: [Ok {}] } - // s = { left: Ok 3.14 } - - // when 0 is - // 1 -> s.left - // 0 -> r - // " - // ), - // "", - // ); - // } - - // // TODO As intended, this fails, but it fails with the wrong error! - // // - // // #[test] - // // fn nums() { - // // infer_eq_without_problem( - // // indoc!( - // // r" - // // s : Num * - // // s = 3.1 - - // // s - // // " - // // ), - // // "", - // // ); - // // } - - // #[test] - // fn peano_map_alias() { - // specializes_to( - // indoc!( - // r#" - // app "test" provides [main] to "./platform" - - // Peano : [S Peano, Z] - - // map : Peano -> Peano - // map = \peano -> - // when peano is - // Z -> Z - // S rest -> S (map rest) - - // main = - // map - // "# - // ), - // "Peano -> Peano", - // ); - // } - - // #[test] - // fn unit_alias() { - // specializes_to( - // indoc!( - // r" - // Unit : [Unit] - - // unit : Unit - // unit = Unit - - // unit - // " - // ), - // "Unit", - // ); - // } - - // #[test] - // fn rigid_in_letnonrec() { - // infer_eq_without_problem( - // indoc!( - // r" - // ConsList a : [Cons a (ConsList a), Nil] - - // toEmpty : ConsList a -> ConsList a - // toEmpty = \_ -> - // result : ConsList a - // result = Nil - - // result - - // toEmpty - // " - // ), - // "ConsList [] -> ConsList []", - // ); - // } - - // #[test] - // fn rigid_in_letrec_ignored() { - // infer_eq_without_problem( - // indoc!( - // r" - // ConsList a : [Cons a (ConsList a), Nil] - - // toEmpty : ConsList a -> ConsList a - // toEmpty = \_ -> - // result : ConsList _ # TODO to enable using `a` we need scoped variables - // result = Nil - - // toEmpty result - - // toEmpty - // " - // ), - // "ConsList [] -> ConsList []", - // ); - // } - - // #[test] - // fn rigid_in_letrec() { - // infer_eq_without_problem( - // indoc!( - // r#" - // app "test" provides [main] to "./platform" - - // ConsList a : [Cons a (ConsList a), Nil] - - // toEmpty : ConsList a -> ConsList a - // toEmpty = \_ -> - // result : ConsList _ # TODO to enable using `a` we need scoped variables - // result = Nil - - // toEmpty result - - // main = - // toEmpty - // "# - // ), - // "ConsList [] -> ConsList []", - // ); - // } - - // #[test] - // fn let_record_pattern_with_annotation() { - // infer_eq_without_problem( - // indoc!( - // r#" - // { x, y } : { x : Str.Str, y : Num.Num (Num.FloatingPoint Num.Binary64) } - // { x, y } = { x : "foo", y : 3.14 } - - // x - // "# - // ), - // "Str", - // ); - // } - - // #[test] - // fn let_record_pattern_with_annotation_alias() { - // specializes_to( - // indoc!( - // r#" - // Foo : { x : Str.Str, y : Num.Num (Num.FloatingPoint Num.Binary64) } - - // { x, y } : Foo - // { x, y } = { x : "foo", y : 3.14 } - - // x - // "# - // ), - // "Str", - // ); - // } - - // // #[test] - // // fn peano_map_infer() { - // // specializes_to( - // // indoc!( - // // r#" - // // app "test" provides [main] to "./platform" - - // // map = - // // \peano -> - // // when peano is - // // Z -> Z - // // S rest -> map rest |> S - - // // main = - // // map - // // "# - // // ), - // // "[S a, Z] as a -> [S b, Z] as b", - // // ); - // // } - - // // #[test] - // // fn peano_map_infer_nested() { - // // specializes_to( - // // indoc!( - // // r" - // // map = \peano -> - // // when peano is - // // Z -> Z - // // S rest -> - // // map rest |> S - - // // map - // // " - // // ), - // // "[S a, Z] as a -> [S b, Z] as b", - // // ); - // // } - - // #[test] - // fn let_record_pattern_with_alias_annotation() { - // infer_eq_without_problem( - // indoc!( - // r#" - // Foo : { x : Str.Str, y : Num.Num (Num.FloatingPoint Num.Binary64) } - - // { x, y } : Foo - // { x, y } = { x : "foo", y : 3.14 } - - // x - // "# - // ), - // "Str", - // ); - // } - - // #[test] - // fn let_tag_pattern_with_annotation() { - // infer_eq_without_problem( - // indoc!( - // r" - // UserId x : [UserId I64] - // UserId x = UserId 42 - - // x - // " - // ), - // "I64", - // ); - // } - - // #[test] - // fn typecheck_record_linked_list_map() { - // infer_eq_without_problem( - // indoc!( - // r" - // ConsList q : [Cons { x: q, xs: ConsList q }, Nil] - - // map : (a -> b), ConsList a -> ConsList b - // map = \f, list -> - // when list is - // Nil -> Nil - // Cons { x, xs } -> - // Cons { x: f x, xs : map f xs } - - // map - // " - // ), - // "([] -> []), ConsList [] -> ConsList []", - // ); - // } - - // // #[test] - // // fn infer_record_linked_list_map() { - // // infer_eq_without_problem( - // // indoc!( - // // r" - // // map = \f, list -> - // // when list is - // // Nil -> Nil - // // Cons { x, xs } -> - // // Cons { x: f x, xs : map f xs } - - // // map - // // " - // // ), - // // "(a -> b), [Cons { x : a, xs : c }, Nil] as c -> [Cons { x : b, xs : d }, Nil] as d", - // // ); - // // } - - // // #[test] - // // fn typecheck_mutually_recursive_tag_union_2() { - // // infer_eq_without_problem( - // // indoc!( - // // r" - // // ListA a b := [Cons a (ListB b a), Nil] - // // ListB a b := [Cons a (ListA b a), Nil] - - // // ConsList q : [Cons q (ConsList q), Nil] - - // // toAs : (b -> a), ListA a b -> ConsList a - // // toAs = \f, @ListA lista -> - // // when lista is - // // Nil -> Nil - // // Cons a (@ListB listb) -> - // // when listb is - // // Nil -> Nil - // // Cons b (@ListA newLista) -> - // // Cons a (Cons (f b) (toAs f newLista)) - - // // toAs - // // " - // // ), - // // "([] -> []), ListA [] [] -> ConsList []", - // // ); - // // } - - // #[test] - // fn typecheck_mutually_recursive_tag_union_listabc() { - // infer_eq_without_problem( - // indoc!( - // r" - // ListA a : [Cons a (ListB a)] - // ListB a : [Cons a (ListC a)] - // ListC a : [Cons a (ListA a), Nil] - - // val : ListC Num.I64 - // val = Cons 1 (Cons 2 (Cons 3 Nil)) - - // val - // " - // ), - // "ListC I64", - // ); - // } - - // // #[test] - // // fn infer_mutually_recursive_tag_union() { - // // infer_eq_without_problem( - // // indoc!( - // // r" - // // toAs = \f, lista -> - // // when lista is - // // Nil -> Nil - // // Cons a listb -> - // // when listb is - // // Nil -> Nil - // // Cons b newLista -> - // // Cons a (Cons (f b) (toAs f newLista)) - - // // toAs - // // " - // // ), - // // "(a -> b), [Cons c [Cons a d, Nil], Nil] as d -> [Cons c [Cons b e], Nil] as e", - // // ); - // // } - - // #[test] - // fn solve_list_get() { - // infer_eq_without_problem( - // indoc!( - // r#" - // List.get ["a"] 0 - // "# - // ), - // "Result Str [OutOfBounds]", - // ); - // } - - // #[test] - // fn type_more_general_than_signature() { - // infer_eq_without_problem( - // indoc!( - // r" - // partition : U64, U64, List (Int a) -> [Pair U64 (List (Int a))] - // partition = \low, high, initialList -> - // when List.get initialList high is - // Ok _ -> - // Pair 0 [] - - // Err _ -> - // Pair (low - 1) initialList - - // partition - // " - // ), - // "U64, U64, List (Int []) -> [Pair U64 (List (Int []))]", - // ); - // } - - // #[test] - // fn quicksort_partition() { - // infer_eq_without_problem( - // indoc!( - // r" - // swap : U64, U64, List a -> List a - // swap = \i, j, list -> - // when Pair (List.get list i) (List.get list j) is - // Pair (Ok atI) (Ok atJ) -> - // list - // |> List.set i atJ - // |> List.set j atI - - // _ -> - // list - - // partition : U64, U64, List (Int a) -> [Pair U64 (List (Int a))] - // partition = \low, high, initialList -> - // when List.get initialList high is - // Ok pivot -> - // go = \i, j, list -> - // if j < high then - // when List.get list j is - // Ok value -> - // if value <= pivot then - // go (i + 1) (j + 1) (swap (i + 1) j list) - // else - // go i (j + 1) list - - // Err _ -> - // Pair i list - // else - // Pair i list - - // when go (low - 1) low initialList is - // Pair newI newList -> - // Pair (newI + 1) (swap (newI + 1) high newList) - - // Err _ -> - // Pair (low - 1) initialList - - // partition - // " - // ), - // "U64, U64, List (Int []) -> [Pair U64 (List (Int []))]", - // ); - // } - - // #[test] - // fn identity_list() { - // infer_eq_without_problem( - // indoc!( - // r" - // idList : List a -> List a - // idList = \list -> list - - // foo : List I64 -> List I64 - // foo = \initialList -> idList initialList - - // foo - // " - // ), - // "List I64 -> List I64", - // ); - // } - - // #[test] - // fn list_get() { - // infer_eq_without_problem( - // indoc!( - // r" - // List.get [10, 9, 8, 7] 1 - // " - // ), - // "Result (Num []) [OutOfBounds]", - // ); - - // infer_eq_without_problem( - // indoc!( - // r" - // List.get - // " - // ), - // "List [], U64 -> Result [] [OutOfBounds]", - // ); - // } - - // #[test] - // fn use_rigid_twice() { - // infer_eq_without_problem( - // indoc!( - // r" - // id1 : q -> q - // id1 = \x -> x - - // id2 : q -> q - // id2 = \x -> x - - // { id1, id2 } - // " - // ), - // "{ id1 : [] -> [], id2 : [] -> [] }", - // ); - // } - - // #[test] - // fn map_insert() { - // infer_eq_without_problem("Dict.insert", "Dict [] [], [], [] -> Dict [] []"); - // } - - // #[test] - // fn num_to_frac() { - // infer_eq_without_problem("Num.toFrac", "Num [] -> Frac []"); - // } - - // #[test] - // fn pow() { - // infer_eq_without_problem("Num.pow", "Frac [], Frac [] -> Frac []"); - // } - - // #[test] - // fn ceiling() { - // infer_eq_without_problem("Num.ceiling", "Frac [] -> Int []"); - // } - - // #[test] - // fn floor() { - // infer_eq_without_problem("Num.floor", "Frac [] -> Int []"); - // } - - // #[test] - // fn div() { - // infer_eq_without_problem("Num.div", "Frac [], Frac [] -> Frac []") - // } - - // #[test] - // fn div_checked() { - // infer_eq_without_problem( - // "Num.divChecked", - // "Frac [], Frac [] -> Result (Frac []) [DivByZero]", - // ) - // } - - // #[test] - // fn div_ceil() { - // infer_eq_without_problem("Num.divCeil", "Int [], Int [] -> Int []"); - // } - - // #[test] - // fn div_ceil_checked() { - // infer_eq_without_problem( - // "Num.divCeilChecked", - // "Int [], Int [] -> Result (Int []) [DivByZero]", - // ); - // } - - // #[test] - // fn div_trunc() { - // infer_eq_without_problem("Num.divTrunc", "Int [], Int [] -> Int []"); - // } - - // #[test] - // fn div_trunc_checked() { - // infer_eq_without_problem( - // "Num.divTruncChecked", - // "Int [], Int [] -> Result (Int []) [DivByZero]", - // ); - // } - - // #[test] - // fn atan() { - // infer_eq_without_problem("Num.atan", "Frac [] -> Frac []"); - // } - - // #[test] - // fn min_i128() { - // infer_eq_without_problem("Num.minI128", "I128"); - // } - - // #[test] - // fn max_i128() { - // infer_eq_without_problem("Num.maxI128", "I128"); - // } - - // #[test] - // fn min_i64() { - // infer_eq_without_problem("Num.minI64", "I64"); - // } - - // #[test] - // fn max_i64() { - // infer_eq_without_problem("Num.maxI64", "I64"); - // } - - // #[test] - // fn min_u64() { - // infer_eq_without_problem("Num.minU64", "U64"); - // } - - // #[test] - // fn max_u64() { - // infer_eq_without_problem("Num.maxU64", "U64"); - // } - - // #[test] - // fn min_i32() { - // infer_eq_without_problem("Num.minI32", "I32"); - // } - - // #[test] - // fn max_i32() { - // infer_eq_without_problem("Num.maxI32", "I32"); - // } - - // #[test] - // fn min_u32() { - // infer_eq_without_problem("Num.minU32", "U32"); - // } - - // #[test] - // fn max_u32() { - // infer_eq_without_problem("Num.maxU32", "U32"); - // } - - // // #[test] - // // fn reconstruct_path() { - // // infer_eq_without_problem( - // // indoc!( - // // r" - // // reconstructPath : Dict position position, position -> List position where position implements Hash & Eq - // // reconstructPath = \cameFrom, goal -> - // // when Dict.get cameFrom goal is - // // Err KeyNotFound -> - // // [] - - // // Ok next -> - // // List.append (reconstructPath cameFrom next) goal - - // // reconstructPath - // // " - // // ), - // // "Dict position position, position -> List position where position implements Hash & Eq", - // // ); - // // } - - // #[test] - // fn use_correct_ext_record() { - // // Related to a bug solved in 81fbab0b3fe4765bc6948727e603fc2d49590b1c - // infer_eq_without_problem( - // indoc!( - // r" - // f = \r -> - // g = r.q - // h = r.p - - // 42 - - // f - // " - // ), - // "{ p : [], q : [] } -> Num []", - // ); - // } - - // // #[test] - // // fn use_correct_ext_tag_union() { - // // // related to a bug solved in 08c82bf151a85e62bce02beeed1e14444381069f - // // infer_eq_without_problem( - // // indoc!( - // // r#" - // // app "test" imports [] provides [main] to "./platform" - - // // boom = \_ -> boom {} - - // // Model position : { openSet : Set position } - - // // cheapestOpen : Model position -> Result position [KeyNotFound] where position implements Hash & Eq - // // cheapestOpen = \model -> - - // // folder = \resSmallestSoFar, position -> - // // when resSmallestSoFar is - // // Err _ -> resSmallestSoFar - // // Ok smallestSoFar -> - // // if position == smallestSoFar.position then resSmallestSoFar - - // // else - // // Ok { position, cost: 0.0 } - - // // Set.walk model.openSet (Ok { position: boom {}, cost: 0.0 }) folder - // // |> Result.map (\x -> x.position) - - // // astar : Model position -> Result position [KeyNotFound] where position implements Hash & Eq - // // astar = \model -> cheapestOpen model - - // // main = - // // astar - // // "# - // // ), - // // "Model position -> Result position [KeyNotFound] where position implements Hash & Eq", - // // ); - // // } - - // #[test] - // fn when_with_or_pattern_and_guard() { - // infer_eq_without_problem( - // indoc!( - // r" - // \x -> - // when x is - // 2 | 3 -> 0 - // a if a < 20 -> 1 - // 3 | 4 if Bool.false -> 2 - // _ -> 3 - // " - // ), - // "Num [] -> Num []", - // ); - // } - - // #[test] - // fn sorting() { - // // based on https://github.com/elm/compiler/issues/2057 - // // Roc seems to do this correctly, tracking to make sure it stays that way - // infer_eq_without_problem( - // indoc!( - // r" - // sort : ConsList cm -> ConsList cm - // sort = - // \xs -> - // f : cm, cm -> Order - // f = \_, _ -> LT - - // sortWith f xs - - // sortBy : (x -> cmpl), ConsList x -> ConsList x - // sortBy = - // \_, list -> - // cmp : x, x -> Order - // cmp = \_, _ -> LT - - // sortWith cmp list - - // always = \x, _ -> x - - // sortWith : (foobar, foobar -> Order), ConsList foobar -> ConsList foobar - // sortWith = - // \_, list -> - // f = \arg -> - // g arg - - // g = \bs -> - // when bs is - // bx -> f bx - - // always Nil (f list) - - // Order : [LT, GT, EQ] - // ConsList a : [Nil, Cons a (ConsList a)] - - // { x: sortWith, y: sort, z: sortBy } - // " - // ), - // "{ x : ([], [] -> Order), ConsList [] -> ConsList [], y : ConsList [] -> ConsList [], z : ([] -> []), ConsList [] -> ConsList [] }" - // ); - // } - - // // Like in elm, this test now fails. Polymorphic recursion (even with an explicit signature) - // // yields a type error. - // // - // // We should at some point investigate why that is. Elm did support polymorphic recursion in - // // earlier versions. - // // - // // #[test] - // // fn wrapper() { - // // // based on https://github.com/elm/compiler/issues/1964 - // // // Roc seems to do this correctly, tracking to make sure it stays that way - // // infer_eq_without_problem( - // // indoc!( - // // r" - // // Type a : [TypeCtor (Type (Wrapper a))] - // // - // // Wrapper a : [Wrapper a] - // // - // // Opaque : [Opaque] - // // - // // encodeType1 : Type a -> Opaque - // // encodeType1 = \thing -> - // // when thing is - // // TypeCtor v0 -> - // // encodeType1 v0 - // // - // // encodeType1 - // // " - // // ), - // // "Type a -> Opaque", - // // ); - // // } - - // #[test] - // fn rigids() { - // infer_eq_without_problem( - // indoc!( - // r" - // f : List a -> List a - // f = \input -> - // # let-polymorphism at work - // x : {} -> List b - // x = \{} -> [] - - // when List.get input 0 is - // Ok val -> List.append (x {}) val - // Err _ -> input - // f - // " - // ), - // "List [] -> List []", - // ); - // } - - // #[cfg(debug_assertions)] - // #[test] - // #[should_panic] - // fn rigid_record_quantification() { - // // the ext here is qualified on the outside (because we have rank 1 types, not rank 2). - // // That means e.g. `f : { bar : String, foo : I64 } -> Bool }` is a valid argument, but - // // that function could not be applied to the `{ foo : I64 }` list. Therefore, this function - // // is not allowed. - // // - // // should hit a debug_assert! in debug mode, and produce a type error in release mode - // infer_eq_without_problem( - // indoc!( - // r" - // test : ({ foo : I64 }ext -> Bool), { foo : I64 } -> Bool - // test = \fn, a -> fn a - - // test - // " - // ), - // "should fail", - // ); - // } - - // // OPTIONAL RECORD FIELDS - - // #[test] - // fn optional_field_unifies_with_missing() { - // infer_eq_without_problem( - // indoc!( - // r" - // negatePoint : { x : I64, y : I64, z ? Num c } -> { x : I64, y : I64, z : Num c } - - // negatePoint { x: 1, y: 2 } - // " - // ), - // "{ x : I64, y : I64, z : Num [] }", - // ); - // } - - // // #[test] - // // fn open_optional_field_unifies_with_missing() { - // // infer_eq_without_problem( - // // indoc!( - // // r#" - // // negatePoint : { x : I64, y : I64, z ? Num c }r -> { x : I64, y : I64, z : Num c }r - - // // a = negatePoint { x: 1, y: 2 } - // // b = negatePoint { x: 1, y: 2, blah : "hi" } - - // // { a, b } - // // "# - // // ), - // // "{ a : { x : I64, y : I64, z : Num [] }, b : { blah : Str, x : I64, y : I64, z : Num [] } }", - // // ); - // // } - - // #[test] - // fn optional_field_unifies_with_present() { - // infer_eq_without_problem( - // indoc!( - // r" - // negatePoint : { x : Num a, y : Num b, z ? c } -> { x : Num a, y : Num b, z : c } - - // negatePoint { x: 1, y: 2.1, z: 0x3 } - // " - // ), - // "{ x : Num [], y : Frac [], z : Int [] }", - // ); - // } - - // // #[test] - // // fn open_optional_field_unifies_with_present() { - // // infer_eq_without_problem( - // // indoc!( - // // r#" - // // negatePoint : { x : Num a, y : Num b, z ? c }r -> { x : Num a, y : Num b, z : c }r - - // // a = negatePoint { x: 1, y: 2.1 } - // // b = negatePoint { x: 1, y: 2.1, blah : "hi" } - - // // { a, b } - // // "# - // // ), - // // "{ a : { x : Num [], y : Frac [], z : c }, b : { blah : Str, x : Num [], y : Frac [], z : c1 } }", - // // ); - // // } - - // #[test] - // fn optional_field_function() { - // infer_eq_without_problem( - // indoc!( - // r" - // \{ x, y ? 0 } -> x + y - // " - // ), - // "{ x : Num [], y ? Num [] } -> Num []", - // ); - // } - - // #[test] - // fn optional_field_let() { - // infer_eq_without_problem( - // indoc!( - // r" - // { x, y ? 0 } = { x: 32 } - - // x + y - // " - // ), - // "Num []", - // ); - // } - - // // #[test] - // // fn optional_field_when() { - // // infer_eq_without_problem( - // // indoc!( - // // r" - // // \r -> - // // when r is - // // { x, y ? 0 } -> x + y - // // " - // // ), - // // "{ x : Num a, y ? Num a } -> Num a", - // // ); - // // } - - // // #[test] - // // fn optional_field_let_with_signature() { - // // infer_eq_without_problem( - // // indoc!( - // // r" - // // \rec -> - // // { x, y } : { x : I64, y ? Bool }* - // // { x, y ? Bool.false } = rec - - // // { x, y } - // // " - // // ), - // // "{ x : I64, y ? Bool }-> { x : I64, y : Bool }", - // // ); - // // } - - // #[test] - // fn list_walk_backwards() { - // infer_eq_without_problem( - // indoc!( - // r" - // List.walkBackwards - // " - // ), - // "List [], [], ([], [] -> []) -> []", - // ); - // } - - // #[test] - // fn list_walk_backwards_example() { - // infer_eq_without_problem( - // indoc!( - // r" - // empty : List I64 - // empty = - // [] - - // List.walkBackwards empty 0 (\a, b -> a + b) - // " - // ), - // "I64", - // ); - // } - - // #[test] - // fn list_walk_with_index_until() { - // infer_eq_without_problem( - // indoc!(r"List.walkWithIndexUntil"), - // "List [], [], ([], [], U64 -> [Break [], Continue []]) -> []", - // ); - // } - - // #[test] - // fn list_drop_at() { - // infer_eq_without_problem( - // indoc!( - // r" - // List.dropAt - // " - // ), - // "List [], U64 -> List []", - // ); - // } - - // #[test] - // fn str_trim() { - // infer_eq_without_problem( - // indoc!( - // r" - // Str.trim - // " - // ), - // "Str -> Str", - // ); - // } - - // #[test] - // fn str_trim_start() { - // infer_eq_without_problem( - // indoc!( - // r" - // Str.trimStart - // " - // ), - // "Str -> Str", - // ); - // } - - // #[test] - // fn list_take_first() { - // infer_eq_without_problem( - // indoc!( - // r" - // List.takeFirst - // " - // ), - // "List [], U64 -> List []", - // ); - // } - - // #[test] - // fn list_take_last() { - // infer_eq_without_problem( - // indoc!( - // r" - // List.takeLast - // " - // ), - // "List [], U64 -> List []", - // ); - // } - - // #[test] - // fn list_sublist() { - // infer_eq_without_problem( - // indoc!( - // r" - // List.sublist - // " - // ), - // "List [], { len : U64, start : U64 } -> List []", - // ); - // } - - // #[test] - // fn list_split() { - // infer_eq_without_problem( - // indoc!("List.split"), - // "List [], U64 -> { before : List [], others : List [] }", - // ); - // } - - // #[test] - // fn list_drop_last() { - // infer_eq_without_problem( - // indoc!( - // r" - // List.dropLast - // " - // ), - // "List [], U64 -> List []", - // ); - // } - - // #[test] - // fn list_intersperse() { - // infer_eq_without_problem( - // indoc!( - // r" - // List.intersperse - // " - // ), - // "List [], [] -> List []", - // ); - // } - // #[test] - // fn function_that_captures_nothing_is_not_captured() { - // // we should make sure that a function that doesn't capture anything it not itself captured - // // such functions will be lifted to the top-level, and are thus globally available! - // infer_eq_without_problem( - // indoc!( - // r" - // f = \x -> x + 1 - - // g = \y -> f y - - // g - // " - // ), - // "Num [] -> Num []", - // ); - // } - - // #[test] - // fn double_named_rigids() { - // infer_eq_without_problem( - // indoc!( - // r#" - // app "test" provides [main] to "./platform" - - // main : List x - // main = - // empty : List x - // empty = [] - - // empty - // "# - // ), - // "List []", - // ); - // } - - // #[test] - // fn double_tag_application() { - // infer_eq_without_problem( - // indoc!( - // r#" - // app "test" provides [main] to "./platform" - - // main = - // if 1 == 1 then - // Foo (Bar) 1 - // else - // Foo Bar 1 - // "# - // ), - // "[Foo [Bar] (Num [])]", - // ); - - // infer_eq_without_problem("Foo Bar 1", "[Foo [Bar] (Num [])]"); - // } - - // #[test] - // fn double_tag_application_pattern() { - // infer_eq_without_problem( - // indoc!( - // r#" - // app "test" provides [main] to "./platform" - - // Bar : [Bar] - // Foo : [Foo Bar I64, Empty] - - // foo : Foo - // foo = Foo Bar 1 - - // main = - // when foo is - // Foo Bar 1 -> - // Foo Bar 2 - - // x -> - // x - // "# - // ), - // "[Empty, Foo [Bar] I64]", - // ); - // } - - // #[test] - // fn recursive_function_with_rigid() { - // infer_eq_without_problem( - // indoc!( - // r#" - // app "test" provides [main] to "./platform" - - // State a : { count : I64, x : a } - - // foo : State a -> I64 - // foo = \state -> - // if state.count == 0 then - // 0 - // else - // 1 + foo { count: state.count - 1, x: state.x } - - // main : I64 - // main = - // foo { count: 3, x: {} } - // "# - // ), - // "I64", - // ); - // } - - // #[test] - // fn rbtree_empty() { - // infer_eq_without_problem( - // indoc!( - // r#" - // app "test" provides [main] to "./platform" - - // # The color of a node. Leaves are considered Black. - // NodeColor : [Red, Black] - - // RBTree k v : [Node NodeColor k v (RBTree k v) (RBTree k v), Empty] - - // # Create an empty dictionary. - // empty : {} -> RBTree k v - // empty = \{} -> - // Empty - - // foo : RBTree I64 I64 - // foo = empty {} - - // main : RBTree I64 I64 - // main = - // foo - // "# - // ), - // "RBTree I64 I64", - // ); - // } - - // #[test] - // fn rbtree_insert() { - // // exposed an issue where pattern variables were not introduced - // // at the correct level in the constraint - // // - // // see 22592eff805511fbe1da63849771ee5f367a6a16 - // infer_eq_without_problem( - // indoc!( - // r#" - // app "test" provides [main] to "./platform" - - // RBTree k : [Node k (RBTree k), Empty] - - // balance : RBTree k -> RBTree k - // balance = \left -> - // when left is - // Node _ Empty -> Empty - - // _ -> Empty - - // main : RBTree {} - // main = - // balance Empty - // "# - // ), - // "RBTree {}", - // ); - // } - - // #[test] - // fn rbtree_full_remove_min() { - // infer_eq_without_problem( - // indoc!( - // r#" - // app "test" provides [main] to "./platform" - - // NodeColor : [Red, Black] - - // RBTree k v : [Node NodeColor k v (RBTree k v) (RBTree k v), Empty] - - // moveRedLeft : RBTree k v -> RBTree k v - // moveRedLeft = \dict -> - // when dict is - // # Node clr k v (Node lClr lK lV lLeft lRight) (Node rClr rK rV ((Node Red rlK rlV rlL rlR) as rLeft) rRight) -> - // # Node clr k v (Node lClr lK lV lLeft lRight) (Node rClr rK rV rLeft rRight) -> - // Node clr k v (Node _ lK lV lLeft lRight) (Node _ rK rV rLeft rRight) -> - // when rLeft is - // Node Red rlK rlV rlL rlR -> - // Node - // Red - // rlK - // rlV - // (Node Black k v (Node Red lK lV lLeft lRight) rlL) - // (Node Black rK rV rlR rRight) - - // _ -> - // when clr is - // Black -> - // Node - // Black - // k - // v - // (Node Red lK lV lLeft lRight) - // (Node Red rK rV rLeft rRight) - - // Red -> - // Node - // Black - // k - // v - // (Node Red lK lV lLeft lRight) - // (Node Red rK rV rLeft rRight) - - // _ -> - // dict - - // balance : NodeColor, k, v, RBTree k v, RBTree k v -> RBTree k v - // balance = \color, key, value, left, right -> - // when right is - // Node Red rK rV rLeft rRight -> - // when left is - // Node Red lK lV lLeft lRight -> - // Node - // Red - // key - // value - // (Node Black lK lV lLeft lRight) - // (Node Black rK rV rLeft rRight) - - // _ -> - // Node color rK rV (Node Red key value left rLeft) rRight - - // _ -> - // when left is - // Node Red lK lV (Node Red llK llV llLeft llRight) lRight -> - // Node - // Red - // lK - // lV - // (Node Black llK llV llLeft llRight) - // (Node Black key value lRight right) - - // _ -> - // Node color key value left right - - // Key k : Num k - - // removeHelpEQGT : Key k, RBTree (Key k) v -> RBTree (Key k) v where k implements Hash & Eq - // removeHelpEQGT = \targetKey, dict -> - // when dict is - // Node color key value left right -> - // if targetKey == key then - // when getMin right is - // Node _ minKey minValue _ _ -> - // balance color minKey minValue left (removeMin right) - - // Empty -> - // Empty - // else - // balance color key value left (removeHelp targetKey right) - - // Empty -> - // Empty - - // getMin : RBTree k v -> RBTree k v - // getMin = \dict -> - // when dict is - // # Node _ _ _ ((Node _ _ _ _ _) as left) _ -> - // Node _ _ _ left _ -> - // when left is - // Node _ _ _ _ _ -> getMin left - // _ -> dict - - // _ -> - // dict - - // moveRedRight : RBTree k v -> RBTree k v - // moveRedRight = \dict -> - // when dict is - // Node clr k v (Node lClr lK lV (Node Red llK llV llLeft llRight) lRight) (Node rClr rK rV rLeft rRight) -> - // Node - // Red - // lK - // lV - // (Node Black llK llV llLeft llRight) - // (Node Black k v lRight (Node Red rK rV rLeft rRight)) - - // Node clr k v (Node lClr lK lV lLeft lRight) (Node rClr rK rV rLeft rRight) -> - // when clr is - // Black -> - // Node - // Black - // k - // v - // (Node Red lK lV lLeft lRight) - // (Node Red rK rV rLeft rRight) - - // Red -> - // Node - // Black - // k - // v - // (Node Red lK lV lLeft lRight) - // (Node Red rK rV rLeft rRight) - - // _ -> - // dict - - // removeHelpPrepEQGT : Key k, RBTree (Key k) v, NodeColor, (Key k), v, RBTree (Key k) v, RBTree (Key k) v -> RBTree (Key k) v - // removeHelpPrepEQGT = \_, dict, color, key, value, left, right -> - // when left is - // Node Red lK lV lLeft lRight -> - // Node - // color - // lK - // lV - // lLeft - // (Node Red key value lRight right) - - // _ -> - // when right is - // Node Black _ _ (Node Black _ _ _ _) _ -> - // moveRedRight dict - - // Node Black _ _ Empty _ -> - // moveRedRight dict - - // _ -> - // dict - - // removeMin : RBTree k v -> RBTree k v - // removeMin = \dict -> - // when dict is - // Node color key value left right -> - // when left is - // Node lColor _ _ lLeft _ -> - // when lColor is - // Black -> - // when lLeft is - // Node Red _ _ _ _ -> - // Node color key value (removeMin left) right - - // _ -> - // when moveRedLeft dict is # here 1 - // Node nColor nKey nValue nLeft nRight -> - // balance nColor nKey nValue (removeMin nLeft) nRight - - // Empty -> - // Empty - - // _ -> - // Node color key value (removeMin left) right - - // _ -> - // Empty - // _ -> - // Empty - - // removeHelp : Key k, RBTree (Key k) v -> RBTree (Key k) v where k implements Hash & Eq - // removeHelp = \targetKey, dict -> - // when dict is - // Empty -> - // Empty - - // Node color key value left right -> - // if targetKey < key then - // when left is - // Node Black _ _ lLeft _ -> - // when lLeft is - // Node Red _ _ _ _ -> - // Node color key value (removeHelp targetKey left) right - - // _ -> - // when moveRedLeft dict is # here 2 - // Node nColor nKey nValue nLeft nRight -> - // balance nColor nKey nValue (removeHelp targetKey nLeft) nRight - - // Empty -> - // Empty - - // _ -> - // Node color key value (removeHelp targetKey left) right - // else - // removeHelpEQGT targetKey (removeHelpPrepEQGT targetKey dict color key value left right) - - // main : RBTree I64 I64 - // main = - // removeHelp 1i64 Empty - // "# - // ), - // "RBTree (Key (Integer Signed64)) I64", - // ); - // } - - // #[test] - // fn rbtree_remove_min_1() { - // infer_eq_without_problem( - // indoc!( - // r#" - // app "test" provides [main] to "./platform" - - // RBTree k : [Node k (RBTree k) (RBTree k), Empty] - - // removeHelp : Num k, RBTree (Num k) -> RBTree (Num k) - // removeHelp = \targetKey, dict -> - // when dict is - // Empty -> - // Empty - - // Node key left right -> - // if targetKey < key then - // when left is - // Node _ lLeft _ -> - // when lLeft is - // Node _ _ _ -> - // Empty - - // _ -> Empty - - // _ -> - // Node key (removeHelp targetKey left) right - // else - // Empty - - // main : RBTree I64 - // main = - // removeHelp 1 Empty - // "# - // ), - // "RBTree I64", - // ); - // } - - // #[test] - // fn rbtree_foobar() { - // infer_eq_without_problem( - // indoc!( - // r#" - // app "test" provides [main] to "./platform" - - // NodeColor : [Red, Black] - - // RBTree k v : [Node NodeColor k v (RBTree k v) (RBTree k v), Empty] - - // removeHelp : Num k, RBTree (Num k) v -> RBTree (Num k) v where k implements Hash & Eq - // removeHelp = \targetKey, dict -> - // when dict is - // Empty -> - // Empty - - // Node color key value left right -> - // if targetKey < key then - // when left is - // Node Black _ _ lLeft _ -> - // when lLeft is - // Node Red _ _ _ _ -> - // Node color key value (removeHelp targetKey left) right - - // _ -> - // when moveRedLeft dict is # here 2 - // Node nColor nKey nValue nLeft nRight -> - // balance nColor nKey nValue (removeHelp targetKey nLeft) nRight - - // Empty -> - // Empty - - // _ -> - // Node color key value (removeHelp targetKey left) right - // else - // removeHelpEQGT targetKey (removeHelpPrepEQGT targetKey dict color key value left right) - - // Key k : Num k - - // balance : NodeColor, k, v, RBTree k v, RBTree k v -> RBTree k v - - // moveRedLeft : RBTree k v -> RBTree k v - - // removeHelpPrepEQGT : Key k, RBTree (Key k) v, NodeColor, (Key k), v, RBTree (Key k) v, RBTree (Key k) v -> RBTree (Key k) v - - // removeHelpEQGT : Key k, RBTree (Key k) v -> RBTree (Key k) v where k implements Hash & Eq - // removeHelpEQGT = \targetKey, dict -> - // when dict is - // Node color key value left right -> - // if targetKey == key then - // when getMin right is - // Node _ minKey minValue _ _ -> - // balance color minKey minValue left (removeMin right) - - // Empty -> - // Empty - // else - // balance color key value left (removeHelp targetKey right) - - // Empty -> - // Empty - - // getMin : RBTree k v -> RBTree k v - - // removeMin : RBTree k v -> RBTree k v - - // main : RBTree I64 I64 - // main = - // removeHelp 1i64 Empty - // "# - // ), - // "RBTree I64 I64", - // ); - // } - - // #[test] - // fn quicksort_partition_help() { - // infer_eq_without_problem( - // indoc!( - // r#" - // app "test" provides [partitionHelp] to "./platform" - - // swap : U64, U64, List a -> List a - // swap = \i, j, list -> - // when Pair (List.get list i) (List.get list j) is - // Pair (Ok atI) (Ok atJ) -> - // list - // |> List.set i atJ - // |> List.set j atI - - // _ -> - // [] - - // partitionHelp : U64, U64, List (Num a), U64, (Num a) -> [Pair U64 (List (Num a))] - // partitionHelp = \i, j, list, high, pivot -> - // if j < high then - // when List.get list j is - // Ok value -> - // if value <= pivot then - // partitionHelp (i + 1) (j + 1) (swap (i + 1) j list) high pivot - // else - // partitionHelp i (j + 1) list high pivot - - // Err _ -> - // Pair i list - // else - // Pair i list - // "# - // ), - // "U64, U64, List (Num []), U64, Num [] -> [Pair U64 (List (Num []))]", - // ); - // } - - // #[test] - // fn rbtree_old_balance_simplified() { - // infer_eq_without_problem( - // indoc!( - // r#" - // app "test" provides [main] to "./platform" - - // RBTree k : [Node k (RBTree k) (RBTree k), Empty] - - // balance : k, RBTree k -> RBTree k - // balance = \key, left -> - // Node key left Empty - - // main : RBTree I64 - // main = - // balance 0 Empty - // "# - // ), - // "RBTree I64", - // ); - // } - - // #[test] - // fn rbtree_balance_simplified() { - // infer_eq_without_problem( - // indoc!( - // r#" - // app "test" provides [main] to "./platform" - - // RBTree k : [Node k (RBTree k) (RBTree k), Empty] - - // node = \x,y,z -> Node x y z - - // balance : k, RBTree k -> RBTree k - // balance = \key, left -> - // node key left Empty - - // main : RBTree I64 - // main = - // balance 0 Empty - // "# - // ), - // "RBTree I64", - // ); - // } - - // #[test] - // fn rbtree_balance() { - // infer_eq_without_problem( - // indoc!( - // r#" - // app "test" provides [main] to "./platform" - - // NodeColor : [Red, Black] - - // RBTree k v : [Node NodeColor k v (RBTree k v) (RBTree k v), Empty] - - // balance : NodeColor, k, v, RBTree k v, RBTree k v -> RBTree k v - // balance = \color, key, value, left, right -> - // when right is - // Node Red rK rV rLeft rRight -> - // when left is - // Node Red lK lV lLeft lRight -> - // Node - // Red - // key - // value - // (Node Black lK lV lLeft lRight) - // (Node Black rK rV rLeft rRight) - - // _ -> - // Node color rK rV (Node Red key value left rLeft) rRight - - // _ -> - // when left is - // Node Red lK lV (Node Red llK llV llLeft llRight) lRight -> - // Node - // Red - // lK - // lV - // (Node Black llK llV llLeft llRight) - // (Node Black key value lRight right) - - // _ -> - // Node color key value left right - - // main : RBTree I64 I64 - // main = - // balance Red 0 0 Empty Empty - // "# - // ), - // "RBTree I64 I64", - // ); - // } - - // #[test] - // fn pattern_rigid_problem() { - // infer_eq_without_problem( - // indoc!( - // r#" - // app "test" provides [main] to "./platform" - - // RBTree k : [Node k (RBTree k) (RBTree k), Empty] - - // balance : k, RBTree k -> RBTree k - // balance = \key, left -> - // when left is - // Node _ _ lRight -> - // Node key lRight Empty - - // _ -> - // Empty - - // main : RBTree I64 - // main = - // balance 0 Empty - // "# - // ), - // "RBTree I64", - // ); - // } - - // #[test] - // fn expr_to_str() { - // infer_eq_without_problem( - // indoc!( - // r#" - // app "test" provides [main] to "./platform" - - // Expr : [Add Expr Expr, Val I64, Var I64] - - // printExpr : Expr -> Str - // printExpr = \e -> - // when e is - // Add a b -> - // "Add (" - // |> Str.concat (printExpr a) - // |> Str.concat ") (" - // |> Str.concat (printExpr b) - // |> Str.concat ")" - // Val v -> Num.toStr v - // Var v -> "Var " |> Str.concat (Num.toStr v) - - // main : Str - // main = printExpr (Var 3) - // "# - // ), - // "Str", - // ); - // } - - // #[test] - // fn int_type_let_polymorphism() { - // infer_eq_without_problem( - // indoc!( - // r#" - // app "test" provides [main] to "./platform" - - // x = 4 - - // f : U8 -> U32 - // f = \z -> Num.intCast z - - // y = f x - - // main = - // x - // "# - // ), - // "Num []", - // ); - // } - - // #[test] - // fn rigid_type_variable_problem() { - // // see https://github.com/roc-lang/roc/issues/1162 - // infer_eq_without_problem( - // indoc!( - // r#" - // app "test" provides [main] to "./platform" - - // RBTree k : [Node k (RBTree k) (RBTree k), Empty] - - // balance : a, RBTree a -> RBTree a - // balance = \key, left -> - // when left is - // Node _ _ lRight -> - // Node key lRight Empty - - // _ -> - // Empty - - // main : RBTree {} - // main = - // balance {} Empty - // "# - // ), - // "RBTree {}", - // ); - // } - - // #[test] - // fn inference_var_inside_arrow() { - // infer_eq_without_problem( - // indoc!( - // r" - // id : _ -> _ - // id = \x -> x - // id - // " - // ), - // "[] -> []", - // ) - // } - - // #[test] - // fn inference_var_inside_ctor() { - // infer_eq_without_problem( - // indoc!( - // r#" - // canIGo : _ -> Result.Result _ _ - // canIGo = \color -> - // when color is - // "green" -> Ok "go!" - // "yellow" -> Err (SlowIt "whoa, let's slow down!") - // "red" -> Err (StopIt "absolutely not") - // _ -> Err (UnknownColor "this is a weird stoplight") - // canIGo - // "# - // ), - // "Str -> Result Str [SlowIt Str, StopIt Str, UnknownColor Str]", - // ) - // } - - // #[test] - // fn inference_var_inside_ctor_linked() { - // infer_eq_without_problem( - // indoc!( - // r" - // swapRcd: {x: _, y: _} -> {x: _, y: _} - // swapRcd = \{x, y} -> {x: y, y: x} - // swapRcd - // " - // ), - // "{ x : [], y : [] } -> { x : [], y : [] }", - // ) - // } - - // #[test] - // fn inference_var_link_with_rigid() { - // infer_eq_without_problem( - // indoc!( - // r" - // swapRcd: {x: tx, y: ty} -> {x: _, y: _} - // swapRcd = \{x, y} -> {x: y, y: x} - // swapRcd - // " - // ), - // "{ x : [], y : [] } -> { x : [], y : [] }", - // ) - // } - - // #[test] - // fn inference_var_inside_tag_ctor() { - // infer_eq_without_problem( - // indoc!( - // r#" - // badComics: [True, False] -> [CowTools _, Thagomizer _] - // badComics = \c -> - // when c is - // True -> CowTools "The Far Side" - // False -> Thagomizer "The Far Side" - // badComics - // "# - // ), - // "[False, True] -> [CowTools Str, Thagomizer Str]", - // ) - // } - - // #[test] - // fn inference_var_tag_union_ext() { - // // TODO: we should really be inferring [Blue, Orange]a -> [Lavender, Peach]a here. - // // See https://github.com/roc-lang/roc/issues/2053 - // infer_eq_without_problem( - // indoc!( - // r" - // pastelize: _ -> [Lavender, Peach]_ - // pastelize = \color -> - // when color is - // Blue -> Lavender - // Orange -> Peach - // col -> col - // pastelize - // " - // ), - // "[Blue, Lavender, Orange, Peach] -> [Blue, Lavender, Orange, Peach]", - // ) - // } - - // #[test] - // fn inference_var_rcd_union_ext() { - // infer_eq_without_problem( - // indoc!( - // r#" - // setRocEmail : _ -> { name: Str, email: Str }_ - // setRocEmail = \person -> - // { person & email: "$(person.name)@roclang.com" } - // setRocEmail - // "# - // ), - // "{ email : Str, name : Str } -> { email : Str, name : Str }", - // ) - // } - - // #[test] - // fn issue_2217() { - // infer_eq_without_problem( - // indoc!( - // r" - // LinkedList elem : [Empty, Prepend (LinkedList elem) elem] - - // fromList : List elem -> LinkedList elem - // fromList = \elems -> List.walk elems Empty Prepend - - // fromList - // " - // ), - // "List [] -> LinkedList []", - // ) - // } - - // #[test] - // fn issue_2217_inlined() { - // infer_eq_without_problem( - // indoc!( - // r" - // fromList : List elem -> [Empty, Prepend (LinkedList elem) elem] as LinkedList elem - // fromList = \elems -> List.walk elems Empty Prepend - - // fromList - // " - // ), - // "List [] -> LinkedList []", - // ) - // } - - // #[test] - // fn infer_union_input_position1() { - // infer_eq_without_problem( - // indoc!( - // r" - // \tag -> - // when tag is - // A -> X - // B -> Y - // " - // ), - // "[A, B] -> [X, Y]", - // ) - // } - - // #[test] - // fn infer_union_input_position2() { - // infer_eq_without_problem( - // indoc!( - // r" - // \tag -> - // when tag is - // A -> X - // B -> Y - // _ -> Z - // " - // ), - // "[A, B] -> [X, Y, Z]", - // ) - // } - - // #[test] - // fn infer_union_input_position3() { - // infer_eq_without_problem( - // indoc!( - // r" - // \tag -> - // when tag is - // A M -> X - // A N -> Y - // " - // ), - // "[A [M, N]] -> [X, Y]", - // ) - // } - - // #[test] - // fn infer_union_input_position4() { - // infer_eq_without_problem( - // indoc!( - // r" - // \tag -> - // when tag is - // A M -> X - // A N -> Y - // A _ -> Z - // " - // ), - // "[A [M, N]] -> [X, Y, Z]", - // ) - // } - - // #[test] - // fn infer_union_input_position5() { - // infer_eq_without_problem( - // indoc!( - // r" - // \tag -> - // when tag is - // A (M J) -> X - // A (N K) -> X - // " - // ), - // "[A [M [J], N [K]]] -> [X]", - // ) - // } - - // #[test] - // fn infer_union_input_position6() { - // infer_eq_without_problem( - // indoc!( - // r" - // \tag -> - // when tag is - // A M -> X - // B -> X - // A N -> X - // " - // ), - // "[A [M, N], B] -> [X]", - // ) - // } - - // #[test] - // fn infer_union_input_position7() { - // infer_eq_without_problem( - // indoc!( - // r" - // \tag -> - // when tag is - // A -> X - // t -> t - // " - // ), - // "[A, X] -> [A, X]", - // ) - // } - - // #[test] - // fn infer_union_input_position8() { - // infer_eq_without_problem( - // indoc!( - // r" - // \opt -> - // when opt is - // Some ({tag: A}) -> 1 - // Some ({tag: B}) -> 1 - // None -> 0 - // " - // ), - // "[None, Some { tag : [A, B] }] -> Num []", - // ) - // } - - // #[test] - // fn infer_union_input_position9() { - // infer_eq_without_problem( - // indoc!( - // r#" - // opt : [Some Str, None] - // opt = Some "" - // rcd = { opt } - - // when rcd is - // { opt: Some s } -> s - // { opt: None } -> "?" - // "# - // ), - // "Str", - // ) - // } - - // #[test] - // fn infer_union_input_position10() { - // infer_eq_without_problem( - // indoc!( - // r" - // \r -> - // when r is - // { x: Blue, y ? 3 } -> y - // { x: Red, y ? 5 } -> y - // " - // ), - // "{ x : [Blue, Red], y ? Num [] } -> Num []", - // ) - // } - - // #[test] - // // Issue #2299 - // fn infer_union_argument_position() { - // infer_eq_without_problem( - // indoc!( - // r" - // \UserId id -> id + 1 - // " - // ), - // "[UserId (Num [])] -> Num []", - // ) - // } - - // #[test] - // fn infer_union_def_position() { - // infer_eq_without_problem( - // indoc!( - // r" - // \email -> - // Email str = email - // Str.isEmpty str - // " - // ), - // "[Email Str] -> Bool", - // ) - // } - - // #[test] - // fn numeric_literal_suffixes() { - // infer_eq_without_problem( - // indoc!( - // r" - // { - // u8: 123u8, - // u16: 123u16, - // u32: 123u32, - // u64: 123u64, - // u128: 123u128, - - // i8: 123i8, - // i16: 123i16, - // i32: 123i32, - // i64: 123i64, - // i128: 123i128, - - // bu8: 0b11u8, - // bu16: 0b11u16, - // bu32: 0b11u32, - // bu64: 0b11u64, - // bu128: 0b11u128, - - // bi8: 0b11i8, - // bi16: 0b11i16, - // bi32: 0b11i32, - // bi64: 0b11i64, - // bi128: 0b11i128, - - // dec: 123.0dec, - // f32: 123.0f32, - // f64: 123.0f64, - - // fdec: 123dec, - // ff32: 123f32, - // ff64: 123f64, - // } - // " - // ), - // r"{ bi128 : I128, bi16 : I16, bi32 : I32, bi64 : I64, bi8 : I8, bu128 : U128, bu16 : U16, bu32 : U32, bu64 : U64, bu8 : U8, dec : Dec, f32 : F32, f64 : F64, fdec : Dec, ff32 : F32, ff64 : F64, i128 : I128, i16 : I16, i32 : I32, i64 : I64, i8 : I8, u128 : U128, u16 : U16, u32 : U32, u64 : U64, u8 : U8 }", - // ) - // } - - // #[test] - // fn numeric_literal_suffixes_in_pattern() { - // infer_eq_without_problem( - // indoc!( - // r" - // { - // u8: (\n -> - // when n is - // 123u8 -> n - // _ -> n), - // u16: (\n -> - // when n is - // 123u16 -> n - // _ -> n), - // u32: (\n -> - // when n is - // 123u32 -> n - // _ -> n), - // u64: (\n -> - // when n is - // 123u64 -> n - // _ -> n), - // u128: (\n -> - // when n is - // 123u128 -> n - // _ -> n), - - // i8: (\n -> - // when n is - // 123i8 -> n - // _ -> n), - // i16: (\n -> - // when n is - // 123i16 -> n - // _ -> n), - // i32: (\n -> - // when n is - // 123i32 -> n - // _ -> n), - // i64: (\n -> - // when n is - // 123i64 -> n - // _ -> n), - // i128: (\n -> - // when n is - // 123i128 -> n - // _ -> n), - - // bu8: (\n -> - // when n is - // 0b11u8 -> n - // _ -> n), - // bu16: (\n -> - // when n is - // 0b11u16 -> n - // _ -> n), - // bu32: (\n -> - // when n is - // 0b11u32 -> n - // _ -> n), - // bu64: (\n -> - // when n is - // 0b11u64 -> n - // _ -> n), - // bu128: (\n -> - // when n is - // 0b11u128 -> n - // _ -> n), - - // bi8: (\n -> - // when n is - // 0b11i8 -> n - // _ -> n), - // bi16: (\n -> - // when n is - // 0b11i16 -> n - // _ -> n), - // bi32: (\n -> - // when n is - // 0b11i32 -> n - // _ -> n), - // bi64: (\n -> - // when n is - // 0b11i64 -> n - // _ -> n), - // bi128: (\n -> - // when n is - // 0b11i128 -> n - // _ -> n), - - // dec: (\n -> - // when n is - // 123.0dec -> n - // _ -> n), - // f32: (\n -> - // when n is - // 123.0f32 -> n - // _ -> n), - // f64: (\n -> - // when n is - // 123.0f64 -> n - // _ -> n), - - // fdec: (\n -> - // when n is - // 123dec -> n - // _ -> n), - // ff32: (\n -> - // when n is - // 123f32 -> n - // _ -> n), - // ff64: (\n -> - // when n is - // 123f64 -> n - // _ -> n), - // } - // " - // ), - // r"{ bi128 : I128 -> I128, bi16 : I16 -> I16, bi32 : I32 -> I32, bi64 : I64 -> I64, bi8 : I8 -> I8, bu128 : U128 -> U128, bu16 : U16 -> U16, bu32 : U32 -> U32, bu64 : U64 -> U64, bu8 : U8 -> U8, dec : Dec -> Dec, f32 : F32 -> F32, f64 : F64 -> F64, fdec : Dec -> Dec, ff32 : F32 -> F32, ff64 : F64 -> F64, i128 : I128 -> I128, i16 : I16 -> I16, i32 : I32 -> I32, i64 : I64 -> I64, i8 : I8 -> I8, u128 : U128 -> U128, u16 : U16 -> U16, u32 : U32 -> U32, u64 : U64 -> U64, u8 : U8 -> U8 }", - // ) - // } + #[test] + fn unbound_num() { + let expected = 42; + expect_mono_expr( + format!("{expected}"), + MonoExpr::Number(Number::I64(expected)), + ); + } } diff --git a/crates/compiler/specialize_types/tests/test_specialize_types.rs b/crates/compiler/specialize_types/tests/test_specialize_types.rs index 34f44e50b8..192036408c 100644 --- a/crates/compiler/specialize_types/tests/test_specialize_types.rs +++ b/crates/compiler/specialize_types/tests/test_specialize_types.rs @@ -47,13 +47,11 @@ mod specialize_types { let (can_problems, type_problems) = format_problems(&src, home, &interns, can_problems, type_problems); - let subs = solved.inner_mut(); - exposed_to_host.retain(|s, _| !abilities_store.is_specialization_name(*s)); debug_assert!(exposed_to_host.len() == 1, "{exposed_to_host:?}"); let (_symbol, variable) = exposed_to_host.into_iter().next().unwrap(); - let mut mono_cache = MonoCache::from_subs(subs); + let mut mono_cache = MonoCache::from_subs(&solved); let mut mono_types = MonoTypes::new(); let debug_info = DebugInfo::new(); let mut record_field_ids = RecordFieldIds::new(); @@ -61,7 +59,7 @@ mod specialize_types { let mut problems = Vec::new(); mono_cache.monomorphize_var( - subs, + solved.inner_mut(), &mut mono_types, &mut record_field_ids, &mut tuple_elem_ids, @@ -72,7 +70,13 @@ mod specialize_types { assert_eq!(problems, Vec::new()); - let actual_str = name_and_print_var(variable, subs, home, &interns, DebugPrint::NOTHING); + let actual_str = name_and_print_var( + variable, + solved.inner_mut(), + home, + &interns, + DebugPrint::NOTHING, + ); Ok((type_problems, can_problems, actual_str)) } diff --git a/crates/compiler/types/src/subs.rs b/crates/compiler/types/src/subs.rs index ba8ebd8fe8..6ec1ab8c2a 100644 --- a/crates/compiler/types/src/subs.rs +++ b/crates/compiler/types/src/subs.rs @@ -422,12 +422,24 @@ pub struct SubsSlice { _marker: std::marker::PhantomData, } +impl PartialEq for SubsSlice { + fn eq(&self, other: &Self) -> bool { + self.start == other.start && self.length == other.length + } +} + /// An index into the Vec of subs pub struct SubsIndex { pub index: u32, _marker: std::marker::PhantomData, } +impl PartialEq for SubsIndex { + fn eq(&self, other: &Self) -> bool { + self.index == other.index + } +} + // make `subs[some_index]` work. The types/trait resolution make sure we get the // element from the right vector @@ -1140,7 +1152,7 @@ impl From for Option { } /// Marks whether a when expression is exhaustive using a variable. -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] pub struct ExhaustiveMark(Variable); impl ExhaustiveMark { @@ -2344,7 +2356,7 @@ roc_error_macros::assert_sizeof_default!((Variable, Option), 4 * 8); roc_error_macros::assert_copyable!(Content); roc_error_macros::assert_copyable!(Descriptor); -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] pub enum Content { /// A type variable which the user did not name in an annotation, /// @@ -2381,7 +2393,7 @@ pub enum Content { /// if b then f else g /// /// has the type {} -[f, g]-> {} where [f, g] is the solved lambda set. -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] pub struct LambdaSet { /// The resolved lambda symbols we know. pub solved: UnionLambdas, @@ -2420,7 +2432,7 @@ pub struct LambdaSet { pub ambient_function: Variable, } -#[derive(Clone, Copy, Debug, Default)] +#[derive(Clone, Copy, Debug, Default, PartialEq)] pub struct AliasVariables { pub variables_start: u32, pub all_variables_len: u16, @@ -2570,7 +2582,7 @@ impl Content { } } -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] pub enum TagExt { /// This tag extension variable measures polymorphism in the openness of the tag, /// or the lack thereof. It can only be unified with @@ -2619,7 +2631,7 @@ impl TagExt { } } -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] pub enum FlatType { Apply(Symbol, VariableSubsSlice), Func(VariableSubsSlice, Variable, Variable), @@ -2777,7 +2789,7 @@ impl Label for Symbol { } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub struct UnionLabels { pub(crate) length: u16, pub(crate) labels_start: u32, @@ -3122,7 +3134,7 @@ pub fn is_empty_tag_union(subs: &Subs, mut var: Variable) -> bool { } } -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] pub struct RecordFields { pub length: u16, pub field_names_start: u32, @@ -3335,7 +3347,7 @@ fn is_empty_record(subs: &Subs, mut var: Variable) -> bool { } } -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] pub struct TupleElems { pub length: u16, diff --git a/crates/compiler/types/src/types.rs b/crates/compiler/types/src/types.rs index 8bb817a208..bffd171e96 100644 --- a/crates/compiler/types/src/types.rs +++ b/crates/compiler/types/src/types.rs @@ -305,7 +305,7 @@ impl FromIterator for AbilitySet { } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub struct OptAbleVar { pub var: Variable, pub opt_abilities: Option, @@ -3561,7 +3561,7 @@ pub enum MemberImpl { Error, } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub struct Alias { pub region: Region, pub type_variables: Vec>, diff --git a/crates/test_compile/Cargo.toml b/crates/test_compile/Cargo.toml index 977a607814..d31e04ece0 100644 --- a/crates/test_compile/Cargo.toml +++ b/crates/test_compile/Cargo.toml @@ -13,17 +13,21 @@ roc_derive = { path = "../compiler/derive", features = [ "debug-derived-symbols", ] } roc_region = { path = "../compiler/region" } +roc_collections = { path = "../compiler/collections" } roc_load = { path = "../compiler/load" } roc_parse = { path = "../compiler/parse" } roc_can = { path = "../compiler/can" } roc_module = { path = "../compiler/module" } roc_types = { path = "../compiler/types" } roc_problem = { path = "../compiler/problem" } +roc_constrain = { path = "../compiler/constrain" } roc_reporting = { path = "../reporting" } roc_target = { path = "../compiler/roc_target" } roc_solve = { path = "../compiler/solve" } test_solve_helpers = { path = "../compiler/test_solve_helpers" } +roc_solve_problem = { path = "../compiler/solve_problem" } +roc_specialize_types.workspace = true bumpalo.workspace = true [dev-dependencies] diff --git a/crates/test_compile/src/help_can.rs b/crates/test_compile/src/help_can.rs index a36ee48d36..36b5c7723f 100644 --- a/crates/test_compile/src/help_can.rs +++ b/crates/test_compile/src/help_can.rs @@ -1,5 +1,3 @@ -use std::path::Path; - use crate::help_parse::ParseExpr; use bumpalo::Bump; use roc_can::{ @@ -15,10 +13,12 @@ use roc_types::{ subs::{VarStore, Variable}, types::{AliasVar, Type}, }; +use std::path::Path; #[derive(Debug)] pub struct CanExprOut { - pub loc_expr: Loc, + pub expr: Expr, + pub region: Region, pub output: Output, pub problems: Vec, pub home: ModuleId, @@ -28,6 +28,8 @@ pub struct CanExprOut { } pub struct CanExpr { + pub(crate) home: ModuleId, + parse_expr: ParseExpr, } @@ -35,6 +37,7 @@ impl Default for CanExpr { fn default() -> Self { Self { parse_expr: ParseExpr::default(), + home: test_home(), } } } @@ -108,7 +111,8 @@ impl CanExpr { }; CanExprOut { - loc_expr, + expr: loc_expr.value, + region: loc_expr.region, output, problems: env.problems, home: env.home, @@ -130,4 +134,8 @@ impl CanExpr { pub fn arena(&self) -> &Bump { &self.parse_expr.arena() } + + pub fn home(&self) -> ModuleId { + self.home + } } diff --git a/crates/test_compile/src/help_constrain.rs b/crates/test_compile/src/help_constrain.rs index e69de29bb2..9989fa9e93 100644 --- a/crates/test_compile/src/help_constrain.rs +++ b/crates/test_compile/src/help_constrain.rs @@ -0,0 +1,77 @@ +use crate::CanExpr; +use bumpalo::Bump; +use roc_can::{ + constraint::{Constraint, Constraints}, + expected::Expected, + expr::Expr, +}; +use roc_constrain::expr::{constrain_expr, Env}; +use roc_module::symbol::ModuleId; +use roc_types::{ + subs::{Subs, Variable}, + types::Types, +}; + +#[derive(Debug)] +pub struct ConstrainedExprOut { + pub expr: Expr, + pub var: Variable, + pub constraint: Constraint, + pub constraints: Constraints, + pub types: Types, +} + +#[derive(Default)] +pub struct ConstrainedExpr { + can_expr: CanExpr, + subs: Subs, + constraints: Constraints, +} + +impl ConstrainedExpr { + pub fn constrain_expr<'a>(&'a self, input: &'a str) -> ConstrainedExprOut { + let mut can_expr_out = self.can_expr.can_expr(input); + + assert_eq!( + can_expr_out.problems, + Vec::new(), + "Encountered unexpected canonicalization problem when trying to constrain expr" + ); + + let mut types = Types::default(); + let mut constraints = Constraints::default(); + let var = can_expr_out.var_store.fresh(); + let var_index = constraints.push_variable(var); + let expected = constraints.push_expected_type(Expected::NoExpectation(var_index)); + let mut env = Env::new(self.can_expr.home); + + let constraint = constrain_expr( + &mut types, + &mut constraints, + &mut env, + can_expr_out.region, + &can_expr_out.expr, + expected, + ); + + ConstrainedExprOut { + expr: can_expr_out.expr, + var: can_expr_out.var, + constraint, + constraints, + types, + } + } + + pub fn into_arena(self) -> Bump { + self.can_expr.into_arena() + } + + pub fn arena(&self) -> &Bump { + &self.can_expr.arena() + } + + pub fn home(&self) -> ModuleId { + self.can_expr.home() + } +} diff --git a/crates/test_compile/src/help_solve.rs b/crates/test_compile/src/help_solve.rs index e69de29bb2..0f39f9611b 100644 --- a/crates/test_compile/src/help_solve.rs +++ b/crates/test_compile/src/help_solve.rs @@ -0,0 +1,81 @@ +use crate::help_constrain::ConstrainedExpr; +use bumpalo::Bump; +use roc_can::{ + abilities::AbilitiesStore, + expr::{Expr, PendingDerives}, + module::ExposedByModule, +}; +use roc_collections::VecMap; +use roc_derive::SharedDerivedModule; +use roc_load::FunctionKind; +use roc_module::symbol::ModuleId; +use roc_solve::{ + module::{SolveConfig, Solved}, + solve, Aliases, +}; +use roc_solve_problem::TypeError; +use roc_types::subs::{Subs, Variable}; + +#[derive(Debug)] +pub struct SolvedExprOut { + pub expr: Expr, + pub problems: Vec, + pub var: Variable, + pub subs: Solved, +} + +#[derive(Default)] +pub struct SolvedExpr { + constrained_expr: ConstrainedExpr, +} + +impl SolvedExpr { + pub fn solve_expr<'a>(&'a self, input: &'a str) -> SolvedExprOut { + let constrained_expr_out = self.constrained_expr.constrain_expr(input); + let mut problems = Vec::new(); + let mut aliases = Aliases::default(); + let subs = Subs::new(); + let mut abilities_store = AbilitiesStore::default(); + let solve_config = SolveConfig { + home: self.constrained_expr.home(), + constraints: &constrained_expr_out.constraints, + root_constraint: constrained_expr_out.constraint, + types: constrained_expr_out.types, + function_kind: FunctionKind::LambdaSet, + pending_derives: PendingDerives::default(), + exposed_by_module: &ExposedByModule::default(), + derived_module: SharedDerivedModule::default(), + module_params: None, + module_params_vars: VecMap::default(), + #[cfg(debug_assertions)] + checkmate: None, + }; + + let solve_output = solve::run( + solve_config, + &mut problems, + subs, + &mut aliases, + &mut abilities_store, + ); + + SolvedExprOut { + expr: constrained_expr_out.expr, + problems, + var: constrained_expr_out.var, + subs: solve_output.solved, + } + } + + pub fn into_arena(self) -> Bump { + self.constrained_expr.into_arena() + } + + pub fn arena(&self) -> &Bump { + &self.constrained_expr.arena() + } + + pub fn home(&self) -> ModuleId { + self.constrained_expr.home() + } +} diff --git a/crates/test_compile/src/help_specialize.rs b/crates/test_compile/src/help_specialize.rs index e69de29bb2..9518511828 100644 --- a/crates/test_compile/src/help_specialize.rs +++ b/crates/test_compile/src/help_specialize.rs @@ -0,0 +1,58 @@ +use crate::SolvedExpr; +use bumpalo::Bump; +use roc_specialize_types::{ + DebugInfo, Env, MonoCache, MonoExprId, MonoExprs, MonoTypes, Problem, RecordFieldIds, + TupleElemIds, +}; + +#[derive(Debug)] +pub struct SpecializedExprOut { + pub mono_expr_id: Option, + pub mono_types: MonoTypes, + pub mono_exprs: MonoExprs, + pub problems: Vec, +} + +#[derive(Default)] +pub struct SpecializedExpr { + solved_expr: SolvedExpr, +} + +impl SpecializedExpr { + pub fn specialize_expr<'a>(&'a self, input: &'a str) -> SpecializedExprOut { + let mut solved_out = self.solved_expr.solve_expr(input); + let mut problems = Vec::new(); + let mut debug_info: Option = None; + let mut types_cache = MonoCache::from_subs(&solved_out.subs); + let mut mono_types = MonoTypes::new(); + let mut mono_exprs = MonoExprs::new(); + + let mut env = Env::new( + &mut solved_out.subs, + &mut types_cache, + &mut mono_types, + &mut mono_exprs, + RecordFieldIds::default(), + TupleElemIds::default(), + &mut debug_info, + &mut problems, + ); + + let mono_expr_id = env.to_mono_expr(solved_out.expr); + + SpecializedExprOut { + mono_expr_id, + problems, + mono_types, + mono_exprs, + } + } + + pub fn into_arena(self) -> Bump { + self.solved_expr.into_arena() + } + + pub fn arena(&self) -> &Bump { + &self.solved_expr.arena() + } +} diff --git a/crates/test_compile/src/lib.rs b/crates/test_compile/src/lib.rs index e48558f97b..16ec9d17ae 100644 --- a/crates/test_compile/src/lib.rs +++ b/crates/test_compile/src/lib.rs @@ -4,3 +4,20 @@ mod help_constrain; mod help_parse; mod help_solve; mod help_specialize; + +pub use help_can::{CanExpr, CanExprOut}; +pub use help_parse::ParseExpr; +pub use help_solve::{SolvedExpr, SolvedExprOut}; +pub use help_specialize::{SpecializedExpr, SpecializedExprOut}; + +pub fn can_expr<'a>(input: &'a str) -> CanExprOut { + CanExpr::default().can_expr(input) +} + +pub fn solve_expr<'a>(input: &'a str) -> SolvedExprOut { + SolvedExpr::default().solve_expr(input) +} + +pub fn specialize_expr<'a>(input: &'a str) -> SpecializedExprOut { + SpecializedExpr::default().specialize_expr(input) +} From 7b80489772bb1c30cf586c7daaa8e6635bd0e6e1 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Fri, 8 Nov 2024 19:20:20 -0500 Subject: [PATCH 22/65] Fill out some more mono expr types --- .../compiler/solve/tests/test_solve_expr.rs | 18 +++ crates/compiler/specialize_types/src/lib.rs | 1 + .../specialize_types/src/mono_expr.rs | 139 +++++++++++------- .../compiler/specialize_types/src/mono_ir.rs | 2 +- .../specialize_types/src/mono_module.rs | 31 ++++ .../compiler/specialize_types/src/mono_num.rs | 2 +- .../specialize_types/src/specialize_type.rs | 3 + crates/test_compile/src/help_specialize.rs | 5 +- 8 files changed, 147 insertions(+), 54 deletions(-) create mode 100644 crates/compiler/solve/tests/test_solve_expr.rs diff --git a/crates/compiler/solve/tests/test_solve_expr.rs b/crates/compiler/solve/tests/test_solve_expr.rs new file mode 100644 index 0000000000..8330d5d0a8 --- /dev/null +++ b/crates/compiler/solve/tests/test_solve_expr.rs @@ -0,0 +1,18 @@ +#[cfg(test)] +mod test_solve_expr { + use roc_can::expr::Expr; + use roc_types::subs::Content; + use test_compile::solve_expr; + + #[test] + fn solve_empty_record() { + let mut output = solve_expr("{}"); + + assert_eq!(output.problems, Vec::new()); + assert!(matches!(output.expr, Expr::EmptyRecord)); + assert_eq!( + Content::Structure(roc_types::subs::FlatType::EmptyRecord), + output.subs.inner_mut().get(output.var).content + ); + } +} diff --git a/crates/compiler/specialize_types/src/lib.rs b/crates/compiler/specialize_types/src/lib.rs index 03df00459b..2acd7728a2 100644 --- a/crates/compiler/specialize_types/src/lib.rs +++ b/crates/compiler/specialize_types/src/lib.rs @@ -13,6 +13,7 @@ pub use debug_info::DebugInfo; pub use foreign_symbol::{ForeignSymbolId, ForeignSymbols}; pub use mono_expr::Env; pub use mono_ir::{MonoExpr, MonoExprId, MonoExprs}; +pub use mono_module::{InternedStrId, Interns}; pub use mono_num::Number; pub use mono_struct::MonoFieldId; pub use mono_type::{MonoType, MonoTypeId, MonoTypes}; diff --git a/crates/compiler/specialize_types/src/mono_expr.rs b/crates/compiler/specialize_types/src/mono_expr.rs index 4c8987b8d6..80052c8be8 100644 --- a/crates/compiler/specialize_types/src/mono_expr.rs +++ b/crates/compiler/specialize_types/src/mono_expr.rs @@ -1,16 +1,19 @@ use crate::{ mono_ir::{MonoExpr, MonoExprId, MonoExprs}, + mono_module::Interns, mono_num::Number, mono_type::{MonoType, MonoTypes, Primitive}, specialize_type::{MonoCache, Problem, RecordFieldIds, TupleElemIds}, DebugInfo, }; +use bumpalo::Bump; use roc_can::expr::{Expr, IntValue}; use roc_collections::Push; use roc_solve::module::Solved; use roc_types::subs::Subs; -pub struct Env<'c, 'd, 's, 't, P> { +pub struct Env<'a, 'c, 'd, 's, 't, P> { + arena: &'a Bump, subs: &'s mut Subs, types_cache: &'c mut MonoCache, mono_types: &'t mut MonoTypes, @@ -18,27 +21,32 @@ pub struct Env<'c, 'd, 's, 't, P> { record_field_ids: RecordFieldIds, tuple_elem_ids: TupleElemIds, debug_info: &'d mut Option, + string_interns: &'a mut Interns<'a>, problems: P, } -impl<'c, 'd, 's, 't, P: Push> Env<'c, 'd, 's, 't, P> { +impl<'a, 'c, 'd, 's, 't, P: Push> Env<'a, 'c, 'd, 's, 't, P> { pub fn new( + arena: &'a Bump, subs: &'s mut Solved, types_cache: &'c mut MonoCache, mono_types: &'t mut MonoTypes, mono_exprs: &'t mut MonoExprs, record_field_ids: RecordFieldIds, tuple_elem_ids: TupleElemIds, + string_interns: &'a mut Interns<'a>, debug_info: &'d mut Option, problems: P, ) -> Self { Env { + arena, subs: subs.inner_mut(), types_cache, mono_types, mono_exprs, record_field_ids, tuple_elem_ids, + string_interns, debug_info, problems, } @@ -68,9 +76,9 @@ impl<'c, 'd, 's, 't, P: Push> Env<'c, 'd, 's, 't, P> { } let mono_expr = match can_expr { - Expr::Num(var, _str, int_value, _bound) => match mono_from_var(var) { + Expr::Float(var, _precision_var, _str, val, _bound) => match mono_from_var(var) { Some(mono_id) => match mono_types.get(mono_id) { - MonoType::Primitive(primitive) => to_num(*primitive, int_value, problems), + MonoType::Primitive(primitive) => to_frac(*primitive, val, problems), other => { return compiler_bug!(Problem::NumSpecializedToWrongType(Some(*other))); } @@ -79,45 +87,42 @@ impl<'c, 'd, 's, 't, P: Push> Env<'c, 'd, 's, 't, P> { return compiler_bug!(Problem::NumSpecializedToWrongType(None)); } }, - Expr::Int(var, _precision_var, _str, val, _bound) => match mono_from_var(var) { + Expr::Num(var, _str, int_value, _) | Expr::Int(var, _, _str, int_value, _) => { + match mono_from_var(var) { + Some(mono_id) => match mono_types.get(mono_id) { + MonoType::Primitive(primitive) => to_num(*primitive, int_value, problems), + other => { + return compiler_bug!(Problem::NumSpecializedToWrongType(Some(*other))); + } + }, + None => { + return compiler_bug!(Problem::NumSpecializedToWrongType(None)); + } + } + } + Expr::SingleQuote(var, _precision_var, char, _bound) => match mono_from_var(var) { + // Single-quote characters monomorphize to an integer. + // TODO if we store these using the same representation as other ints (e.g. Expr::Int, + // or keeping a separate value but storing an IntValue instead of a char), then + // even though we verify them differently, then we can combine this branch with Num and Int. Some(mono_id) => match mono_types.get(mono_id) { - MonoType::Primitive(primitive) => to_int(*primitive, val, problems), + MonoType::Primitive(primitive) => char_to_int(*primitive, char, problems), other => { - return compiler_bug!(Problem::NumSpecializedToWrongType(Some(*other))); + return compiler_bug!(Problem::CharSpecializedToWrongType(Some(*other))); } }, None => { - return compiler_bug!(Problem::NumSpecializedToWrongType(None)); + return compiler_bug!(Problem::CharSpecializedToWrongType(None)); } }, + Expr::Str(contents) => { + MonoExpr::Str(self.string_interns.get(self.arena.alloc(contents))) + } Expr::EmptyRecord => { // Empty records are zero-sized and should be discarded. return None; } _ => todo!(), - // Expr::Float(var, _precision_var, _str, val, _bound) => { - // match mono_from_var(var) { - // Some(mono_id) => { - // match mono_types.get(mono_id) { - // MonoType::Number(number) => { - // Some(mono_types.add(to_frac(number, val, problems))) - // } - // _ => { - // // TODO push problem and return Malformed expr: Num specialized to something that wasn't a number, namely: _____ - // } - // } - // } - // None => { - // // TODO push problem and return Malformed expr: Num's type param specialized to Unit somehow - // } - // } - // } - // Expr::SingleQuote(variable, _precision_var, _char, _bound) => { - // // Single quote monomorphizes to an integer. - // // TODO let's just start writing some tests for converting numbers and single quotes and strings. - // // TODO also, verify that doing nonsense like a type annotation of Num {} is handled gracefully. - // } - // Expr::Str(_) => todo!(), // Expr::List { // elem_var, // loc_elems, @@ -233,13 +238,13 @@ impl<'c, 'd, 's, 't, P: Push> Env<'c, 'd, 's, 't, P> { } } -/// Convert a number literal (e.g. `42`) to a monomorphized type. -/// This is allowed to convert to either an integer or a fraction. +/// Convert a number literal (e.g. `42`) or integer literal (e.g. `0x42`) to a monomorphized type. +/// Nums are allowed to convert to either an integer or a fraction. Integer literals should have +/// given a compile-time error if they ended up unifying to a fractional type, but we can +/// gracefully allow them to compile to that type anyway. fn to_num(primitive: Primitive, val: IntValue, problems: &mut impl Push) -> MonoExpr { match primitive { - Primitive::Dec => MonoExpr::Number(Number::Dec(val.as_i128())), - Primitive::F32 => MonoExpr::Number(Number::F32(val.as_i128() as f32)), - Primitive::F64 => MonoExpr::Number(Number::F64(val.as_i128() as f64)), + // These are ordered roughly by most to least common integer types Primitive::U8 => MonoExpr::Number(Number::U8(val.as_i128() as u8)), Primitive::I8 => MonoExpr::Number(Number::I8(val.as_i128() as i8)), Primitive::U16 => MonoExpr::Number(Number::U16(val.as_i128() as u16)), @@ -248,6 +253,12 @@ fn to_num(primitive: Primitive, val: IntValue, problems: &mut impl Push Primitive::I32 => MonoExpr::Number(Number::I32(val.as_i128() as i32)), Primitive::U64 => MonoExpr::Number(Number::U64(val.as_i128() as u64)), Primitive::I64 => MonoExpr::Number(Number::I64(val.as_i128() as i64)), + Primitive::F32 => MonoExpr::Number(Number::F32(val.as_i128() as f32)), + Primitive::F64 => MonoExpr::Number(Number::F64(val.as_i128() as f64)), + Primitive::Dec => MonoExpr::Number(Number::Dec(match val { + IntValue::I128(bytes) => i128::from_ne_bytes(bytes) as f64, + IntValue::U128(bytes) => u128::from_ne_bytes(bytes) as f64, + })), Primitive::U128 => MonoExpr::Number(Number::U128(val.as_u128())), Primitive::I128 => MonoExpr::Number(Number::I128(val.as_i128())), Primitive::Str => { @@ -258,22 +269,25 @@ fn to_num(primitive: Primitive, val: IntValue, problems: &mut impl Push } } -/// Convert an integer literal (e.g. `0x5`) to a monomorphized type. -/// If somehow its type was not an integer type, that's a compiler bug! -fn to_int(primitive: Primitive, val: IntValue, problems: &mut impl Push) -> MonoExpr { +/// Convert a fractional literal (e.g. `0.5`) to a monomorphized type. +/// If somehow its type was not a fractional type, that's a compiler bug! +fn to_frac(primitive: Primitive, val: f64, problems: &mut impl Push) -> MonoExpr { match primitive { - // These are ordered roughly by most to least common integer types - Primitive::U64 => MonoExpr::Number(Number::U64(val.as_u64())), - Primitive::U8 => MonoExpr::Number(Number::U8(val.as_u8())), - Primitive::I64 => MonoExpr::Number(Number::I64(val.as_i64())), - Primitive::I32 => MonoExpr::Number(Number::I32(val.as_i32())), - Primitive::U16 => MonoExpr::Number(Number::U16(val.as_u16())), - Primitive::U32 => MonoExpr::Number(Number::U32(val.as_u32())), - Primitive::I128 => MonoExpr::Number(Number::I128(val.as_i128())), - Primitive::U128 => MonoExpr::Number(Number::U128(val.as_u128())), - Primitive::I16 => MonoExpr::Number(Number::I16(val.as_i16())), - Primitive::I8 => MonoExpr::Number(Number::I8(val.as_i8())), - Primitive::Str | Primitive::Dec | Primitive::F32 | Primitive::F64 => { + // These are ordered roughly by most to least common fractional types + Primitive::F32 => MonoExpr::Number(Number::F32(val as f32)), + Primitive::F64 => MonoExpr::Number(Number::F64(val)), + Primitive::Dec => MonoExpr::Number(Number::Dec(val)), + Primitive::U8 + | Primitive::I8 + | Primitive::U16 + | Primitive::I16 + | Primitive::U32 + | Primitive::I32 + | Primitive::U64 + | Primitive::I64 + | Primitive::U128 + | Primitive::I128 + | Primitive::Str => { let problem = Problem::NumSpecializedToWrongType(Some(MonoType::Primitive(primitive))); problems.push(problem); MonoExpr::CompilerBug(problem) @@ -281,6 +295,29 @@ fn to_int(primitive: Primitive, val: IntValue, problems: &mut impl Push } } +/// Convert a single-quote character (e.g. `'r'`) to a monomorphized type. +/// If somehow its type was not an integer type, that's a compiler bug! +fn char_to_int(primitive: Primitive, ch: char, problems: &mut impl Push) -> MonoExpr { + match primitive { + // These are ordered roughly by most to least common character types + Primitive::U8 => MonoExpr::Number(Number::U8(ch as u8)), + Primitive::U64 => MonoExpr::Number(Number::U64(ch as u64)), + Primitive::U16 => MonoExpr::Number(Number::U16(ch as u16)), + Primitive::U32 => MonoExpr::Number(Number::U32(ch as u32)), + Primitive::U128 => MonoExpr::Number(Number::U128(ch as u128)), + Primitive::I64 => MonoExpr::Number(Number::I64(ch as i64)), + Primitive::I32 => MonoExpr::Number(Number::I32(ch as i32)), + Primitive::I128 => MonoExpr::Number(Number::I128(ch as i128)), + Primitive::I16 => MonoExpr::Number(Number::I16(ch as i16)), + Primitive::I8 => MonoExpr::Number(Number::I8(ch as i8)), + Primitive::Str | Primitive::Dec | Primitive::F32 | Primitive::F64 => { + let problem = Problem::CharSpecializedToWrongType(Some(MonoType::Primitive(primitive))); + problems.push(problem); + MonoExpr::CompilerBug(problem) + } + } +} + // /// Convert a fraction literal (e.g. `4.2`) to a monomorphized type. // /// If somehow its type was not a fraction type, that's a compiler bug! // fn to_frac( diff --git a/crates/compiler/specialize_types/src/mono_ir.rs b/crates/compiler/specialize_types/src/mono_ir.rs index 0211f43ca3..6a6657410d 100644 --- a/crates/compiler/specialize_types/src/mono_ir.rs +++ b/crates/compiler/specialize_types/src/mono_ir.rs @@ -61,7 +61,7 @@ pub struct MonoExprId { #[derive(Clone, Copy, Debug, PartialEq)] pub enum MonoExpr { - Str, + Str(InternedStrId), Number(Number), List { elem_type: MonoTypeId, diff --git a/crates/compiler/specialize_types/src/mono_module.rs b/crates/compiler/specialize_types/src/mono_module.rs index bf7f4de1a9..f1d7214dae 100644 --- a/crates/compiler/specialize_types/src/mono_module.rs +++ b/crates/compiler/specialize_types/src/mono_module.rs @@ -21,5 +21,36 @@ impl MonoModule { } } +/// TODO move this to its own crate #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct InternedStrId(u32); + +/// TODO move this to its own crate +pub struct Interns<'a> { + interned: Vec<&'a str>, +} + +impl<'a> Interns<'a> { + pub fn new() -> Self { + Self { + interned: Vec::new(), + } + } + + pub fn get(&mut self, string: &'a str) -> InternedStrId { + match self + .interned + .iter() + .position(|&interned| interned == string) + { + Some(index) => InternedStrId(index as u32), + None => { + let answer = InternedStrId(self.interned.len() as u32); + + self.interned.push(string); + + answer + } + } + } +} diff --git a/crates/compiler/specialize_types/src/mono_num.rs b/crates/compiler/specialize_types/src/mono_num.rs index 70fa9150a9..1a8e60761a 100644 --- a/crates/compiler/specialize_types/src/mono_num.rs +++ b/crates/compiler/specialize_types/src/mono_num.rs @@ -14,7 +14,7 @@ pub enum Number { U128(u128), F32(f32), F64(f64), - Dec(i128), + Dec(f64), } impl Number { diff --git a/crates/compiler/specialize_types/src/specialize_type.rs b/crates/compiler/specialize_types/src/specialize_type.rs index 0622b1e495..24ea5994b0 100644 --- a/crates/compiler/specialize_types/src/specialize_type.rs +++ b/crates/compiler/specialize_types/src/specialize_type.rs @@ -27,6 +27,9 @@ pub enum Problem { NumSpecializedToWrongType( Option, // `None` means it specialized to Unit ), + CharSpecializedToWrongType( + Option, // `None` means it specialized to Unit + ), } /// For MonoTypes that are records, store their field indices. diff --git a/crates/test_compile/src/help_specialize.rs b/crates/test_compile/src/help_specialize.rs index 9518511828..812174e996 100644 --- a/crates/test_compile/src/help_specialize.rs +++ b/crates/test_compile/src/help_specialize.rs @@ -1,7 +1,7 @@ use crate::SolvedExpr; use bumpalo::Bump; use roc_specialize_types::{ - DebugInfo, Env, MonoCache, MonoExprId, MonoExprs, MonoTypes, Problem, RecordFieldIds, + DebugInfo, Env, Interns, MonoCache, MonoExprId, MonoExprs, MonoTypes, Problem, RecordFieldIds, TupleElemIds, }; @@ -26,14 +26,17 @@ impl SpecializedExpr { let mut types_cache = MonoCache::from_subs(&solved_out.subs); let mut mono_types = MonoTypes::new(); let mut mono_exprs = MonoExprs::new(); + let mut string_interns = Interns::new(); let mut env = Env::new( + self.solved_expr.arena(), &mut solved_out.subs, &mut types_cache, &mut mono_types, &mut mono_exprs, RecordFieldIds::default(), TupleElemIds::default(), + &mut string_interns, &mut debug_info, &mut problems, ); From 8b73efc2ececf1828be4fa4925312dbc7c80ce1c Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Fri, 8 Nov 2024 21:07:18 -0500 Subject: [PATCH 23/65] Test monomorphizing string literals --- .../specialize_types/src/mono_expr.rs | 16 ++- .../specialize_types/src/mono_module.rs | 15 +- .../specialize_types/src/specialize_type.rs | 2 +- .../tests/test_specialize_expr.rs | 135 ++++++++++++++++-- .../tests/test_specialize_types.rs | 2 +- crates/test_compile/src/help_specialize.rs | 37 ++--- crates/test_compile/src/lib.rs | 5 +- 7 files changed, 173 insertions(+), 39 deletions(-) diff --git a/crates/compiler/specialize_types/src/mono_expr.rs b/crates/compiler/specialize_types/src/mono_expr.rs index 80052c8be8..f5e64954b2 100644 --- a/crates/compiler/specialize_types/src/mono_expr.rs +++ b/crates/compiler/specialize_types/src/mono_expr.rs @@ -12,7 +12,7 @@ use roc_collections::Push; use roc_solve::module::Solved; use roc_types::subs::Subs; -pub struct Env<'a, 'c, 'd, 's, 't, P> { +pub struct Env<'a, 'c, 'd, 'i, 's, 't, P> { arena: &'a Bump, subs: &'s mut Subs, types_cache: &'c mut MonoCache, @@ -21,11 +21,11 @@ pub struct Env<'a, 'c, 'd, 's, 't, P> { record_field_ids: RecordFieldIds, tuple_elem_ids: TupleElemIds, debug_info: &'d mut Option, - string_interns: &'a mut Interns<'a>, + string_interns: &'i mut Interns<'a>, problems: P, } -impl<'a, 'c, 'd, 's, 't, P: Push> Env<'a, 'c, 'd, 's, 't, P> { +impl<'a, 'c, 'd, 'i, 's, 't, P: Push> Env<'a, 'c, 'd, 'i, 's, 't, P> { pub fn new( arena: &'a Bump, subs: &'s mut Solved, @@ -34,7 +34,7 @@ impl<'a, 'c, 'd, 's, 't, P: Push> Env<'a, 'c, 'd, 's, 't, P> { mono_exprs: &'t mut MonoExprs, record_field_ids: RecordFieldIds, tuple_elem_ids: TupleElemIds, - string_interns: &'a mut Interns<'a>, + string_interns: &'i mut Interns<'a>, debug_info: &'d mut Option, problems: P, ) -> Self { @@ -88,6 +88,7 @@ impl<'a, 'c, 'd, 's, 't, P: Push> Env<'a, 'c, 'd, 's, 't, P> { } }, Expr::Num(var, _str, int_value, _) | Expr::Int(var, _, _str, int_value, _) => { + // Numbers can specialize match mono_from_var(var) { Some(mono_id) => match mono_types.get(mono_id) { MonoType::Primitive(primitive) => to_num(*primitive, int_value, problems), @@ -115,9 +116,10 @@ impl<'a, 'c, 'd, 's, 't, P: Push> Env<'a, 'c, 'd, 's, 't, P> { return compiler_bug!(Problem::CharSpecializedToWrongType(None)); } }, - Expr::Str(contents) => { - MonoExpr::Str(self.string_interns.get(self.arena.alloc(contents))) - } + Expr::Str(contents) => MonoExpr::Str( + self.string_interns + .get(self.arena, self.arena.alloc(contents)), + ), Expr::EmptyRecord => { // Empty records are zero-sized and should be discarded. return None; diff --git a/crates/compiler/specialize_types/src/mono_module.rs b/crates/compiler/specialize_types/src/mono_module.rs index f1d7214dae..4ddc741204 100644 --- a/crates/compiler/specialize_types/src/mono_module.rs +++ b/crates/compiler/specialize_types/src/mono_module.rs @@ -1,3 +1,4 @@ +use bumpalo::Bump; use roc_solve::module::Solved; use roc_types::subs::Subs; @@ -26,6 +27,7 @@ impl MonoModule { pub struct InternedStrId(u32); /// TODO move this to its own crate +#[derive(Debug)] pub struct Interns<'a> { interned: Vec<&'a str>, } @@ -37,7 +39,7 @@ impl<'a> Interns<'a> { } } - pub fn get(&mut self, string: &'a str) -> InternedStrId { + pub fn get(&mut self, _arena: &'a Bump, string: &'a str) -> InternedStrId { match self .interned .iter() @@ -53,4 +55,15 @@ impl<'a> Interns<'a> { } } } + + pub fn try_get(&self, _arena: &'a Bump, string: &'a str) -> Option { + match self + .interned + .iter() + .position(|&interned| interned == string) + { + Some(index) => Some(InternedStrId(index as u32)), + None => None, + } + } } diff --git a/crates/compiler/specialize_types/src/specialize_type.rs b/crates/compiler/specialize_types/src/specialize_type.rs index 24ea5994b0..3e4eb2dd81 100644 --- a/crates/compiler/specialize_types/src/specialize_type.rs +++ b/crates/compiler/specialize_types/src/specialize_type.rs @@ -48,7 +48,7 @@ pub struct MonoCache { } impl MonoCache { - pub fn from_subs(subs: &Solved) -> Self { + pub fn from_solved_subs(subs: &Solved) -> Self { Self { inner: VecMap::with_capacity(subs.inner().len()), } diff --git a/crates/compiler/specialize_types/tests/test_specialize_expr.rs b/crates/compiler/specialize_types/tests/test_specialize_expr.rs index 040a87b5b2..18c04d9482 100644 --- a/crates/compiler/specialize_types/tests/test_specialize_expr.rs +++ b/crates/compiler/specialize_types/tests/test_specialize_expr.rs @@ -5,31 +5,148 @@ extern crate bumpalo; #[cfg(test)] mod specialize_types { - use roc_specialize_types::{MonoExpr, Number}; - use test_compile::specialize_expr; + use bumpalo::Bump; + use roc_load::LoadedModule; + use roc_solve::FunctionKind; + use roc_specialize_types::{ + DebugInfo, Env, Interns, MonoCache, MonoExpr, MonoExprs, MonoTypes, Number, RecordFieldIds, + TupleElemIds, + }; + use test_compile::{trim_and_deindent, SpecializedExprOut}; + use test_solve_helpers::{format_problems, run_load_and_infer}; + + // HELPERS + + fn specialize_expr<'a>( + arena: &'a Bump, + src: &str, + string_interns: &mut Interns<'a>, + ) -> SpecializedExprOut { + let ( + LoadedModule { + module_id: home, + mut declarations_by_id, + mut can_problems, + mut type_problems, + interns, + mut solved, + mut exposed_to_host, + abilities_store, + .. + }, + src, + ) = run_load_and_infer( + trim_and_deindent(&arena, src), + [], + false, + FunctionKind::LambdaSet, + ) + .unwrap(); + + let mut can_problems = can_problems.remove(&home).unwrap_or_default(); + let type_problems = type_problems.remove(&home).unwrap_or_default(); + + // Disregard UnusedDef problems, because those are unavoidable when + // returning a function from the test expression. + can_problems.retain(|prob| { + !matches!( + prob, + roc_problem::can::Problem::UnusedDef(_, _) + | roc_problem::can::Problem::UnusedBranchDef(..) + ) + }); + + let (can_problems, type_problems) = + format_problems(&src, home, &interns, can_problems, type_problems); + + assert_eq!(can_problems, String::new()); + assert_eq!(type_problems, String::new()); + + exposed_to_host.retain(|s, _| !abilities_store.is_specialization_name(*s)); + + let mut problems = Vec::new(); + let mut debug_info: Option = None; + let mut types_cache = MonoCache::from_solved_subs(&solved); + let mut mono_types = MonoTypes::new(); + let mut mono_exprs = MonoExprs::new(); + + let mut env = Env::new( + &arena, + &mut solved, + &mut types_cache, + &mut mono_types, + &mut mono_exprs, + RecordFieldIds::default(), + TupleElemIds::default(), + string_interns, + &mut debug_info, + &mut problems, + ); + + let mut home_decls = declarations_by_id.remove(&home).unwrap(); + let main_expr = home_decls.expressions.pop().unwrap().value; + + // This should be our only expr + assert_eq!(0, home_decls.expressions.len()); + + let mono_expr_id = env.to_mono_expr(main_expr); + + SpecializedExprOut { + mono_expr_id, + problems, + mono_types, + mono_exprs, + } + } fn expect_no_expr(input: impl AsRef) { - let out = specialize_expr(input.as_ref()); + let arena = Bump::new(); + let mut interns = Interns::new(); + let out = specialize_expr(&arena, input.as_ref(), &mut interns); let actual = out.mono_expr_id.map(|id| out.mono_exprs.get(id)); assert_eq!(None, actual, "This input expr should have specialized to being dicarded as zero-sized, but it didn't: {:?}", input.as_ref()); } fn expect_mono_expr(input: impl AsRef, mono_expr: MonoExpr) { - let out = specialize_expr(input.as_ref()); - let mono_expr_id = out - .mono_expr_id - .expect("This input expr should not have been discarded as zero-sized, but it was discarded: {input:?}"); + expect_mono_expr_with_interns(|_, _| {}, input, |_| mono_expr); + } - assert_eq!(&mono_expr, out.mono_exprs.get(mono_expr_id)); + fn expect_mono_expr_with_interns( + from_interns: impl for<'a> FnOnce(&'a Bump, &Interns<'a>) -> T, + input: impl AsRef, + to_mono_expr: impl FnOnce(T) -> MonoExpr, + ) { + let arena = Bump::new(); + let mut string_interns = Interns::new(); + let out = specialize_expr(&arena, input.as_ref(), &mut string_interns); + let mono_expr_id = out + .mono_expr_id + .expect("This input expr should not have been discarded as zero-sized, but it was discarded: {input:?}"); + + let actual_expr = out.mono_exprs.get(mono_expr_id); // Must run first, to populate string interns! + + let expected_expr = to_mono_expr(from_interns(&arena, &string_interns)); + + assert_eq!(&expected_expr, actual_expr); } #[test] fn empty_record() { - let todo = (); // TODO need to get earlier stage working, specifically constraint + solve, by replicating existing solve tests. expect_no_expr("{}"); } + #[test] + fn string_literal() { + let string = "foo"; + let expected = format!("\"{string}\""); + expect_mono_expr_with_interns( + |arena, interns| interns.try_get(arena, string).unwrap(), + expected, + |id| MonoExpr::Str(id), + ); + } + #[test] fn unbound_num() { let expected = 42; diff --git a/crates/compiler/specialize_types/tests/test_specialize_types.rs b/crates/compiler/specialize_types/tests/test_specialize_types.rs index 192036408c..8a25b6c841 100644 --- a/crates/compiler/specialize_types/tests/test_specialize_types.rs +++ b/crates/compiler/specialize_types/tests/test_specialize_types.rs @@ -51,7 +51,7 @@ mod specialize_types { debug_assert!(exposed_to_host.len() == 1, "{exposed_to_host:?}"); let (_symbol, variable) = exposed_to_host.into_iter().next().unwrap(); - let mut mono_cache = MonoCache::from_subs(&solved); + let mut mono_cache = MonoCache::from_solved_subs(&solved); let mut mono_types = MonoTypes::new(); let debug_info = DebugInfo::new(); let mut record_field_ids = RecordFieldIds::new(); diff --git a/crates/test_compile/src/help_specialize.rs b/crates/test_compile/src/help_specialize.rs index 812174e996..ff83266b90 100644 --- a/crates/test_compile/src/help_specialize.rs +++ b/crates/test_compile/src/help_specialize.rs @@ -19,29 +19,34 @@ pub struct SpecializedExpr { } impl SpecializedExpr { - pub fn specialize_expr<'a>(&'a self, input: &'a str) -> SpecializedExprOut { + pub fn specialize_expr<'a>( + &'a self, + input: &'a str, + string_interns: &'a mut Interns<'a>, + ) -> SpecializedExprOut { let mut solved_out = self.solved_expr.solve_expr(input); let mut problems = Vec::new(); let mut debug_info: Option = None; - let mut types_cache = MonoCache::from_subs(&solved_out.subs); + let mut types_cache = MonoCache::from_solved_subs(&solved_out.subs); let mut mono_types = MonoTypes::new(); let mut mono_exprs = MonoExprs::new(); - let mut string_interns = Interns::new(); - let mut env = Env::new( - self.solved_expr.arena(), - &mut solved_out.subs, - &mut types_cache, - &mut mono_types, - &mut mono_exprs, - RecordFieldIds::default(), - TupleElemIds::default(), - &mut string_interns, - &mut debug_info, - &mut problems, - ); + let mono_expr_id = { + let mut env = Env::new( + self.solved_expr.arena(), + &mut solved_out.subs, + &mut types_cache, + &mut mono_types, + &mut mono_exprs, + RecordFieldIds::default(), + TupleElemIds::default(), + string_interns, + &mut debug_info, + &mut problems, + ); - let mono_expr_id = env.to_mono_expr(solved_out.expr); + env.to_mono_expr(solved_out.expr) + }; SpecializedExprOut { mono_expr_id, diff --git a/crates/test_compile/src/lib.rs b/crates/test_compile/src/lib.rs index 16ec9d17ae..69e28ce482 100644 --- a/crates/test_compile/src/lib.rs +++ b/crates/test_compile/src/lib.rs @@ -5,6 +5,7 @@ mod help_parse; mod help_solve; mod help_specialize; +pub use deindent::trim_and_deindent; pub use help_can::{CanExpr, CanExprOut}; pub use help_parse::ParseExpr; pub use help_solve::{SolvedExpr, SolvedExprOut}; @@ -17,7 +18,3 @@ pub fn can_expr<'a>(input: &'a str) -> CanExprOut { pub fn solve_expr<'a>(input: &'a str) -> SolvedExprOut { SolvedExpr::default().solve_expr(input) } - -pub fn specialize_expr<'a>(input: &'a str) -> SpecializedExprOut { - SpecializedExpr::default().specialize_expr(input) -} From 2c2a45d9d9befb99def55a20bccd3c1ec7a9839d Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sat, 9 Nov 2024 00:44:27 -0500 Subject: [PATCH 24/65] Monomorphize numbers --- crates/compiler/collections/src/push.rs | 1 + .../specialize_types/src/mono_expr.rs | 7 +- .../specialize_types/src/mono_type.rs | 87 +++++++++- .../specialize_types/src/specialize_type.rs | 150 ++++++++++++++++-- 4 files changed, 226 insertions(+), 19 deletions(-) diff --git a/crates/compiler/collections/src/push.rs b/crates/compiler/collections/src/push.rs index 1a05826416..c3fb5cf2d2 100644 --- a/crates/compiler/collections/src/push.rs +++ b/crates/compiler/collections/src/push.rs @@ -7,6 +7,7 @@ impl Push for Vec { self.push(entry); } } + impl Push for &mut Vec { fn push(&mut self, entry: T) { (*self).push(entry); diff --git a/crates/compiler/specialize_types/src/mono_expr.rs b/crates/compiler/specialize_types/src/mono_expr.rs index f5e64954b2..433eba33cc 100644 --- a/crates/compiler/specialize_types/src/mono_expr.rs +++ b/crates/compiler/specialize_types/src/mono_expr.rs @@ -263,7 +263,7 @@ fn to_num(primitive: Primitive, val: IntValue, problems: &mut impl Push })), Primitive::U128 => MonoExpr::Number(Number::U128(val.as_u128())), Primitive::I128 => MonoExpr::Number(Number::I128(val.as_i128())), - Primitive::Str => { + Primitive::Str | Primitive::Crash => { let problem = Problem::NumSpecializedToWrongType(Some(MonoType::Primitive(primitive))); problems.push(problem); MonoExpr::CompilerBug(problem) @@ -289,7 +289,8 @@ fn to_frac(primitive: Primitive, val: f64, problems: &mut impl Push) -> | Primitive::I64 | Primitive::U128 | Primitive::I128 - | Primitive::Str => { + | Primitive::Str + | Primitive::Crash => { let problem = Problem::NumSpecializedToWrongType(Some(MonoType::Primitive(primitive))); problems.push(problem); MonoExpr::CompilerBug(problem) @@ -312,7 +313,7 @@ fn char_to_int(primitive: Primitive, ch: char, problems: &mut impl Push Primitive::I128 => MonoExpr::Number(Number::I128(ch as i128)), Primitive::I16 => MonoExpr::Number(Number::I16(ch as i16)), Primitive::I8 => MonoExpr::Number(Number::I8(ch as i8)), - Primitive::Str | Primitive::Dec | Primitive::F32 | Primitive::F64 => { + Primitive::Str | Primitive::Dec | Primitive::F32 | Primitive::F64 | Primitive::Crash => { let problem = Problem::CharSpecializedToWrongType(Some(MonoType::Primitive(primitive))); problems.push(problem); MonoExpr::CompilerBug(problem) diff --git a/crates/compiler/specialize_types/src/mono_type.rs b/crates/compiler/specialize_types/src/mono_type.rs index 0c9bbefe81..31725d231c 100644 --- a/crates/compiler/specialize_types/src/mono_type.rs +++ b/crates/compiler/specialize_types/src/mono_type.rs @@ -7,6 +7,68 @@ pub struct MonoTypeId { } impl MonoTypeId { + pub const CRASH: Self = Self { + inner: Index::new(0), + }; + + pub const STR: Self = Self { + inner: Index::new(1), + }; + + pub const U8: Self = Self { + inner: Index::new(2), + }; + + pub const I8: Self = Self { + inner: Index::new(3), + }; + + pub const U16: Self = Self { + inner: Index::new(4), + }; + + pub const I16: Self = Self { + inner: Index::new(5), + }; + + pub const U32: Self = Self { + inner: Index::new(6), + }; + + pub const I32: Self = Self { + inner: Index::new(7), + }; + pub const U64: Self = Self { + inner: Index::new(8), + }; + + pub const I64: Self = Self { + inner: Index::new(9), + }; + + pub const U128: Self = Self { + inner: Index::new(10), + }; + + pub const I128: Self = Self { + inner: Index::new(11), + }; + + pub const F32: Self = Self { + inner: Index::new(12), + }; + + pub const F64: Self = Self { + inner: Index::new(13), + }; + + pub const DEC: Self = Self { + inner: Index::new(14), + }; + + pub const DEFAULT_INT: Self = Self::I64; // TODO change this to I128 + pub const DEFAULT_FRAC: Self = Self::DEC; + fn new(inner: Index) -> Self { Self { inner } } @@ -22,7 +84,23 @@ pub struct MonoTypes { impl MonoTypes { pub fn new() -> Self { Self { - entries: Vec::new(), + entries: vec![ + MonoType::Primitive(Primitive::Crash), + MonoType::Primitive(Primitive::Str), + MonoType::Primitive(Primitive::U8), + MonoType::Primitive(Primitive::I8), + MonoType::Primitive(Primitive::U16), + MonoType::Primitive(Primitive::I16), + MonoType::Primitive(Primitive::U32), + MonoType::Primitive(Primitive::I32), + MonoType::Primitive(Primitive::U64), + MonoType::Primitive(Primitive::I64), + MonoType::Primitive(Primitive::U128), + MonoType::Primitive(Primitive::I128), + MonoType::Primitive(Primitive::F32), + MonoType::Primitive(Primitive::F64), + MonoType::Primitive(Primitive::Dec), + ], ids: Vec::new(), slices: Vec::new(), } @@ -150,10 +228,8 @@ impl MonoTypes { /// In the future, we may promote common builtin types to Primitives, e.g. List U8, List Str, etc. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Primitive { + Crash, Str, - Dec, - F32, - F64, U8, I8, U16, @@ -164,6 +240,9 @@ pub enum Primitive { I64, U128, I128, + F32, + F64, + Dec, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] diff --git a/crates/compiler/specialize_types/src/specialize_type.rs b/crates/compiler/specialize_types/src/specialize_type.rs index 3e4eb2dd81..b32d404eaa 100644 --- a/crates/compiler/specialize_types/src/specialize_type.rs +++ b/crates/compiler/specialize_types/src/specialize_type.rs @@ -9,10 +9,14 @@ use crate::{ MonoFieldId, MonoType, }; use roc_collections::{Push, VecMap}; -use roc_module::ident::{Lowercase, TagName}; +use roc_module::{ + ident::{Lowercase, TagName}, + symbol::Symbol, +}; use roc_solve::module::Solved; use roc_types::subs::{ - Content, FlatType, RecordFields, Subs, TagExt, TupleElems, UnionLabels, UnionTags, Variable, + Content, FlatType, RecordFields, Subs, SubsSlice, TagExt, TupleElems, UnionLabels, UnionTags, + Variable, }; #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -30,6 +34,7 @@ pub enum Problem { CharSpecializedToWrongType( Option, // `None` means it specialized to Unit ), + BadNumTypeParam, } /// For MonoTypes that are records, store their field indices. @@ -98,19 +103,31 @@ fn lower_var>( // TODO: we could replace this cache by having Subs store a Content::Monomorphic(MonoTypeId) // and then overwrite it rather than having a separate cache. That memory is already in cache // for sure, and the lookups should be faster because they're O(1) but don't require hashing. + // Kinda creates a cyclic dep though. if let Some(mono_id) = env.cache.inner.get(&root_var) { return Some(*mono_id); } // Convert the Content to a MonoType, often by passing an iterator. None of these iterators introduce allocations. - let opt_mono_id = match *subs.get_content_without_compacting(root_var) { + let mono_id = match *subs.get_content_without_compacting(root_var) { Content::Structure(flat_type) => match flat_type { FlatType::Apply(symbol, args) => { - let new_args = args - .into_iter() - .flat_map(|var_index| lower_var(env, subs, subs[var_index])); + if symbol.is_builtin() { + if symbol == Symbol::NUM_NUM { + num_args_to_mono_id(args, subs, env.problems) + } else if symbol == Symbol::LIST_LIST { + todo!(); + // let mut new_args = args + // .into_iter() + // .flat_map(|var_index| lower_var(env, subs, subs[var_index])); - todo!("maybe instead of making new_args, branch and then call lower_var to create Primitive, List, Box, etc."); + // let arg = new_args.next(); + } else { + todo!() + } + } else { + todo!("handle non-builtin Apply"); + } } // FlatType::Func(args, _capture, ret) => { // let mono_args = args @@ -240,13 +257,122 @@ fn lower_var>( } }; - if let Some(mono_id) = opt_mono_id { - // This var is now known to be monomorphic, so we don't repeat this work again later. - // (We don't insert entries for Unit values.) - env.cache.inner.insert(root_var, mono_id); + // This var is now known to be monomorphic, so we don't repeat this work again later. + // (We don't insert entries for Unit values.) + env.cache.inner.insert(root_var, mono_id); + + Some(mono_id) +} + +fn num_args_to_mono_id( + args: SubsSlice, + subs: &Subs, + problems: &mut impl Push, +) -> MonoTypeId { + match args.into_iter().next() { + Some(arg_index) if args.len() == 1 => { + match subs.get_content_without_compacting(subs[arg_index]) { + Content::Structure(flat_type) => { + if let FlatType::Apply(outer_symbol, args) = flat_type { + let outer_symbol = *outer_symbol; + + match args.into_iter().next() { + Some(arg_index) if args.len() == 1 => { + match subs.get_content_without_compacting(subs[arg_index]) { + Content::Structure(flat_type) => { + if let FlatType::Apply(inner_symbol, args) = flat_type { + let inner_symbol = *inner_symbol; + + if args.is_empty() { + if outer_symbol == Symbol::NUM_INTEGER { + if inner_symbol == Symbol::NUM_UNSIGNED8 { + return MonoTypeId::U8; + } else if inner_symbol == Symbol::NUM_SIGNED8 { + return MonoTypeId::I8; + } else if inner_symbol == Symbol::NUM_UNSIGNED16 + { + return MonoTypeId::U16; + } else if inner_symbol == Symbol::NUM_SIGNED16 { + return MonoTypeId::I16; + } else if inner_symbol == Symbol::NUM_UNSIGNED32 + { + return MonoTypeId::U32; + } else if inner_symbol == Symbol::NUM_SIGNED32 { + return MonoTypeId::I32; + } else if inner_symbol == Symbol::NUM_UNSIGNED64 + { + return MonoTypeId::U64; + } else if inner_symbol == Symbol::NUM_SIGNED64 { + return MonoTypeId::I64; + } else if inner_symbol + == Symbol::NUM_UNSIGNED128 + { + return MonoTypeId::U128; + } else if inner_symbol == Symbol::NUM_SIGNED128 + { + return MonoTypeId::I128; + } + } else if outer_symbol == Symbol::NUM_FLOATINGPOINT + { + if inner_symbol == Symbol::NUM_BINARY32 { + return MonoTypeId::F32; + } else if inner_symbol == Symbol::NUM_BINARY64 { + return MonoTypeId::F64; + } else if inner_symbol == Symbol::NUM_DECIMAL { + return MonoTypeId::DEC; + } + } + } + } + } + Content::FlexVar(_) => { + if outer_symbol == Symbol::NUM_INTEGER { + // Int * + return MonoTypeId::DEFAULT_INT; + } else if outer_symbol == Symbol::NUM_FLOATINGPOINT { + // Frac * + return MonoTypeId::DEFAULT_FRAC; + } + } + Content::Alias( + symbol, + alias_variables, + variable, + alias_kind, + ) => todo!(), + Content::RangedNumber(numeric_range) => todo!(), + _ => { + // no-op + } + } + } + _ => { + // no-op + } + } + } + } + Content::FlexVar(subs_index) => { + // Num * + return MonoTypeId::DEFAULT_INT; + } + Content::Alias(symbol, alias_variables, variable, alias_kind) => todo!(), + Content::RangedNumber(numeric_range) => todo!(), + _ => { + // fall through + } + } + } + _ => { + // fall through + } } - opt_mono_id + // If we got here, it's because the Num type parameter(s) don't fit the form we expect. + // Specialize to a crash! + problems.push(Problem::BadNumTypeParam); + + MonoTypeId::CRASH } fn resolve_tag_ext( From 074161733e8e02735c28005ecd722b7a374f875a Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Wed, 13 Nov 2024 21:19:16 -0500 Subject: [PATCH 25/65] wip --- .../specialize_types/src/mono_type.rs | 15 +- .../specialize_types/src/specialize_type.rs | 45 ++++- .../specialize_types/tests/helpers/mod.rs | 126 ++++++++++++ .../tests/specialize_primitives.rs | 181 ++++++++++++++++++ .../tests/test_specialize_expr.rs | 158 --------------- 5 files changed, 359 insertions(+), 166 deletions(-) create mode 100644 crates/compiler/specialize_types/tests/helpers/mod.rs create mode 100644 crates/compiler/specialize_types/tests/specialize_primitives.rs delete mode 100644 crates/compiler/specialize_types/tests/test_specialize_expr.rs diff --git a/crates/compiler/specialize_types/src/mono_type.rs b/crates/compiler/specialize_types/src/mono_type.rs index 31725d231c..11101d662b 100644 --- a/crates/compiler/specialize_types/src/mono_type.rs +++ b/crates/compiler/specialize_types/src/mono_type.rs @@ -106,8 +106,7 @@ impl MonoTypes { } } pub fn get(&self, id: MonoTypeId) -> &MonoType { - todo!("builtins are stored inline"); - // Overall strategy: + // Future strategy: // - Look at the three high bits to figure out which of the 8 MonoTypes we're dealing with // - The non-parameterized builtins have 000 as their high bits, and the whole MonoTypeId can be cast to a Primitive. // - The parameterized builtins don't need to store a length, just an index. We store that index inline. @@ -116,6 +115,18 @@ impl MonoTypes { // - This means we use 2 bits for discriminant and another 2 bits for which parameterized type it is // - This means we get 29-bit indices, so a maximum of ~500M MonoTypes per module. Should be plenty. // - In the future, we can promote common collection types (e.g. List Str, List U8) to Primitives. + + let opt = self.entries.get(id.inner.index()); + + #[cfg(debug_assertions)] + { + opt.expect("A MonoTypeId corresponded to an index that wasn't in MonoTypes. This should never happen!") + } + + #[cfg(not(debug_assertions))] + unsafe { + opt.unwrap_unchecked() + } } pub(crate) fn add_primitive(&mut self, primitive: Primitive) -> MonoTypeId { diff --git a/crates/compiler/specialize_types/src/specialize_type.rs b/crates/compiler/specialize_types/src/specialize_type.rs index b32d404eaa..50fbf2d0de 100644 --- a/crates/compiler/specialize_types/src/specialize_type.rs +++ b/crates/compiler/specialize_types/src/specialize_type.rs @@ -244,17 +244,30 @@ fn lower_var>( | Content::RecursionVar { .. } => Content::Structure(FlatType::EmptyTagUnion), Content::LambdaSet(lambda_set) => Content::LambdaSet(lambda_set), Content::ErasedLambda => Content::ErasedLambda, - Content::Alias(symbol, args, real, kind) => { - let todo = (); // TODO we should unwrap this, but doing that probably means changing this from root_var to other stuff. - let new_real = lower_var(cache, subs, problems, *real); - Content::Alias(*symbol, *args, new_real, *kind) - } Content::Error => Content::Error, */ }, - _ => { + Content::RangedNumber(range) => { + use roc_types::num::NumericRange::*; + + match range { + IntAtLeastSigned(int_lit_width) => int_lit_width_to_mono_type_id(int_lit_width), + IntAtLeastEitherSign(int_lit_width) => int_lit_width_to_mono_type_id(int_lit_width), + NumAtLeastSigned(int_lit_width) => int_lit_width_to_mono_type_id(int_lit_width), + NumAtLeastEitherSign(int_lit_width) => int_lit_width_to_mono_type_id(int_lit_width), + } + } + Content::Alias(_symbol, args, real, kind) => { + let mono_id = lower_var(env, subs, real)?; + // let mono_args = args + // .into_iter() + // .flat_map(|arg| lower_var(env, subs, subs[arg])); + todo!(); } + content => { + todo!("specialize this Content: {content:?}"); + } }; // This var is now known to be monomorphic, so we don't repeat this work again later. @@ -264,6 +277,26 @@ fn lower_var>( Some(mono_id) } +fn int_lit_width_to_mono_type_id(int_lit_width: roc_can::num::IntLitWidth) -> MonoTypeId { + use roc_can::num::IntLitWidth; + + match int_lit_width { + IntLitWidth::U8 => MonoTypeId::U8, + IntLitWidth::U16 => MonoTypeId::U16, + IntLitWidth::U32 => MonoTypeId::U32, + IntLitWidth::U64 => MonoTypeId::U64, + IntLitWidth::U128 => MonoTypeId::U128, + IntLitWidth::I8 => MonoTypeId::I8, + IntLitWidth::I16 => MonoTypeId::I16, + IntLitWidth::I32 => MonoTypeId::I32, + IntLitWidth::I64 => MonoTypeId::I64, + IntLitWidth::I128 => MonoTypeId::I128, + IntLitWidth::F32 => MonoTypeId::F32, + IntLitWidth::F64 => MonoTypeId::F64, + IntLitWidth::Dec => MonoTypeId::DEC, + } +} + fn num_args_to_mono_id( args: SubsSlice, subs: &Subs, diff --git a/crates/compiler/specialize_types/tests/helpers/mod.rs b/crates/compiler/specialize_types/tests/helpers/mod.rs new file mode 100644 index 0000000000..f9d49c6bdd --- /dev/null +++ b/crates/compiler/specialize_types/tests/helpers/mod.rs @@ -0,0 +1,126 @@ +use bumpalo::Bump; +use roc_load::LoadedModule; +use roc_solve::FunctionKind; +use roc_specialize_types::{ + DebugInfo, Env, Interns, MonoCache, MonoExpr, MonoExprs, MonoTypes, RecordFieldIds, + TupleElemIds, +}; +use test_compile::{trim_and_deindent, SpecializedExprOut}; +use test_solve_helpers::{format_problems, run_load_and_infer}; + +fn specialize_expr<'a>( + arena: &'a Bump, + src: &str, + string_interns: &mut Interns<'a>, +) -> SpecializedExprOut { + let ( + LoadedModule { + module_id: home, + mut declarations_by_id, + mut can_problems, + mut type_problems, + interns, + mut solved, + mut exposed_to_host, + abilities_store, + .. + }, + src, + ) = run_load_and_infer( + trim_and_deindent(&arena, src), + [], + false, + FunctionKind::LambdaSet, + ) + .unwrap(); + + let mut can_problems = can_problems.remove(&home).unwrap_or_default(); + let type_problems = type_problems.remove(&home).unwrap_or_default(); + + // Disregard UnusedDef problems, because those are unavoidable when + // returning a function from the test expression. + can_problems.retain(|prob| { + !matches!( + prob, + roc_problem::can::Problem::UnusedDef(_, _) + | roc_problem::can::Problem::UnusedBranchDef(..) + ) + }); + + let (can_problems, type_problems) = + format_problems(&src, home, &interns, can_problems, type_problems); + + assert_eq!(can_problems, String::new()); + assert_eq!(type_problems, String::new()); + + exposed_to_host.retain(|s, _| !abilities_store.is_specialization_name(*s)); + + let mut problems = Vec::new(); + let mut debug_info: Option = None; + let mut types_cache = MonoCache::from_solved_subs(&solved); + let mut mono_types = MonoTypes::new(); + let mut mono_exprs = MonoExprs::new(); + + let mut env = Env::new( + &arena, + &mut solved, + &mut types_cache, + &mut mono_types, + &mut mono_exprs, + RecordFieldIds::default(), + TupleElemIds::default(), + string_interns, + &mut debug_info, + &mut problems, + ); + + let mut home_decls = declarations_by_id.remove(&home).unwrap(); + let main_expr = home_decls.expressions.pop().unwrap().value; + + // This should be our only expr + assert_eq!(0, home_decls.expressions.len()); + + let mono_expr_id = env.to_mono_expr(main_expr); + + SpecializedExprOut { + mono_expr_id, + problems, + mono_types, + mono_exprs, + } +} + +#[track_caller] +pub fn expect_no_expr(input: impl AsRef) { + let arena = Bump::new(); + let mut interns = Interns::new(); + let out = specialize_expr(&arena, input.as_ref(), &mut interns); + let actual = out.mono_expr_id.map(|id| out.mono_exprs.get(id)); + + assert_eq!(None, actual, "This input expr should have specialized to being dicarded as zero-sized, but it didn't: {:?}", input.as_ref()); +} + +#[track_caller] +pub fn expect_mono_expr(input: impl AsRef, mono_expr: MonoExpr) { + expect_mono_expr_with_interns(|_, _| {}, input, |_| mono_expr); +} + +#[track_caller] +pub fn expect_mono_expr_with_interns( + from_interns: impl for<'a> FnOnce(&'a Bump, &Interns<'a>) -> T, + input: impl AsRef, + to_mono_expr: impl FnOnce(T) -> MonoExpr, +) { + let arena = Bump::new(); + let mut string_interns = Interns::new(); + let out = specialize_expr(&arena, input.as_ref(), &mut string_interns); + let mono_expr_id = out + .mono_expr_id + .expect("This input expr should not have been discarded as zero-sized, but it was discarded: {input:?}"); + + let actual_expr = out.mono_exprs.get(mono_expr_id); // Must run first, to populate string interns! + + let expected_expr = to_mono_expr(from_interns(&arena, &string_interns)); + + assert_eq!(&expected_expr, actual_expr); +} diff --git a/crates/compiler/specialize_types/tests/specialize_primitives.rs b/crates/compiler/specialize_types/tests/specialize_primitives.rs new file mode 100644 index 0000000000..6366e20c31 --- /dev/null +++ b/crates/compiler/specialize_types/tests/specialize_primitives.rs @@ -0,0 +1,181 @@ +#[macro_use] +extern crate pretty_assertions; + +extern crate bumpalo; + +mod helpers; + +#[cfg(test)] +mod specialize_primitives { + use roc_specialize_types::{MonoExpr, Number}; + + use super::helpers::{expect_mono_expr, expect_mono_expr_with_interns, expect_no_expr}; + + #[test] + fn empty_record() { + expect_no_expr("{}"); + } + + #[test] + fn string_literal() { + let string = "foo"; + let expected = format!("\"{string}\""); + expect_mono_expr_with_interns( + |arena, interns| interns.try_get(arena, string).unwrap(), + expected, + |id| MonoExpr::Str(id), + ); + } + + #[test] + fn unbound_zero() { + let expected = 0; + expect_mono_expr( + format!("{expected}"), + MonoExpr::Number(Number::I8(expected)), + ); + } + + #[test] + fn unbound_negative_i8() { + let expected = -42; + expect_mono_expr( + format!("{expected}"), + MonoExpr::Number(Number::I8(expected)), + ); + } + + #[test] + fn unbound_positive_i8() { + let expected = 42; + expect_mono_expr( + format!("{expected}"), + MonoExpr::Number(Number::I8(expected)), + ); + } + + #[test] + fn unbound_u8() { + let expected = 128; + expect_mono_expr( + format!("{expected}"), + MonoExpr::Number(Number::U8(expected)), + ); + } + + #[test] + fn unbound_negative_i16() { + let expected = -5_000; + expect_mono_expr( + format!("{expected}"), + MonoExpr::Number(Number::I16(expected)), + ); + } + + #[test] + fn unbound_positive_i16() { + let expected = 5_000; + expect_mono_expr( + format!("{expected}"), + MonoExpr::Number(Number::I16(expected)), + ); + } + + #[test] + fn unbound_u16() { + let expected = 65_000; + expect_mono_expr( + format!("{expected}"), + MonoExpr::Number(Number::U16(expected)), + ); + } + #[test] + fn unbound_negative_i32() { + let expected = -2_000_000_000; + expect_mono_expr( + format!("{expected}"), + MonoExpr::Number(Number::I32(expected)), + ); + } + + #[test] + fn unbound_positive_i32() { + let expected = 2_000_000_000; + expect_mono_expr( + format!("{expected}"), + MonoExpr::Number(Number::I32(expected)), + ); + } + + #[test] + fn unbound_u32() { + let expected = 4_000_000_000; + expect_mono_expr( + format!("{expected}"), + MonoExpr::Number(Number::U32(expected)), + ); + } + + #[test] + fn unbound_negative_i64() { + let expected = -9_000_000_000_000_000_000; + expect_mono_expr( + format!("{expected}"), + MonoExpr::Number(Number::I64(expected)), + ); + } + + #[test] + fn unbound_positive_i64() { + let expected = 9_000_000_000_000_000_000; + expect_mono_expr( + format!("{expected}"), + MonoExpr::Number(Number::I64(expected)), + ); + } + + #[test] + fn unbound_u64() { + let expected = 18_000_000_000_000_000_000; + expect_mono_expr( + format!("{expected}"), + MonoExpr::Number(Number::U64(expected)), + ); + } + + #[test] + fn unbound_negative_i128() { + let expected = -170_141_183_460_469_231_731_687_303_715_884_105_728; + expect_mono_expr( + format!("{expected}"), + MonoExpr::Number(Number::I128(expected)), + ); + } + + #[test] + fn unbound_positive_i128() { + let expected = 170_141_183_460_469_231_731_687_303_715_884_105_727; + expect_mono_expr( + format!("{expected}"), + MonoExpr::Number(Number::I128(expected)), + ); + } + + #[test] + fn unbound_u128() { + let expected = 340_282_366_920_938_463_463_374_607_431_768_211_455; + expect_mono_expr( + format!("{expected}"), + MonoExpr::Number(Number::U128(expected)), + ); + } + + #[test] + fn unbound_f64() { + let expected = 3.14159265359; + expect_mono_expr( + format!("{expected}"), + MonoExpr::Number(Number::Dec(expected)), + ); + } +} diff --git a/crates/compiler/specialize_types/tests/test_specialize_expr.rs b/crates/compiler/specialize_types/tests/test_specialize_expr.rs deleted file mode 100644 index 18c04d9482..0000000000 --- a/crates/compiler/specialize_types/tests/test_specialize_expr.rs +++ /dev/null @@ -1,158 +0,0 @@ -#[macro_use] -extern crate pretty_assertions; - -extern crate bumpalo; - -#[cfg(test)] -mod specialize_types { - use bumpalo::Bump; - use roc_load::LoadedModule; - use roc_solve::FunctionKind; - use roc_specialize_types::{ - DebugInfo, Env, Interns, MonoCache, MonoExpr, MonoExprs, MonoTypes, Number, RecordFieldIds, - TupleElemIds, - }; - use test_compile::{trim_and_deindent, SpecializedExprOut}; - use test_solve_helpers::{format_problems, run_load_and_infer}; - - // HELPERS - - fn specialize_expr<'a>( - arena: &'a Bump, - src: &str, - string_interns: &mut Interns<'a>, - ) -> SpecializedExprOut { - let ( - LoadedModule { - module_id: home, - mut declarations_by_id, - mut can_problems, - mut type_problems, - interns, - mut solved, - mut exposed_to_host, - abilities_store, - .. - }, - src, - ) = run_load_and_infer( - trim_and_deindent(&arena, src), - [], - false, - FunctionKind::LambdaSet, - ) - .unwrap(); - - let mut can_problems = can_problems.remove(&home).unwrap_or_default(); - let type_problems = type_problems.remove(&home).unwrap_or_default(); - - // Disregard UnusedDef problems, because those are unavoidable when - // returning a function from the test expression. - can_problems.retain(|prob| { - !matches!( - prob, - roc_problem::can::Problem::UnusedDef(_, _) - | roc_problem::can::Problem::UnusedBranchDef(..) - ) - }); - - let (can_problems, type_problems) = - format_problems(&src, home, &interns, can_problems, type_problems); - - assert_eq!(can_problems, String::new()); - assert_eq!(type_problems, String::new()); - - exposed_to_host.retain(|s, _| !abilities_store.is_specialization_name(*s)); - - let mut problems = Vec::new(); - let mut debug_info: Option = None; - let mut types_cache = MonoCache::from_solved_subs(&solved); - let mut mono_types = MonoTypes::new(); - let mut mono_exprs = MonoExprs::new(); - - let mut env = Env::new( - &arena, - &mut solved, - &mut types_cache, - &mut mono_types, - &mut mono_exprs, - RecordFieldIds::default(), - TupleElemIds::default(), - string_interns, - &mut debug_info, - &mut problems, - ); - - let mut home_decls = declarations_by_id.remove(&home).unwrap(); - let main_expr = home_decls.expressions.pop().unwrap().value; - - // This should be our only expr - assert_eq!(0, home_decls.expressions.len()); - - let mono_expr_id = env.to_mono_expr(main_expr); - - SpecializedExprOut { - mono_expr_id, - problems, - mono_types, - mono_exprs, - } - } - - fn expect_no_expr(input: impl AsRef) { - let arena = Bump::new(); - let mut interns = Interns::new(); - let out = specialize_expr(&arena, input.as_ref(), &mut interns); - let actual = out.mono_expr_id.map(|id| out.mono_exprs.get(id)); - - assert_eq!(None, actual, "This input expr should have specialized to being dicarded as zero-sized, but it didn't: {:?}", input.as_ref()); - } - - fn expect_mono_expr(input: impl AsRef, mono_expr: MonoExpr) { - expect_mono_expr_with_interns(|_, _| {}, input, |_| mono_expr); - } - - fn expect_mono_expr_with_interns( - from_interns: impl for<'a> FnOnce(&'a Bump, &Interns<'a>) -> T, - input: impl AsRef, - to_mono_expr: impl FnOnce(T) -> MonoExpr, - ) { - let arena = Bump::new(); - let mut string_interns = Interns::new(); - let out = specialize_expr(&arena, input.as_ref(), &mut string_interns); - let mono_expr_id = out - .mono_expr_id - .expect("This input expr should not have been discarded as zero-sized, but it was discarded: {input:?}"); - - let actual_expr = out.mono_exprs.get(mono_expr_id); // Must run first, to populate string interns! - - let expected_expr = to_mono_expr(from_interns(&arena, &string_interns)); - - assert_eq!(&expected_expr, actual_expr); - } - - #[test] - fn empty_record() { - expect_no_expr("{}"); - } - - #[test] - fn string_literal() { - let string = "foo"; - let expected = format!("\"{string}\""); - expect_mono_expr_with_interns( - |arena, interns| interns.try_get(arena, string).unwrap(), - expected, - |id| MonoExpr::Str(id), - ); - } - - #[test] - fn unbound_num() { - let expected = 42; - expect_mono_expr( - format!("{expected}"), - MonoExpr::Number(Number::I64(expected)), - ); - } -} From c883a6b5aca905181d5020fd1cd89b453f45ed98 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sat, 16 Nov 2024 13:24:32 -0500 Subject: [PATCH 26/65] Monomorphize record literals --- .../specialize_types/src/mono_expr.rs | 86 ++++- .../compiler/specialize_types/src/mono_ir.rs | 89 ++++- .../specialize_types/src/specialize_expr.rs | 310 ------------------ .../specialize_types/src/specialize_type.rs | 1 + .../specialize_types/tests/helpers/mod.rs | 4 +- crates/soa/src/soa_slice.rs | 22 ++ 6 files changed, 171 insertions(+), 341 deletions(-) delete mode 100644 crates/compiler/specialize_types/src/specialize_expr.rs diff --git a/crates/compiler/specialize_types/src/mono_expr.rs b/crates/compiler/specialize_types/src/mono_expr.rs index 433eba33cc..ad68d7a074 100644 --- a/crates/compiler/specialize_types/src/mono_expr.rs +++ b/crates/compiler/specialize_types/src/mono_expr.rs @@ -1,5 +1,5 @@ use crate::{ - mono_ir::{MonoExpr, MonoExprId, MonoExprs}, + mono_ir::{sort_fields, MonoExpr, MonoExprId, MonoExprs}, mono_module::Interns, mono_num::Number, mono_type::{MonoType, MonoTypes, Primitive}, @@ -9,8 +9,10 @@ use crate::{ use bumpalo::Bump; use roc_can::expr::{Expr, IntValue}; use roc_collections::Push; +use roc_region::all::Region; use roc_solve::module::Solved; use roc_types::subs::Subs; +use soa::{Index, Slice}; pub struct Env<'a, 'c, 'd, 'i, 's, 't, P> { arena: &'a Bump, @@ -52,7 +54,12 @@ impl<'a, 'c, 'd, 'i, 's, 't, P: Push> Env<'a, 'c, 'd, 'i, 's, 't, P> { } } - pub fn to_mono_expr(&mut self, can_expr: Expr) -> Option { + pub fn to_mono_expr( + &mut self, + can_expr: Expr, + region: Region, + get_expr_id: impl FnOnce() -> Option, + ) -> Option { let problems = &mut self.problems; let mono_types = &mut self.mono_types; let mut mono_from_var = |var| { @@ -67,11 +74,13 @@ impl<'a, 'c, 'd, 'i, 's, 't, P: Push> Env<'a, 'c, 'd, 'i, 's, 't, P> { ) }; - let mut add = |expr| self.mono_exprs.add(expr); macro_rules! compiler_bug { - ($problem:expr) => {{ + ($problem:expr, $region:expr) => {{ problems.push($problem); - Some(add(MonoExpr::CompilerBug($problem))) + Some( + self.mono_exprs + .add(MonoExpr::CompilerBug($problem), $region), + ) }}; } @@ -80,11 +89,14 @@ impl<'a, 'c, 'd, 'i, 's, 't, P: Push> Env<'a, 'c, 'd, 'i, 's, 't, P> { Some(mono_id) => match mono_types.get(mono_id) { MonoType::Primitive(primitive) => to_frac(*primitive, val, problems), other => { - return compiler_bug!(Problem::NumSpecializedToWrongType(Some(*other))); + return compiler_bug!( + Problem::NumSpecializedToWrongType(Some(*other)), + region + ); } }, None => { - return compiler_bug!(Problem::NumSpecializedToWrongType(None)); + return compiler_bug!(Problem::NumSpecializedToWrongType(None), region); } }, Expr::Num(var, _str, int_value, _) | Expr::Int(var, _, _str, int_value, _) => { @@ -93,11 +105,14 @@ impl<'a, 'c, 'd, 'i, 's, 't, P: Push> Env<'a, 'c, 'd, 'i, 's, 't, P> { Some(mono_id) => match mono_types.get(mono_id) { MonoType::Primitive(primitive) => to_num(*primitive, int_value, problems), other => { - return compiler_bug!(Problem::NumSpecializedToWrongType(Some(*other))); + return compiler_bug!( + Problem::NumSpecializedToWrongType(Some(*other)), + region + ); } }, None => { - return compiler_bug!(Problem::NumSpecializedToWrongType(None)); + return compiler_bug!(Problem::NumSpecializedToWrongType(None), region); } } } @@ -109,11 +124,14 @@ impl<'a, 'c, 'd, 'i, 's, 't, P: Push> Env<'a, 'c, 'd, 'i, 's, 't, P> { Some(mono_id) => match mono_types.get(mono_id) { MonoType::Primitive(primitive) => char_to_int(*primitive, char, problems), other => { - return compiler_bug!(Problem::CharSpecializedToWrongType(Some(*other))); + return compiler_bug!( + Problem::CharSpecializedToWrongType(Some(*other)), + region + ); } }, None => { - return compiler_bug!(Problem::CharSpecializedToWrongType(None)); + return compiler_bug!(Problem::CharSpecializedToWrongType(None), region); } }, Expr::Str(contents) => MonoExpr::Str( @@ -124,6 +142,42 @@ impl<'a, 'c, 'd, 'i, 's, 't, P: Push> Env<'a, 'c, 'd, 'i, 's, 't, P> { // Empty records are zero-sized and should be discarded. return None; } + Expr::Record { record_var, fields } => { + // Sort the fields alphabetically by name. + let mut fields = sort_fields(fields, self.arena); + + // Reserve a slice of IDs up front. This is so that we have a contiguous array + // of field IDs at the end of this, each corresponding to the appropriate record field. + let field_ids: Slice = self.mono_exprs.reserve_ids(fields.len() as u16); + let mut next_field_id = field_ids.start(); + + // Generate a MonoExpr for each field, using the reserved IDs so that we end up with + // that Slice being populated with the exprs in the fields, with the correct ordering. + fields.retain(|(_name, field)| { + let loc_expr = field.loc_expr; + self.to_mono_expr(loc_expr.value, loc_expr.region, || unsafe { + // Safety: This will run *at most* field.len() times, possibly less, + // so this will never create an index that's out of bounds. + let answer = MonoExprId::new_unchecked(Index::new(next_field_id)); + next_field_id += 1; + Some(answer) + }) + // Discard all the zero-sized fields as we go. We don't need to keep the contents + // of the Option because we already know it's the ID we passed in. + .is_some() + }); + + // If we dropped any fields because they were being zero-sized, + // drop the same number of reserved IDs so that they still line up. + field_ids.truncate(fields.len() as u16); + + // If all fields ended up being zero-sized, this would compile to an empty record; return None. + let field_ids = field_ids.into_nonempty_slice()?; + + let todo = (); // TODO: store debuginfo for the record type, including ideally type alias and/or opaque type names. + + MonoExpr::Struct(field_ids) + } _ => todo!(), // Expr::List { // elem_var, @@ -163,10 +217,6 @@ impl<'a, 'c, 'd, 'i, 's, 't, P: Push> Env<'a, 'c, 'd, 'i, 's, 't, P> { // ret_var, // } => todo!(), // Expr::Closure(closure_data) => todo!(), - // Expr::Record { record_var, fields } => { - // // TODO *after* having converted to mono (dropping zero-sized fields), if no fields remain, then return None. - // todo!() - // } // Expr::Tuple { tuple_var, elems } => todo!(), // Expr::ImportParams(module_id, region, _) => todo!(), // Expr::Crash { msg, ret_var } => todo!(), @@ -236,7 +286,11 @@ impl<'a, 'c, 'd, 'i, 's, 't, P: Push> Env<'a, 'c, 'd, 'i, 's, 't, P> { } }; - Some(add(mono_expr)) + let mono_expr_id = get_expr_id()?; + + self.mono_exprs.insert(mono_expr_id, mono_expr, region); + + Some(mono_expr_id) } } diff --git a/crates/compiler/specialize_types/src/mono_ir.rs b/crates/compiler/specialize_types/src/mono_ir.rs index 6a6657410d..654b4b8cc5 100644 --- a/crates/compiler/specialize_types/src/mono_ir.rs +++ b/crates/compiler/specialize_types/src/mono_ir.rs @@ -2,11 +2,13 @@ use crate::{ foreign_symbol::ForeignSymbolId, mono_module::InternedStrId, mono_num::Number, mono_struct::MonoFieldId, mono_type::MonoTypeId, specialize_type::Problem, }; -use roc_can::expr::Recursive; -use roc_collections::soa::Slice; -use roc_module::low_level::LowLevel; +use bumpalo::Bump; +use roc_can::expr::{Field, Recursive}; use roc_module::symbol::Symbol; -use soa::{Id, NonEmptySlice, Slice2, Slice3}; +use roc_module::{ident::Lowercase, low_level::LowLevel}; +use roc_region::all::Region; +use soa::{Id, NonEmptySlice, Slice, Slice2, Slice3}; +use std::iter; #[derive(Clone, Copy, Debug, PartialEq)] pub struct MonoPatternId { @@ -26,24 +28,30 @@ pub struct Def { #[derive(Debug)] pub struct MonoExprs { + // TODO convert to Vec2 exprs: Vec, + regions: Vec, } impl MonoExprs { pub fn new() -> Self { - Self { exprs: Vec::new() } + Self { + exprs: Vec::new(), + regions: Vec::new(), + } } - pub fn add(&mut self, expr: MonoExpr) -> MonoExprId { + pub fn add(&mut self, expr: MonoExpr, region: Region) -> MonoExprId { let index = self.exprs.len() as u32; self.exprs.push(expr); + self.regions.push(region); MonoExprId { inner: Id::new(index), } } - pub fn get(&self, id: MonoExprId) -> &MonoExpr { + pub fn get_expr(&self, id: MonoExprId) -> &MonoExpr { debug_assert!( self.exprs.get(id.inner.index()).is_some(), "A MonoExprId was not found in MonoExprs. This should never happen!" @@ -52,6 +60,48 @@ impl MonoExprs { // Safety: we should only ever hand out MonoExprIds that are valid indices into here. unsafe { self.exprs.get_unchecked(id.inner.index() as usize) } } + + pub fn get_region(&self, id: MonoExprId) -> Region { + debug_assert!( + self.regions.get(id.inner.index()).is_some(), + "A MonoExprId was not found in MonoExprs. This should never happen!" + ); + + // Safety: we should only ever hand out MonoExprIds that are valid indices into here. + unsafe { *self.regions.get_unchecked(id.inner.index() as usize) } + } + + pub fn reserve_ids(&self, len: u16) -> Slice { + let answer = Slice::new(self.exprs.len() as u32, len); + + // These should all be overwritten; if they aren't, that's a problem! + self.exprs.extend(iter::repeat(MonoExpr::CompilerBug( + Problem::UninitializedReservedExpr, + ))); + self.regions.extend(iter::repeat(Region::zero())); + + answer + } + + pub(crate) fn insert(&self, id: MonoExprId, mono_expr: MonoExpr, region: Region) { + debug_assert!( + self.exprs.get(id.inner.index()).is_some(), + "A MonoExprId was not found in MonoExprs. This should never happen!" + ); + + debug_assert!( + self.regions.get(id.inner.index()).is_some(), + "A MonoExprId was not found in MonoExprs. This should never happen!" + ); + + let index = id.inner.index() as usize; + + // Safety: we should only ever hand out MonoExprIds that are valid indices into here. + unsafe { + *self.exprs.get_unchecked_mut(index) = mono_expr; + *self.regions.get_unchecked_mut(index) = region; + } + } } #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -59,6 +109,12 @@ pub struct MonoExprId { inner: Id, } +impl MonoExprId { + pub(crate) unsafe fn new_unchecked(inner: Id) -> Self { + Self { inner } + } +} + #[derive(Clone, Copy, Debug, PartialEq)] pub enum MonoExpr { Str(InternedStrId), @@ -136,12 +192,9 @@ pub enum MonoExpr { recursive: Recursive, }, - /// Either a record literal or a tuple literal. - /// Rather than storing field names, we instead store a u16 field index. - Struct { - struct_type: MonoTypeId, - fields: Slice2, - }, + /// A record literal or a tuple literal. + /// These have already been sorted alphabetically. + Struct(NonEmptySlice), /// The "crash" keyword. Importantly, during code gen we must mark this as "nothing happens after this" Crash { @@ -247,3 +300,13 @@ pub enum DestructType { Optional(MonoTypeId, MonoExprId), Guard(MonoTypeId, MonoPatternId), } + +/// Sort the given fields alphabetically by name. +pub fn sort_fields<'a>( + fields: impl IntoIterator, + arena: &'a Bump, +) -> bumpalo::collections::Vec<'a, (Lowercase, Field)> { + let mut fields = bumpalo::collections::Vec::from_iter_in(fields.into_iter(), arena); + fields.sort_by_key(|(name, _field)| name); + fields +} diff --git a/crates/compiler/specialize_types/src/specialize_expr.rs b/crates/compiler/specialize_types/src/specialize_expr.rs deleted file mode 100644 index 217fa97e3b..0000000000 --- a/crates/compiler/specialize_types/src/specialize_expr.rs +++ /dev/null @@ -1,310 +0,0 @@ -use crate::expr::{self, Declarations, Expr, FunctionDef}; -use crate::specialize_type::{MonoCache, Problem}; -use roc_collections::VecMap; -use roc_module::symbol::Symbol; -use roc_types::subs::{Subs, Variable}; - -struct Context { - symbols: Symbol, - fresh_tvar: Box Variable>, - specializations: Specializations, -} - -struct Specializations { - symbols: Symbol, - fenv: Vec<(Symbol, expr::FunctionDef)>, - specializations: Vec<(SpecializationKey, NeededSpecialization)>, -} - -#[derive(PartialEq, Eq)] -struct SpecializationKey(Symbol, Variable); - -struct NeededSpecialization { - def: expr::FunctionDef, - name_new: Symbol, - t_new: Variable, - specialized: Option, -} - -impl Specializations { - fn make(symbols: Symbol, program: &expr::Declarations) -> Self { - let fenv = program - .iter_top_down() - .filter_map(|(_, tag)| match tag { - expr::DeclarationTag::Function(idx) - | expr::DeclarationTag::Recursive(idx) - | expr::DeclarationTag::TailRecursive(idx) => { - let func = &program.function_bodies[idx.index()]; - Some((func.value.name, func.value.clone())) - } - _ => None, - }) - .collect(); - - Specializations { - symbols, - fenv, - specializations: Vec::new(), - } - } - - fn specialize_fn( - &mut self, - mono_cache: &mut MonoCache, - name: Symbol, - t_new: Variable, - ) -> Option { - let specialization = (name, t_new); - - if let Some((_, needed)) = self - .specializations - .iter() - .find(|(key, _)| *key == specialization) - { - Some(needed.name_new) - } else { - let def = self.fenv.iter().find(|(n, _)| *n == name)?.1.clone(); - let name_new = self.symbols.fresh_symbol_named(name); - let needed_specialization = NeededSpecialization { - def, - name_new, - t_new, - specialized: None, - }; - self.specializations - .push((specialization, needed_specialization)); - Some(name_new) - } - } - - fn next_needed_specialization(&mut self) -> Option<&mut NeededSpecialization> { - self.specializations.iter_mut().find_map(|(_, ns)| { - if ns.specialized.is_none() { - Some(ns) - } else { - None - } - }) - } - - fn solved_specializations(&self) -> Vec { - self.specializations - .iter() - .filter_map(|(_, ns)| ns.specialized.clone()) - .collect() - } -} - -fn specialize_expr( - ctx: &mut Context, - ty_cache: &mut Subs, - mono_cache: &mut MonoCache, - expr: &Expr, -) -> Expr { - match expr { - Expr::Var(x) => { - if let Some(y) = ctx - .specializations - .specialize_fn(mono_cache, *x, expr.get_type()) - { - Expr::Var(y) - } else { - expr.clone() - } - } - Expr::Int(i) => Expr::Int(*i), - Expr::Str(s) => Expr::Str(s.clone()), - Expr::Tag(t, args) => { - let new_args = args - .iter() - .map(|a| specialize_expr(ctx, ty_cache, mono_cache, a)) - .collect(); - Expr::Tag(*t, new_args) - } - Expr::Record(fields) => { - let new_fields = fields - .iter() - .map(|(f, e)| (*f, specialize_expr(ctx, ty_cache, mono_cache, e))) - .collect(); - Expr::Record(new_fields) - } - Expr::Access(e, f) => { - Expr::Access(Box::new(specialize_expr(ctx, ty_cache, mono_cache, e)), *f) - } - Expr::Let(def, rest) => { - let new_def = match def { - expr::Def::Letfn(f) => expr::Def::Letfn(FunctionDef { - recursive: f.recursive, - bind: ( - mono_cache.monomorphize_var(ty_cache, &mut Vec::new(), f.bind.0), - f.bind.1, - ), - arg: ( - mono_cache.monomorphize_var(ty_cache, &mut Vec::new(), f.arg.0), - f.arg.1, - ), - body: Box::new(specialize_expr(ctx, ty_cache, mono_cache, &f.body)), - }), - expr::Def::Letval(v) => expr::Def::Letval(expr::Letval { - bind: ( - mono_cache.monomorphize_var(ty_cache, &mut Vec::new(), v.bind.0), - v.bind.1, - ), - body: Box::new(specialize_expr(ctx, ty_cache, mono_cache, &v.body)), - }), - }; - let new_rest = Box::new(specialize_expr(ctx, ty_cache, mono_cache, rest)); - Expr::Let(Box::new(new_def), new_rest) - } - Expr::Clos { arg, body } => { - let new_arg = ( - mono_cache.monomorphize_var(ty_cache, &mut Vec::new(), arg.0), - arg.1, - ); - let new_body = Box::new(specialize_expr(ctx, ty_cache, mono_cache, body)); - Expr::Clos { - arg: new_arg, - body: new_body, - } - } - Expr::Call(f, a) => { - let new_f = Box::new(specialize_expr(ctx, ty_cache, mono_cache, f)); - let new_a = Box::new(specialize_expr(ctx, ty_cache, mono_cache, a)); - Expr::Call(new_f, new_a) - } - Expr::KCall(kfn, args) => { - let new_args = args - .iter() - .map(|a| specialize_expr(ctx, ty_cache, mono_cache, a)) - .collect(); - Expr::KCall(*kfn, new_args) - } - Expr::When(e, branches) => { - let new_e = Box::new(specialize_expr(ctx, ty_cache, mono_cache, e)); - let new_branches = branches - .iter() - .map(|(p, e)| { - let new_p = specialize_pattern(ctx, ty_cache, mono_cache, p); - let new_e = specialize_expr(ctx, ty_cache, mono_cache, e); - (new_p, new_e) - }) - .collect(); - Expr::When(new_e, new_branches) - } - } -} - -fn specialize_pattern( - ctx: &mut Context, - ty_cache: &mut Subs, - mono_cache: &mut MonoCache, - pattern: &expr::Pattern, -) -> expr::Pattern { - match pattern { - expr::Pattern::PVar(x) => expr::Pattern::PVar(*x), - expr::Pattern::PTag(tag, args) => { - let new_args = args - .iter() - .map(|a| specialize_pattern(ctx, ty_cache, mono_cache, a)) - .collect(); - expr::Pattern::PTag(*tag, new_args) - } - } -} - -fn specialize_let_fn( - ctx: &mut Context, - ty_cache: &mut Subs, - mono_cache: &mut MonoCache, - t_new: Variable, - name_new: Symbol, - f: &expr::FunctionDef, -) -> expr::Def { - mono_cache.monomorphize_var(ty_cache, &mut Vec::new(), t_new); - let t = mono_cache.monomorphize_var(ty_cache, &mut Vec::new(), f.bind.0); - let t_arg = mono_cache.monomorphize_var(ty_cache, &mut Vec::new(), f.arg.0); - let body = specialize_expr(ctx, ty_cache, mono_cache, &f.body); - - expr::Def::Letfn(FunctionDef { - recursive: f.recursive, - bind: (t, name_new), - arg: (t_arg, f.arg.1), - body: Box::new(body), - }) -} - -fn specialize_let_val(ctx: &mut Context, v: &expr::Letval) -> expr::Def { - let mut ty_cache = Subs::new(); - let mut mono_cache = MonoCache::from_subs(&ty_cache); - let t = mono_cache.monomorphize_var(&mut ty_cache, &mut Vec::new(), v.bind.0); - let body = specialize_expr(ctx, &mut ty_cache, &mut mono_cache, &v.body); - expr::Def::Letval(expr::Letval { - bind: (t, v.bind.1), - body: Box::new(body), - }) -} - -fn specialize_run_def(ctx: &mut Context, run: &expr::Run) -> expr::Run { - let mut ty_cache = Subs::new(); - let mut mono_cache = MonoCache::from_subs(&ty_cache); - let t = mono_cache.monomorphize_var(&mut ty_cache, &mut Vec::new(), run.bind.0); - let body = specialize_expr(ctx, &mut ty_cache, &mut mono_cache, &run.body); - expr::Run { - bind: (t, run.bind.1), - body: Box::new(body), - ty: run.ty, - } -} - -fn make_context( - symbols: Symbol, - fresh_tvar: Box Variable>, - program: &expr::Declarations, -) -> Context { - Context { - symbols, - fresh_tvar, - specializations: Specializations::make(symbols, program), - } -} - -fn loop_specializations(ctx: &mut Context) { - while let Some(needed) = ctx.specializations.next_needed_specialization() { - let mut ty_cache = Subs::new(); - let mut mono_cache = MonoCache::from_subs(&ty_cache); - let def = specialize_let_fn( - ctx, - &mut ty_cache, - &mut mono_cache, - needed.t_new, - needed.name_new, - &needed.def, - ); - needed.specialized = Some(def); - } -} - -pub fn lower(ctx: &mut Context, program: &expr::Declarations) -> expr::Declarations { - let mut new_program = expr::Declarations::new(); - - for (idx, tag) in program.iter_top_down() { - match tag { - expr::DeclarationTag::Value => { - let def = specialize_let_val(ctx, &program.expressions[idx]); - new_program.push_def(def); - } - expr::DeclarationTag::Run(run_idx) => { - let run = specialize_run_def(ctx, &program.expressions[run_idx.index()]); - new_program.push_run(run); - } - _ => {} - } - } - - loop_specializations(ctx); - - let other_defs = ctx.specializations.solved_specializations(); - new_program.extend(other_defs); - - new_program -} diff --git a/crates/compiler/specialize_types/src/specialize_type.rs b/crates/compiler/specialize_types/src/specialize_type.rs index 50fbf2d0de..a7b9b9d84c 100644 --- a/crates/compiler/specialize_types/src/specialize_type.rs +++ b/crates/compiler/specialize_types/src/specialize_type.rs @@ -35,6 +35,7 @@ pub enum Problem { Option, // `None` means it specialized to Unit ), BadNumTypeParam, + UninitializedReservedExpr, } /// For MonoTypes that are records, store their field indices. diff --git a/crates/compiler/specialize_types/tests/helpers/mod.rs b/crates/compiler/specialize_types/tests/helpers/mod.rs index f9d49c6bdd..7d0bd8c2a4 100644 --- a/crates/compiler/specialize_types/tests/helpers/mod.rs +++ b/crates/compiler/specialize_types/tests/helpers/mod.rs @@ -95,7 +95,7 @@ pub fn expect_no_expr(input: impl AsRef) { let arena = Bump::new(); let mut interns = Interns::new(); let out = specialize_expr(&arena, input.as_ref(), &mut interns); - let actual = out.mono_expr_id.map(|id| out.mono_exprs.get(id)); + let actual = out.mono_expr_id.map(|id| out.mono_exprs.get_expr(id)); assert_eq!(None, actual, "This input expr should have specialized to being dicarded as zero-sized, but it didn't: {:?}", input.as_ref()); } @@ -118,7 +118,7 @@ pub fn expect_mono_expr_with_interns( .mono_expr_id .expect("This input expr should not have been discarded as zero-sized, but it was discarded: {input:?}"); - let actual_expr = out.mono_exprs.get(mono_expr_id); // Must run first, to populate string interns! + let actual_expr = out.mono_exprs.get_expr(mono_expr_id); // Must run first, to populate string interns! let expected_expr = to_mono_expr(from_interns(&arena, &string_interns)); diff --git a/crates/soa/src/soa_slice.rs b/crates/soa/src/soa_slice.rs index 4edd9f115b..af53d1712d 100644 --- a/crates/soa/src/soa_slice.rs +++ b/crates/soa/src/soa_slice.rs @@ -118,6 +118,18 @@ impl Slice { _marker: PhantomData, } } + + pub const fn truncate(&self, length: u16) -> Self { + Self { + start: self.start, + length, + _marker: PhantomData, + } + } + + pub fn into_nonempty_slice(&self) -> Option> { + NonZeroU16::new(self.length).map(|nonzero_len| NonEmptySlice::new(self.start, nonzero_len)) + } } impl IntoIterator for Slice { @@ -225,6 +237,16 @@ impl NonEmptySlice { pub const unsafe fn from_slice_unchecked(slice: Slice) -> Self { Self::new(slice.start, NonZeroU16::new_unchecked(slice.length)) } + + pub const fn truncate(&self, length: NonZeroU16) -> Self { + Self { + inner: Slice { + start: self.inner.start, + length: length.get(), + _marker: PhantomData, + }, + } + } } impl IntoIterator for NonEmptySlice { From 03370da6d6d7cca70a8b7c8ada1ec3d560171e2f Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sat, 16 Nov 2024 14:39:57 -0500 Subject: [PATCH 27/65] Unwrap single-field records --- .../specialize_types/src/mono_expr.rs | 171 +++++++++--------- .../compiler/specialize_types/src/mono_ir.rs | 41 +++-- .../specialize_types/tests/helpers/mod.rs | 7 +- .../tests/specialize_primitives.rs | 5 - .../tests/specialize_structs.rs | 46 +++++ crates/test_compile/src/help_constrain.rs | 3 + crates/test_compile/src/help_solve.rs | 3 + crates/test_compile/src/help_specialize.rs | 6 +- 8 files changed, 172 insertions(+), 110 deletions(-) create mode 100644 crates/compiler/specialize_types/tests/specialize_structs.rs diff --git a/crates/compiler/specialize_types/src/mono_expr.rs b/crates/compiler/specialize_types/src/mono_expr.rs index ad68d7a074..0ae2ba92bf 100644 --- a/crates/compiler/specialize_types/src/mono_expr.rs +++ b/crates/compiler/specialize_types/src/mono_expr.rs @@ -1,18 +1,17 @@ use crate::{ - mono_ir::{sort_fields, MonoExpr, MonoExprId, MonoExprs}, + mono_ir::{MonoExpr, MonoExprId, MonoExprs}, mono_module::Interns, mono_num::Number, mono_type::{MonoType, MonoTypes, Primitive}, specialize_type::{MonoCache, Problem, RecordFieldIds, TupleElemIds}, DebugInfo, }; -use bumpalo::Bump; +use bumpalo::{collections::Vec, Bump}; use roc_can::expr::{Expr, IntValue}; use roc_collections::Push; -use roc_region::all::Region; use roc_solve::module::Solved; use roc_types::subs::Subs; -use soa::{Index, Slice}; +use soa::{Index, NonEmptySlice, Slice}; pub struct Env<'a, 'c, 'd, 'i, 's, 't, P> { arena: &'a Bump, @@ -54,12 +53,7 @@ impl<'a, 'c, 'd, 'i, 's, 't, P: Push> Env<'a, 'c, 'd, 'i, 's, 't, P> { } } - pub fn to_mono_expr( - &mut self, - can_expr: Expr, - region: Region, - get_expr_id: impl FnOnce() -> Option, - ) -> Option { + pub fn to_mono_expr(&mut self, can_expr: &Expr) -> Option { let problems = &mut self.problems; let mono_types = &mut self.mono_types; let mut mono_from_var = |var| { @@ -75,76 +69,75 @@ impl<'a, 'c, 'd, 'i, 's, 't, P: Push> Env<'a, 'c, 'd, 'i, 's, 't, P> { }; macro_rules! compiler_bug { - ($problem:expr, $region:expr) => {{ + ($problem:expr) => {{ problems.push($problem); - Some( - self.mono_exprs - .add(MonoExpr::CompilerBug($problem), $region), - ) + Some(MonoExpr::CompilerBug($problem)) }}; } - let mono_expr = match can_expr { - Expr::Float(var, _precision_var, _str, val, _bound) => match mono_from_var(var) { + match can_expr { + Expr::Float(var, _precision_var, _str, val, _bound) => match mono_from_var(*var) { Some(mono_id) => match mono_types.get(mono_id) { - MonoType::Primitive(primitive) => to_frac(*primitive, val, problems), - other => { - return compiler_bug!( - Problem::NumSpecializedToWrongType(Some(*other)), - region - ); - } + MonoType::Primitive(primitive) => Some(to_frac(*primitive, *val, problems)), + other => compiler_bug!(Problem::NumSpecializedToWrongType(Some(*other))), }, None => { - return compiler_bug!(Problem::NumSpecializedToWrongType(None), region); + compiler_bug!(Problem::NumSpecializedToWrongType(None)) } }, - Expr::Num(var, _str, int_value, _) | Expr::Int(var, _, _str, int_value, _) => { - // Numbers can specialize - match mono_from_var(var) { + Expr::Num(var, _, int_value, _) | Expr::Int(var, _, _, int_value, _) => { + // Number literals and int literals both specify integer numbers, so to_num() can work on both. + match mono_from_var(*var) { Some(mono_id) => match mono_types.get(mono_id) { - MonoType::Primitive(primitive) => to_num(*primitive, int_value, problems), - other => { - return compiler_bug!( - Problem::NumSpecializedToWrongType(Some(*other)), - region - ); + MonoType::Primitive(primitive) => { + Some(to_num(*primitive, *int_value, problems)) } + other => compiler_bug!(Problem::NumSpecializedToWrongType(Some(*other))), }, - None => { - return compiler_bug!(Problem::NumSpecializedToWrongType(None), region); - } + None => compiler_bug!(Problem::NumSpecializedToWrongType(None)), } } - Expr::SingleQuote(var, _precision_var, char, _bound) => match mono_from_var(var) { + Expr::SingleQuote(var, _, char, _) => match mono_from_var(*var) { // Single-quote characters monomorphize to an integer. // TODO if we store these using the same representation as other ints (e.g. Expr::Int, // or keeping a separate value but storing an IntValue instead of a char), then - // even though we verify them differently, then we can combine this branch with Num and Int. + // even though we verify them differently, we can combine this branch with Num and Int. Some(mono_id) => match mono_types.get(mono_id) { - MonoType::Primitive(primitive) => char_to_int(*primitive, char, problems), - other => { - return compiler_bug!( - Problem::CharSpecializedToWrongType(Some(*other)), - region - ); + MonoType::Primitive(primitive) => { + Some(char_to_int(*primitive, *char, problems)) } + other => compiler_bug!(Problem::CharSpecializedToWrongType(Some(*other))), }, - None => { - return compiler_bug!(Problem::CharSpecializedToWrongType(None), region); - } + None => compiler_bug!(Problem::CharSpecializedToWrongType(None)), }, - Expr::Str(contents) => MonoExpr::Str( - self.string_interns - .get(self.arena, self.arena.alloc(contents)), - ), + Expr::Str(contents) => Some(MonoExpr::Str(self.string_interns.get( + self.arena, + // TODO should be able to remove this alloc_str() once canonical Expr stores an arena-allocated string. + self.arena.alloc_str(contents), + ))), Expr::EmptyRecord => { // Empty records are zero-sized and should be discarded. return None; } - Expr::Record { record_var, fields } => { + Expr::Record { + record_var: _, + fields, + } => { + let todo = (); // TODO: store debuginfo for the record type, including ideally type alias and/or opaque type names. Do this before early-returning for single-field records. + + // Check for records with 0-1 fields before sorting or reserving a slice of IDs (which might be unnecessary). + // We'll check again after discarding zero-sized fields, because we might end up with 0 or 1 fields remaining. + if fields.len() <= 1 { + return fields + .into_iter() + .next() + .and_then(|(_, field)| self.to_mono_expr(&field.loc_expr.value)); + } + // Sort the fields alphabetically by name. - let mut fields = sort_fields(fields, self.arena); + let mut fields = Vec::from_iter_in(fields.into_iter(), self.arena); + + fields.sort_by(|(name1, _), (name2, _)| name1.cmp(name2)); // Reserve a slice of IDs up front. This is so that we have a contiguous array // of field IDs at the end of this, each corresponding to the appropriate record field. @@ -154,29 +147,43 @@ impl<'a, 'c, 'd, 'i, 's, 't, P: Push> Env<'a, 'c, 'd, 'i, 's, 't, P> { // Generate a MonoExpr for each field, using the reserved IDs so that we end up with // that Slice being populated with the exprs in the fields, with the correct ordering. fields.retain(|(_name, field)| { - let loc_expr = field.loc_expr; - self.to_mono_expr(loc_expr.value, loc_expr.region, || unsafe { - // Safety: This will run *at most* field.len() times, possibly less, - // so this will never create an index that's out of bounds. - let answer = MonoExprId::new_unchecked(Index::new(next_field_id)); - next_field_id += 1; - Some(answer) - }) - // Discard all the zero-sized fields as we go. We don't need to keep the contents - // of the Option because we already know it's the ID we passed in. - .is_some() + match dbg!(self.to_mono_expr(&field.loc_expr.value)) { + Some(mono_expr) => { + // Safety: This will run *at most* field.len() times, possibly less, + // so this will never create an index that's out of bounds. + let mono_expr_id = + unsafe { MonoExprId::new_unchecked(Index::new(next_field_id)) }; + + next_field_id += 1; + + self.mono_exprs + .insert(mono_expr_id, mono_expr, field.loc_expr.region); + + dbg!(true) + } + None => { + // Discard all the zero-sized fields as we go. + dbg!(false) + } + } }); - // If we dropped any fields because they were being zero-sized, - // drop the same number of reserved IDs so that they still line up. - field_ids.truncate(fields.len() as u16); - - // If all fields ended up being zero-sized, this would compile to an empty record; return None. - let field_ids = field_ids.into_nonempty_slice()?; - - let todo = (); // TODO: store debuginfo for the record type, including ideally type alias and/or opaque type names. - - MonoExpr::Struct(field_ids) + // Check for zero-sized and single-field records again now that we've discarded zero-sized fields, + // because we might have ended up with 0 or 1 remaining fields. + if fields.len() > 1 { + // Safety: We just verified that there's more than 1 field. + unsafe { + Some(MonoExpr::Struct(NonEmptySlice::new_unchecked( + field_ids.start, + fields.len() as u16, + ))) + } + } else { + // If there are 0 fields remaining, return None. If there's 1, unwrap it. + fields + .first() + .and_then(|(_, field)| self.to_mono_expr(&field.loc_expr.value)) + } } _ => todo!(), // Expr::List { @@ -281,16 +288,10 @@ impl<'a, 'c, 'd, 'i, 's, 't, P: Push> Env<'a, 'c, 'd, 'i, 's, 't, P> { // symbol, // } => todo!(), // Expr::TypedHole(variable) => todo!(), - Expr::RuntimeError(_runtime_error) => { - todo!("generate a MonoExpr::Crash based on the runtime error"); - } - }; - - let mono_expr_id = get_expr_id()?; - - self.mono_exprs.insert(mono_expr_id, mono_expr, region); - - Some(mono_expr_id) + // Expr::RuntimeError(_runtime_error) => { + // todo!("generate a MonoExpr::Crash based on the runtime error"); + // } + } } } diff --git a/crates/compiler/specialize_types/src/mono_ir.rs b/crates/compiler/specialize_types/src/mono_ir.rs index 654b4b8cc5..18a91f6ad2 100644 --- a/crates/compiler/specialize_types/src/mono_ir.rs +++ b/crates/compiler/specialize_types/src/mono_ir.rs @@ -7,7 +7,7 @@ use roc_can::expr::{Field, Recursive}; use roc_module::symbol::Symbol; use roc_module::{ident::Lowercase, low_level::LowLevel}; use roc_region::all::Region; -use soa::{Id, NonEmptySlice, Slice, Slice2, Slice3}; +use soa::{Id, Index, NonEmptySlice, Slice, Slice2, Slice3}; use std::iter; #[derive(Clone, Copy, Debug, PartialEq)] @@ -71,19 +71,34 @@ impl MonoExprs { unsafe { *self.regions.get_unchecked(id.inner.index() as usize) } } - pub fn reserve_ids(&self, len: u16) -> Slice { - let answer = Slice::new(self.exprs.len() as u32, len); + pub fn reserve_id(&mut self) -> MonoExprId { + let answer = MonoExprId { + inner: Index::new(self.exprs.len() as u32), + }; // These should all be overwritten; if they aren't, that's a problem! - self.exprs.extend(iter::repeat(MonoExpr::CompilerBug( - Problem::UninitializedReservedExpr, - ))); - self.regions.extend(iter::repeat(Region::zero())); + self.exprs + .push(MonoExpr::CompilerBug(Problem::UninitializedReservedExpr)); + self.regions.push(Region::zero()); answer } - pub(crate) fn insert(&self, id: MonoExprId, mono_expr: MonoExpr, region: Region) { + pub fn reserve_ids(&mut self, len: u16) -> Slice { + let answer = Slice::new(self.exprs.len() as u32, len); + + // These should all be overwritten; if they aren't, that's a problem! + self.exprs.extend( + iter::repeat(MonoExpr::CompilerBug(Problem::UninitializedReservedExpr)) + .take(len as usize), + ); + self.regions + .extend(iter::repeat(Region::zero()).take(len as usize)); + + answer + } + + pub(crate) fn insert(&mut self, id: MonoExprId, mono_expr: MonoExpr, region: Region) { debug_assert!( self.exprs.get(id.inner.index()).is_some(), "A MonoExprId was not found in MonoExprs. This should never happen!" @@ -300,13 +315,3 @@ pub enum DestructType { Optional(MonoTypeId, MonoExprId), Guard(MonoTypeId, MonoPatternId), } - -/// Sort the given fields alphabetically by name. -pub fn sort_fields<'a>( - fields: impl IntoIterator, - arena: &'a Bump, -) -> bumpalo::collections::Vec<'a, (Lowercase, Field)> { - let mut fields = bumpalo::collections::Vec::from_iter_in(fields.into_iter(), arena); - fields.sort_by_key(|(name, _field)| name); - fields -} diff --git a/crates/compiler/specialize_types/tests/helpers/mod.rs b/crates/compiler/specialize_types/tests/helpers/mod.rs index 7d0bd8c2a4..8cc74cfe60 100644 --- a/crates/compiler/specialize_types/tests/helpers/mod.rs +++ b/crates/compiler/specialize_types/tests/helpers/mod.rs @@ -1,5 +1,6 @@ use bumpalo::Bump; use roc_load::LoadedModule; +use roc_region::all::Region; use roc_solve::FunctionKind; use roc_specialize_types::{ DebugInfo, Env, Interns, MonoCache, MonoExpr, MonoExprs, MonoTypes, RecordFieldIds, @@ -80,13 +81,17 @@ fn specialize_expr<'a>( // This should be our only expr assert_eq!(0, home_decls.expressions.len()); - let mono_expr_id = env.to_mono_expr(main_expr); + let region = Region::zero(); + let mono_expr_id = env + .to_mono_expr(&main_expr) + .map(|mono_expr| mono_exprs.add(mono_expr, region)); SpecializedExprOut { mono_expr_id, problems, mono_types, mono_exprs, + region, } } diff --git a/crates/compiler/specialize_types/tests/specialize_primitives.rs b/crates/compiler/specialize_types/tests/specialize_primitives.rs index 6366e20c31..1996b67f39 100644 --- a/crates/compiler/specialize_types/tests/specialize_primitives.rs +++ b/crates/compiler/specialize_types/tests/specialize_primitives.rs @@ -11,11 +11,6 @@ mod specialize_primitives { use super::helpers::{expect_mono_expr, expect_mono_expr_with_interns, expect_no_expr}; - #[test] - fn empty_record() { - expect_no_expr("{}"); - } - #[test] fn string_literal() { let string = "foo"; diff --git a/crates/compiler/specialize_types/tests/specialize_structs.rs b/crates/compiler/specialize_types/tests/specialize_structs.rs new file mode 100644 index 0000000000..7bfc9f2570 --- /dev/null +++ b/crates/compiler/specialize_types/tests/specialize_structs.rs @@ -0,0 +1,46 @@ +#[macro_use] +extern crate pretty_assertions; + +extern crate bumpalo; + +mod helpers; + +#[cfg(test)] +mod specialize_structs { + use roc_specialize_types::{MonoExpr, Number}; + + use super::helpers::{expect_mono_expr, expect_mono_expr_with_interns, expect_no_expr}; + + #[test] + fn empty_record() { + expect_no_expr("{}"); + } + + #[test] + fn one_field_with_empty_record() { + expect_no_expr("{ discardedField: {} }"); + } + + #[test] + fn one_field_record_string_literal() { + let string = "foo"; + let expected = format!("{{ discardedField: \"{string}\" }}"); + expect_mono_expr_with_interns( + |arena, interns| interns.try_get(arena, string).unwrap(), + expected, + |id| MonoExpr::Str(id), + ); + } + + #[test] + fn one_field_after_dropping_zero_sized() { + let string = "foo"; + let expected = + format!("{{ discarded: {{}}, discardedToo: \"{string}\", alsoDiscarded: {{}} }}"); + expect_mono_expr_with_interns( + |arena, interns| interns.try_get(arena, string).unwrap(), + expected, + |id| MonoExpr::Str(id), + ); + } +} diff --git a/crates/test_compile/src/help_constrain.rs b/crates/test_compile/src/help_constrain.rs index 9989fa9e93..534cacaae7 100644 --- a/crates/test_compile/src/help_constrain.rs +++ b/crates/test_compile/src/help_constrain.rs @@ -7,6 +7,7 @@ use roc_can::{ }; use roc_constrain::expr::{constrain_expr, Env}; use roc_module::symbol::ModuleId; +use roc_region::all::Region; use roc_types::{ subs::{Subs, Variable}, types::Types, @@ -19,6 +20,7 @@ pub struct ConstrainedExprOut { pub constraint: Constraint, pub constraints: Constraints, pub types: Types, + pub region: Region, } #[derive(Default)] @@ -60,6 +62,7 @@ impl ConstrainedExpr { constraint, constraints, types, + region: can_expr_out.region, } } diff --git a/crates/test_compile/src/help_solve.rs b/crates/test_compile/src/help_solve.rs index 0f39f9611b..e3495f47a4 100644 --- a/crates/test_compile/src/help_solve.rs +++ b/crates/test_compile/src/help_solve.rs @@ -9,6 +9,7 @@ use roc_collections::VecMap; use roc_derive::SharedDerivedModule; use roc_load::FunctionKind; use roc_module::symbol::ModuleId; +use roc_region::all::Region; use roc_solve::{ module::{SolveConfig, Solved}, solve, Aliases, @@ -19,6 +20,7 @@ use roc_types::subs::{Subs, Variable}; #[derive(Debug)] pub struct SolvedExprOut { pub expr: Expr, + pub region: Region, pub problems: Vec, pub var: Variable, pub subs: Solved, @@ -61,6 +63,7 @@ impl SolvedExpr { SolvedExprOut { expr: constrained_expr_out.expr, + region: constrained_expr_out.region, problems, var: constrained_expr_out.var, subs: solve_output.solved, diff --git a/crates/test_compile/src/help_specialize.rs b/crates/test_compile/src/help_specialize.rs index ff83266b90..88342ea82b 100644 --- a/crates/test_compile/src/help_specialize.rs +++ b/crates/test_compile/src/help_specialize.rs @@ -1,5 +1,6 @@ use crate::SolvedExpr; use bumpalo::Bump; +use roc_region::all::Region; use roc_specialize_types::{ DebugInfo, Env, Interns, MonoCache, MonoExprId, MonoExprs, MonoTypes, Problem, RecordFieldIds, TupleElemIds, @@ -8,6 +9,7 @@ use roc_specialize_types::{ #[derive(Debug)] pub struct SpecializedExprOut { pub mono_expr_id: Option, + pub region: Region, pub mono_types: MonoTypes, pub mono_exprs: MonoExprs, pub problems: Vec, @@ -45,11 +47,13 @@ impl SpecializedExpr { &mut problems, ); - env.to_mono_expr(solved_out.expr) + env.to_mono_expr(&solved_out.expr) + .map(|mono_expr| mono_exprs.add(mono_expr, Region::zero())) }; SpecializedExprOut { mono_expr_id, + region: solved_out.region, problems, mono_types, mono_exprs, From 219a0398a269eb0c56c7935197e9ad974a8140e6 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sat, 16 Nov 2024 18:31:32 -0500 Subject: [PATCH 28/65] Introduce string-based mono expr tests --- .../specialize_types/src/mono_expr.rs | 2 +- .../compiler/specialize_types/src/mono_ir.rs | 12 ++ .../specialize_types/src/mono_module.rs | 16 +- .../specialize_types/tests/helpers/mod.rs | 162 +++++++++++++++++- .../tests/specialize_primitives.rs | 10 +- .../tests/specialize_structs.rs | 25 ++- crates/soa/src/soa_slice.rs | 4 + 7 files changed, 207 insertions(+), 24 deletions(-) diff --git a/crates/compiler/specialize_types/src/mono_expr.rs b/crates/compiler/specialize_types/src/mono_expr.rs index 0ae2ba92bf..359caddb47 100644 --- a/crates/compiler/specialize_types/src/mono_expr.rs +++ b/crates/compiler/specialize_types/src/mono_expr.rs @@ -110,7 +110,7 @@ impl<'a, 'c, 'd, 'i, 's, 't, P: Push> Env<'a, 'c, 'd, 'i, 's, 't, P> { }, None => compiler_bug!(Problem::CharSpecializedToWrongType(None)), }, - Expr::Str(contents) => Some(MonoExpr::Str(self.string_interns.get( + Expr::Str(contents) => Some(MonoExpr::Str(self.string_interns.get_id( self.arena, // TODO should be able to remove this alloc_str() once canonical Expr stores an arena-allocated string. self.arena.alloc_str(contents), diff --git a/crates/compiler/specialize_types/src/mono_ir.rs b/crates/compiler/specialize_types/src/mono_ir.rs index 18a91f6ad2..f53633bd66 100644 --- a/crates/compiler/specialize_types/src/mono_ir.rs +++ b/crates/compiler/specialize_types/src/mono_ir.rs @@ -117,6 +117,18 @@ impl MonoExprs { *self.regions.get_unchecked_mut(index) = region; } } + + pub fn iter_slice(&self, expr_ids: Slice) -> impl Iterator { + expr_ids.indices().into_iter().map(|index| { + debug_assert!( + self.exprs.get(index).is_some(), + "A Slice index was not found in MonoExprs. This should never happen!" + ); + + // Safety: we should only ever hand out MonoExprId slices that are valid indices into here. + unsafe { self.exprs.get_unchecked(index) } + }) + } } #[derive(Clone, Copy, Debug, PartialEq, Eq)] diff --git a/crates/compiler/specialize_types/src/mono_module.rs b/crates/compiler/specialize_types/src/mono_module.rs index 4ddc741204..c46b7e2e6a 100644 --- a/crates/compiler/specialize_types/src/mono_module.rs +++ b/crates/compiler/specialize_types/src/mono_module.rs @@ -39,7 +39,19 @@ impl<'a> Interns<'a> { } } - pub fn get(&mut self, _arena: &'a Bump, string: &'a str) -> InternedStrId { + pub fn get_str(&self, _arena: &'a Bump, id: InternedStrId) -> &'a str { + let index = id.0 as usize; + + #[cfg(debug_assertions)] + { + assert!(self.interned.get(index).is_some(), "Got an InternedStringId ({index}) that was outside the bounds of the backing array. This should never happen!"); + } + + // Safety: We should only ever give out InternedStrId values that are in this range. + unsafe { self.interned.get_unchecked(index) } + } + + pub fn get_id(&mut self, _arena: &'a Bump, string: &'a str) -> InternedStrId { match self .interned .iter() @@ -56,7 +68,7 @@ impl<'a> Interns<'a> { } } - pub fn try_get(&self, _arena: &'a Bump, string: &'a str) -> Option { + pub fn try_get_id(&self, _arena: &'a Bump, string: &'a str) -> Option { match self .interned .iter() diff --git a/crates/compiler/specialize_types/tests/helpers/mod.rs b/crates/compiler/specialize_types/tests/helpers/mod.rs index 8cc74cfe60..5f32958d8d 100644 --- a/crates/compiler/specialize_types/tests/helpers/mod.rs +++ b/crates/compiler/specialize_types/tests/helpers/mod.rs @@ -1,4 +1,5 @@ use bumpalo::Bump; +use core::fmt::Write; use roc_load::LoadedModule; use roc_region::all::Region; use roc_solve::FunctionKind; @@ -107,14 +108,161 @@ pub fn expect_no_expr(input: impl AsRef) { #[track_caller] pub fn expect_mono_expr(input: impl AsRef, mono_expr: MonoExpr) { - expect_mono_expr_with_interns(|_, _| {}, input, |_| mono_expr); + expect_mono_expr_with_interns(input, |_, _| mono_expr); } #[track_caller] -pub fn expect_mono_expr_with_interns( - from_interns: impl for<'a> FnOnce(&'a Bump, &Interns<'a>) -> T, +pub fn expect_mono_expr_str(input: impl AsRef, expr_str: impl AsRef) { + expect_mono_expr_custom( + input, + |_, _, _| expr_str.as_ref().to_string(), + |arena, mono_exprs, interns, expr| { + dbg_mono_expr(arena, mono_exprs, interns, expr).to_string() + }, + ); +} + +fn dbg_mono_expr<'a>( + arena: &'a Bump, + mono_exprs: &MonoExprs, + interns: &Interns<'a>, + expr: &MonoExpr, +) -> &'a str { + let mut buf = bumpalo::collections::String::new_in(arena); + + dbg_mono_expr_help(arena, mono_exprs, interns, expr, &mut buf); + + buf.into_bump_str() +} + +fn dbg_mono_expr_help<'a>( + arena: &'a Bump, + mono_exprs: &MonoExprs, + interns: &Interns<'a>, + expr: &MonoExpr, + buf: &mut impl Write, +) { + match expr { + MonoExpr::Str(interned_str_id) => { + write!(buf, "Str({:?})", interns.get_str(arena, *interned_str_id)).unwrap(); + } + MonoExpr::Number(number) => { + write!(buf, "Number({:?})", number).unwrap(); + } + MonoExpr::Struct(expr_ids) => { + write!(buf, "Struct([").unwrap(); + + for (index, expr) in mono_exprs.iter_slice(expr_ids.as_slice()).enumerate() { + if index > 0 { + write!(buf, ", ").unwrap(); + } + + dbg_mono_expr_help(arena, mono_exprs, interns, expr, buf); + } + + write!(buf, "])").unwrap(); + } + // MonoExpr::List { elem_type, elems } => todo!(), + // MonoExpr::Lookup(symbol, mono_type_id) => todo!(), + // MonoExpr::ParameterizedLookup { + // name, + // lookup_type, + // params_name, + // params_type, + // } => todo!(), + // MonoExpr::When { + // cond, + // cond_type, + // branch_type, + // branches, + // } => todo!(), + // MonoExpr::If { + // branch_type, + // branches, + // final_else, + // } => todo!(), + // MonoExpr::LetRec { defs, ending_expr } => todo!(), + // MonoExpr::LetNonRec { def, ending_expr } => todo!(), + // MonoExpr::Call { + // fn_type, + // fn_expr, + // args, + // closure_type, + // } => todo!(), + // MonoExpr::RunLowLevel { op, args, ret_type } => todo!(), + // MonoExpr::ForeignCall { + // foreign_symbol, + // args, + // ret_type, + // } => todo!(), + // MonoExpr::Lambda { + // fn_type, + // arguments, + // body, + // captured_symbols, + // recursive, + // } => todo!(), + // MonoExpr::Crash { msg, expr_type } => todo!(), + // MonoExpr::StructAccess { + // record_expr, + // record_type, + // field_type, + // field_id, + // } => todo!(), + // MonoExpr::RecordUpdate { + // record_type, + // record_name, + // updates, + // } => todo!(), + // MonoExpr::SmallTag { + // discriminant, + // tag_union_type, + // args, + // } => todo!(), + // MonoExpr::BigTag { + // discriminant, + // tag_union_type, + // args, + // } => todo!(), + // MonoExpr::Expect { + // condition, + // continuation, + // lookups_in_cond, + // } => todo!(), + // MonoExpr::Dbg { + // source_location, + // source, + // msg, + // continuation, + // expr_type, + // name, + // } => todo!(), + MonoExpr::CompilerBug(problem) => { + write!(buf, "CompilerBug({:?})", problem).unwrap(); + } + other => { + todo!("Implement dbg_mono_expr for {:?}", other) + } + } +} + +#[track_caller] +pub fn expect_mono_expr_with_interns( input: impl AsRef, - to_mono_expr: impl FnOnce(T) -> MonoExpr, + from_interns: impl for<'a> Fn(&'a Bump, &Interns<'a>) -> MonoExpr, +) { + expect_mono_expr_custom( + input, + |arena, _exprs, interns| from_interns(arena, interns), + |_, _, _, expr| *expr, + ); +} + +#[track_caller] +pub fn expect_mono_expr_custom( + input: impl AsRef, + to_expected: impl for<'a> Fn(&'a Bump, &MonoExprs, &Interns<'a>) -> T, + to_actual: impl for<'a> Fn(&'a Bump, &MonoExprs, &Interns<'a>, &MonoExpr) -> T, ) { let arena = Bump::new(); let mut string_interns = Interns::new(); @@ -124,8 +272,8 @@ pub fn expect_mono_expr_with_interns( .expect("This input expr should not have been discarded as zero-sized, but it was discarded: {input:?}"); let actual_expr = out.mono_exprs.get_expr(mono_expr_id); // Must run first, to populate string interns! + let actual = to_actual(&arena, &out.mono_exprs, &string_interns, actual_expr); + let expected = to_expected(&arena, &out.mono_exprs, &string_interns); - let expected_expr = to_mono_expr(from_interns(&arena, &string_interns)); - - assert_eq!(&expected_expr, actual_expr); + assert_eq!(expected, actual); } diff --git a/crates/compiler/specialize_types/tests/specialize_primitives.rs b/crates/compiler/specialize_types/tests/specialize_primitives.rs index 1996b67f39..983ba13b96 100644 --- a/crates/compiler/specialize_types/tests/specialize_primitives.rs +++ b/crates/compiler/specialize_types/tests/specialize_primitives.rs @@ -9,17 +9,15 @@ mod helpers; mod specialize_primitives { use roc_specialize_types::{MonoExpr, Number}; - use super::helpers::{expect_mono_expr, expect_mono_expr_with_interns, expect_no_expr}; + use super::helpers::{expect_mono_expr, expect_mono_expr_with_interns}; #[test] fn string_literal() { let string = "foo"; let expected = format!("\"{string}\""); - expect_mono_expr_with_interns( - |arena, interns| interns.try_get(arena, string).unwrap(), - expected, - |id| MonoExpr::Str(id), - ); + expect_mono_expr_with_interns(expected, |arena, interns| { + MonoExpr::Str(interns.try_get_id(arena, string).unwrap()) + }); } #[test] diff --git a/crates/compiler/specialize_types/tests/specialize_structs.rs b/crates/compiler/specialize_types/tests/specialize_structs.rs index 7bfc9f2570..38135a026c 100644 --- a/crates/compiler/specialize_types/tests/specialize_structs.rs +++ b/crates/compiler/specialize_types/tests/specialize_structs.rs @@ -9,6 +9,8 @@ mod helpers; mod specialize_structs { use roc_specialize_types::{MonoExpr, Number}; + use crate::helpers::expect_mono_expr_str; + use super::helpers::{expect_mono_expr, expect_mono_expr_with_interns, expect_no_expr}; #[test] @@ -25,11 +27,9 @@ mod specialize_structs { fn one_field_record_string_literal() { let string = "foo"; let expected = format!("{{ discardedField: \"{string}\" }}"); - expect_mono_expr_with_interns( - |arena, interns| interns.try_get(arena, string).unwrap(), - expected, - |id| MonoExpr::Str(id), - ); + expect_mono_expr_with_interns(expected, |arena, interns| { + MonoExpr::Str(interns.try_get_id(arena, string).unwrap()) + }); } #[test] @@ -37,10 +37,19 @@ mod specialize_structs { let string = "foo"; let expected = format!("{{ discarded: {{}}, discardedToo: \"{string}\", alsoDiscarded: {{}} }}"); - expect_mono_expr_with_interns( - |arena, interns| interns.try_get(arena, string).unwrap(), + expect_mono_expr_with_interns(expected, |arena, interns| { + MonoExpr::Str(interns.try_get_id(arena, string).unwrap()) + }); + } + + #[test] + fn two_fields() { + let one = 42; + let two = 50; + let expected = format!("{{ one: {one}, two: {two} }}"); + expect_mono_expr_str( expected, - |id| MonoExpr::Str(id), + format!("Struct([Number(I8({one:?})), Number(I8({two:?}))])"), ); } } diff --git a/crates/soa/src/soa_slice.rs b/crates/soa/src/soa_slice.rs index af53d1712d..04dacec553 100644 --- a/crates/soa/src/soa_slice.rs +++ b/crates/soa/src/soa_slice.rs @@ -247,6 +247,10 @@ impl NonEmptySlice { }, } } + + pub fn as_slice(&self) -> Slice { + self.inner + } } impl IntoIterator for NonEmptySlice { From 05e5fb41894c447c109e30dc1f9526cc91b5e5e7 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sat, 16 Nov 2024 18:53:53 -0500 Subject: [PATCH 29/65] Fix a test, delete some dead code --- .../compiler/specialize_types/src/mono_ir.rs | 5 +- .../compiler/specialize_types/src/mono_num.rs | 74 ------ .../specialize_types/src/specialize_type.rs | 244 +++++++++--------- .../tests/specialize_primitives.rs | 3 +- .../tests/specialize_structs.rs | 13 +- .../tests/test_specialize_types.rs | 2 - 6 files changed, 128 insertions(+), 213 deletions(-) diff --git a/crates/compiler/specialize_types/src/mono_ir.rs b/crates/compiler/specialize_types/src/mono_ir.rs index f53633bd66..d639a25fcf 100644 --- a/crates/compiler/specialize_types/src/mono_ir.rs +++ b/crates/compiler/specialize_types/src/mono_ir.rs @@ -2,10 +2,9 @@ use crate::{ foreign_symbol::ForeignSymbolId, mono_module::InternedStrId, mono_num::Number, mono_struct::MonoFieldId, mono_type::MonoTypeId, specialize_type::Problem, }; -use bumpalo::Bump; -use roc_can::expr::{Field, Recursive}; +use roc_can::expr::Recursive; +use roc_module::low_level::LowLevel; use roc_module::symbol::Symbol; -use roc_module::{ident::Lowercase, low_level::LowLevel}; use roc_region::all::Region; use soa::{Id, Index, NonEmptySlice, Slice, Slice2, Slice3}; use std::iter; diff --git a/crates/compiler/specialize_types/src/mono_num.rs b/crates/compiler/specialize_types/src/mono_num.rs index 1a8e60761a..5456e7ba54 100644 --- a/crates/compiler/specialize_types/src/mono_num.rs +++ b/crates/compiler/specialize_types/src/mono_num.rs @@ -1,5 +1,3 @@ -use roc_can::expr::IntValue; - #[derive(Clone, Copy, Debug, PartialEq)] pub enum Number { I8(i8), @@ -16,75 +14,3 @@ pub enum Number { F64(f64), Dec(f64), } - -impl Number { - fn u8(val: IntValue) -> Self { - match val { - IntValue::I128(i128) => Self::U8(i128::from_ne_bytes(i128) as u8), - IntValue::U128(u128) => Self::U8(u128::from_ne_bytes(u128) as u8), - } - } - - fn i8(val: IntValue) -> Self { - match val { - IntValue::I128(i128) => Self::I8(i128::from_ne_bytes(i128) as i8), - IntValue::U128(u128) => Self::I8(u128::from_ne_bytes(u128) as i8), - } - } - - fn u16(val: IntValue) -> Self { - match val { - IntValue::I128(i128) => Self::U16(i128::from_ne_bytes(i128) as u16), - IntValue::U128(u128) => Self::U16(u128::from_ne_bytes(u128) as u16), - } - } - - fn i16(val: IntValue) -> Self { - match val { - IntValue::I128(i128) => Self::I16(i128::from_ne_bytes(i128) as i16), - IntValue::U128(u128) => Self::I16(u128::from_ne_bytes(u128) as i16), - } - } - - fn u32(val: IntValue) -> Self { - match val { - IntValue::I128(i128) => Self::U32(i128::from_ne_bytes(i128) as u32), - IntValue::U128(u128) => Self::U32(u128::from_ne_bytes(u128) as u32), - } - } - - fn i32(val: IntValue) -> Self { - match val { - IntValue::I128(i128) => Self::I32(i128::from_ne_bytes(i128) as i32), - IntValue::U128(u128) => Self::I32(u128::from_ne_bytes(u128) as i32), - } - } - - fn u64(val: IntValue) -> Self { - match val { - IntValue::I128(i128) => Self::U64(i128::from_ne_bytes(i128) as u64), - IntValue::U128(u128) => Self::U64(u128::from_ne_bytes(u128) as u64), - } - } - - fn i64(val: IntValue) -> Self { - match val { - IntValue::I128(i128) => Self::I64(i128::from_ne_bytes(i128) as i64), - IntValue::U128(u128) => Self::I64(u128::from_ne_bytes(u128) as i64), - } - } - - fn u128(val: IntValue) -> Self { - match val { - IntValue::I128(i128) => Self::U128(i128::from_ne_bytes(i128) as u128), - IntValue::U128(u128) => Self::U128(u128::from_ne_bytes(u128)), - } - } - - fn i128(val: IntValue) -> Self { - match val { - IntValue::I128(i128) => Self::I128(i128::from_ne_bytes(i128)), - IntValue::U128(u128) => Self::I128(u128::from_ne_bytes(u128) as i128), - } - } -} diff --git a/crates/compiler/specialize_types/src/specialize_type.rs b/crates/compiler/specialize_types/src/specialize_type.rs index a7b9b9d84c..c203c5c020 100644 --- a/crates/compiler/specialize_types/src/specialize_type.rs +++ b/crates/compiler/specialize_types/src/specialize_type.rs @@ -9,15 +9,9 @@ use crate::{ MonoFieldId, MonoType, }; use roc_collections::{Push, VecMap}; -use roc_module::{ - ident::{Lowercase, TagName}, - symbol::Symbol, -}; +use roc_module::{ident::Lowercase, symbol::Symbol}; use roc_solve::module::Solved; -use roc_types::subs::{ - Content, FlatType, RecordFields, Subs, SubsSlice, TagExt, TupleElems, UnionLabels, UnionTags, - Variable, -}; +use roc_types::subs::{Content, FlatType, Subs, SubsSlice, Variable}; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Problem { @@ -409,139 +403,139 @@ fn num_args_to_mono_id( MonoTypeId::CRASH } -fn resolve_tag_ext( - subs: &mut Subs, - mono_types: &mut MonoTypes, - problems: &mut impl Push, - mut tags: UnionTags, - mut ext: TagExt, -) -> Vec<(TagName, Vec)> { - let mut all_tags = Vec::new(); +// fn resolve_tag_ext( +// subs: &mut Subs, +// mono_types: &mut MonoTypes, +// problems: &mut impl Push, +// mut tags: UnionTags, +// mut ext: TagExt, +// ) -> Vec<(TagName, Vec)> { +// let mut all_tags = Vec::new(); - // Collapse (recursively) all the tags in ext_var into a flat list of tags. - loop { - for (tag, vars) in tags.iter_from_subs(subs) { - all_tags.push((tag.clone(), vars.to_vec())); - } +// // Collapse (recursively) all the tags in ext_var into a flat list of tags. +// loop { +// for (tag, vars) in tags.iter_from_subs(subs) { +// all_tags.push((tag.clone(), vars.to_vec())); +// } - match subs.get_content_without_compacting(ext.var()) { - Content::Structure(FlatType::TagUnion(new_tags, new_ext)) => { - // Update tags and ext and loop back again to process them. - tags = *new_tags; - ext = *new_ext; - } - Content::Structure(FlatType::FunctionOrTagUnion(tag_names, _symbols, new_ext)) => { - for index in tag_names.into_iter() { - all_tags.push((subs[index].clone(), Vec::new())); - } - ext = *new_ext; - } - Content::Structure(FlatType::EmptyTagUnion) => break, - Content::FlexVar(_) | Content::FlexAbleVar(_, _) => break, - Content::Alias(_, _, real, _) => { - // Follow the alias and process it on the next iteration of the loop. - ext = TagExt::Any(*real); +// match subs.get_content_without_compacting(ext.var()) { +// Content::Structure(FlatType::TagUnion(new_tags, new_ext)) => { +// // Update tags and ext and loop back again to process them. +// tags = *new_tags; +// ext = *new_ext; +// } +// Content::Structure(FlatType::FunctionOrTagUnion(tag_names, _symbols, new_ext)) => { +// for index in tag_names.into_iter() { +// all_tags.push((subs[index].clone(), Vec::new())); +// } +// ext = *new_ext; +// } +// Content::Structure(FlatType::EmptyTagUnion) => break, +// Content::FlexVar(_) | Content::FlexAbleVar(_, _) => break, +// Content::Alias(_, _, real, _) => { +// // Follow the alias and process it on the next iteration of the loop. +// ext = TagExt::Any(*real); - // We just processed these tags, so don't process them again! - tags = UnionLabels::default(); - } - _ => { - // This should never happen! If it does, record a Problem and break. - problems.push(Problem::TagUnionExtWasNotTagUnion); +// // We just processed these tags, so don't process them again! +// tags = UnionLabels::default(); +// } +// _ => { +// // This should never happen! If it does, record a Problem and break. +// problems.push(Problem::TagUnionExtWasNotTagUnion); - break; - } - } - } +// break; +// } +// } +// } - all_tags -} +// all_tags +// } -fn lower_record>( - env: &mut Env<'_, '_, '_, '_, '_, '_, P>, - subs: &Subs, - mut fields: RecordFields, - mut ext: Variable, -) -> Vec<(Lowercase, Option)> { - let mut labeled_mono_ids = Vec::with_capacity(fields.len()); +// fn lower_record>( +// env: &mut Env<'_, '_, '_, '_, '_, '_, P>, +// subs: &Subs, +// mut fields: RecordFields, +// mut ext: Variable, +// ) -> Vec<(Lowercase, Option)> { +// let mut labeled_mono_ids = Vec::with_capacity(fields.len()); - // Collapse (recursively) all the fields in ext into a flat list of fields. - loop { - // Add all the current fields to the answer. - labeled_mono_ids.extend( - fields - .sorted_iterator(subs, ext) - .map(|(label, field)| (label, lower_var(env, subs, *field.as_inner()))), - ); +// // Collapse (recursively) all the fields in ext into a flat list of fields. +// loop { +// // Add all the current fields to the answer. +// labeled_mono_ids.extend( +// fields +// .sorted_iterator(subs, ext) +// .map(|(label, field)| (label, lower_var(env, subs, *field.as_inner()))), +// ); - // If the ext record is nonempty, set its fields to be the next ones we handle, and loop back. - match subs.get_content_without_compacting(ext) { - Content::Structure(FlatType::Record(new_fields, new_ext)) => { - // Update fields and ext and loop back again to process them. - fields = *new_fields; - ext = *new_ext; - } - Content::Structure(FlatType::EmptyRecord) - | Content::FlexVar(_) - | Content::FlexAbleVar(_, _) => return labeled_mono_ids, - Content::Alias(_, _, real, _) => { - // Follow the alias and process it on the next iteration of the loop. - ext = *real; +// // If the ext record is nonempty, set its fields to be the next ones we handle, and loop back. +// match subs.get_content_without_compacting(ext) { +// Content::Structure(FlatType::Record(new_fields, new_ext)) => { +// // Update fields and ext and loop back again to process them. +// fields = *new_fields; +// ext = *new_ext; +// } +// Content::Structure(FlatType::EmptyRecord) +// | Content::FlexVar(_) +// | Content::FlexAbleVar(_, _) => return labeled_mono_ids, +// Content::Alias(_, _, real, _) => { +// // Follow the alias and process it on the next iteration of the loop. +// ext = *real; - // We just processed these fields, so don't process them again! - fields = RecordFields::empty(); - } - _ => { - // This should never happen! If it does, record a Problem and early return. - env.problems.push(Problem::RecordExtWasNotRecord); +// // We just processed these fields, so don't process them again! +// fields = RecordFields::empty(); +// } +// _ => { +// // This should never happen! If it does, record a Problem and early return. +// env.problems.push(Problem::RecordExtWasNotRecord); - return labeled_mono_ids; - } - } - } -} +// return labeled_mono_ids; +// } +// } +// } +// } -fn resolve_tuple_ext( - subs: &mut Subs, - mono_types: &mut MonoTypes, - problems: &mut impl Push, - mut elems: TupleElems, - mut ext: Variable, -) -> Vec<(usize, Variable)> { - let mut all_elems = Vec::new(); +// fn resolve_tuple_ext( +// subs: &mut Subs, +// mono_types: &mut MonoTypes, +// problems: &mut impl Push, +// mut elems: TupleElems, +// mut ext: Variable, +// ) -> Vec<(usize, Variable)> { +// let mut all_elems = Vec::new(); - // Collapse (recursively) all the elements in ext into a flat list of elements. - loop { - for (idx, var_index) in elems.iter_all() { - all_elems.push((idx.index as usize, subs[var_index])); - } +// // Collapse (recursively) all the elements in ext into a flat list of elements. +// loop { +// for (idx, var_index) in elems.iter_all() { +// all_elems.push((idx.index as usize, subs[var_index])); +// } - match subs.get_content_without_compacting(ext) { - Content::Structure(FlatType::Tuple(new_elems, new_ext)) => { - // Update elems and ext and loop back again to process them. - elems = *new_elems; - ext = *new_ext; - } - Content::Structure(FlatType::EmptyTuple) => break, - Content::FlexVar(_) | Content::FlexAbleVar(_, _) => break, - Content::Alias(_, _, real, _) => { - // Follow the alias and process it on the next iteration of the loop. - ext = *real; +// match subs.get_content_without_compacting(ext) { +// Content::Structure(FlatType::Tuple(new_elems, new_ext)) => { +// // Update elems and ext and loop back again to process them. +// elems = *new_elems; +// ext = *new_ext; +// } +// Content::Structure(FlatType::EmptyTuple) => break, +// Content::FlexVar(_) | Content::FlexAbleVar(_, _) => break, +// Content::Alias(_, _, real, _) => { +// // Follow the alias and process it on the next iteration of the loop. +// ext = *real; - // We just processed these elements, so don't process them again! - elems = TupleElems::empty(); - } - _ => { - // This should never happen! If it does, record a Problem and break. - problems.push(Problem::TupleExtWasNotTuple); +// // We just processed these elements, so don't process them again! +// elems = TupleElems::empty(); +// } +// _ => { +// // This should never happen! If it does, record a Problem and break. +// problems.push(Problem::TupleExtWasNotTuple); - break; - } - } - } +// break; +// } +// } +// } - all_elems -} +// all_elems +// } // /// Lower the given vars in-place. // fn lower_vars<'a>( diff --git a/crates/compiler/specialize_types/tests/specialize_primitives.rs b/crates/compiler/specialize_types/tests/specialize_primitives.rs index 983ba13b96..71c7b8b5e2 100644 --- a/crates/compiler/specialize_types/tests/specialize_primitives.rs +++ b/crates/compiler/specialize_types/tests/specialize_primitives.rs @@ -1,8 +1,7 @@ #[macro_use] extern crate pretty_assertions; -extern crate bumpalo; - +#[cfg(test)] mod helpers; #[cfg(test)] diff --git a/crates/compiler/specialize_types/tests/specialize_structs.rs b/crates/compiler/specialize_types/tests/specialize_structs.rs index 38135a026c..f0d2e7e042 100644 --- a/crates/compiler/specialize_types/tests/specialize_structs.rs +++ b/crates/compiler/specialize_types/tests/specialize_structs.rs @@ -1,17 +1,16 @@ #[macro_use] extern crate pretty_assertions; -extern crate bumpalo; - +#[cfg(test)] mod helpers; #[cfg(test)] mod specialize_structs { - use roc_specialize_types::{MonoExpr, Number}; + use roc_specialize_types::MonoExpr; use crate::helpers::expect_mono_expr_str; - use super::helpers::{expect_mono_expr, expect_mono_expr_with_interns, expect_no_expr}; + use super::helpers::{expect_mono_expr_with_interns, expect_no_expr}; #[test] fn empty_record() { @@ -46,10 +45,10 @@ mod specialize_structs { fn two_fields() { let one = 42; let two = 50; - let expected = format!("{{ one: {one}, two: {two} }}"); + expect_mono_expr_str( - expected, - format!("Struct([Number(I8({one:?})), Number(I8({two:?}))])"), + format!("{{ one: {one}, two: {two} }}"), + format!("Struct([Number(I8({one})), Number(I8({two}))])"), ); } } diff --git a/crates/compiler/specialize_types/tests/test_specialize_types.rs b/crates/compiler/specialize_types/tests/test_specialize_types.rs index 8a25b6c841..e0fec8b136 100644 --- a/crates/compiler/specialize_types/tests/test_specialize_types.rs +++ b/crates/compiler/specialize_types/tests/test_specialize_types.rs @@ -3,8 +3,6 @@ extern crate pretty_assertions; #[macro_use] extern crate indoc; -extern crate bumpalo; - #[cfg(test)] mod specialize_types { use roc_load::LoadedModule; From ae2855628e9da99580da702489b7c4fcbac0628b Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sat, 16 Nov 2024 23:48:57 -0500 Subject: [PATCH 30/65] Try fixing a surprising constraint behavior --- crates/compiler/constrain/src/builtins.rs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/crates/compiler/constrain/src/builtins.rs b/crates/compiler/constrain/src/builtins.rs index 569e137d3d..85bd9dd7f4 100644 --- a/crates/compiler/constrain/src/builtins.rs +++ b/crates/compiler/constrain/src/builtins.rs @@ -30,7 +30,6 @@ pub(crate) fn add_numeric_bound_constr( match range { NumericBound::None => { // no additional constraints, just a Num * - num_num(Variable(num_var)) } NumericBound::FloatExact(width) => { let actual_type = constraints.push_variable(float_width_to_variable(width)); @@ -41,8 +40,6 @@ pub(crate) fn add_numeric_bound_constr( constraints.equal_types(type_index, expected_index, category, region); num_constraints.extend([because_suffix]); - - Variable(num_var) } NumericBound::IntExact(width) => { let actual_type = constraints.push_variable(int_lit_width_to_variable(width)); @@ -53,8 +50,6 @@ pub(crate) fn add_numeric_bound_constr( constraints.equal_types(type_index, expected_index, category, region); num_constraints.extend([because_suffix]); - - Variable(num_var) } NumericBound::Range(range) => { let precision_type = constraints.push_variable(precision_var); @@ -66,10 +61,10 @@ pub(crate) fn add_numeric_bound_constr( let constr = constraints.equal_types(precision_type, expected_index, category, region); num_constraints.extend([constr]); - - num_num(Variable(num_var)) } } + + num_num(Variable(num_var)) } #[inline(always)] From dd031855a929e26e6942c72ea4d349239c185a7d Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sat, 16 Nov 2024 23:49:20 -0500 Subject: [PATCH 31/65] Revert "Try fixing a surprising constraint behavior" This reverts commit ae2855628e9da99580da702489b7c4fcbac0628b. --- crates/compiler/constrain/src/builtins.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/crates/compiler/constrain/src/builtins.rs b/crates/compiler/constrain/src/builtins.rs index 85bd9dd7f4..569e137d3d 100644 --- a/crates/compiler/constrain/src/builtins.rs +++ b/crates/compiler/constrain/src/builtins.rs @@ -30,6 +30,7 @@ pub(crate) fn add_numeric_bound_constr( match range { NumericBound::None => { // no additional constraints, just a Num * + num_num(Variable(num_var)) } NumericBound::FloatExact(width) => { let actual_type = constraints.push_variable(float_width_to_variable(width)); @@ -40,6 +41,8 @@ pub(crate) fn add_numeric_bound_constr( constraints.equal_types(type_index, expected_index, category, region); num_constraints.extend([because_suffix]); + + Variable(num_var) } NumericBound::IntExact(width) => { let actual_type = constraints.push_variable(int_lit_width_to_variable(width)); @@ -50,6 +53,8 @@ pub(crate) fn add_numeric_bound_constr( constraints.equal_types(type_index, expected_index, category, region); num_constraints.extend([because_suffix]); + + Variable(num_var) } NumericBound::Range(range) => { let precision_type = constraints.push_variable(precision_var); @@ -61,10 +66,10 @@ pub(crate) fn add_numeric_bound_constr( let constr = constraints.equal_types(precision_type, expected_index, category, region); num_constraints.extend([constr]); + + num_num(Variable(num_var)) } } - - num_num(Variable(num_var)) } #[inline(always)] From 2e8ea853f7165111571e2306701a09e7e9f2fbaa Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sun, 17 Nov 2024 00:00:33 -0500 Subject: [PATCH 32/65] Fix floating-point monomorphization --- .../specialize_types/src/foreign_symbol.rs | 2 +- .../specialize_types/src/mono_expr.rs | 30 +- .../compiler/specialize_types/src/mono_ir.rs | 2 + .../specialize_types/src/mono_module.rs | 4 +- .../specialize_types/src/mono_struct.rs | 2 +- .../specialize_types/src/mono_type.rs | 3 + .../specialize_types/src/specialize_type.rs | 285 +++++++++++------- .../specialize_types/tests/helpers/mod.rs | 3 + crates/test_compile/src/help_constrain.rs | 1 + 9 files changed, 218 insertions(+), 114 deletions(-) diff --git a/crates/compiler/specialize_types/src/foreign_symbol.rs b/crates/compiler/specialize_types/src/foreign_symbol.rs index 56caf0b012..8b1ead1461 100644 --- a/crates/compiler/specialize_types/src/foreign_symbol.rs +++ b/crates/compiler/specialize_types/src/foreign_symbol.rs @@ -12,7 +12,7 @@ impl ForeignSymbols { unsafe { self.inner.get_unchecked(id.inner.index()) } } - pub(crate) fn new() -> Self { + pub fn new() -> Self { Self { inner: Default::default(), } diff --git a/crates/compiler/specialize_types/src/mono_expr.rs b/crates/compiler/specialize_types/src/mono_expr.rs index 359caddb47..d0740c1ea5 100644 --- a/crates/compiler/specialize_types/src/mono_expr.rs +++ b/crates/compiler/specialize_types/src/mono_expr.rs @@ -10,7 +10,7 @@ use bumpalo::{collections::Vec, Bump}; use roc_can::expr::{Expr, IntValue}; use roc_collections::Push; use roc_solve::module::Solved; -use roc_types::subs::Subs; +use roc_types::subs::{Content, Subs}; use soa::{Index, NonEmptySlice, Slice}; pub struct Env<'a, 'c, 'd, 'i, 's, 't, P> { @@ -76,15 +76,27 @@ impl<'a, 'c, 'd, 'i, 's, 't, P: Push> Env<'a, 'c, 'd, 'i, 's, 't, P> { } match can_expr { - Expr::Float(var, _precision_var, _str, val, _bound) => match mono_from_var(*var) { - Some(mono_id) => match mono_types.get(mono_id) { - MonoType::Primitive(primitive) => Some(to_frac(*primitive, *val, problems)), - other => compiler_bug!(Problem::NumSpecializedToWrongType(Some(*other))), - }, - None => { - compiler_bug!(Problem::NumSpecializedToWrongType(None)) + Expr::Float(var, _precision_var, _str, val, _bound) => { + match dbg!(self.subs.get_content_without_compacting(*var)) { + Content::FlexVar(_) => { + // Plain decimal number literals like `4.2` can still have an unbound var. + Some(MonoExpr::Number(Number::Dec(*val))) + } + _ => match mono_from_var(*var) { + Some(mono_id) => match mono_types.get(mono_id) { + MonoType::Primitive(primitive) => { + Some(to_frac(*primitive, *val, problems)) + } + other => { + compiler_bug!(Problem::NumSpecializedToWrongType(Some(*other))) + } + }, + None => { + compiler_bug!(Problem::NumSpecializedToWrongType(None)) + } + }, } - }, + } Expr::Num(var, _, int_value, _) | Expr::Int(var, _, _, int_value, _) => { // Number literals and int literals both specify integer numbers, so to_num() can work on both. match mono_from_var(*var) { diff --git a/crates/compiler/specialize_types/src/mono_ir.rs b/crates/compiler/specialize_types/src/mono_ir.rs index d639a25fcf..2acd0179e0 100644 --- a/crates/compiler/specialize_types/src/mono_ir.rs +++ b/crates/compiler/specialize_types/src/mono_ir.rs @@ -289,6 +289,7 @@ pub struct WhenBranch { pub guard: Option, } +#[allow(dead_code)] #[derive(Clone, Copy, Debug)] pub enum MonoPattern { Identifier(IdentId), @@ -320,6 +321,7 @@ pub enum MonoPattern { Underscore, } +#[allow(dead_code)] #[derive(Clone, Copy, Debug)] pub enum DestructType { Required, diff --git a/crates/compiler/specialize_types/src/mono_module.rs b/crates/compiler/specialize_types/src/mono_module.rs index c46b7e2e6a..7a98271283 100644 --- a/crates/compiler/specialize_types/src/mono_module.rs +++ b/crates/compiler/specialize_types/src/mono_module.rs @@ -4,6 +4,7 @@ use roc_types::subs::Subs; use crate::{foreign_symbol::ForeignSymbols, mono_type::MonoTypes, DebugInfo}; +#[allow(dead_code)] pub struct MonoModule { mono_types: MonoTypes, foreign_symbols: ForeignSymbols, @@ -11,8 +12,9 @@ pub struct MonoModule { debug_info: DebugInfo, } +#[allow(dead_code)] impl MonoModule { - pub fn from_typed_can_module(subs: &Solved) -> Self { + pub fn from_typed_can_module(_subs: &Solved) -> Self { Self { mono_types: MonoTypes::new(), foreign_symbols: ForeignSymbols::new(), diff --git a/crates/compiler/specialize_types/src/mono_struct.rs b/crates/compiler/specialize_types/src/mono_struct.rs index 5a6b59b56a..71032aba4d 100644 --- a/crates/compiler/specialize_types/src/mono_struct.rs +++ b/crates/compiler/specialize_types/src/mono_struct.rs @@ -3,7 +3,7 @@ pub struct MonoFieldId { inner: u16, } impl MonoFieldId { - pub(crate) fn new(index: u16) -> Self { + pub fn new(index: u16) -> Self { Self { inner: index } } diff --git a/crates/compiler/specialize_types/src/mono_type.rs b/crates/compiler/specialize_types/src/mono_type.rs index 11101d662b..107f933b8a 100644 --- a/crates/compiler/specialize_types/src/mono_type.rs +++ b/crates/compiler/specialize_types/src/mono_type.rs @@ -77,10 +77,13 @@ impl MonoTypeId { #[derive(Debug)] pub struct MonoTypes { entries: Vec, + #[allow(dead_code)] ids: Vec, + #[allow(dead_code)] slices: Vec<(NonZeroU16, MonoTypeId)>, // TODO make this a Vec2 } +#[allow(dead_code)] impl MonoTypes { pub fn new() -> Self { Self { diff --git a/crates/compiler/specialize_types/src/specialize_type.rs b/crates/compiler/specialize_types/src/specialize_type.rs index c203c5c020..1dec5089e5 100644 --- a/crates/compiler/specialize_types/src/specialize_type.rs +++ b/crates/compiler/specialize_types/src/specialize_type.rs @@ -11,7 +11,10 @@ use crate::{ use roc_collections::{Push, VecMap}; use roc_module::{ident::Lowercase, symbol::Symbol}; use roc_solve::module::Solved; -use roc_types::subs::{Content, FlatType, Subs, SubsSlice, Variable}; +use roc_types::{ + subs::{Content, FlatType, Subs, SubsSlice, Variable}, + types::AliasKind, +}; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Problem { @@ -81,10 +84,14 @@ impl MonoCache { struct Env<'c, 'd, 'e, 'f, 'm, 'p, P: Push> { cache: &'c mut MonoCache, + #[allow(dead_code)] mono_types: &'m mut MonoTypes, + #[allow(dead_code)] field_ids: &'f mut RecordFieldIds, + #[allow(dead_code)] elem_ids: &'e mut TupleElemIds, problems: &'p mut P, + #[allow(dead_code)] debug_info: &'d mut Option, } @@ -104,22 +111,11 @@ fn lower_var>( } // Convert the Content to a MonoType, often by passing an iterator. None of these iterators introduce allocations. - let mono_id = match *subs.get_content_without_compacting(root_var) { + let mono_id = match dbg!(*subs.get_content_without_compacting(root_var)) { Content::Structure(flat_type) => match flat_type { FlatType::Apply(symbol, args) => { if symbol.is_builtin() { - if symbol == Symbol::NUM_NUM { - num_args_to_mono_id(args, subs, env.problems) - } else if symbol == Symbol::LIST_LIST { - todo!(); - // let mut new_args = args - // .into_iter() - // .flat_map(|var_index| lower_var(env, subs, subs[var_index])); - - // let arg = new_args.next(); - } else { - todo!() - } + lower_builtin(env, subs, symbol, args) } else { todo!("handle non-builtin Apply"); } @@ -252,13 +248,26 @@ fn lower_var>( NumAtLeastEitherSign(int_lit_width) => int_lit_width_to_mono_type_id(int_lit_width), } } - Content::Alias(_symbol, args, real, kind) => { - let mono_id = lower_var(env, subs, real)?; - // let mono_args = args - // .into_iter() - // .flat_map(|arg| lower_var(env, subs, subs[arg])); + Content::Alias(symbol, args, real, kind) => { + match kind { + AliasKind::Opaque if symbol.is_builtin() => { + let args_slice = SubsSlice::new(args.variables_start, args.type_variables_len); + lower_builtin(env, subs, symbol, args_slice) + } + _ => { + let mono_id = lower_var(env, subs, real)?; + let todo = (); // TODO record in debuginfo the alias name for whatever we're lowering. - todo!(); + mono_id + } + } + } + Content::FlexVar(_) + | Content::RigidVar(_) + | Content::FlexAbleVar(_, _) + | Content::RigidAbleVar(_, _) => { + // The only way we should reach this branch is in something like a `crash`. + MonoTypeId::CRASH } content => { todo!("specialize this Content: {content:?}"); @@ -292,107 +301,157 @@ fn int_lit_width_to_mono_type_id(int_lit_width: roc_can::num::IntLitWidth) -> Mo } } -fn num_args_to_mono_id( +/// This works on the arg(s) to a Num.Num +fn num_num_args_to_mono_id( + outer_symbol: Symbol, args: SubsSlice, subs: &Subs, problems: &mut impl Push, ) -> MonoTypeId { match args.into_iter().next() { Some(arg_index) if args.len() == 1 => { - match subs.get_content_without_compacting(subs[arg_index]) { - Content::Structure(flat_type) => { - if let FlatType::Apply(outer_symbol, args) = flat_type { - let outer_symbol = *outer_symbol; + let mut content = subs.get_content_without_compacting(subs[arg_index]); - match args.into_iter().next() { - Some(arg_index) if args.len() == 1 => { - match subs.get_content_without_compacting(subs[arg_index]) { - Content::Structure(flat_type) => { - if let FlatType::Apply(inner_symbol, args) = flat_type { - let inner_symbol = *inner_symbol; + // Unroll aliases in this loop, as many aliases as we encounter. + loop { + match content { + Content::Structure(flat_type) => { + if let FlatType::Apply(inner_symbol, args) = flat_type { + let inner_symbol = *inner_symbol; - if args.is_empty() { - if outer_symbol == Symbol::NUM_INTEGER { - if inner_symbol == Symbol::NUM_UNSIGNED8 { - return MonoTypeId::U8; - } else if inner_symbol == Symbol::NUM_SIGNED8 { - return MonoTypeId::I8; - } else if inner_symbol == Symbol::NUM_UNSIGNED16 - { - return MonoTypeId::U16; - } else if inner_symbol == Symbol::NUM_SIGNED16 { - return MonoTypeId::I16; - } else if inner_symbol == Symbol::NUM_UNSIGNED32 - { - return MonoTypeId::U32; - } else if inner_symbol == Symbol::NUM_SIGNED32 { - return MonoTypeId::I32; - } else if inner_symbol == Symbol::NUM_UNSIGNED64 - { - return MonoTypeId::U64; - } else if inner_symbol == Symbol::NUM_SIGNED64 { - return MonoTypeId::I64; - } else if inner_symbol - == Symbol::NUM_UNSIGNED128 - { - return MonoTypeId::U128; - } else if inner_symbol == Symbol::NUM_SIGNED128 - { - return MonoTypeId::I128; - } - } else if outer_symbol == Symbol::NUM_FLOATINGPOINT - { - if inner_symbol == Symbol::NUM_BINARY32 { - return MonoTypeId::F32; - } else if inner_symbol == Symbol::NUM_BINARY64 { - return MonoTypeId::F64; - } else if inner_symbol == Symbol::NUM_DECIMAL { - return MonoTypeId::DEC; - } - } - } - } + if args.is_empty() { + if outer_symbol == Symbol::NUM_INTEGER { + if inner_symbol == Symbol::NUM_UNSIGNED8 { + return MonoTypeId::U8; + } else if inner_symbol == Symbol::NUM_SIGNED8 { + return MonoTypeId::I8; + } else if inner_symbol == Symbol::NUM_UNSIGNED16 { + return MonoTypeId::U16; + } else if inner_symbol == Symbol::NUM_SIGNED16 { + return MonoTypeId::I16; + } else if inner_symbol == Symbol::NUM_UNSIGNED32 { + return MonoTypeId::U32; + } else if inner_symbol == Symbol::NUM_SIGNED32 { + return MonoTypeId::I32; + } else if inner_symbol == Symbol::NUM_UNSIGNED64 { + return MonoTypeId::U64; + } else if inner_symbol == Symbol::NUM_SIGNED64 { + return MonoTypeId::I64; + } else if inner_symbol == Symbol::NUM_UNSIGNED128 { + return MonoTypeId::U128; + } else if inner_symbol == Symbol::NUM_SIGNED128 { + return MonoTypeId::I128; } - Content::FlexVar(_) => { - if outer_symbol == Symbol::NUM_INTEGER { - // Int * - return MonoTypeId::DEFAULT_INT; - } else if outer_symbol == Symbol::NUM_FLOATINGPOINT { - // Frac * - return MonoTypeId::DEFAULT_FRAC; - } - } - Content::Alias( - symbol, - alias_variables, - variable, - alias_kind, - ) => todo!(), - Content::RangedNumber(numeric_range) => todo!(), - _ => { - // no-op + } else if outer_symbol == Symbol::NUM_FLOATINGPOINT { + if inner_symbol == Symbol::NUM_BINARY32 { + return MonoTypeId::F32; + } else if inner_symbol == Symbol::NUM_BINARY64 { + return MonoTypeId::F64; + } else if inner_symbol == Symbol::NUM_DECIMAL { + return MonoTypeId::DEC; } } } - _ => { - // no-op + } + } + Content::FlexVar(_) => { + if outer_symbol == Symbol::NUM_INTEGER { + // Int * + return MonoTypeId::DEFAULT_INT; + } else if outer_symbol == Symbol::NUM_FLOATINGPOINT { + // Frac * + return MonoTypeId::DEFAULT_FRAC; + } + } + Content::Alias(_symbol, _alias_variables, variable, alias_kind) => { + match alias_kind { + AliasKind::Structural => { + // Unwrap the alias and continue the loop. + // + // (Unlike in most aliases, here we don't care about the name + // for debug info purposes; all we care about is determining + // whether it's one of the builtin types.) + content = subs.get_content_without_compacting(*variable); + } + AliasKind::Opaque => { + // This should never happen (type-checking should have caught it), + // so if an opaque type made it here, it's definitely a compiler bug! + break; } } } - } - Content::FlexVar(subs_index) => { - // Num * - return MonoTypeId::DEFAULT_INT; - } - Content::Alias(symbol, alias_variables, variable, alias_kind) => todo!(), - Content::RangedNumber(numeric_range) => todo!(), - _ => { - // fall through + Content::RangedNumber(numeric_range) => todo!(), + _ => { + // This is an invalid number type, so break out of + // the alias-unrolling loop in order to return an error. + break; + } } } } _ => { - // fall through + // This is an invalid number type, so fall through to the error case. + } + } + + // If we got here, it's because the Num type parameter(s) don't fit the form we expect. + // Specialize to a crash! + problems.push(Problem::BadNumTypeParam); + + MonoTypeId::CRASH +} + +fn number_args_to_mono_id( + args: SubsSlice, + subs: &Subs, + problems: &mut impl Push, +) -> MonoTypeId { + match args.into_iter().next() { + Some(arg_index) if args.len() == 1 => { + let mut content = subs.get_content_without_compacting(subs[arg_index]); + + // Unroll aliases in this loop, as many aliases as we encounter. + loop { + match content { + Content::Structure(flat_type) => { + if let FlatType::Apply(outer_symbol, args) = flat_type { + return num_num_args_to_mono_id(*outer_symbol, *args, subs, problems); + } else { + break; + } + } + Content::FlexVar(_) => { + // Num * + return MonoTypeId::DEFAULT_INT; + } + Content::Alias(_symbol, _alias_variables, variable, alias_kind) => { + match alias_kind { + AliasKind::Structural => { + // Unwrap the alias and continue the loop. + // + // (Unlike in most aliases, here we don't care about the name + // for debug info purposes; all we care about is determining + // whether it's one of the builtin types.) + content = subs.get_content_without_compacting(*variable); + } + AliasKind::Opaque => { + // This should never happen (type-checking should have caught it), + // so if an opaque type made it here, it's definitely a compiler bug! + break; + } + } + } + Content::RangedNumber(numeric_range) => todo!(), + _ => { + // This is an invalid number type, so break out of + // the alias-unrolling loop in order to return an error. + break; + } + } + } + } + _ => { + // The Num type did not have exactly 1 type parameter; fall through to the error case. } } @@ -550,3 +609,25 @@ fn num_args_to_mono_id( // *var = ; // } // } + +fn lower_builtin>( + env: &mut Env<'_, '_, '_, '_, '_, '_, P>, + subs: &Subs, + symbol: Symbol, + args: SubsSlice, +) -> MonoTypeId { + if symbol == Symbol::NUM_NUM { + number_args_to_mono_id(args, subs, env.problems) + } else if symbol == Symbol::NUM_FLOATINGPOINT { + num_num_args_to_mono_id(symbol, args, subs, env.problems) + } else if symbol == Symbol::LIST_LIST { + todo!(); + // let mut new_args = args + // .into_iter() + // .flat_map(|var_index| lower_var(env, subs, subs[var_index])); + + // let arg = new_args.next(); + } else { + todo!("implement lower_builtin for symbol {symbol:?} - or, if all the builtins are already in here, report a compiler bug instead of panicking like this."); + } +} diff --git a/crates/compiler/specialize_types/tests/helpers/mod.rs b/crates/compiler/specialize_types/tests/helpers/mod.rs index 5f32958d8d..dec72f907c 100644 --- a/crates/compiler/specialize_types/tests/helpers/mod.rs +++ b/crates/compiler/specialize_types/tests/helpers/mod.rs @@ -96,6 +96,7 @@ fn specialize_expr<'a>( } } +#[allow(dead_code)] #[track_caller] pub fn expect_no_expr(input: impl AsRef) { let arena = Bump::new(); @@ -106,11 +107,13 @@ pub fn expect_no_expr(input: impl AsRef) { assert_eq!(None, actual, "This input expr should have specialized to being dicarded as zero-sized, but it didn't: {:?}", input.as_ref()); } +#[allow(dead_code)] #[track_caller] pub fn expect_mono_expr(input: impl AsRef, mono_expr: MonoExpr) { expect_mono_expr_with_interns(input, |_, _| mono_expr); } +#[allow(dead_code)] #[track_caller] pub fn expect_mono_expr_str(input: impl AsRef, expr_str: impl AsRef) { expect_mono_expr_custom( diff --git a/crates/test_compile/src/help_constrain.rs b/crates/test_compile/src/help_constrain.rs index 534cacaae7..e73c5d315a 100644 --- a/crates/test_compile/src/help_constrain.rs +++ b/crates/test_compile/src/help_constrain.rs @@ -23,6 +23,7 @@ pub struct ConstrainedExprOut { pub region: Region, } +#[allow(dead_code)] #[derive(Default)] pub struct ConstrainedExpr { can_expr: CanExpr, From 7832a7f16fe3f794f1fd3f18a9bb1f4bcbab843d Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sun, 17 Nov 2024 00:37:07 -0500 Subject: [PATCH 33/65] Delete unnecessary specialize_types tests --- .../specialize_types/src/specialize_type.rs | 13 +- .../tests/test_specialize_types.rs | 4897 ----------------- 2 files changed, 5 insertions(+), 4905 deletions(-) delete mode 100644 crates/compiler/specialize_types/tests/test_specialize_types.rs diff --git a/crates/compiler/specialize_types/src/specialize_type.rs b/crates/compiler/specialize_types/src/specialize_type.rs index 1dec5089e5..fe7b6684e2 100644 --- a/crates/compiler/specialize_types/src/specialize_type.rs +++ b/crates/compiler/specialize_types/src/specialize_type.rs @@ -227,14 +227,6 @@ fn lower_var>( FlatType::EmptyTuple | FlatType::EmptyTagUnion => None, }, - Content::RangedNumber(_) // RangedNumber goes in Num's type parameter slot, so monomorphize it to [] - | Content::FlexVar(_) - | Content::RigidVar(_) - | Content::FlexAbleVar(_, _) - | Content::RigidAbleVar(_, _) - | Content::RecursionVar { .. } => Content::Structure(FlatType::EmptyTagUnion), - Content::LambdaSet(lambda_set) => Content::LambdaSet(lambda_set), - Content::ErasedLambda => Content::ErasedLambda, Content::Error => Content::Error, */ }, @@ -269,6 +261,11 @@ fn lower_var>( // The only way we should reach this branch is in something like a `crash`. MonoTypeId::CRASH } + Content::ErasedLambda | Content::LambdaSet(_) => { + unreachable!( + "This new monomorphization implementation must not do anything with lambda sets, because they'll be handled later!" + ); + } content => { todo!("specialize this Content: {content:?}"); } diff --git a/crates/compiler/specialize_types/tests/test_specialize_types.rs b/crates/compiler/specialize_types/tests/test_specialize_types.rs deleted file mode 100644 index e0fec8b136..0000000000 --- a/crates/compiler/specialize_types/tests/test_specialize_types.rs +++ /dev/null @@ -1,4897 +0,0 @@ -#[macro_use] -extern crate pretty_assertions; -#[macro_use] -extern crate indoc; - -#[cfg(test)] -mod specialize_types { - use roc_load::LoadedModule; - use roc_solve::FunctionKind; - use roc_specialize_types::{DebugInfo, MonoCache, MonoTypes, RecordFieldIds, TupleElemIds}; - use test_solve_helpers::{format_problems, run_load_and_infer}; - - use roc_types::pretty_print::{name_and_print_var, DebugPrint}; - - // HELPERS - - fn infer_eq_help(src: &str) -> Result<(String, String, String), std::io::Error> { - let ( - LoadedModule { - module_id: home, - mut can_problems, - mut type_problems, - interns, - mut solved, - mut exposed_to_host, - abilities_store, - .. - }, - src, - ) = run_load_and_infer(src, [], false, FunctionKind::LambdaSet)?; - - let mut can_problems = can_problems.remove(&home).unwrap_or_default(); - let type_problems = type_problems.remove(&home).unwrap_or_default(); - - // Disregard UnusedDef problems, because those are unavoidable when - // returning a function from the test expression. - can_problems.retain(|prob| { - !matches!( - prob, - roc_problem::can::Problem::UnusedDef(_, _) - | roc_problem::can::Problem::UnusedBranchDef(..) - ) - }); - - let (can_problems, type_problems) = - format_problems(&src, home, &interns, can_problems, type_problems); - - exposed_to_host.retain(|s, _| !abilities_store.is_specialization_name(*s)); - - debug_assert!(exposed_to_host.len() == 1, "{exposed_to_host:?}"); - let (_symbol, variable) = exposed_to_host.into_iter().next().unwrap(); - let mut mono_cache = MonoCache::from_solved_subs(&solved); - let mut mono_types = MonoTypes::new(); - let debug_info = DebugInfo::new(); - let mut record_field_ids = RecordFieldIds::new(); - let mut tuple_elem_ids = TupleElemIds::new(); - let mut problems = Vec::new(); - - mono_cache.monomorphize_var( - solved.inner_mut(), - &mut mono_types, - &mut record_field_ids, - &mut tuple_elem_ids, - &mut problems, - &mut Some(debug_info), - variable, - ); - - assert_eq!(problems, Vec::new()); - - let actual_str = name_and_print_var( - variable, - solved.inner_mut(), - home, - &interns, - DebugPrint::NOTHING, - ); - - Ok((type_problems, can_problems, actual_str)) - } - - fn specializes_to(src: &str, expected: &str) { - let (_, can_problems, actual) = infer_eq_help(src).unwrap(); - - assert!( - can_problems.is_empty(), - "Canonicalization problems: {can_problems}" - ); - - assert_eq!(actual, expected.to_string()); - } - - fn infer_eq_without_problem(src: &str, expected: &str) { - let (type_problems, can_problems, actual) = infer_eq_help(src).unwrap(); - - assert!( - can_problems.is_empty(), - "Canonicalization problems: {can_problems}" - ); - - if !type_problems.is_empty() { - // fail with an assert, but print the problems normally so rust doesn't try to diff - // an empty vec with the problems. - panic!("expected:\n{expected:?}\ninferred:\n{actual:?}\nproblems:\n{type_problems}",); - } - assert_eq!(actual, expected.to_string()); - } - - #[test] - fn str_literal_solo() { - specializes_to("\"test\"", "Str"); - } - - #[test] - fn int_literal_solo() { - specializes_to("5", "Num []"); - } - - #[test] - fn frac_literal_solo() { - specializes_to("0.5", "Frac []"); - } - - #[test] - fn dec_literal() { - specializes_to( - indoc!( - r" - val : Dec - val = 1.2 - - val - " - ), - "Dec", - ); - } - - #[test] - fn str_starts_with() { - specializes_to("Str.startsWith", "Str, Str -> Bool"); - } - - #[test] - fn str_from_int() { - infer_eq_without_problem("Num.toStr", "Num [] -> Str"); - } - - #[test] - fn str_from_utf8() { - infer_eq_without_problem( - "Str.fromUtf8", - "List U8 -> Result Str [BadUtf8 Utf8ByteProblem U64]", - ); - } - - #[test] - fn list_concat_utf8() { - infer_eq_without_problem("List.concatUtf8", "List U8, Str -> List U8") - } - - // LIST - - #[test] - fn empty_list() { - specializes_to("[]", "List []"); - } - - #[test] - fn list_of_lists() { - specializes_to("[[]]", "List (List [])"); - } - - #[test] - fn triple_nested_list() { - specializes_to("[[[]]]", "List (List (List []))"); - } - - #[test] - fn nested_empty_list() { - specializes_to("[[], [[]]]", "List (List (List []))"); - } - - #[test] - fn list_of_one_int() { - specializes_to("[42]", "List (Num [])"); - } - - #[test] - fn triple_nested_int_list() { - specializes_to("[[[5]]]", "List (List (List (Num [])))"); - } - - #[test] - fn list_of_ints() { - specializes_to("[1, 2, 3]", "List (Num [])"); - } - - #[test] - fn nested_list_of_ints() { - specializes_to("[[1], [2, 3]]", "List (List (Num []))"); - } - - #[test] - fn list_of_one_string() { - specializes_to(r#"["cowabunga"]"#, "List Str"); - } - - #[test] - fn triple_nested_string_list() { - specializes_to(r#"[[["foo"]]]"#, "List (List (List Str))"); - } - - #[test] - fn list_of_strings() { - specializes_to(r#"["foo", "bar"]"#, "List Str"); - } - - // INTERPOLATED STRING - - #[test] - fn infer_interpolated_string() { - specializes_to( - indoc!( - r#" - whatItIs = "great" - - "type inference is $(whatItIs)!" - "# - ), - "Str", - ); - } - - #[test] - fn infer_interpolated_var() { - specializes_to( - indoc!( - r#" - whatItIs = "great" - - str = "type inference is $(whatItIs)!" - - whatItIs - "# - ), - "Str", - ); - } - - #[test] - fn infer_interpolated_field() { - specializes_to( - indoc!( - r#" - rec = { whatItIs: "great" } - - str = "type inference is $(rec.whatItIs)!" - - rec - "# - ), - "{ whatItIs : Str }", - ); - } - - // LIST MISMATCH - - #[test] - fn mismatch_heterogeneous_list() { - specializes_to( - indoc!( - r#" - ["foo", 5] - "# - ), - "List ", - ); - } - - #[test] - fn mismatch_heterogeneous_nested_list() { - specializes_to( - indoc!( - r#" - [["foo", 5]] - "# - ), - "List (List )", - ); - } - - #[test] - fn mismatch_heterogeneous_nested_empty_list() { - specializes_to( - indoc!( - r" - [[1], [[]]] - " - ), - "List ", - ); - } - - // CLOSURE - - #[test] - fn always_return_empty_record() { - specializes_to( - indoc!( - r" - \_ -> {} - " - ), - "[] -> {}", - ); - } - - #[test] - fn two_arg_return_int() { - specializes_to( - indoc!( - r" - \_, _ -> 42 - " - ), - "[], [] -> Num []", - ); - } - - #[test] - fn three_arg_return_string() { - specializes_to( - indoc!( - r#" - \_, _, _ -> "test!" - "# - ), - "[], [], [] -> Str", - ); - } - - // DEF - - #[test] - fn def_empty_record() { - specializes_to( - indoc!( - r" - foo = {} - - foo - " - ), - "{}", - ); - } - - #[test] - fn def_string() { - specializes_to( - indoc!( - r#" - str = "thing" - - str - "# - ), - "Str", - ); - } - - #[test] - fn def_1_arg_closure() { - specializes_to( - indoc!( - r" - fn = \_ -> {} - - fn - " - ), - "[] -> {}", - ); - } - - #[test] - fn applied_tag() { - infer_eq_without_problem( - indoc!( - r#" - List.map ["a", "b"] \elem -> Foo elem - "# - ), - "List [Foo Str]", - ) - } - - // Tests (TagUnion, Func) - #[test] - fn applied_tag_function() { - infer_eq_without_problem( - indoc!( - r#" - foo = Foo - - foo "hi" - "# - ), - "[Foo Str]", - ) - } - - // Tests (TagUnion, Func) - #[test] - fn applied_tag_function_list_map() { - infer_eq_without_problem( - indoc!( - r#" - List.map ["a", "b"] Foo - "# - ), - "List [Foo Str]", - ) - } - - // Tests (TagUnion, Func) - #[test] - fn applied_tag_function_list() { - infer_eq_without_problem( - indoc!( - r" - [\x -> Bar x, Foo] - " - ), - "List ([] -> [Bar [], Foo []])", - ) - } - - // Tests (Func, TagUnion) - #[test] - fn applied_tag_function_list_other_way() { - infer_eq_without_problem( - indoc!( - r" - [Foo, \x -> Bar x] - " - ), - "List ([] -> [Bar [], Foo []])", - ) - } - - // Tests (Func, TagUnion) - #[test] - fn applied_tag_function_record() { - infer_eq_without_problem( - indoc!( - r" - foo0 = Foo - foo1 = Foo - foo2 = Foo - - { - x: [foo0, Foo], - y: [foo1, \x -> Foo x], - z: [foo2, \x,y -> Foo x y] - } - " - ), - "{ x : List [Foo], y : List ([] -> [Foo []]), z : List ([], [] -> [Foo [] []]) }", - ) - } - - // Tests (TagUnion, Func) - #[test] - fn applied_tag_function_with_annotation() { - infer_eq_without_problem( - indoc!( - r" - x : List [Foo I64] - x = List.map [1, 2] Foo - - x - " - ), - "List [Foo I64]", - ) - } - - #[test] - fn def_2_arg_closure() { - specializes_to( - indoc!( - r" - func = \_, _ -> 42 - - func - " - ), - "[], [] -> Num []", - ); - } - - #[test] - fn def_3_arg_closure() { - specializes_to( - indoc!( - r#" - f = \_, _, _ -> "test!" - - f - "# - ), - "[], [], [] -> Str", - ); - } - - #[test] - fn def_multiple_functions() { - specializes_to( - indoc!( - r#" - a = \_, _, _ -> "test!" - - b = a - - b - "# - ), - "[], [], [] -> Str", - ); - } - - #[test] - fn def_multiple_strings() { - specializes_to( - indoc!( - r#" - a = "test!" - - b = a - - b - "# - ), - "Str", - ); - } - - #[test] - fn def_multiple_ints() { - specializes_to( - indoc!( - r" - c = b - - b = a - - a = 42 - - c - " - ), - "Num []", - ); - } - - #[test] - fn def_returning_closure() { - specializes_to( - indoc!( - r" - f = \z -> z - g = \z -> z - - (\x -> - a = f x - b = g x - x - ) - " - ), - "[] -> []", - ); - } - - // CALLING FUNCTIONS - - #[test] - fn call_returns_int() { - specializes_to( - indoc!( - r#" - alwaysFive = \_ -> 5 - - alwaysFive "stuff" - "# - ), - "Num []", - ); - } - - #[test] - fn identity_returns_given_type() { - specializes_to( - indoc!( - r#" - identity = \a -> a - - identity "hi" - "# - ), - "Str", - ); - } - - #[test] - fn identity_infers_principal_type() { - specializes_to( - indoc!( - r" - identity = \x -> x - - y = identity 5 - - identity - " - ), - "[] -> []", - ); - } - - #[test] - fn identity_works_on_incompatible_types() { - specializes_to( - indoc!( - r#" - identity = \a -> a - - x = identity 5 - y = identity "hi" - - x - "# - ), - "Num []", - ); - } - - #[test] - fn call_returns_list() { - specializes_to( - indoc!( - r" - enlist = \val -> [val] - - enlist 5 - " - ), - "List (Num [])", - ); - } - - #[test] - fn indirect_always() { - specializes_to( - indoc!( - r#" - always = \val -> (\_ -> val) - alwaysFoo = always "foo" - - alwaysFoo 42 - "# - ), - "Str", - ); - } - - #[test] - fn pizza_desugar() { - specializes_to( - indoc!( - r" - 1 |> (\a -> a) - " - ), - "Num []", - ); - } - - #[test] - fn pizza_desugar_two_arguments() { - specializes_to( - indoc!( - r#" - always2 = \a, _ -> a - - 1 |> always2 "foo" - "# - ), - "Num []", - ); - } - - #[test] - fn anonymous_identity() { - specializes_to( - indoc!( - r" - (\a -> a) 3.14 - " - ), - "Frac []", - ); - } - - #[test] - fn identity_of_identity() { - specializes_to( - indoc!( - r" - (\val -> val) (\val -> val) - " - ), - "[] -> []", - ); - } - - #[test] - fn recursive_identity() { - specializes_to( - indoc!( - r" - identity = \val -> val - - identity identity - " - ), - "[] -> []", - ); - } - - #[test] - fn identity_function() { - specializes_to( - indoc!( - r" - \val -> val - " - ), - "[] -> []", - ); - } - - #[test] - fn use_apply() { - specializes_to( - indoc!( - r" - identity = \a -> a - apply = \f, x -> f x - - apply identity 5 - " - ), - "Num []", - ); - } - - #[test] - fn apply_function() { - specializes_to( - indoc!( - r" - \f, x -> f x - " - ), - "([] -> []), [] -> []", - ); - } - - // #[test] - // TODO FIXME this should pass, but instead fails to canonicalize - // fn use_flip() { - // infer_eq( - // indoc!( - // r" - // flip = \f -> (\a b -> f b a) - // neverendingInt = \f int -> f int - // x = neverendingInt (\a -> a) 5 - - // flip neverendingInt - // " - // ), - // "(Num [], (a -> a)) -> Num []", - // ); - // } - - #[test] - fn flip_function() { - specializes_to( - indoc!( - r" - \f -> (\a, b -> f b a) - " - ), - "([], [] -> []) -> ([], [] -> [])", - ); - } - - #[test] - fn always_function() { - specializes_to( - indoc!( - r" - \val -> \_ -> val - " - ), - "[] -> ([] -> [])", - ); - } - - #[test] - fn pass_a_function() { - specializes_to( - indoc!( - r" - \f -> f {} - " - ), - "({} -> []) -> []", - ); - } - - // OPERATORS - - // #[test] - // fn div_operator() { - // infer_eq( - // indoc!( - // r" - // \l r -> l / r - // " - // ), - // "F64, F64 -> F64", - // ); - // } - - // #[test] - // fn basic_float_division() { - // infer_eq( - // indoc!( - // r" - // 1 / 2 - // " - // ), - // "F64", - // ); - // } - - // #[test] - // fn basic_int_division() { - // infer_eq( - // indoc!( - // r" - // 1 // 2 - // " - // ), - // "Num []", - // ); - // } - - // #[test] - // fn basic_addition() { - // infer_eq( - // indoc!( - // r" - // 1 + 2 - // " - // ), - // "Num []", - // ); - // } - - // #[test] - // fn basic_circular_type() { - // infer_eq( - // indoc!( - // r" - // \x -> x x - // " - // ), - // "", - // ); - // } - - // #[test] - // fn y_combinator_has_circular_type() { - // assert_eq!( - // infer(indoc!(r" - // \f -> (\x -> f x x) (\x -> f x x) - // ")), - // Erroneous(Problem::CircularType) - // ); - // } - - // #[test] - // fn no_higher_ranked_types() { - // // This should error because it can't type of alwaysFive - // infer_eq( - // indoc!( - // r#" - // \always -> [always [], always ""] - // "# - // ), - // "", - // ); - // } - - #[test] - fn always_with_list() { - specializes_to( - indoc!( - r#" - alwaysFive = \_ -> 5 - - [alwaysFive "foo", alwaysFive []] - "# - ), - "List (Num [])", - ); - } - - #[test] - fn if_with_int_literals() { - specializes_to( - indoc!( - r" - if Bool.true then - 42 - else - 24 - " - ), - "Num []", - ); - } - - #[test] - fn when_with_int_literals() { - specializes_to( - indoc!( - r" - when 1 is - 1 -> 2 - 3 -> 4 - " - ), - "Num []", - ); - } - - // RECORDS - - #[test] - fn empty_record() { - specializes_to("{}", "{}"); - } - - #[test] - fn one_field_record() { - specializes_to("{ x: 5 }", "{ x : Num [] }"); - } - - #[test] - fn two_field_record() { - specializes_to("{ x: 5, y : 3.14 }", "{ x : Num [], y : Frac [] }"); - } - - #[test] - fn record_literal_accessor() { - specializes_to("{ x: 5, y : 3.14 }.x", "Num []"); - } - - #[test] - fn record_literal_accessor_function() { - specializes_to(".x { x: 5, y : 3.14 }", "Num []"); - } - - #[test] - fn tuple_literal_accessor() { - specializes_to("(5, 3.14 ).0", "Num []"); - } - - #[test] - fn tuple_literal_accessor_function() { - specializes_to(".0 (5, 3.14 )", "Num []"); - } - - // #[test] - // fn tuple_literal_ty() { - // specializes_to("(5, 3.14 )", "( Num [], Frac [] )"); - // } - - // #[test] - // fn tuple_literal_accessor_ty() { - // specializes_to(".0", "( [] ) -> []"); - // specializes_to(".4", "( _, _, _, _, [] ) -> []"); - // specializes_to(".5", "( ... 5 omitted, [] ) -> []"); - // specializes_to(".200", "( ... 200 omitted, [] ) -> []"); - // } - - #[test] - fn tuple_accessor_generalization() { - specializes_to( - indoc!( - r#" - get0 = .0 - - { a: get0 (1, 2), b: get0 ("a", "b", "c") } - "# - ), - "{ a : Num [], b : Str }", - ); - } - - #[test] - fn record_arg() { - specializes_to("\\rec -> rec.x", "{ x : [] } -> []"); - } - - #[test] - fn record_with_bound_var() { - specializes_to( - indoc!( - r" - fn = \rec -> - x = rec.x - - rec - - fn - " - ), - "{ x : [] } -> { x : [] }", - ); - } - - #[test] - fn using_type_signature() { - specializes_to( - indoc!( - r" - bar : custom -> custom - bar = \x -> x - - bar - " - ), - "[] -> []", - ); - } - - #[test] - fn type_signature_without_body() { - specializes_to( - indoc!( - r#" - foo: Str -> {} - - foo "hi" - "# - ), - "{}", - ); - } - - #[test] - fn type_signature_without_body_rigid() { - specializes_to( - indoc!( - r" - foo : Num * -> custom - - foo 2 - " - ), - "[]", - ); - } - - #[test] - fn accessor_function() { - specializes_to(".foo", "{ foo : [] } -> []"); - } - - #[test] - fn type_signature_without_body_record() { - specializes_to( - indoc!( - r" - { x, y } : { x : ({} -> custom), y : {} } - - x - " - ), - "{} -> []", - ); - } - - #[test] - fn empty_record_pattern() { - specializes_to( - indoc!( - r" - # technically, an empty record can be destructured - thunk = \{} -> 42 - - xEmpty = if thunk {} == 42 then { x: {} } else { x: {} } - - when xEmpty is - { x: {} } -> {} - " - ), - "{}", - ); - } - - #[test] - fn record_type_annotation() { - // check that a closed record remains closed - specializes_to( - indoc!( - r" - foo : { x : custom } -> custom - foo = \{ x } -> x - - foo - " - ), - "{ x : [] } -> []", - ); - } - - #[test] - fn record_update() { - specializes_to( - indoc!( - r#" - user = { year: "foo", name: "Sam" } - - { user & year: "foo" } - "# - ), - "{ name : Str, year : Str }", - ); - } - - #[test] - fn bare_tag() { - specializes_to( - indoc!( - r" - Foo - " - ), - "[Foo]", - ); - } - - #[test] - fn single_tag_pattern() { - specializes_to( - indoc!( - r" - \Foo -> 42 - " - ), - "[Foo] -> Num []", - ); - } - - #[test] - fn two_tag_pattern() { - specializes_to( - indoc!( - r" - \x -> - when x is - True -> 1 - False -> 0 - " - ), - "[False, True] -> Num []", - ); - } - - #[test] - fn tag_application() { - specializes_to( - indoc!( - r#" - Foo "happy" 12 - "# - ), - "[Foo Str (Num [])]", - ); - } - - #[test] - fn record_extraction() { - specializes_to( - indoc!( - r" - f = \x -> - when x is - { a, b: _ } -> a - - f - " - ), - "{ a : [], b : [] } -> []", - ); - } - - #[test] - fn record_field_pattern_match_with_guard() { - specializes_to( - indoc!( - r" - when { x: 5 } is - { x: 4 } -> 4 - " - ), - "Num []", - ); - } - - #[test] - fn tag_union_pattern_match() { - specializes_to( - indoc!( - r" - \Foo x -> Foo x - " - ), - "[Foo []] -> [Foo []]", - ); - } - - #[test] - fn tag_union_pattern_match_ignored_field() { - specializes_to( - indoc!( - r#" - \Foo x _ -> Foo x "y" - "# - ), - "[Foo [] []] -> [Foo [] Str]", - ); - } - - #[test] - fn tag_with_field() { - specializes_to( - indoc!( - r#" - when Foo "blah" is - Foo x -> x - "# - ), - "Str", - ); - } - - #[test] - fn qualified_annotation_num_integer() { - specializes_to( - indoc!( - r" - int : Num.Num (Num.Integer Num.Signed64) - - int - " - ), - "I64", - ); - } - #[test] - fn qualified_annotated_num_integer() { - specializes_to( - indoc!( - r" - int : Num.Num (Num.Integer Num.Signed64) - int = 5 - - int - " - ), - "I64", - ); - } - #[test] - fn annotation_num_integer() { - specializes_to( - indoc!( - r" - int : Num (Integer Signed64) - - int - " - ), - "I64", - ); - } - #[test] - fn annotated_num_integer() { - specializes_to( - indoc!( - r" - int : Num (Integer Signed64) - int = 5 - - int - " - ), - "I64", - ); - } - - #[test] - fn qualified_annotation_using_i128() { - specializes_to( - indoc!( - r" - int : Num.I128 - - int - " - ), - "I128", - ); - } - #[test] - fn qualified_annotated_using_i128() { - specializes_to( - indoc!( - r" - int : Num.I128 - int = 5 - - int - " - ), - "I128", - ); - } - #[test] - fn annotation_using_i128() { - specializes_to( - indoc!( - r" - int : I128 - - int - " - ), - "I128", - ); - } - #[test] - fn annotated_using_i128() { - specializes_to( - indoc!( - r" - int : I128 - int = 5 - - int - " - ), - "I128", - ); - } - - #[test] - fn qualified_annotation_using_u128() { - specializes_to( - indoc!( - r" - int : Num.U128 - - int - " - ), - "U128", - ); - } - #[test] - fn qualified_annotated_using_u128() { - specializes_to( - indoc!( - r" - int : Num.U128 - int = 5 - - int - " - ), - "U128", - ); - } - #[test] - fn annotation_using_u128() { - specializes_to( - indoc!( - r" - int : U128 - - int - " - ), - "U128", - ); - } - #[test] - fn annotated_using_u128() { - specializes_to( - indoc!( - r" - int : U128 - int = 5 - - int - " - ), - "U128", - ); - } - - #[test] - fn qualified_annotation_using_i64() { - specializes_to( - indoc!( - r" - int : Num.I64 - - int - " - ), - "I64", - ); - } - #[test] - fn qualified_annotated_using_i64() { - specializes_to( - indoc!( - r" - int : Num.I64 - int = 5 - - int - " - ), - "I64", - ); - } - #[test] - fn annotation_using_i64() { - specializes_to( - indoc!( - r" - int : I64 - - int - " - ), - "I64", - ); - } - #[test] - fn annotated_using_i64() { - specializes_to( - indoc!( - r" - int : I64 - int = 5 - - int - " - ), - "I64", - ); - } - - #[test] - fn qualified_annotation_using_u64() { - specializes_to( - indoc!( - r" - int : Num.U64 - - int - " - ), - "U64", - ); - } - #[test] - fn qualified_annotated_using_u64() { - specializes_to( - indoc!( - r" - int : Num.U64 - int = 5 - - int - " - ), - "U64", - ); - } - #[test] - fn annotation_using_u64() { - specializes_to( - indoc!( - r" - int : U64 - - int - " - ), - "U64", - ); - } - #[test] - fn annotated_using_u64() { - specializes_to( - indoc!( - r" - int : U64 - int = 5 - - int - " - ), - "U64", - ); - } - - #[test] - fn qualified_annotation_using_i32() { - specializes_to( - indoc!( - r" - int : Num.I32 - - int - " - ), - "I32", - ); - } - #[test] - fn qualified_annotated_using_i32() { - specializes_to( - indoc!( - r" - int : Num.I32 - int = 5 - - int - " - ), - "I32", - ); - } - #[test] - fn annotation_using_i32() { - specializes_to( - indoc!( - r" - int : I32 - - int - " - ), - "I32", - ); - } - #[test] - fn annotated_using_i32() { - specializes_to( - indoc!( - r" - int : I32 - int = 5 - - int - " - ), - "I32", - ); - } - - #[test] - fn qualified_annotation_using_u32() { - specializes_to( - indoc!( - r" - int : Num.U32 - - int - " - ), - "U32", - ); - } - #[test] - fn qualified_annotated_using_u32() { - specializes_to( - indoc!( - r" - int : Num.U32 - int = 5 - - int - " - ), - "U32", - ); - } - #[test] - fn annotation_using_u32() { - specializes_to( - indoc!( - r" - int : U32 - - int - " - ), - "U32", - ); - } - #[test] - fn annotated_using_u32() { - specializes_to( - indoc!( - r" - int : U32 - int = 5 - - int - " - ), - "U32", - ); - } - - #[test] - fn qualified_annotation_using_i16() { - specializes_to( - indoc!( - r" - int : Num.I16 - - int - " - ), - "I16", - ); - } - #[test] - fn qualified_annotated_using_i16() { - specializes_to( - indoc!( - r" - int : Num.I16 - int = 5 - - int - " - ), - "I16", - ); - } - #[test] - fn annotation_using_i16() { - specializes_to( - indoc!( - r" - int : I16 - - int - " - ), - "I16", - ); - } - #[test] - fn annotated_using_i16() { - specializes_to( - indoc!( - r" - int : I16 - int = 5 - - int - " - ), - "I16", - ); - } - - #[test] - fn qualified_annotation_using_u16() { - specializes_to( - indoc!( - r" - int : Num.U16 - - int - " - ), - "U16", - ); - } - #[test] - fn qualified_annotated_using_u16() { - specializes_to( - indoc!( - r" - int : Num.U16 - int = 5 - - int - " - ), - "U16", - ); - } - #[test] - fn annotation_using_u16() { - specializes_to( - indoc!( - r" - int : U16 - - int - " - ), - "U16", - ); - } - #[test] - fn annotated_using_u16() { - specializes_to( - indoc!( - r" - int : U16 - int = 5 - - int - " - ), - "U16", - ); - } - - #[test] - fn qualified_annotation_using_i8() { - specializes_to( - indoc!( - r" - int : Num.I8 - - int - " - ), - "I8", - ); - } - #[test] - fn qualified_annotated_using_i8() { - specializes_to( - indoc!( - r" - int : Num.I8 - int = 5 - - int - " - ), - "I8", - ); - } - #[test] - fn annotation_using_i8() { - specializes_to( - indoc!( - r" - int : I8 - - int - " - ), - "I8", - ); - } - #[test] - fn annotated_using_i8() { - specializes_to( - indoc!( - r" - int : I8 - int = 5 - - int - " - ), - "I8", - ); - } - - #[test] - fn qualified_annotation_using_u8() { - specializes_to( - indoc!( - r" - int : Num.U8 - - int - " - ), - "U8", - ); - } - #[test] - fn qualified_annotated_using_u8() { - specializes_to( - indoc!( - r" - int : Num.U8 - int = 5 - - int - " - ), - "U8", - ); - } - #[test] - fn annotation_using_u8() { - specializes_to( - indoc!( - r" - int : U8 - - int - " - ), - "U8", - ); - } - #[test] - fn annotated_using_u8() { - specializes_to( - indoc!( - r" - int : U8 - int = 5 - - int - " - ), - "U8", - ); - } - - #[test] - fn qualified_annotation_num_floatingpoint() { - specializes_to( - indoc!( - r" - float : Num.Num (Num.FloatingPoint Num.Binary64) - - float - " - ), - "F64", - ); - } - #[test] - fn qualified_annotated_num_floatingpoint() { - specializes_to( - indoc!( - r" - float : Num.Num (Num.FloatingPoint Num.Binary64) - float = 5.5 - - float - " - ), - "F64", - ); - } - #[test] - fn annotation_num_floatingpoint() { - specializes_to( - indoc!( - r" - float : Num (FloatingPoint Binary64) - - float - " - ), - "F64", - ); - } - #[test] - fn annotated_num_floatingpoint() { - specializes_to( - indoc!( - r" - float : Num (FloatingPoint Binary64) - float = 5.5 - - float - " - ), - "F64", - ); - } - - #[test] - fn qualified_annotation_f64() { - specializes_to( - indoc!( - r" - float : Num.F64 - - float - " - ), - "F64", - ); - } - #[test] - fn qualified_annotated_f64() { - specializes_to( - indoc!( - r" - float : Num.F64 - float = 5.5 - - float - " - ), - "F64", - ); - } - #[test] - fn annotation_f64() { - specializes_to( - indoc!( - r" - float : F64 - - float - " - ), - "F64", - ); - } - #[test] - fn annotated_f64() { - specializes_to( - indoc!( - r" - float : F64 - float = 5.5 - - float - " - ), - "F64", - ); - } - - #[test] - fn qualified_annotation_f32() { - specializes_to( - indoc!( - r" - float : Num.F32 - - float - " - ), - "F32", - ); - } - #[test] - fn qualified_annotated_f32() { - specializes_to( - indoc!( - r" - float : Num.F32 - float = 5.5 - - float - " - ), - "F32", - ); - } - #[test] - fn annotation_f32() { - specializes_to( - indoc!( - r" - float : F32 - - float - " - ), - "F32", - ); - } - #[test] - fn annotated_f32() { - specializes_to( - indoc!( - r" - float : F32 - float = 5.5 - - float - " - ), - "F32", - ); - } - - #[test] - fn fake_result_ok() { - specializes_to( - indoc!( - r" - Res a e : [Okay a, Error e] - - ok : Res I64 * - ok = Okay 5 - - ok - " - ), - "Res I64 []", - ); - } - - #[test] - fn fake_result_err() { - specializes_to( - indoc!( - r#" - Res a e : [Okay a, Error e] - - err : Res * Str - err = Error "blah" - - err - "# - ), - "Res [] Str", - ); - } - - #[test] - fn basic_result_ok() { - specializes_to( - indoc!( - r" - ok : Result I64 * - ok = Ok 5 - - ok - " - ), - "Result I64 []", - ); - } - - #[test] - fn basic_result_err() { - specializes_to( - indoc!( - r#" - err : Result * Str - err = Err "blah" - - err - "# - ), - "Result [] Str", - ); - } - - #[test] - fn basic_result_conditional() { - specializes_to( - indoc!( - r#" - ok : Result I64 _ - ok = Ok 5 - - err : Result _ Str - err = Err "blah" - - if 1 > 0 then - ok - else - err - "# - ), - "Result I64 Str", - ); - } - - // #[test] - // fn annotation_using_num_used() { - // // There was a problem where `I64`, because it is only an annotation - // // wasn't added to the vars_by_symbol. - // infer_eq_without_problem( - // indoc!( - // r" - // int : I64 - - // p = (\x -> x) int - - // p - // " - // ), - // "I64", - // ); - // } - - #[test] - fn num_identity() { - infer_eq_without_problem( - indoc!( - r" - numIdentity : Num.Num a -> Num.Num a - numIdentity = \x -> x - - y = numIdentity 3.14 - - { numIdentity, x : numIdentity 42, y } - " - ), - "{ numIdentity : Num [] -> Num [], x : Num [], y : Frac [] }", - ); - } - - #[test] - fn when_with_annotation() { - infer_eq_without_problem( - indoc!( - r" - x : Num.Num (Num.Integer Num.Signed64) - x = - when 2 is - 3 -> 4 - _ -> 5 - - x - " - ), - "I64", - ); - } - - // TODO add more realistic function when able - #[test] - fn integer_sum() { - infer_eq_without_problem( - indoc!( - r" - f = \n -> - when n is - 0 -> 0 - _ -> f n - - f - " - ), - "Num [] -> Num []", - ); - } - - #[test] - fn identity_map() { - 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 - " - ), - "([] -> []), [Identity []] -> [Identity []]", - ); - } - - #[test] - fn to_bit() { - infer_eq_without_problem( - indoc!( - r" - toBit = \bool -> - when bool is - True -> 1 - False -> 0 - - toBit - " - ), - "[False, True] -> Num []", - ); - } - - // this test is related to a bug where ext_var would have an incorrect rank. - // This match has duplicate cases, but we ignore that. - #[test] - fn to_bit_record() { - specializes_to( - indoc!( - r#" - foo = \rec -> - when rec is - { x: _ } -> "1" - { y: _ } -> "2" - - foo - "# - ), - "{ x : [], y : [] } -> Str", - ); - } - - #[test] - fn from_bit() { - infer_eq_without_problem( - indoc!( - r" - fromBit = \int -> - when int is - 0 -> False - _ -> True - - fromBit - " - ), - "Num [] -> [False, True]", - ); - } - - #[test] - fn result_map_explicit() { - infer_eq_without_problem( - indoc!( - r" - map : (a -> b), [Err e, Ok a] -> [Err e, Ok b] - map = \f, result -> - when result is - Ok v -> Ok (f v) - Err e -> Err e - - map - " - ), - "([] -> []), [Err [], Ok []] -> [Err [], Ok []]", - ); - } - - #[test] - fn result_map_alias() { - infer_eq_without_problem( - indoc!( - r" - Res e a : [Ok a, Err e] - - map : (a -> b), Res e a -> Res e b - map = \f, result -> - when result is - Ok v -> Ok (f v) - Err e -> Err e - - map - " - ), - "([] -> []), Res [] [] -> Res [] []", - ); - } - - #[test] - fn record_from_load() { - infer_eq_without_problem( - indoc!( - r" - foo = \{ x } -> x - - foo { x: 5 } - " - ), - "Num []", - ); - } - - #[test] - fn defs_from_load() { - infer_eq_without_problem( - indoc!( - r" - alwaysThreePointZero = \_ -> 3.0 - - answer = 42 - - identity = \a -> a - - threePointZero = identity (alwaysThreePointZero {}) - - threePointZero - " - ), - "Frac []", - ); - } - - #[test] - fn use_as_in_signature() { - infer_eq_without_problem( - indoc!( - r#" - foo : Str.Str as Foo -> Foo - foo = \_ -> "foo" - - foo - "# - ), - "Foo -> Foo", - ); - } - - #[test] - fn use_alias_in_let() { - infer_eq_without_problem( - indoc!( - r#" - Foo : Str.Str - - foo : Foo -> Foo - foo = \_ -> "foo" - - foo - "# - ), - "Foo -> Foo", - ); - } - - #[test] - fn use_alias_with_argument_in_let() { - infer_eq_without_problem( - indoc!( - r" - Foo a : { foo : a } - - v : Foo (Num.Num (Num.Integer Num.Signed64)) - v = { foo: 42 } - - v - " - ), - "Foo I64", - ); - } - - #[test] - fn identity_alias() { - infer_eq_without_problem( - indoc!( - r" - Foo a : { foo : a } - - id : Foo a -> Foo a - id = \x -> x - - id - " - ), - "Foo [] -> Foo []", - ); - } - - #[test] - fn linked_list_empty() { - infer_eq_without_problem( - indoc!( - r" - empty : [Cons a (ConsList a), Nil] as ConsList a - empty = Nil - - empty - " - ), - "ConsList []", - ); - } - - #[test] - fn linked_list_singleton() { - infer_eq_without_problem( - indoc!( - r" - singleton : a -> [Cons a (ConsList a), Nil] as ConsList a - singleton = \x -> Cons x Nil - - singleton - " - ), - "[] -> ConsList []", - ); - } - - #[test] - fn peano_length() { - infer_eq_without_problem( - indoc!( - r" - Peano : [S Peano, Z] - - length : Peano -> Num.Num (Num.Integer Num.Signed64) - length = \peano -> - when peano is - Z -> 0 - S v -> length v - - length - " - ), - "Peano -> I64", - ); - } - - #[test] - fn peano_map() { - infer_eq_without_problem( - indoc!( - r" - map : [S Peano, Z] as Peano -> Peano - map = \peano -> - when peano is - Z -> Z - S v -> S (map v) - - map - " - ), - "Peano -> Peano", - ); - } - - // #[test] - // fn infer_linked_list_map() { - // infer_eq_without_problem( - // indoc!( - // r" - // map = \f, list -> - // when list is - // Nil -> Nil - // Cons x xs -> - // a = f x - // b = map f xs - - // Cons a b - - // map - // " - // ), - // "([] -> []), [Cons [] c, Nil] as c -> [Cons [] d, Nil] as d", - // ); - // } - - // #[test] - // fn typecheck_linked_list_map() { - // infer_eq_without_problem( - // indoc!( - // r" - // ConsList a := [Cons a (ConsList a), Nil] - - // map : (a -> b), ConsList a -> ConsList b - // map = \f, list -> - // when list is - // Nil -> Nil - // Cons x xs -> - // Cons (f x) (map f xs) - - // map - // " - // ), - // "([] -> []), ConsList [] -> ConsList []", - // ); - // } - - #[test] - fn mismatch_in_alias_args_gets_reported() { - specializes_to( - indoc!( - r#" - Foo a : a - - r : Foo {} - r = {} - - s : Foo Str.Str - s = "bar" - - when {} is - _ -> s - _ -> r - "# - ), - "", - ); - } - - #[test] - fn mismatch_in_apply_gets_reported() { - specializes_to( - indoc!( - r" - r : { x : (Num.Num (Num.Integer Signed64)) } - r = { x : 1 } - - s : { left : { x : Num.Num (Num.FloatingPoint Num.Binary64) } } - s = { left: { x : 3.14 } } - - when 0 is - 1 -> s.left - 0 -> r - " - ), - "", - ); - } - - #[test] - fn mismatch_in_tag_gets_reported() { - specializes_to( - indoc!( - r" - r : [Ok Str.Str] - r = Ok 1 - - s : { left: [Ok {}] } - s = { left: Ok 3.14 } - - when 0 is - 1 -> s.left - 0 -> r - " - ), - "", - ); - } - - // TODO As intended, this fails, but it fails with the wrong error! - // - // #[test] - // fn nums() { - // infer_eq_without_problem( - // indoc!( - // r" - // s : Num * - // s = 3.1 - - // s - // " - // ), - // "", - // ); - // } - - #[test] - fn peano_map_alias() { - specializes_to( - indoc!( - r#" - app "test" provides [main] to "./platform" - - Peano : [S Peano, Z] - - map : Peano -> Peano - map = \peano -> - when peano is - Z -> Z - S rest -> S (map rest) - - main = - map - "# - ), - "Peano -> Peano", - ); - } - - #[test] - fn unit_alias() { - specializes_to( - indoc!( - r" - Unit : [Unit] - - unit : Unit - unit = Unit - - unit - " - ), - "Unit", - ); - } - - #[test] - fn rigid_in_letnonrec() { - infer_eq_without_problem( - indoc!( - r" - ConsList a : [Cons a (ConsList a), Nil] - - toEmpty : ConsList a -> ConsList a - toEmpty = \_ -> - result : ConsList a - result = Nil - - result - - toEmpty - " - ), - "ConsList [] -> ConsList []", - ); - } - - #[test] - fn rigid_in_letrec_ignored() { - infer_eq_without_problem( - indoc!( - r" - ConsList a : [Cons a (ConsList a), Nil] - - toEmpty : ConsList a -> ConsList a - toEmpty = \_ -> - result : ConsList _ # TODO to enable using `a` we need scoped variables - result = Nil - - toEmpty result - - toEmpty - " - ), - "ConsList [] -> ConsList []", - ); - } - - #[test] - fn rigid_in_letrec() { - infer_eq_without_problem( - indoc!( - r#" - app "test" provides [main] to "./platform" - - ConsList a : [Cons a (ConsList a), Nil] - - toEmpty : ConsList a -> ConsList a - toEmpty = \_ -> - result : ConsList _ # TODO to enable using `a` we need scoped variables - result = Nil - - toEmpty result - - main = - toEmpty - "# - ), - "ConsList [] -> ConsList []", - ); - } - - #[test] - fn let_record_pattern_with_annotation() { - infer_eq_without_problem( - indoc!( - r#" - { x, y } : { x : Str.Str, y : Num.Num (Num.FloatingPoint Num.Binary64) } - { x, y } = { x : "foo", y : 3.14 } - - x - "# - ), - "Str", - ); - } - - #[test] - fn let_record_pattern_with_annotation_alias() { - specializes_to( - indoc!( - r#" - Foo : { x : Str.Str, y : Num.Num (Num.FloatingPoint Num.Binary64) } - - { x, y } : Foo - { x, y } = { x : "foo", y : 3.14 } - - x - "# - ), - "Str", - ); - } - - // #[test] - // fn peano_map_infer() { - // specializes_to( - // indoc!( - // r#" - // app "test" provides [main] to "./platform" - - // map = - // \peano -> - // when peano is - // Z -> Z - // S rest -> map rest |> S - - // main = - // map - // "# - // ), - // "[S a, Z] as a -> [S b, Z] as b", - // ); - // } - - // #[test] - // fn peano_map_infer_nested() { - // specializes_to( - // indoc!( - // r" - // map = \peano -> - // when peano is - // Z -> Z - // S rest -> - // map rest |> S - - // map - // " - // ), - // "[S a, Z] as a -> [S b, Z] as b", - // ); - // } - - #[test] - fn let_record_pattern_with_alias_annotation() { - infer_eq_without_problem( - indoc!( - r#" - Foo : { x : Str.Str, y : Num.Num (Num.FloatingPoint Num.Binary64) } - - { x, y } : Foo - { x, y } = { x : "foo", y : 3.14 } - - x - "# - ), - "Str", - ); - } - - #[test] - fn let_tag_pattern_with_annotation() { - infer_eq_without_problem( - indoc!( - r" - UserId x : [UserId I64] - UserId x = UserId 42 - - x - " - ), - "I64", - ); - } - - #[test] - fn typecheck_record_linked_list_map() { - infer_eq_without_problem( - indoc!( - r" - ConsList q : [Cons { x: q, xs: ConsList q }, Nil] - - map : (a -> b), ConsList a -> ConsList b - map = \f, list -> - when list is - Nil -> Nil - Cons { x, xs } -> - Cons { x: f x, xs : map f xs } - - map - " - ), - "([] -> []), ConsList [] -> ConsList []", - ); - } - - // #[test] - // fn infer_record_linked_list_map() { - // infer_eq_without_problem( - // indoc!( - // r" - // map = \f, list -> - // when list is - // Nil -> Nil - // Cons { x, xs } -> - // Cons { x: f x, xs : map f xs } - - // map - // " - // ), - // "(a -> b), [Cons { x : a, xs : c }, Nil] as c -> [Cons { x : b, xs : d }, Nil] as d", - // ); - // } - - // #[test] - // fn typecheck_mutually_recursive_tag_union_2() { - // infer_eq_without_problem( - // indoc!( - // r" - // ListA a b := [Cons a (ListB b a), Nil] - // ListB a b := [Cons a (ListA b a), Nil] - - // ConsList q : [Cons q (ConsList q), Nil] - - // toAs : (b -> a), ListA a b -> ConsList a - // toAs = \f, @ListA lista -> - // when lista is - // Nil -> Nil - // Cons a (@ListB listb) -> - // when listb is - // Nil -> Nil - // Cons b (@ListA newLista) -> - // Cons a (Cons (f b) (toAs f newLista)) - - // toAs - // " - // ), - // "([] -> []), ListA [] [] -> ConsList []", - // ); - // } - - #[test] - fn typecheck_mutually_recursive_tag_union_listabc() { - infer_eq_without_problem( - indoc!( - r" - ListA a : [Cons a (ListB a)] - ListB a : [Cons a (ListC a)] - ListC a : [Cons a (ListA a), Nil] - - val : ListC Num.I64 - val = Cons 1 (Cons 2 (Cons 3 Nil)) - - val - " - ), - "ListC I64", - ); - } - - // #[test] - // fn infer_mutually_recursive_tag_union() { - // infer_eq_without_problem( - // indoc!( - // r" - // toAs = \f, lista -> - // when lista is - // Nil -> Nil - // Cons a listb -> - // when listb is - // Nil -> Nil - // Cons b newLista -> - // Cons a (Cons (f b) (toAs f newLista)) - - // toAs - // " - // ), - // "(a -> b), [Cons c [Cons a d, Nil], Nil] as d -> [Cons c [Cons b e], Nil] as e", - // ); - // } - - #[test] - fn solve_list_get() { - infer_eq_without_problem( - indoc!( - r#" - List.get ["a"] 0 - "# - ), - "Result Str [OutOfBounds]", - ); - } - - #[test] - fn type_more_general_than_signature() { - infer_eq_without_problem( - indoc!( - r" - partition : U64, U64, List (Int a) -> [Pair U64 (List (Int a))] - partition = \low, high, initialList -> - when List.get initialList high is - Ok _ -> - Pair 0 [] - - Err _ -> - Pair (low - 1) initialList - - partition - " - ), - "U64, U64, List (Int []) -> [Pair U64 (List (Int []))]", - ); - } - - #[test] - fn quicksort_partition() { - infer_eq_without_problem( - indoc!( - r" - swap : U64, U64, List a -> List a - swap = \i, j, list -> - when Pair (List.get list i) (List.get list j) is - Pair (Ok atI) (Ok atJ) -> - list - |> List.set i atJ - |> List.set j atI - - _ -> - list - - partition : U64, U64, List (Int a) -> [Pair U64 (List (Int a))] - partition = \low, high, initialList -> - when List.get initialList high is - Ok pivot -> - go = \i, j, list -> - if j < high then - when List.get list j is - Ok value -> - if value <= pivot then - go (i + 1) (j + 1) (swap (i + 1) j list) - else - go i (j + 1) list - - Err _ -> - Pair i list - else - Pair i list - - when go (low - 1) low initialList is - Pair newI newList -> - Pair (newI + 1) (swap (newI + 1) high newList) - - Err _ -> - Pair (low - 1) initialList - - partition - " - ), - "U64, U64, List (Int []) -> [Pair U64 (List (Int []))]", - ); - } - - #[test] - fn identity_list() { - infer_eq_without_problem( - indoc!( - r" - idList : List a -> List a - idList = \list -> list - - foo : List I64 -> List I64 - foo = \initialList -> idList initialList - - - foo - " - ), - "List I64 -> List I64", - ); - } - - #[test] - fn list_get() { - infer_eq_without_problem( - indoc!( - r" - List.get [10, 9, 8, 7] 1 - " - ), - "Result (Num []) [OutOfBounds]", - ); - - infer_eq_without_problem( - indoc!( - r" - List.get - " - ), - "List [], U64 -> Result [] [OutOfBounds]", - ); - } - - #[test] - fn use_rigid_twice() { - infer_eq_without_problem( - indoc!( - r" - id1 : q -> q - id1 = \x -> x - - id2 : q -> q - id2 = \x -> x - - { id1, id2 } - " - ), - "{ id1 : [] -> [], id2 : [] -> [] }", - ); - } - - #[test] - fn map_insert() { - infer_eq_without_problem("Dict.insert", "Dict [] [], [], [] -> Dict [] []"); - } - - #[test] - fn num_to_frac() { - infer_eq_without_problem("Num.toFrac", "Num [] -> Frac []"); - } - - #[test] - fn pow() { - infer_eq_without_problem("Num.pow", "Frac [], Frac [] -> Frac []"); - } - - #[test] - fn ceiling() { - infer_eq_without_problem("Num.ceiling", "Frac [] -> Int []"); - } - - #[test] - fn floor() { - infer_eq_without_problem("Num.floor", "Frac [] -> Int []"); - } - - #[test] - fn div() { - infer_eq_without_problem("Num.div", "Frac [], Frac [] -> Frac []") - } - - #[test] - fn div_checked() { - infer_eq_without_problem( - "Num.divChecked", - "Frac [], Frac [] -> Result (Frac []) [DivByZero]", - ) - } - - #[test] - fn div_ceil() { - infer_eq_without_problem("Num.divCeil", "Int [], Int [] -> Int []"); - } - - #[test] - fn div_ceil_checked() { - infer_eq_without_problem( - "Num.divCeilChecked", - "Int [], Int [] -> Result (Int []) [DivByZero]", - ); - } - - #[test] - fn div_trunc() { - infer_eq_without_problem("Num.divTrunc", "Int [], Int [] -> Int []"); - } - - #[test] - fn div_trunc_checked() { - infer_eq_without_problem( - "Num.divTruncChecked", - "Int [], Int [] -> Result (Int []) [DivByZero]", - ); - } - - #[test] - fn atan() { - infer_eq_without_problem("Num.atan", "Frac [] -> Frac []"); - } - - #[test] - fn min_i128() { - infer_eq_without_problem("Num.minI128", "I128"); - } - - #[test] - fn max_i128() { - infer_eq_without_problem("Num.maxI128", "I128"); - } - - #[test] - fn min_i64() { - infer_eq_without_problem("Num.minI64", "I64"); - } - - #[test] - fn max_i64() { - infer_eq_without_problem("Num.maxI64", "I64"); - } - - #[test] - fn min_u64() { - infer_eq_without_problem("Num.minU64", "U64"); - } - - #[test] - fn max_u64() { - infer_eq_without_problem("Num.maxU64", "U64"); - } - - #[test] - fn min_i32() { - infer_eq_without_problem("Num.minI32", "I32"); - } - - #[test] - fn max_i32() { - infer_eq_without_problem("Num.maxI32", "I32"); - } - - #[test] - fn min_u32() { - infer_eq_without_problem("Num.minU32", "U32"); - } - - #[test] - fn max_u32() { - infer_eq_without_problem("Num.maxU32", "U32"); - } - - // #[test] - // fn reconstruct_path() { - // infer_eq_without_problem( - // indoc!( - // r" - // reconstructPath : Dict position position, position -> List position where position implements Hash & Eq - // reconstructPath = \cameFrom, goal -> - // when Dict.get cameFrom goal is - // Err KeyNotFound -> - // [] - - // Ok next -> - // List.append (reconstructPath cameFrom next) goal - - // reconstructPath - // " - // ), - // "Dict position position, position -> List position where position implements Hash & Eq", - // ); - // } - - #[test] - fn use_correct_ext_record() { - // Related to a bug solved in 81fbab0b3fe4765bc6948727e603fc2d49590b1c - infer_eq_without_problem( - indoc!( - r" - f = \r -> - g = r.q - h = r.p - - 42 - - f - " - ), - "{ p : [], q : [] } -> Num []", - ); - } - - // #[test] - // fn use_correct_ext_tag_union() { - // // related to a bug solved in 08c82bf151a85e62bce02beeed1e14444381069f - // infer_eq_without_problem( - // indoc!( - // r#" - // app "test" imports [] provides [main] to "./platform" - - // boom = \_ -> boom {} - - // Model position : { openSet : Set position } - - // cheapestOpen : Model position -> Result position [KeyNotFound] where position implements Hash & Eq - // cheapestOpen = \model -> - - // folder = \resSmallestSoFar, position -> - // when resSmallestSoFar is - // Err _ -> resSmallestSoFar - // Ok smallestSoFar -> - // if position == smallestSoFar.position then resSmallestSoFar - - // else - // Ok { position, cost: 0.0 } - - // Set.walk model.openSet (Ok { position: boom {}, cost: 0.0 }) folder - // |> Result.map (\x -> x.position) - - // astar : Model position -> Result position [KeyNotFound] where position implements Hash & Eq - // astar = \model -> cheapestOpen model - - // main = - // astar - // "# - // ), - // "Model position -> Result position [KeyNotFound] where position implements Hash & Eq", - // ); - // } - - #[test] - fn when_with_or_pattern_and_guard() { - infer_eq_without_problem( - indoc!( - r" - \x -> - when x is - 2 | 3 -> 0 - a if a < 20 -> 1 - 3 | 4 if Bool.false -> 2 - _ -> 3 - " - ), - "Num [] -> Num []", - ); - } - - #[test] - fn sorting() { - // based on https://github.com/elm/compiler/issues/2057 - // Roc seems to do this correctly, tracking to make sure it stays that way - infer_eq_without_problem( - indoc!( - r" - sort : ConsList cm -> ConsList cm - sort = - \xs -> - f : cm, cm -> Order - f = \_, _ -> LT - - sortWith f xs - - sortBy : (x -> cmpl), ConsList x -> ConsList x - sortBy = - \_, list -> - cmp : x, x -> Order - cmp = \_, _ -> LT - - sortWith cmp list - - always = \x, _ -> x - - sortWith : (foobar, foobar -> Order), ConsList foobar -> ConsList foobar - sortWith = - \_, list -> - f = \arg -> - g arg - - g = \bs -> - when bs is - bx -> f bx - - always Nil (f list) - - Order : [LT, GT, EQ] - ConsList a : [Nil, Cons a (ConsList a)] - - { x: sortWith, y: sort, z: sortBy } - " - ), - "{ x : ([], [] -> Order), ConsList [] -> ConsList [], y : ConsList [] -> ConsList [], z : ([] -> []), ConsList [] -> ConsList [] }" - ); - } - - // Like in elm, this test now fails. Polymorphic recursion (even with an explicit signature) - // yields a type error. - // - // We should at some point investigate why that is. Elm did support polymorphic recursion in - // earlier versions. - // - // #[test] - // fn wrapper() { - // // based on https://github.com/elm/compiler/issues/1964 - // // Roc seems to do this correctly, tracking to make sure it stays that way - // infer_eq_without_problem( - // indoc!( - // r" - // Type a : [TypeCtor (Type (Wrapper a))] - // - // Wrapper a : [Wrapper a] - // - // Opaque : [Opaque] - // - // encodeType1 : Type a -> Opaque - // encodeType1 = \thing -> - // when thing is - // TypeCtor v0 -> - // encodeType1 v0 - // - // encodeType1 - // " - // ), - // "Type a -> Opaque", - // ); - // } - - #[test] - fn rigids() { - infer_eq_without_problem( - indoc!( - r" - f : List a -> List a - f = \input -> - # let-polymorphism at work - x : {} -> List b - x = \{} -> [] - - when List.get input 0 is - Ok val -> List.append (x {}) val - Err _ -> input - f - " - ), - "List [] -> List []", - ); - } - - #[cfg(debug_assertions)] - #[test] - #[should_panic] - fn rigid_record_quantification() { - // the ext here is qualified on the outside (because we have rank 1 types, not rank 2). - // That means e.g. `f : { bar : String, foo : I64 } -> Bool }` is a valid argument, but - // that function could not be applied to the `{ foo : I64 }` list. Therefore, this function - // is not allowed. - // - // should hit a debug_assert! in debug mode, and produce a type error in release mode - infer_eq_without_problem( - indoc!( - r" - test : ({ foo : I64 }ext -> Bool), { foo : I64 } -> Bool - test = \fn, a -> fn a - - test - " - ), - "should fail", - ); - } - - // OPTIONAL RECORD FIELDS - - #[test] - fn optional_field_unifies_with_missing() { - infer_eq_without_problem( - indoc!( - r" - negatePoint : { x : I64, y : I64, z ? Num c } -> { x : I64, y : I64, z : Num c } - - negatePoint { x: 1, y: 2 } - " - ), - "{ x : I64, y : I64, z : Num [] }", - ); - } - - // #[test] - // fn open_optional_field_unifies_with_missing() { - // infer_eq_without_problem( - // indoc!( - // r#" - // negatePoint : { x : I64, y : I64, z ? Num c }r -> { x : I64, y : I64, z : Num c }r - - // a = negatePoint { x: 1, y: 2 } - // b = negatePoint { x: 1, y: 2, blah : "hi" } - - // { a, b } - // "# - // ), - // "{ a : { x : I64, y : I64, z : Num [] }, b : { blah : Str, x : I64, y : I64, z : Num [] } }", - // ); - // } - - #[test] - fn optional_field_unifies_with_present() { - infer_eq_without_problem( - indoc!( - r" - negatePoint : { x : Num a, y : Num b, z ? c } -> { x : Num a, y : Num b, z : c } - - negatePoint { x: 1, y: 2.1, z: 0x3 } - " - ), - "{ x : Num [], y : Frac [], z : Int [] }", - ); - } - - // #[test] - // fn open_optional_field_unifies_with_present() { - // infer_eq_without_problem( - // indoc!( - // r#" - // negatePoint : { x : Num a, y : Num b, z ? c }r -> { x : Num a, y : Num b, z : c }r - - // a = negatePoint { x: 1, y: 2.1 } - // b = negatePoint { x: 1, y: 2.1, blah : "hi" } - - // { a, b } - // "# - // ), - // "{ a : { x : Num [], y : Frac [], z : c }, b : { blah : Str, x : Num [], y : Frac [], z : c1 } }", - // ); - // } - - #[test] - fn optional_field_function() { - infer_eq_without_problem( - indoc!( - r" - \{ x, y ? 0 } -> x + y - " - ), - "{ x : Num [], y ? Num [] } -> Num []", - ); - } - - #[test] - fn optional_field_let() { - infer_eq_without_problem( - indoc!( - r" - { x, y ? 0 } = { x: 32 } - - x + y - " - ), - "Num []", - ); - } - - // #[test] - // fn optional_field_when() { - // infer_eq_without_problem( - // indoc!( - // r" - // \r -> - // when r is - // { x, y ? 0 } -> x + y - // " - // ), - // "{ x : Num a, y ? Num a } -> Num a", - // ); - // } - - // #[test] - // fn optional_field_let_with_signature() { - // infer_eq_without_problem( - // indoc!( - // r" - // \rec -> - // { x, y } : { x : I64, y ? Bool }* - // { x, y ? Bool.false } = rec - - // { x, y } - // " - // ), - // "{ x : I64, y ? Bool }-> { x : I64, y : Bool }", - // ); - // } - - #[test] - fn list_walk_backwards() { - infer_eq_without_problem( - indoc!( - r" - List.walkBackwards - " - ), - "List [], [], ([], [] -> []) -> []", - ); - } - - #[test] - fn list_walk_backwards_example() { - infer_eq_without_problem( - indoc!( - r" - empty : List I64 - empty = - [] - - List.walkBackwards empty 0 (\a, b -> a + b) - " - ), - "I64", - ); - } - - #[test] - fn list_walk_with_index_until() { - infer_eq_without_problem( - indoc!(r"List.walkWithIndexUntil"), - "List [], [], ([], [], U64 -> [Break [], Continue []]) -> []", - ); - } - - #[test] - fn list_drop_at() { - infer_eq_without_problem( - indoc!( - r" - List.dropAt - " - ), - "List [], U64 -> List []", - ); - } - - #[test] - fn str_trim() { - infer_eq_without_problem( - indoc!( - r" - Str.trim - " - ), - "Str -> Str", - ); - } - - #[test] - fn str_trim_start() { - infer_eq_without_problem( - indoc!( - r" - Str.trimStart - " - ), - "Str -> Str", - ); - } - - #[test] - fn list_take_first() { - infer_eq_without_problem( - indoc!( - r" - List.takeFirst - " - ), - "List [], U64 -> List []", - ); - } - - #[test] - fn list_take_last() { - infer_eq_without_problem( - indoc!( - r" - List.takeLast - " - ), - "List [], U64 -> List []", - ); - } - - #[test] - fn list_sublist() { - infer_eq_without_problem( - indoc!( - r" - List.sublist - " - ), - "List [], { len : U64, start : U64 } -> List []", - ); - } - - #[test] - fn list_split() { - infer_eq_without_problem( - indoc!("List.split"), - "List [], U64 -> { before : List [], others : List [] }", - ); - } - - #[test] - fn list_drop_last() { - infer_eq_without_problem( - indoc!( - r" - List.dropLast - " - ), - "List [], U64 -> List []", - ); - } - - #[test] - fn list_intersperse() { - infer_eq_without_problem( - indoc!( - r" - List.intersperse - " - ), - "List [], [] -> List []", - ); - } - #[test] - fn function_that_captures_nothing_is_not_captured() { - // we should make sure that a function that doesn't capture anything it not itself captured - // such functions will be lifted to the top-level, and are thus globally available! - infer_eq_without_problem( - indoc!( - r" - f = \x -> x + 1 - - g = \y -> f y - - g - " - ), - "Num [] -> Num []", - ); - } - - #[test] - fn double_named_rigids() { - infer_eq_without_problem( - indoc!( - r#" - app "test" provides [main] to "./platform" - - - main : List x - main = - empty : List x - empty = [] - - empty - "# - ), - "List []", - ); - } - - #[test] - fn double_tag_application() { - infer_eq_without_problem( - indoc!( - r#" - app "test" provides [main] to "./platform" - - - main = - if 1 == 1 then - Foo (Bar) 1 - else - Foo Bar 1 - "# - ), - "[Foo [Bar] (Num [])]", - ); - - infer_eq_without_problem("Foo Bar 1", "[Foo [Bar] (Num [])]"); - } - - #[test] - fn double_tag_application_pattern() { - infer_eq_without_problem( - indoc!( - r#" - app "test" provides [main] to "./platform" - - Bar : [Bar] - Foo : [Foo Bar I64, Empty] - - foo : Foo - foo = Foo Bar 1 - - main = - when foo is - Foo Bar 1 -> - Foo Bar 2 - - x -> - x - "# - ), - "[Empty, Foo [Bar] I64]", - ); - } - - #[test] - fn recursive_function_with_rigid() { - infer_eq_without_problem( - indoc!( - r#" - app "test" provides [main] to "./platform" - - State a : { count : I64, x : a } - - foo : State a -> I64 - foo = \state -> - if state.count == 0 then - 0 - else - 1 + foo { count: state.count - 1, x: state.x } - - main : I64 - main = - foo { count: 3, x: {} } - "# - ), - "I64", - ); - } - - #[test] - fn rbtree_empty() { - infer_eq_without_problem( - indoc!( - r#" - app "test" provides [main] to "./platform" - - # The color of a node. Leaves are considered Black. - NodeColor : [Red, Black] - - RBTree k v : [Node NodeColor k v (RBTree k v) (RBTree k v), Empty] - - # Create an empty dictionary. - empty : {} -> RBTree k v - empty = \{} -> - Empty - - foo : RBTree I64 I64 - foo = empty {} - - main : RBTree I64 I64 - main = - foo - "# - ), - "RBTree I64 I64", - ); - } - - #[test] - fn rbtree_insert() { - // exposed an issue where pattern variables were not introduced - // at the correct level in the constraint - // - // see 22592eff805511fbe1da63849771ee5f367a6a16 - infer_eq_without_problem( - indoc!( - r#" - app "test" provides [main] to "./platform" - - RBTree k : [Node k (RBTree k), Empty] - - balance : RBTree k -> RBTree k - balance = \left -> - when left is - Node _ Empty -> Empty - - _ -> Empty - - main : RBTree {} - main = - balance Empty - "# - ), - "RBTree {}", - ); - } - - #[test] - fn rbtree_full_remove_min() { - infer_eq_without_problem( - indoc!( - r#" - app "test" provides [main] to "./platform" - - NodeColor : [Red, Black] - - RBTree k v : [Node NodeColor k v (RBTree k v) (RBTree k v), Empty] - - moveRedLeft : RBTree k v -> RBTree k v - moveRedLeft = \dict -> - when dict is - # Node clr k v (Node lClr lK lV lLeft lRight) (Node rClr rK rV ((Node Red rlK rlV rlL rlR) as rLeft) rRight) -> - # Node clr k v (Node lClr lK lV lLeft lRight) (Node rClr rK rV rLeft rRight) -> - Node clr k v (Node _ lK lV lLeft lRight) (Node _ rK rV rLeft rRight) -> - when rLeft is - Node Red rlK rlV rlL rlR -> - Node - Red - rlK - rlV - (Node Black k v (Node Red lK lV lLeft lRight) rlL) - (Node Black rK rV rlR rRight) - - _ -> - when clr is - Black -> - Node - Black - k - v - (Node Red lK lV lLeft lRight) - (Node Red rK rV rLeft rRight) - - Red -> - Node - Black - k - v - (Node Red lK lV lLeft lRight) - (Node Red rK rV rLeft rRight) - - _ -> - dict - - balance : NodeColor, k, v, RBTree k v, RBTree k v -> RBTree k v - balance = \color, key, value, left, right -> - when right is - Node Red rK rV rLeft rRight -> - when left is - Node Red lK lV lLeft lRight -> - Node - Red - key - value - (Node Black lK lV lLeft lRight) - (Node Black rK rV rLeft rRight) - - _ -> - Node color rK rV (Node Red key value left rLeft) rRight - - _ -> - when left is - Node Red lK lV (Node Red llK llV llLeft llRight) lRight -> - Node - Red - lK - lV - (Node Black llK llV llLeft llRight) - (Node Black key value lRight right) - - _ -> - Node color key value left right - - - Key k : Num k - - removeHelpEQGT : Key k, RBTree (Key k) v -> RBTree (Key k) v where k implements Hash & Eq - removeHelpEQGT = \targetKey, dict -> - when dict is - Node color key value left right -> - if targetKey == key then - when getMin right is - Node _ minKey minValue _ _ -> - balance color minKey minValue left (removeMin right) - - Empty -> - Empty - else - balance color key value left (removeHelp targetKey right) - - Empty -> - Empty - - getMin : RBTree k v -> RBTree k v - getMin = \dict -> - when dict is - # Node _ _ _ ((Node _ _ _ _ _) as left) _ -> - Node _ _ _ left _ -> - when left is - Node _ _ _ _ _ -> getMin left - _ -> dict - - _ -> - dict - - - moveRedRight : RBTree k v -> RBTree k v - moveRedRight = \dict -> - when dict is - Node clr k v (Node lClr lK lV (Node Red llK llV llLeft llRight) lRight) (Node rClr rK rV rLeft rRight) -> - Node - Red - lK - lV - (Node Black llK llV llLeft llRight) - (Node Black k v lRight (Node Red rK rV rLeft rRight)) - - Node clr k v (Node lClr lK lV lLeft lRight) (Node rClr rK rV rLeft rRight) -> - when clr is - Black -> - Node - Black - k - v - (Node Red lK lV lLeft lRight) - (Node Red rK rV rLeft rRight) - - Red -> - Node - Black - k - v - (Node Red lK lV lLeft lRight) - (Node Red rK rV rLeft rRight) - - _ -> - dict - - - removeHelpPrepEQGT : Key k, RBTree (Key k) v, NodeColor, (Key k), v, RBTree (Key k) v, RBTree (Key k) v -> RBTree (Key k) v - removeHelpPrepEQGT = \_, dict, color, key, value, left, right -> - when left is - Node Red lK lV lLeft lRight -> - Node - color - lK - lV - lLeft - (Node Red key value lRight right) - - _ -> - when right is - Node Black _ _ (Node Black _ _ _ _) _ -> - moveRedRight dict - - Node Black _ _ Empty _ -> - moveRedRight dict - - _ -> - dict - - - removeMin : RBTree k v -> RBTree k v - removeMin = \dict -> - when dict is - Node color key value left right -> - when left is - Node lColor _ _ lLeft _ -> - when lColor is - Black -> - when lLeft is - Node Red _ _ _ _ -> - Node color key value (removeMin left) right - - _ -> - when moveRedLeft dict is # here 1 - Node nColor nKey nValue nLeft nRight -> - balance nColor nKey nValue (removeMin nLeft) nRight - - Empty -> - Empty - - _ -> - Node color key value (removeMin left) right - - _ -> - Empty - _ -> - Empty - - removeHelp : Key k, RBTree (Key k) v -> RBTree (Key k) v where k implements Hash & Eq - removeHelp = \targetKey, dict -> - when dict is - Empty -> - Empty - - Node color key value left right -> - if targetKey < key then - when left is - Node Black _ _ lLeft _ -> - when lLeft is - Node Red _ _ _ _ -> - Node color key value (removeHelp targetKey left) right - - _ -> - when moveRedLeft dict is # here 2 - Node nColor nKey nValue nLeft nRight -> - balance nColor nKey nValue (removeHelp targetKey nLeft) nRight - - Empty -> - Empty - - _ -> - Node color key value (removeHelp targetKey left) right - else - removeHelpEQGT targetKey (removeHelpPrepEQGT targetKey dict color key value left right) - - - main : RBTree I64 I64 - main = - removeHelp 1i64 Empty - "# - ), - "RBTree (Key (Integer Signed64)) I64", - ); - } - - #[test] - fn rbtree_remove_min_1() { - infer_eq_without_problem( - indoc!( - r#" - app "test" provides [main] to "./platform" - - RBTree k : [Node k (RBTree k) (RBTree k), Empty] - - removeHelp : Num k, RBTree (Num k) -> RBTree (Num k) - removeHelp = \targetKey, dict -> - when dict is - Empty -> - Empty - - Node key left right -> - if targetKey < key then - when left is - Node _ lLeft _ -> - when lLeft is - Node _ _ _ -> - Empty - - _ -> Empty - - _ -> - Node key (removeHelp targetKey left) right - else - Empty - - - main : RBTree I64 - main = - removeHelp 1 Empty - "# - ), - "RBTree I64", - ); - } - - #[test] - fn rbtree_foobar() { - infer_eq_without_problem( - indoc!( - r#" - app "test" provides [main] to "./platform" - - NodeColor : [Red, Black] - - RBTree k v : [Node NodeColor k v (RBTree k v) (RBTree k v), Empty] - - removeHelp : Num k, RBTree (Num k) v -> RBTree (Num k) v where k implements Hash & Eq - removeHelp = \targetKey, dict -> - when dict is - Empty -> - Empty - - Node color key value left right -> - if targetKey < key then - when left is - Node Black _ _ lLeft _ -> - when lLeft is - Node Red _ _ _ _ -> - Node color key value (removeHelp targetKey left) right - - _ -> - when moveRedLeft dict is # here 2 - Node nColor nKey nValue nLeft nRight -> - balance nColor nKey nValue (removeHelp targetKey nLeft) nRight - - Empty -> - Empty - - _ -> - Node color key value (removeHelp targetKey left) right - else - removeHelpEQGT targetKey (removeHelpPrepEQGT targetKey dict color key value left right) - - Key k : Num k - - balance : NodeColor, k, v, RBTree k v, RBTree k v -> RBTree k v - - moveRedLeft : RBTree k v -> RBTree k v - - removeHelpPrepEQGT : Key k, RBTree (Key k) v, NodeColor, (Key k), v, RBTree (Key k) v, RBTree (Key k) v -> RBTree (Key k) v - - removeHelpEQGT : Key k, RBTree (Key k) v -> RBTree (Key k) v where k implements Hash & Eq - removeHelpEQGT = \targetKey, dict -> - when dict is - Node color key value left right -> - if targetKey == key then - when getMin right is - Node _ minKey minValue _ _ -> - balance color minKey minValue left (removeMin right) - - Empty -> - Empty - else - balance color key value left (removeHelp targetKey right) - - Empty -> - Empty - - getMin : RBTree k v -> RBTree k v - - removeMin : RBTree k v -> RBTree k v - - main : RBTree I64 I64 - main = - removeHelp 1i64 Empty - "# - ), - "RBTree I64 I64", - ); - } - - #[test] - fn quicksort_partition_help() { - infer_eq_without_problem( - indoc!( - r#" - app "test" provides [partitionHelp] to "./platform" - - swap : U64, U64, List a -> List a - swap = \i, j, list -> - when Pair (List.get list i) (List.get list j) is - Pair (Ok atI) (Ok atJ) -> - list - |> List.set i atJ - |> List.set j atI - - _ -> - [] - - partitionHelp : U64, U64, List (Num a), U64, (Num a) -> [Pair U64 (List (Num a))] - partitionHelp = \i, j, list, high, pivot -> - if j < high then - when List.get list j is - Ok value -> - if value <= pivot then - partitionHelp (i + 1) (j + 1) (swap (i + 1) j list) high pivot - else - partitionHelp i (j + 1) list high pivot - - Err _ -> - Pair i list - else - Pair i list - "# - ), - "U64, U64, List (Num []), U64, Num [] -> [Pair U64 (List (Num []))]", - ); - } - - #[test] - fn rbtree_old_balance_simplified() { - infer_eq_without_problem( - indoc!( - r#" - app "test" provides [main] to "./platform" - - RBTree k : [Node k (RBTree k) (RBTree k), Empty] - - balance : k, RBTree k -> RBTree k - balance = \key, left -> - Node key left Empty - - main : RBTree I64 - main = - balance 0 Empty - "# - ), - "RBTree I64", - ); - } - - #[test] - fn rbtree_balance_simplified() { - infer_eq_without_problem( - indoc!( - r#" - app "test" provides [main] to "./platform" - - RBTree k : [Node k (RBTree k) (RBTree k), Empty] - - node = \x,y,z -> Node x y z - - balance : k, RBTree k -> RBTree k - balance = \key, left -> - node key left Empty - - main : RBTree I64 - main = - balance 0 Empty - "# - ), - "RBTree I64", - ); - } - - #[test] - fn rbtree_balance() { - infer_eq_without_problem( - indoc!( - r#" - app "test" provides [main] to "./platform" - - NodeColor : [Red, Black] - - RBTree k v : [Node NodeColor k v (RBTree k v) (RBTree k v), Empty] - - balance : NodeColor, k, v, RBTree k v, RBTree k v -> RBTree k v - balance = \color, key, value, left, right -> - when right is - Node Red rK rV rLeft rRight -> - when left is - Node Red lK lV lLeft lRight -> - Node - Red - key - value - (Node Black lK lV lLeft lRight) - (Node Black rK rV rLeft rRight) - - _ -> - Node color rK rV (Node Red key value left rLeft) rRight - - _ -> - when left is - Node Red lK lV (Node Red llK llV llLeft llRight) lRight -> - Node - Red - lK - lV - (Node Black llK llV llLeft llRight) - (Node Black key value lRight right) - - _ -> - Node color key value left right - - main : RBTree I64 I64 - main = - balance Red 0 0 Empty Empty - "# - ), - "RBTree I64 I64", - ); - } - - #[test] - fn pattern_rigid_problem() { - infer_eq_without_problem( - indoc!( - r#" - app "test" provides [main] to "./platform" - - RBTree k : [Node k (RBTree k) (RBTree k), Empty] - - balance : k, RBTree k -> RBTree k - balance = \key, left -> - when left is - Node _ _ lRight -> - Node key lRight Empty - - _ -> - Empty - - - main : RBTree I64 - main = - balance 0 Empty - "# - ), - "RBTree I64", - ); - } - - #[test] - fn expr_to_str() { - infer_eq_without_problem( - indoc!( - r#" - app "test" provides [main] to "./platform" - - Expr : [Add Expr Expr, Val I64, Var I64] - - printExpr : Expr -> Str - printExpr = \e -> - when e is - Add a b -> - "Add (" - |> Str.concat (printExpr a) - |> Str.concat ") (" - |> Str.concat (printExpr b) - |> Str.concat ")" - Val v -> Num.toStr v - Var v -> "Var " |> Str.concat (Num.toStr v) - - main : Str - main = printExpr (Var 3) - "# - ), - "Str", - ); - } - - #[test] - fn int_type_let_polymorphism() { - infer_eq_without_problem( - indoc!( - r#" - app "test" provides [main] to "./platform" - - x = 4 - - f : U8 -> U32 - f = \z -> Num.intCast z - - y = f x - - main = - x - "# - ), - "Num []", - ); - } - - #[test] - fn rigid_type_variable_problem() { - // see https://github.com/roc-lang/roc/issues/1162 - infer_eq_without_problem( - indoc!( - r#" - app "test" provides [main] to "./platform" - - RBTree k : [Node k (RBTree k) (RBTree k), Empty] - - balance : a, RBTree a -> RBTree a - balance = \key, left -> - when left is - Node _ _ lRight -> - Node key lRight Empty - - _ -> - Empty - - - main : RBTree {} - main = - balance {} Empty - "# - ), - "RBTree {}", - ); - } - - #[test] - fn inference_var_inside_arrow() { - infer_eq_without_problem( - indoc!( - r" - id : _ -> _ - id = \x -> x - id - " - ), - "[] -> []", - ) - } - - #[test] - fn inference_var_inside_ctor() { - infer_eq_without_problem( - indoc!( - r#" - canIGo : _ -> Result.Result _ _ - canIGo = \color -> - when color is - "green" -> Ok "go!" - "yellow" -> Err (SlowIt "whoa, let's slow down!") - "red" -> Err (StopIt "absolutely not") - _ -> Err (UnknownColor "this is a weird stoplight") - canIGo - "# - ), - "Str -> Result Str [SlowIt Str, StopIt Str, UnknownColor Str]", - ) - } - - #[test] - fn inference_var_inside_ctor_linked() { - infer_eq_without_problem( - indoc!( - r" - swapRcd: {x: _, y: _} -> {x: _, y: _} - swapRcd = \{x, y} -> {x: y, y: x} - swapRcd - " - ), - "{ x : [], y : [] } -> { x : [], y : [] }", - ) - } - - #[test] - fn inference_var_link_with_rigid() { - infer_eq_without_problem( - indoc!( - r" - swapRcd: {x: tx, y: ty} -> {x: _, y: _} - swapRcd = \{x, y} -> {x: y, y: x} - swapRcd - " - ), - "{ x : [], y : [] } -> { x : [], y : [] }", - ) - } - - #[test] - fn inference_var_inside_tag_ctor() { - infer_eq_without_problem( - indoc!( - r#" - badComics: [True, False] -> [CowTools _, Thagomizer _] - badComics = \c -> - when c is - True -> CowTools "The Far Side" - False -> Thagomizer "The Far Side" - badComics - "# - ), - "[False, True] -> [CowTools Str, Thagomizer Str]", - ) - } - - #[test] - fn inference_var_tag_union_ext() { - // TODO: we should really be inferring [Blue, Orange]a -> [Lavender, Peach]a here. - // See https://github.com/roc-lang/roc/issues/2053 - infer_eq_without_problem( - indoc!( - r" - pastelize: _ -> [Lavender, Peach]_ - pastelize = \color -> - when color is - Blue -> Lavender - Orange -> Peach - col -> col - pastelize - " - ), - "[Blue, Lavender, Orange, Peach] -> [Blue, Lavender, Orange, Peach]", - ) - } - - #[test] - fn inference_var_rcd_union_ext() { - infer_eq_without_problem( - indoc!( - r#" - setRocEmail : _ -> { name: Str, email: Str }_ - setRocEmail = \person -> - { person & email: "$(person.name)@roclang.com" } - setRocEmail - "# - ), - "{ email : Str, name : Str } -> { email : Str, name : Str }", - ) - } - - #[test] - fn issue_2217() { - infer_eq_without_problem( - indoc!( - r" - LinkedList elem : [Empty, Prepend (LinkedList elem) elem] - - fromList : List elem -> LinkedList elem - fromList = \elems -> List.walk elems Empty Prepend - - fromList - " - ), - "List [] -> LinkedList []", - ) - } - - #[test] - fn issue_2217_inlined() { - infer_eq_without_problem( - indoc!( - r" - fromList : List elem -> [Empty, Prepend (LinkedList elem) elem] as LinkedList elem - fromList = \elems -> List.walk elems Empty Prepend - - fromList - " - ), - "List [] -> LinkedList []", - ) - } - - #[test] - fn infer_union_input_position1() { - infer_eq_without_problem( - indoc!( - r" - \tag -> - when tag is - A -> X - B -> Y - " - ), - "[A, B] -> [X, Y]", - ) - } - - #[test] - fn infer_union_input_position2() { - infer_eq_without_problem( - indoc!( - r" - \tag -> - when tag is - A -> X - B -> Y - _ -> Z - " - ), - "[A, B] -> [X, Y, Z]", - ) - } - - #[test] - fn infer_union_input_position3() { - infer_eq_without_problem( - indoc!( - r" - \tag -> - when tag is - A M -> X - A N -> Y - " - ), - "[A [M, N]] -> [X, Y]", - ) - } - - #[test] - fn infer_union_input_position4() { - infer_eq_without_problem( - indoc!( - r" - \tag -> - when tag is - A M -> X - A N -> Y - A _ -> Z - " - ), - "[A [M, N]] -> [X, Y, Z]", - ) - } - - #[test] - fn infer_union_input_position5() { - infer_eq_without_problem( - indoc!( - r" - \tag -> - when tag is - A (M J) -> X - A (N K) -> X - " - ), - "[A [M [J], N [K]]] -> [X]", - ) - } - - #[test] - fn infer_union_input_position6() { - infer_eq_without_problem( - indoc!( - r" - \tag -> - when tag is - A M -> X - B -> X - A N -> X - " - ), - "[A [M, N], B] -> [X]", - ) - } - - #[test] - fn infer_union_input_position7() { - infer_eq_without_problem( - indoc!( - r" - \tag -> - when tag is - A -> X - t -> t - " - ), - "[A, X] -> [A, X]", - ) - } - - #[test] - fn infer_union_input_position8() { - infer_eq_without_problem( - indoc!( - r" - \opt -> - when opt is - Some ({tag: A}) -> 1 - Some ({tag: B}) -> 1 - None -> 0 - " - ), - "[None, Some { tag : [A, B] }] -> Num []", - ) - } - - #[test] - fn infer_union_input_position9() { - infer_eq_without_problem( - indoc!( - r#" - opt : [Some Str, None] - opt = Some "" - rcd = { opt } - - when rcd is - { opt: Some s } -> s - { opt: None } -> "?" - "# - ), - "Str", - ) - } - - #[test] - fn infer_union_input_position10() { - infer_eq_without_problem( - indoc!( - r" - \r -> - when r is - { x: Blue, y ? 3 } -> y - { x: Red, y ? 5 } -> y - " - ), - "{ x : [Blue, Red], y ? Num [] } -> Num []", - ) - } - - #[test] - // Issue #2299 - fn infer_union_argument_position() { - infer_eq_without_problem( - indoc!( - r" - \UserId id -> id + 1 - " - ), - "[UserId (Num [])] -> Num []", - ) - } - - #[test] - fn infer_union_def_position() { - infer_eq_without_problem( - indoc!( - r" - \email -> - Email str = email - Str.isEmpty str - " - ), - "[Email Str] -> Bool", - ) - } - - #[test] - fn numeric_literal_suffixes() { - infer_eq_without_problem( - indoc!( - r" - { - u8: 123u8, - u16: 123u16, - u32: 123u32, - u64: 123u64, - u128: 123u128, - - i8: 123i8, - i16: 123i16, - i32: 123i32, - i64: 123i64, - i128: 123i128, - - bu8: 0b11u8, - bu16: 0b11u16, - bu32: 0b11u32, - bu64: 0b11u64, - bu128: 0b11u128, - - bi8: 0b11i8, - bi16: 0b11i16, - bi32: 0b11i32, - bi64: 0b11i64, - bi128: 0b11i128, - - dec: 123.0dec, - f32: 123.0f32, - f64: 123.0f64, - - fdec: 123dec, - ff32: 123f32, - ff64: 123f64, - } - " - ), - r"{ bi128 : I128, bi16 : I16, bi32 : I32, bi64 : I64, bi8 : I8, bu128 : U128, bu16 : U16, bu32 : U32, bu64 : U64, bu8 : U8, dec : Dec, f32 : F32, f64 : F64, fdec : Dec, ff32 : F32, ff64 : F64, i128 : I128, i16 : I16, i32 : I32, i64 : I64, i8 : I8, u128 : U128, u16 : U16, u32 : U32, u64 : U64, u8 : U8 }", - ) - } - - #[test] - fn numeric_literal_suffixes_in_pattern() { - infer_eq_without_problem( - indoc!( - r" - { - u8: (\n -> - when n is - 123u8 -> n - _ -> n), - u16: (\n -> - when n is - 123u16 -> n - _ -> n), - u32: (\n -> - when n is - 123u32 -> n - _ -> n), - u64: (\n -> - when n is - 123u64 -> n - _ -> n), - u128: (\n -> - when n is - 123u128 -> n - _ -> n), - - i8: (\n -> - when n is - 123i8 -> n - _ -> n), - i16: (\n -> - when n is - 123i16 -> n - _ -> n), - i32: (\n -> - when n is - 123i32 -> n - _ -> n), - i64: (\n -> - when n is - 123i64 -> n - _ -> n), - i128: (\n -> - when n is - 123i128 -> n - _ -> n), - - bu8: (\n -> - when n is - 0b11u8 -> n - _ -> n), - bu16: (\n -> - when n is - 0b11u16 -> n - _ -> n), - bu32: (\n -> - when n is - 0b11u32 -> n - _ -> n), - bu64: (\n -> - when n is - 0b11u64 -> n - _ -> n), - bu128: (\n -> - when n is - 0b11u128 -> n - _ -> n), - - bi8: (\n -> - when n is - 0b11i8 -> n - _ -> n), - bi16: (\n -> - when n is - 0b11i16 -> n - _ -> n), - bi32: (\n -> - when n is - 0b11i32 -> n - _ -> n), - bi64: (\n -> - when n is - 0b11i64 -> n - _ -> n), - bi128: (\n -> - when n is - 0b11i128 -> n - _ -> n), - - dec: (\n -> - when n is - 123.0dec -> n - _ -> n), - f32: (\n -> - when n is - 123.0f32 -> n - _ -> n), - f64: (\n -> - when n is - 123.0f64 -> n - _ -> n), - - fdec: (\n -> - when n is - 123dec -> n - _ -> n), - ff32: (\n -> - when n is - 123f32 -> n - _ -> n), - ff64: (\n -> - when n is - 123f64 -> n - _ -> n), - } - " - ), - r"{ bi128 : I128 -> I128, bi16 : I16 -> I16, bi32 : I32 -> I32, bi64 : I64 -> I64, bi8 : I8 -> I8, bu128 : U128 -> U128, bu16 : U16 -> U16, bu32 : U32 -> U32, bu64 : U64 -> U64, bu8 : U8 -> U8, dec : Dec -> Dec, f32 : F32 -> F32, f64 : F64 -> F64, fdec : Dec -> Dec, ff32 : F32 -> F32, ff64 : F64 -> F64, i128 : I128 -> I128, i16 : I16 -> I16, i32 : I32 -> I32, i64 : I64 -> I64, i8 : I8 -> I8, u128 : U128 -> U128, u16 : U16 -> U16, u32 : U32 -> U32, u64 : U64 -> U64, u8 : U8 -> U8 }", - ) - } -} From 073df686bca85ad8e2bad93c9971f7716a7f5a41 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sun, 17 Nov 2024 01:48:21 -0500 Subject: [PATCH 34/65] Remove some dbg!s --- crates/compiler/specialize_types/src/mono_expr.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/compiler/specialize_types/src/mono_expr.rs b/crates/compiler/specialize_types/src/mono_expr.rs index d0740c1ea5..a60d412ad7 100644 --- a/crates/compiler/specialize_types/src/mono_expr.rs +++ b/crates/compiler/specialize_types/src/mono_expr.rs @@ -77,7 +77,7 @@ impl<'a, 'c, 'd, 'i, 's, 't, P: Push> Env<'a, 'c, 'd, 'i, 's, 't, P> { match can_expr { Expr::Float(var, _precision_var, _str, val, _bound) => { - match dbg!(self.subs.get_content_without_compacting(*var)) { + match self.subs.get_content_without_compacting(*var) { Content::FlexVar(_) => { // Plain decimal number literals like `4.2` can still have an unbound var. Some(MonoExpr::Number(Number::Dec(*val))) @@ -159,7 +159,7 @@ impl<'a, 'c, 'd, 'i, 's, 't, P: Push> Env<'a, 'c, 'd, 'i, 's, 't, P> { // Generate a MonoExpr for each field, using the reserved IDs so that we end up with // that Slice being populated with the exprs in the fields, with the correct ordering. fields.retain(|(_name, field)| { - match dbg!(self.to_mono_expr(&field.loc_expr.value)) { + match self.to_mono_expr(&field.loc_expr.value) { Some(mono_expr) => { // Safety: This will run *at most* field.len() times, possibly less, // so this will never create an index that's out of bounds. @@ -171,11 +171,11 @@ impl<'a, 'c, 'd, 'i, 's, 't, P: Push> Env<'a, 'c, 'd, 'i, 's, 't, P> { self.mono_exprs .insert(mono_expr_id, mono_expr, field.loc_expr.region); - dbg!(true) + true } None => { // Discard all the zero-sized fields as we go. - dbg!(false) + false } } }); From 13efa083ddc188ff9a5e4116d2403a6bb65bd01c Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Mon, 18 Nov 2024 21:36:24 -0500 Subject: [PATCH 35/65] Improve representation of record fields in mono --- crates/compiler/specialize_types/src/lib.rs | 2 +- .../specialize_types/src/mono_expr.rs | 192 ++++++--- .../compiler/specialize_types/src/mono_ir.rs | 154 ++++--- .../specialize_types/src/specialize_type.rs | 404 +++++++++--------- .../specialize_types/tests/helpers/mod.rs | 8 +- crates/test_compile/src/help_specialize.rs | 6 +- 6 files changed, 459 insertions(+), 307 deletions(-) diff --git a/crates/compiler/specialize_types/src/lib.rs b/crates/compiler/specialize_types/src/lib.rs index 2acd7728a2..1caff70436 100644 --- a/crates/compiler/specialize_types/src/lib.rs +++ b/crates/compiler/specialize_types/src/lib.rs @@ -17,4 +17,4 @@ pub use mono_module::{InternedStrId, Interns}; pub use mono_num::Number; pub use mono_struct::MonoFieldId; pub use mono_type::{MonoType, MonoTypeId, MonoTypes}; -pub use specialize_type::{MonoCache, Problem, RecordFieldIds, TupleElemIds}; +pub use specialize_type::{MonoTypeCache, Problem, RecordFieldIds, TupleElemIds}; diff --git a/crates/compiler/specialize_types/src/mono_expr.rs b/crates/compiler/specialize_types/src/mono_expr.rs index a60d412ad7..1e8a883b6a 100644 --- a/crates/compiler/specialize_types/src/mono_expr.rs +++ b/crates/compiler/specialize_types/src/mono_expr.rs @@ -1,22 +1,52 @@ use crate::{ - mono_ir::{MonoExpr, MonoExprId, MonoExprs}, + mono_ir::{MonoExpr, MonoExprId, MonoExprs, MonoStmt, MonoStmtId}, mono_module::Interns, mono_num::Number, mono_type::{MonoType, MonoTypes, Primitive}, - specialize_type::{MonoCache, Problem, RecordFieldIds, TupleElemIds}, - DebugInfo, + specialize_type::{MonoTypeCache, Problem, RecordFieldIds, TupleElemIds}, + DebugInfo, MonoTypeId, }; use bumpalo::{collections::Vec, Bump}; use roc_can::expr::{Expr, IntValue}; -use roc_collections::Push; +use roc_collections::{Push, VecMap}; +use roc_module::{ident::Lowercase, symbol::ModuleId}; +use roc_region::all::Region; use roc_solve::module::Solved; -use roc_types::subs::{Content, Subs}; -use soa::{Index, NonEmptySlice, Slice}; +use roc_types::subs::{Content, FlatType, Subs, Variable}; +use soa::NonEmptySlice; + +/// Function bodies that have already been specialized. +pub struct MonoFnCache { + inner: VecMap<(ModuleId, Variable, MonoTypeId), MonoExprId>, +} + +impl MonoFnCache { + pub fn monomorphize_fn<'a, F: 'a + FnOnce(ModuleId, Variable) -> &'a Expr>( + &mut self, + // Sometimes we need to create specializations of functions that are defined in other modules. + module_id: ModuleId, + // The function Variable stored in the original function's canonical Expr. We use this as a way to + // uniquely identify the function expr within its module, since each fn Expr gets its own unique var. + // Doing it with Variable instead of IdentId lets us cache specializations of anonymous functions too. + fn_var: Variable, + // Given a ModuleId and Variable (to uniquely identify the canonical fn Expr within its module), + // get the canonical Expr of the function itself. We need this to create a specialization of it. + get_fn_expr: F, + // This tells us which specialization of the function we want. + mono_type_id: MonoTypeId, + ) -> MonoExprId { + *self + .inner + .get_or_insert((module_id, fn_var, mono_type_id), || { + todo!("TODO lower the fn_expr using Env etc. (May need to add args to this method, not sure.)"); + }) + } +} pub struct Env<'a, 'c, 'd, 'i, 's, 't, P> { arena: &'a Bump, subs: &'s mut Subs, - types_cache: &'c mut MonoCache, + types_cache: &'c mut MonoTypeCache, mono_types: &'t mut MonoTypes, mono_exprs: &'t mut MonoExprs, record_field_ids: RecordFieldIds, @@ -30,7 +60,7 @@ impl<'a, 'c, 'd, 'i, 's, 't, P: Push> Env<'a, 'c, 'd, 'i, 's, 't, P> { pub fn new( arena: &'a Bump, subs: &'s mut Solved, - types_cache: &'c mut MonoCache, + types_cache: &'c mut MonoTypeCache, mono_types: &'t mut MonoTypes, mono_exprs: &'t mut MonoExprs, record_field_ids: RecordFieldIds, @@ -58,6 +88,7 @@ impl<'a, 'c, 'd, 'i, 's, 't, P: Push> Env<'a, 'c, 'd, 'i, 's, 't, P> { let mono_types = &mut self.mono_types; let mut mono_from_var = |var| { self.types_cache.monomorphize_var( + self.arena, self.subs, mono_types, &mut self.record_field_ids, @@ -151,59 +182,116 @@ impl<'a, 'c, 'd, 'i, 's, 't, P: Push> Env<'a, 'c, 'd, 'i, 's, 't, P> { fields.sort_by(|(name1, _), (name2, _)| name1.cmp(name2)); - // Reserve a slice of IDs up front. This is so that we have a contiguous array - // of field IDs at the end of this, each corresponding to the appropriate record field. - let field_ids: Slice = self.mono_exprs.reserve_ids(fields.len() as u16); - let mut next_field_id = field_ids.start(); + // We want to end up with a Slice of these, so accumulate a buffer of them + // and then add them to MonoExprs all at once, so they're added as one contiguous slice + // regardless of what else got added to MonoExprs as we were monomorphizing them. + let mut buf: Vec<(MonoExpr, Region)> = + Vec::with_capacity_in(fields.len(), self.arena); - // Generate a MonoExpr for each field, using the reserved IDs so that we end up with - // that Slice being populated with the exprs in the fields, with the correct ordering. - fields.retain(|(_name, field)| { - match self.to_mono_expr(&field.loc_expr.value) { - Some(mono_expr) => { - // Safety: This will run *at most* field.len() times, possibly less, - // so this will never create an index that's out of bounds. - let mono_expr_id = - unsafe { MonoExprId::new_unchecked(Index::new(next_field_id)) }; + buf.extend( + // flat_map these so we discard all the fields that monomorphized to None + fields.into_iter().flat_map(|(_name, field)| { + self.to_mono_expr(&field.loc_expr.value) + .map(|mono_expr| (mono_expr, field.loc_expr.region)) + }), + ); - next_field_id += 1; - - self.mono_exprs - .insert(mono_expr_id, mono_expr, field.loc_expr.region); - - true - } - None => { - // Discard all the zero-sized fields as we go. - false - } - } - }); - - // Check for zero-sized and single-field records again now that we've discarded zero-sized fields, - // because we might have ended up with 0 or 1 remaining fields. - if fields.len() > 1 { - // Safety: We just verified that there's more than 1 field. - unsafe { - Some(MonoExpr::Struct(NonEmptySlice::new_unchecked( - field_ids.start, - fields.len() as u16, - ))) - } - } else { - // If there are 0 fields remaining, return None. If there's 1, unwrap it. - fields - .first() - .and_then(|(_, field)| self.to_mono_expr(&field.loc_expr.value)) + // If we ended up with exactly 1 field, return it unwrapped. + if buf.len() == 1 { + return buf.pop().map(|(expr, _region)| expr); } + + NonEmptySlice::from_slice(self.mono_exprs.extend(buf.iter().copied())) + .map(MonoExpr::Struct) } + // Expr::Call((fn_var, fn_expr, capture_var, ret_var), args, called_via) => { + // let opt_ret_type = mono_from_var(*var); + + // if opt_ret_type.is_none() { + // let fn_type = match self.subs.get_content_without_compacting(fn_var) { + // Content::Structure(FlatType::Func(arg_vars, closure_var, ret_var)) => { + // let todo = (); // TODO make is_effectful actually use the function's effectfulness! + // let is_effectful = false; + + // // Calls to pure functions that return zero-sized types should be discarded. + // if !is_effectful { + // return None; + // } + + // // Use the Content we already have to directly monomorphize the function, rather than + // // calling monomorphize_var and having it redo the Subs lookup and conditionals we just did. + // self.types_cache.monomorphize_fn( + // self.subs, + // self.mono_types, + // &mut self.record_field_ids, + // &mut self.tuple_elem_ids, + // &mut self.problems, + // self.debug_info, + // *arg_vars, + // *ret_var, + // )? + // } + // _ => { + // // This function didn't have a function type. Compiler bug! + // return Some(MonoExpr::CompilerBug(Problem::FnDidNotHaveFnType)); + // } + // }; + + // let todo = (); // TODO this is where we need to specialize, which means...duplicating the fn expr body maybe? and caching it under the mono type? + // let fn_expr = self.to_mono_expr(can_expr, stmts)?; + // let args = todo!(); // TODO compute the args. This is tricky because of preallocated slices! + // let capture_type = mono_from_var(*capture_var); + + // let todo = (); // How do we pre-reserve the statements? Is that possible? It does seem necessary...might not be possible though. Maybe we just need to make Vec rather than Slice on these. + + // // We aren't returning anything, and this is an effectful function, so just push a statement to call it and move on. + // stmts.push(self.mono_stmts.add(MonoStmt::CallVoid { + // fn_type, + // fn_expr, + // args, + // capture_type, + // })); + + // None + // } else { + // let fn_type = mono_from_var(*fn_var)?; + // let todo = (); // TODO this is where we need to specialize, which means...duplicating the fn expr body maybe? and caching it under the mono type? + // let fn_expr = self.to_mono_expr(can_expr, stmts)?; + // let args = todo!(); // TODO compute the args. This is tricky because of preallocated slices! + // let capture_type = mono_from_var(*capture_var); + + // Some(MonoExpr::Call { + // fn_type, + // fn_expr, + // args, + // capture_type, + // }) + // } + // } + // Expr::Var(symbol, var) => Some(MonoExpr::Lookup(*symbol, mono_from_var(*var)?)), + // Expr::LetNonRec(def, loc) => { + // let expr = self.to_mono_expr(def.loc_expr.value, stmts)?; + // let todo = (); // TODO if this is an underscore pattern and we're doing a fn call, convert it to Stmt::CallVoid + // let pattern = self.to_mono_pattern(def.loc_pattern.value); + + // // TODO do we need to use any of these other fields? e.g. for the types? + // // pub struct Def { + // // pub loc_pattern: Loc, + // // pub loc_expr: Loc, + // // pub expr_var: Variable, + // // pub pattern_vars: SendMap, + // // pub annotation: Option, + // // } + + // todo!("split up the pattern into various Assign statements."); + // } + // Expr::LetRec(vec, loc, illegal_cycle_mark) => todo!(), _ => todo!(), // Expr::List { // elem_var, // loc_elems, // } => todo!(), // Expr::IngestedFile(path_buf, arc, variable) => todo!(), - // Expr::Var(symbol, variable) => todo!(), // Expr::ParamsVar { // symbol, // var, @@ -226,8 +314,6 @@ impl<'a, 'c, 'd, 'i, 's, 't, P: Push> Env<'a, 'c, 'd, 'i, 's, 't, P> { // branches, // final_else, // } => todo!(), - // Expr::LetRec(vec, loc, illegal_cycle_mark) => todo!(), - // Expr::LetNonRec(def, loc) => todo!(), // Expr::Call(_, vec, called_via) => todo!(), // Expr::RunLowLevel { op, args, ret_var } => todo!(), // Expr::ForeignCall { diff --git a/crates/compiler/specialize_types/src/mono_ir.rs b/crates/compiler/specialize_types/src/mono_ir.rs index 2acd0179e0..dfe5ac12e1 100644 --- a/crates/compiler/specialize_types/src/mono_ir.rs +++ b/crates/compiler/specialize_types/src/mono_ir.rs @@ -117,8 +117,8 @@ impl MonoExprs { } } - pub fn iter_slice(&self, expr_ids: Slice) -> impl Iterator { - expr_ids.indices().into_iter().map(|index| { + pub fn iter_slice(&self, exprs: Slice) -> impl Iterator { + exprs.indices().into_iter().map(|index| { debug_assert!( self.exprs.get(index).is_some(), "A Slice index was not found in MonoExprs. This should never happen!" @@ -128,6 +128,20 @@ impl MonoExprs { unsafe { self.exprs.get_unchecked(index) } }) } + + pub fn extend( + &mut self, + exprs: impl Iterator + Clone, + ) -> Slice { + let start = self.exprs.len(); + + self.exprs.extend(exprs.clone().map(|(expr, _region)| expr)); + self.regions.extend(exprs.map(|(_expr, region)| region)); + + let len = self.exprs.len() - start; + + Slice::new(start as u32, len as u16) + } } #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -141,6 +155,82 @@ impl MonoExprId { } } +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct MonoStmtId { + inner: Id, +} + +impl MonoStmtId { + pub(crate) unsafe fn new_unchecked(inner: Id) -> Self { + Self { inner } + } +} + +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum MonoStmt { + /// Assign to a variable. + Assign(IdentId, MonoExprId), + AssignRec(IdentId, MonoExprId), + + /// Introduce a variable, e.g. `var foo_` (we'll MonoStmt::Assign to it later.) + Declare(IdentId), + + /// The `return` statement + Return(MonoExprId), + + /// The "crash" keyword. Importantly, during code gen we must mark this as "nothing happens after this" + Crash { + msg: MonoExprId, + /// The type of the `crash` expression (which will have unified to whatever's around it) + expr_type: MonoTypeId, + }, + + Expect { + condition: MonoExprId, + /// If the expectation fails, we print the values of all the named variables + /// in the final expr. These are those values. + lookups_in_cond: Slice2, + }, + + Dbg { + source_location: InternedStrId, + source: InternedStrId, + expr: MonoExprId, + expr_type: MonoTypeId, + name: IdentId, + }, + + // Call a function that has no return value (or which we are discarding due to an underscore pattern). + CallVoid { + fn_type: MonoTypeId, + fn_expr: MonoExprId, + args: Slice2, + /// This is the type of the closure based only on canonical IR info, + /// not considering what other closures might later influence it. + /// Lambda set specialization may change this type later! + capture_type: MonoTypeId, + }, + + // Branching + When { + /// The actual condition of the when expression. + cond: MonoExprId, + cond_type: MonoTypeId, + /// Type of each branch (and therefore the type of the entire `when` expression) + branch_type: MonoTypeId, + /// Note: if the branches weren't exhaustive, we will have already generated a default + /// branch which crashes if it's reached. (The compiler will have reported an error already; + /// this is for if you want to run anyway.) + branches: NonEmptySlice, + }, + If { + /// Type of each branch (and therefore the type of the entire `if` expression) + branch_type: MonoTypeId, + branches: Slice<(MonoStmtId, MonoStmtId)>, + final_else: Option, + }, +} + #[derive(Clone, Copy, Debug, PartialEq)] pub enum MonoExpr { Str(InternedStrId), @@ -159,35 +249,6 @@ pub enum MonoExpr { params_type: MonoTypeId, }, - // Branching - When { - /// The actual condition of the when expression. - cond: MonoExprId, - cond_type: MonoTypeId, - /// Type of each branch (and therefore the type of the entire `when` expression) - branch_type: MonoTypeId, - /// Note: if the branches weren't exhaustive, we will have already generated a default - /// branch which crashes if it's reached. (The compiler will have reported an error already; - /// this is for if you want to run anyway.) - branches: NonEmptySlice, - }, - If { - /// Type of each branch (and therefore the type of the entire `if` expression) - branch_type: MonoTypeId, - branches: Slice<(MonoExprId, MonoExprId)>, - final_else: Option, - }, - - // Let - LetRec { - defs: Slice, - ending_expr: MonoExprId, - }, - LetNonRec { - def: Def, - ending_expr: MonoExprId, - }, - /// This is *only* for calling functions, not for tag application. /// The Tag variant contains any applied values inside it. Call { @@ -197,7 +258,7 @@ pub enum MonoExpr { /// This is the type of the closure based only on canonical IR info, /// not considering what other closures might later influence it. /// Lambda set specialization may change this type later! - closure_type: MonoTypeId, + capture_type: MonoTypeId, }, RunLowLevel { op: LowLevel, @@ -220,14 +281,7 @@ pub enum MonoExpr { /// A record literal or a tuple literal. /// These have already been sorted alphabetically. - Struct(NonEmptySlice), - - /// The "crash" keyword. Importantly, during code gen we must mark this as "nothing happens after this" - Crash { - msg: MonoExprId, - /// The type of the `crash` expression (which will have unified to whatever's around it) - expr_type: MonoTypeId, - }, + Struct(NonEmptySlice), /// Look up exactly one field on a record, tuple, or tag payload. /// At this point we've already unified those concepts and have @@ -264,28 +318,18 @@ pub enum MonoExpr { args: Slice2, }, - Expect { - condition: MonoExprId, - continuation: MonoExprId, - /// If the expectation fails, we print the values of all the named variables - /// in the final expr. These are those values. - lookups_in_cond: Slice2, - }, - Dbg { - source_location: InternedStrId, - source: InternedStrId, - msg: MonoExprId, - continuation: MonoExprId, - expr_type: MonoTypeId, - name: IdentId, + Block { + stmts: Slice, + final_expr: MonoExprId, }, + CompilerBug(Problem), } #[derive(Clone, Copy, Debug, PartialEq)] pub struct WhenBranch { pub patterns: Slice, - pub body: MonoExprId, + pub body: Slice, pub guard: Option, } diff --git a/crates/compiler/specialize_types/src/specialize_type.rs b/crates/compiler/specialize_types/src/specialize_type.rs index fe7b6684e2..f209b99acc 100644 --- a/crates/compiler/specialize_types/src/specialize_type.rs +++ b/crates/compiler/specialize_types/src/specialize_type.rs @@ -8,6 +8,7 @@ use crate::{ mono_type::{MonoTypeId, MonoTypes}, MonoFieldId, MonoType, }; +use bumpalo::{collections::Vec, Bump}; use roc_collections::{Push, VecMap}; use roc_module::{ident::Lowercase, symbol::Symbol}; use roc_solve::module::Solved; @@ -33,6 +34,7 @@ pub enum Problem { ), BadNumTypeParam, UninitializedReservedExpr, + FnDidNotHaveFnType, } /// For MonoTypes that are records, store their field indices. @@ -46,11 +48,11 @@ pub type RecordFieldIds = VecMap>; pub type TupleElemIds = VecMap>; /// Variables that have already been monomorphized. -pub struct MonoCache { +pub struct MonoTypeCache { inner: VecMap, } -impl MonoCache { +impl MonoTypeCache { pub fn from_solved_subs(subs: &Solved) -> Self { Self { inner: VecMap::with_capacity(subs.inner().len()), @@ -61,6 +63,7 @@ impl MonoCache { /// (e.g. a zero-sized type like empty record, empty tuple, a record of just those, etc.) pub fn monomorphize_var( &mut self, + arena: &Bump, subs: &Subs, mono_types: &mut MonoTypes, field_indices: &mut RecordFieldIds, @@ -70,6 +73,7 @@ impl MonoCache { var: Variable, ) -> Option { let mut env = Env { + arena, cache: self, mono_types, field_ids: field_indices, @@ -78,12 +82,13 @@ impl MonoCache { debug_info, }; - lower_var(&mut env, subs, var) + env.lower_var(subs, var) } } -struct Env<'c, 'd, 'e, 'f, 'm, 'p, P: Push> { - cache: &'c mut MonoCache, +struct Env<'a, 'c, 'd, 'e, 'f, 'm, 'p, P> { + arena: &'a Bump, + cache: &'c mut MonoTypeCache, #[allow(dead_code)] mono_types: &'m mut MonoTypes, #[allow(dead_code)] @@ -95,187 +100,226 @@ struct Env<'c, 'd, 'e, 'f, 'm, 'p, P: Push> { debug_info: &'d mut Option, } -fn lower_var>( - env: &mut Env<'_, '_, '_, '_, '_, '_, P>, - subs: &Subs, - var: Variable, -) -> Option { - let root_var = subs.get_root_key_without_compacting(var); +impl<'a, 'c, 'd, 'e, 'f, 'm, 'p, P: Push> Env<'a, 'c, 'd, 'e, 'f, 'm, 'p, P> { + fn lower_builtin( + &mut self, + subs: &Subs, + symbol: Symbol, + args: SubsSlice, + ) -> MonoTypeId { + if symbol == Symbol::NUM_NUM { + number_args_to_mono_id(args, subs, self.problems) + } else if symbol == Symbol::NUM_FLOATINGPOINT { + num_num_args_to_mono_id(symbol, args, subs, self.problems) + } else if symbol == Symbol::LIST_LIST { + todo!(); + // let mut new_args = args + // .into_iter() + // .flat_map(|var_index| self.lower_var( subs, subs[var_index])); - // TODO: we could replace this cache by having Subs store a Content::Monomorphic(MonoTypeId) - // and then overwrite it rather than having a separate cache. That memory is already in cache - // for sure, and the lookups should be faster because they're O(1) but don't require hashing. - // Kinda creates a cyclic dep though. - if let Some(mono_id) = env.cache.inner.get(&root_var) { - return Some(*mono_id); + // let arg = new_args.next(); + } else { + todo!("implement lower_builtin for symbol {symbol:?} - or, if all the builtins are already in here, report a compiler bug instead of panicking like this."); + } } - // Convert the Content to a MonoType, often by passing an iterator. None of these iterators introduce allocations. - let mono_id = match dbg!(*subs.get_content_without_compacting(root_var)) { - Content::Structure(flat_type) => match flat_type { - FlatType::Apply(symbol, args) => { - if symbol.is_builtin() { - lower_builtin(env, subs, symbol, args) - } else { - todo!("handle non-builtin Apply"); + /// Exposed separately because sometimes we already looked up the Content and know it's a function, + /// and want to continue from there without redoing the lookup. + pub fn monomorphize_fn( + &mut self, + subs: &Subs, + arg_vars: SubsSlice, + ret_var: Variable, + ) -> MonoTypeId { + let func = self.lower_var(subs, ret_var); + let mut mono_args = Vec::with_capacity_in(arg_vars.len(), self.arena); + + mono_args.extend( + arg_vars + .into_iter() + .flat_map(|var_index| self.lower_var(subs, subs[var_index])), + ); + + let todo = (); // TODO populate debuginfo as appropriate + + self.mono_types.add_function(func, mono_args) + } + + fn lower_var(&mut self, subs: &Subs, var: Variable) -> Option { + let root_var = subs.get_root_key_without_compacting(var); + + // TODO: we could replace this cache by having Subs store a Content::Monomorphic(MonoTypeId) + // and then overwrite it rather than having a separate cache. That memory is already in cache + // for sure, and the lookups should be faster because they're O(1) but don't require hashing. + // Kinda creates a cyclic dep though. + if let Some(mono_id) = self.cache.inner.get(&root_var) { + return Some(*mono_id); + } + + // Convert the Content to a MonoType, often by passing an iterator. None of these iterators introduce allocations. + let mono_id = match dbg!(*subs.get_content_without_compacting(root_var)) { + Content::Structure(flat_type) => match flat_type { + FlatType::Apply(symbol, args) => { + if symbol.is_builtin() { + self.lower_builtin(subs, symbol, args) + } else { + todo!("handle non-builtin Apply"); + } } - } - // FlatType::Func(args, _capture, ret) => { - // let mono_args = args - // .into_iter() - // .flat_map(|var_index| lower_var(env, subs[var_index])); + FlatType::Func(args, _capture, ret) => self.monomorphize_fn(subs, args, ret), + _ => { + todo!(); + } /* + FlatType::Record(fields, ext) => { + let mut labeled_mono_ids = lower_record(env, fields, ext); - // let todo = (); // TODO populate debuginfo (if it's Some, meaning we want it) - // let func = lower_var(env, ret); - // Some(env.mono_types.add_function(func, mono_args)) - // } - _ => { - todo!(); - } /* - FlatType::Record(fields, ext) => { - let mut labeled_mono_ids = lower_record(env, fields, ext); - - // Handle the special cases of 0 fields and 1 field. - match labeled_mono_ids.first() { - Some((label, first_field_id)) => { - if labeled_mono_ids.len() == 1 { - // If we ended up with a single field, return it unwrapped. - let todo = (); // TODO populate debuginfo using the label (if it's Some, meaning we want it) - let todo = (); // To preserve debuginfo, we need to actually clone this mono_id and not just return the same one. - return Some(*first_field_id); + // Handle the special cases of 0 fields and 1 field. + match labeled_mono_ids.first() { + Some((label, first_field_id)) => { + if labeled_mono_ids.len() == 1 { + // If we ended up with a single field, return it unwrapped. + let todo = (); // TODO populate debuginfo using the label (if it's Some, meaning we want it) + let todo = (); // To preserve debuginfo, we need to actually clone this mono_id and not just return the same one. + return Some(*first_field_id); + } + } + None => { + // If we ended up with an empty record, + // after removing other empty things, return None. + return None; } } - None => { - // If we ended up with an empty record, - // after removing other empty things, return None. - return None; + + // Now we know we have at least 2 fields, so sort them by field name. + // This can be unstable sort because all field names are known to be unique, + // so sorting unstable won't be observable (and is faster than stable). + labeled_mono_ids.sort_unstable_by(|(label1, _), (label2, _)| label1.cmp(label2)); + + let todo = (); // TODO populate debuginfo (if it's Some, meaning we want it) + + // Safety: we already verified that this has at least 2 elements, and + // we would have early returned before this point if we had fewer than 2. + let mono_id = unsafe { + mono_types.add_struct_unchecked(labeled_mono_ids.iter().map(|(_label, mono_id)| *mono_id)) + }; + + let labeled_indices = VecMap::from_iter(labeled_mono_ids.into_iter().enumerate().map(|(index, (label, _mono_id))| (label, MonoFieldId::new(index as u16)))); + + self.field_ids.insert(mono_id, labeled_indices); + + Some(mono_id) + } + FlatType::Tuple(elems, ext) => { + let indexed_mono_ids = lower_tuple(env, elems, ext); + + // This can be unstable sort because all indices are known to be unique, + // so sorting unstable won't be observable (and is faster than stable). + indexed_mono_ids.sort_unstable_by(|(index1, _), (index2, _)| index1.cmp(index2)); + + let todo = (); // TODO populate debuginfo (if it's Some, meaning we want it) + mono_types.add_struct(indexed_mono_ids.iter().map(|(_, mono_id)| *mono_id)) + } + FlatType::TagUnion(tags, ext) => { + let tagged_payload_ids = lower_tag_union(env, tags, ext); + + // This can be unstable sort because all tag names are known to be unique, + // so sorting unstable won't be observable (and is faster than stable). + tagged_payload_ids.sort_unstable_by(|(tag1, _), (tag2, _)| tag1.cmp(tag2)); + + let todo = (); // TODO populate debuginfo (if it's Some, meaning we want it) + mono_types.add_tag_union(tagged_payload_ids.iter().map(|(_, mono_id)| *mono_id)) + } + FlatType::FunctionOrTagUnion(tag_names, _symbols, ext) => { + // If this is still a FunctionOrTagUnion, turn it into a TagUnion. + + // First, resolve the ext var. + let mut tags = resolve_tag_ext(subs, problems, UnionTags::default(), *ext); + + // Now lower all the tags we gathered from the ext var. + // (Do this in a separate pass to avoid borrow errors on Subs.) + lower_vars(tags.iter_mut().flat_map(|(_, vars)| vars.iter_mut()), cache, subs, problems); + + // Then, add the tag names with no payloads. (There are no variables to lower here.) + for index in tag_names.into_iter() { + tags.push(((subs[index]).clone(), Vec::new())); } + + Content::Structure(FlatType::TagUnion( + UnionTags::insert_into_subs(subs, tags), + TagExt::Any(Variable::EMPTY_TAG_UNION), + )) } + FlatType::RecursiveTagUnion(rec, tags, ext) => { + let mut tags = resolve_tag_ext(subs, problems, *tags, *ext); - // Now we know we have at least 2 fields, so sort them by field name. - // This can be unstable sort because all field names are known to be unique, - // so sorting unstable won't be observable (and is faster than stable). - labeled_mono_ids.sort_unstable_by(|(label1, _), (label2, _)| label1.cmp(label2)); + // Now lower all the tags we gathered. Do this in a separate pass to avoid borrow errors on Subs. + lower_vars(tags.iter_mut().flat_map(|(_, vars)| vars.iter_mut()), cache, subs, problems); - let todo = (); // TODO populate debuginfo (if it's Some, meaning we want it) - - // Safety: we already verified that this has at least 2 elements, and - // we would have early returned before this point if we had fewer than 2. - let mono_id = unsafe { - mono_types.add_struct_unchecked(labeled_mono_ids.iter().map(|(_label, mono_id)| *mono_id)) - }; - - let labeled_indices = VecMap::from_iter(labeled_mono_ids.into_iter().enumerate().map(|(index, (label, _mono_id))| (label, MonoFieldId::new(index as u16)))); - - env.field_ids.insert(mono_id, labeled_indices); - - Some(mono_id) - } - FlatType::Tuple(elems, ext) => { - let indexed_mono_ids = lower_tuple(env, elems, ext); - - // This can be unstable sort because all indices are known to be unique, - // so sorting unstable won't be observable (and is faster than stable). - indexed_mono_ids.sort_unstable_by(|(index1, _), (index2, _)| index1.cmp(index2)); - - let todo = (); // TODO populate debuginfo (if it's Some, meaning we want it) - mono_types.add_struct(indexed_mono_ids.iter().map(|(_, mono_id)| *mono_id)) - } - FlatType::TagUnion(tags, ext) => { - let tagged_payload_ids = lower_tag_union(env, tags, ext); - - // This can be unstable sort because all tag names are known to be unique, - // so sorting unstable won't be observable (and is faster than stable). - tagged_payload_ids.sort_unstable_by(|(tag1, _), (tag2, _)| tag1.cmp(tag2)); - - let todo = (); // TODO populate debuginfo (if it's Some, meaning we want it) - mono_types.add_tag_union(tagged_payload_ids.iter().map(|(_, mono_id)| *mono_id)) - } - FlatType::FunctionOrTagUnion(tag_names, _symbols, ext) => { - // If this is still a FunctionOrTagUnion, turn it into a TagUnion. - - // First, resolve the ext var. - let mut tags = resolve_tag_ext(subs, problems, UnionTags::default(), *ext); - - // Now lower all the tags we gathered from the ext var. - // (Do this in a separate pass to avoid borrow errors on Subs.) - lower_vars(tags.iter_mut().flat_map(|(_, vars)| vars.iter_mut()), cache, subs, problems); - - // Then, add the tag names with no payloads. (There are no variables to lower here.) - for index in tag_names.into_iter() { - tags.push(((subs[index]).clone(), Vec::new())); + Content::Structure(FlatType::RecursiveTagUnion( + lower_var(cache, subs, problems, *rec), + UnionTags::insert_into_subs(subs, tags), + TagExt::Any(Variable::EMPTY_TAG_UNION), + )) } + FlatType::EmptyRecord| + FlatType::EmptyTuple | + FlatType::EmptyTagUnion => None, + }, + Content::Error => Content::Error, + */ + }, + Content::RangedNumber(range) => { + use roc_types::num::NumericRange::*; - Content::Structure(FlatType::TagUnion( - UnionTags::insert_into_subs(subs, tags), - TagExt::Any(Variable::EMPTY_TAG_UNION), - )) - } - FlatType::RecursiveTagUnion(rec, tags, ext) => { - let mut tags = resolve_tag_ext(subs, problems, *tags, *ext); - - // Now lower all the tags we gathered. Do this in a separate pass to avoid borrow errors on Subs. - lower_vars(tags.iter_mut().flat_map(|(_, vars)| vars.iter_mut()), cache, subs, problems); - - Content::Structure(FlatType::RecursiveTagUnion( - lower_var(cache, subs, problems, *rec), - UnionTags::insert_into_subs(subs, tags), - TagExt::Any(Variable::EMPTY_TAG_UNION), - )) - } - FlatType::EmptyRecord| - FlatType::EmptyTuple | - FlatType::EmptyTagUnion => None, - }, - Content::Error => Content::Error, - */ - }, - Content::RangedNumber(range) => { - use roc_types::num::NumericRange::*; - - match range { - IntAtLeastSigned(int_lit_width) => int_lit_width_to_mono_type_id(int_lit_width), - IntAtLeastEitherSign(int_lit_width) => int_lit_width_to_mono_type_id(int_lit_width), - NumAtLeastSigned(int_lit_width) => int_lit_width_to_mono_type_id(int_lit_width), - NumAtLeastEitherSign(int_lit_width) => int_lit_width_to_mono_type_id(int_lit_width), - } - } - Content::Alias(symbol, args, real, kind) => { - match kind { - AliasKind::Opaque if symbol.is_builtin() => { - let args_slice = SubsSlice::new(args.variables_start, args.type_variables_len); - lower_builtin(env, subs, symbol, args_slice) - } - _ => { - let mono_id = lower_var(env, subs, real)?; - let todo = (); // TODO record in debuginfo the alias name for whatever we're lowering. - - mono_id + match range { + IntAtLeastSigned(int_lit_width) => int_lit_width_to_mono_type_id(int_lit_width), + IntAtLeastEitherSign(int_lit_width) => { + int_lit_width_to_mono_type_id(int_lit_width) + } + NumAtLeastSigned(int_lit_width) => int_lit_width_to_mono_type_id(int_lit_width), + NumAtLeastEitherSign(int_lit_width) => { + int_lit_width_to_mono_type_id(int_lit_width) + } } } - } - Content::FlexVar(_) - | Content::RigidVar(_) - | Content::FlexAbleVar(_, _) - | Content::RigidAbleVar(_, _) => { - // The only way we should reach this branch is in something like a `crash`. - MonoTypeId::CRASH - } - Content::ErasedLambda | Content::LambdaSet(_) => { - unreachable!( + Content::Alias(symbol, args, real, kind) => { + match kind { + AliasKind::Opaque if symbol.is_builtin() => { + let args_slice = + SubsSlice::new(args.variables_start, args.type_variables_len); + self.lower_builtin(subs, symbol, args_slice) + } + _ => { + let mono_id = self.lower_var(subs, real)?; + let todo = (); // TODO record in debuginfo the alias name for whatever we're lowering. + + mono_id + } + } + } + Content::FlexVar(_) + | Content::RigidVar(_) + | Content::FlexAbleVar(_, _) + | Content::RigidAbleVar(_, _) => { + // The only way we should reach this branch is in something like a `crash`. + MonoTypeId::CRASH + } + Content::ErasedLambda | Content::LambdaSet(_) => { + unreachable!( "This new monomorphization implementation must not do anything with lambda sets, because they'll be handled later!" ); - } - content => { - todo!("specialize this Content: {content:?}"); - } - }; + } + content => { + todo!("specialize this Content: {content:?}"); + } + }; - // This var is now known to be monomorphic, so we don't repeat this work again later. - // (We don't insert entries for Unit values.) - env.cache.inner.insert(root_var, mono_id); + // This var is now known to be monomorphic, so we don't repeat this work again later. + // (We don't insert entries for Unit values.) + self.cache.inner.insert(root_var, mono_id); - Some(mono_id) + Some(mono_id) + } } fn int_lit_width_to_mono_type_id(int_lit_width: roc_can::num::IntLitWidth) -> MonoTypeId { @@ -521,7 +565,7 @@ fn number_args_to_mono_id( // labeled_mono_ids.extend( // fields // .sorted_iterator(subs, ext) -// .map(|(label, field)| (label, lower_var(env, subs, *field.as_inner()))), +// .map(|(label, field)| (label, self.lower_var( subs, *field.as_inner()))), // ); // // If the ext record is nonempty, set its fields to be the next ones we handle, and loop back. @@ -602,29 +646,7 @@ fn number_args_to_mono_id( // problems: &mut impl Push, // ) { // for var in vars { -// if let Some(var) = lower_var(env, *var) // hmm not sure if this is still a good idea as a helper function +// if let Some(var) = self.lower_var( *var) // hmm not sure if this is still a good idea as a helper function // *var = ; // } // } - -fn lower_builtin>( - env: &mut Env<'_, '_, '_, '_, '_, '_, P>, - subs: &Subs, - symbol: Symbol, - args: SubsSlice, -) -> MonoTypeId { - if symbol == Symbol::NUM_NUM { - number_args_to_mono_id(args, subs, env.problems) - } else if symbol == Symbol::NUM_FLOATINGPOINT { - num_num_args_to_mono_id(symbol, args, subs, env.problems) - } else if symbol == Symbol::LIST_LIST { - todo!(); - // let mut new_args = args - // .into_iter() - // .flat_map(|var_index| lower_var(env, subs, subs[var_index])); - - // let arg = new_args.next(); - } else { - todo!("implement lower_builtin for symbol {symbol:?} - or, if all the builtins are already in here, report a compiler bug instead of panicking like this."); - } -} diff --git a/crates/compiler/specialize_types/tests/helpers/mod.rs b/crates/compiler/specialize_types/tests/helpers/mod.rs index dec72f907c..9056a66753 100644 --- a/crates/compiler/specialize_types/tests/helpers/mod.rs +++ b/crates/compiler/specialize_types/tests/helpers/mod.rs @@ -4,7 +4,7 @@ use roc_load::LoadedModule; use roc_region::all::Region; use roc_solve::FunctionKind; use roc_specialize_types::{ - DebugInfo, Env, Interns, MonoCache, MonoExpr, MonoExprs, MonoTypes, RecordFieldIds, + DebugInfo, Env, Interns, MonoExpr, MonoExprs, MonoTypeCache, MonoTypes, RecordFieldIds, TupleElemIds, }; use test_compile::{trim_and_deindent, SpecializedExprOut}; @@ -59,7 +59,7 @@ fn specialize_expr<'a>( let mut problems = Vec::new(); let mut debug_info: Option = None; - let mut types_cache = MonoCache::from_solved_subs(&solved); + let mut types_cache = MonoTypeCache::from_solved_subs(&solved); let mut mono_types = MonoTypes::new(); let mut mono_exprs = MonoExprs::new(); @@ -152,10 +152,10 @@ fn dbg_mono_expr_help<'a>( MonoExpr::Number(number) => { write!(buf, "Number({:?})", number).unwrap(); } - MonoExpr::Struct(expr_ids) => { + MonoExpr::Struct(field_exprs) => { write!(buf, "Struct([").unwrap(); - for (index, expr) in mono_exprs.iter_slice(expr_ids.as_slice()).enumerate() { + for (index, expr) in mono_exprs.iter_slice(field_exprs.as_slice()).enumerate() { if index > 0 { write!(buf, ", ").unwrap(); } diff --git a/crates/test_compile/src/help_specialize.rs b/crates/test_compile/src/help_specialize.rs index 88342ea82b..c1ea189b0a 100644 --- a/crates/test_compile/src/help_specialize.rs +++ b/crates/test_compile/src/help_specialize.rs @@ -2,8 +2,8 @@ use crate::SolvedExpr; use bumpalo::Bump; use roc_region::all::Region; use roc_specialize_types::{ - DebugInfo, Env, Interns, MonoCache, MonoExprId, MonoExprs, MonoTypes, Problem, RecordFieldIds, - TupleElemIds, + DebugInfo, Env, Interns, MonoExprId, MonoExprs, MonoTypeCache, MonoTypes, Problem, + RecordFieldIds, TupleElemIds, }; #[derive(Debug)] @@ -29,7 +29,7 @@ impl SpecializedExpr { let mut solved_out = self.solved_expr.solve_expr(input); let mut problems = Vec::new(); let mut debug_info: Option = None; - let mut types_cache = MonoCache::from_solved_subs(&solved_out.subs); + let mut types_cache = MonoTypeCache::from_solved_subs(&solved_out.subs); let mut mono_types = MonoTypes::new(); let mut mono_exprs = MonoExprs::new(); From 2974dcbc18fc2a92692c7827f9d4c7c35448ba00 Mon Sep 17 00:00:00 2001 From: Agus Zubiaga Date: Sat, 23 Nov 2024 02:20:47 -0300 Subject: [PATCH 36/65] Fix warnings in specialize_types --- .../specialize_types/src/debug_info.rs | 1 + .../specialize_types/src/foreign_symbol.rs | 2 +- crates/compiler/specialize_types/src/lib.rs | 4 ++++ .../specialize_types/src/mono_expr.rs | 17 +++++++++-------- .../compiler/specialize_types/src/mono_ir.rs | 12 +++++------- .../specialize_types/src/mono_module.rs | 12 +++--------- .../specialize_types/src/mono_type.rs | 7 ++----- .../specialize_types/src/specialize_type.rs | 19 +++++++------------ .../specialize_types/tests/helpers/mod.rs | 10 +++++----- .../tests/specialize_primitives.rs | 2 +- crates/soa/src/soa_slice.rs | 6 ++++++ crates/test_compile/src/deindent.rs | 2 +- crates/test_compile/src/help_can.rs | 4 ++-- crates/test_compile/src/help_constrain.rs | 2 +- crates/test_compile/src/help_solve.rs | 2 +- crates/test_compile/src/help_specialize.rs | 2 +- crates/test_compile/src/lib.rs | 4 ++-- 17 files changed, 52 insertions(+), 56 deletions(-) diff --git a/crates/compiler/specialize_types/src/debug_info.rs b/crates/compiler/specialize_types/src/debug_info.rs index f3d7829b45..4283a978b7 100644 --- a/crates/compiler/specialize_types/src/debug_info.rs +++ b/crates/compiler/specialize_types/src/debug_info.rs @@ -1,3 +1,4 @@ +#[derive(Default)] pub struct DebugInfo; impl DebugInfo { diff --git a/crates/compiler/specialize_types/src/foreign_symbol.rs b/crates/compiler/specialize_types/src/foreign_symbol.rs index 8b1ead1461..b7f3dcc7e5 100644 --- a/crates/compiler/specialize_types/src/foreign_symbol.rs +++ b/crates/compiler/specialize_types/src/foreign_symbol.rs @@ -1,7 +1,7 @@ use roc_module::ident::ForeignSymbol; use soa::Id; -#[derive(Debug)] +#[derive(Debug, Default)] pub struct ForeignSymbols { inner: Vec, } diff --git a/crates/compiler/specialize_types/src/lib.rs b/crates/compiler/specialize_types/src/lib.rs index 1caff70436..66a104de33 100644 --- a/crates/compiler/specialize_types/src/lib.rs +++ b/crates/compiler/specialize_types/src/lib.rs @@ -1,3 +1,7 @@ +// TODO [mono2]: re-enable when ready +#![allow(dead_code)] +#![allow(clippy::too_many_arguments)] + mod debug_info; mod foreign_symbol; mod mono_expr; diff --git a/crates/compiler/specialize_types/src/mono_expr.rs b/crates/compiler/specialize_types/src/mono_expr.rs index 1e8a883b6a..c566ccbd1e 100644 --- a/crates/compiler/specialize_types/src/mono_expr.rs +++ b/crates/compiler/specialize_types/src/mono_expr.rs @@ -1,5 +1,5 @@ use crate::{ - mono_ir::{MonoExpr, MonoExprId, MonoExprs, MonoStmt, MonoStmtId}, + mono_ir::{MonoExpr, MonoExprId, MonoExprs}, mono_module::Interns, mono_num::Number, mono_type::{MonoType, MonoTypes, Primitive}, @@ -9,10 +9,10 @@ use crate::{ use bumpalo::{collections::Vec, Bump}; use roc_can::expr::{Expr, IntValue}; use roc_collections::{Push, VecMap}; -use roc_module::{ident::Lowercase, symbol::ModuleId}; +use roc_module::symbol::ModuleId; use roc_region::all::Region; use roc_solve::module::Solved; -use roc_types::subs::{Content, FlatType, Subs, Variable}; +use roc_types::subs::{Content, Subs, Variable}; use soa::NonEmptySlice; /// Function bodies that have already been specialized. @@ -31,7 +31,8 @@ impl MonoFnCache { fn_var: Variable, // Given a ModuleId and Variable (to uniquely identify the canonical fn Expr within its module), // get the canonical Expr of the function itself. We need this to create a specialization of it. - get_fn_expr: F, + // TODO [mono2] + _get_fn_expr: F, // This tells us which specialization of the function we want. mono_type_id: MonoTypeId, ) -> MonoExprId { @@ -94,7 +95,7 @@ impl<'a, 'c, 'd, 'i, 's, 't, P: Push> Env<'a, 'c, 'd, 'i, 's, 't, P> { &mut self.record_field_ids, &mut self.tuple_elem_ids, problems, - &mut self.debug_info, + self.debug_info, var, ) }; @@ -160,13 +161,13 @@ impl<'a, 'c, 'd, 'i, 's, 't, P: Push> Env<'a, 'c, 'd, 'i, 's, 't, P> { ))), Expr::EmptyRecord => { // Empty records are zero-sized and should be discarded. - return None; + None } Expr::Record { record_var: _, fields, } => { - let todo = (); // TODO: store debuginfo for the record type, including ideally type alias and/or opaque type names. Do this before early-returning for single-field records. + // TODO [mono2]: store debuginfo for the record type, including ideally type alias and/or opaque type names. Do this before early-returning for single-field records. // Check for records with 0-1 fields before sorting or reserving a slice of IDs (which might be unnecessary). // We'll check again after discarding zero-sized fields, because we might end up with 0 or 1 fields remaining. @@ -178,7 +179,7 @@ impl<'a, 'c, 'd, 'i, 's, 't, P: Push> Env<'a, 'c, 'd, 'i, 's, 't, P> { } // Sort the fields alphabetically by name. - let mut fields = Vec::from_iter_in(fields.into_iter(), self.arena); + let mut fields = Vec::from_iter_in(fields, self.arena); fields.sort_by(|(name1, _), (name2, _)| name1.cmp(name2)); diff --git a/crates/compiler/specialize_types/src/mono_ir.rs b/crates/compiler/specialize_types/src/mono_ir.rs index dfe5ac12e1..854e8b4883 100644 --- a/crates/compiler/specialize_types/src/mono_ir.rs +++ b/crates/compiler/specialize_types/src/mono_ir.rs @@ -25,7 +25,7 @@ pub struct Def { pub expr_type: MonoTypeId, } -#[derive(Debug)] +#[derive(Debug, Default)] pub struct MonoExprs { // TODO convert to Vec2 exprs: Vec, @@ -57,7 +57,7 @@ impl MonoExprs { ); // Safety: we should only ever hand out MonoExprIds that are valid indices into here. - unsafe { self.exprs.get_unchecked(id.inner.index() as usize) } + unsafe { self.exprs.get_unchecked(id.inner.index()) } } pub fn get_region(&self, id: MonoExprId) -> Region { @@ -67,7 +67,7 @@ impl MonoExprs { ); // Safety: we should only ever hand out MonoExprIds that are valid indices into here. - unsafe { *self.regions.get_unchecked(id.inner.index() as usize) } + unsafe { *self.regions.get_unchecked(id.inner.index()) } } pub fn reserve_id(&mut self) -> MonoExprId { @@ -108,7 +108,7 @@ impl MonoExprs { "A MonoExprId was not found in MonoExprs. This should never happen!" ); - let index = id.inner.index() as usize; + let index = id.inner.index(); // Safety: we should only ever hand out MonoExprIds that are valid indices into here. unsafe { @@ -118,7 +118,7 @@ impl MonoExprs { } pub fn iter_slice(&self, exprs: Slice) -> impl Iterator { - exprs.indices().into_iter().map(|index| { + exprs.indices().map(|index| { debug_assert!( self.exprs.get(index).is_some(), "A Slice index was not found in MonoExprs. This should never happen!" @@ -333,7 +333,6 @@ pub struct WhenBranch { pub guard: Option, } -#[allow(dead_code)] #[derive(Clone, Copy, Debug)] pub enum MonoPattern { Identifier(IdentId), @@ -365,7 +364,6 @@ pub enum MonoPattern { Underscore, } -#[allow(dead_code)] #[derive(Clone, Copy, Debug)] pub enum DestructType { Required, diff --git a/crates/compiler/specialize_types/src/mono_module.rs b/crates/compiler/specialize_types/src/mono_module.rs index 7a98271283..dc10d9ca89 100644 --- a/crates/compiler/specialize_types/src/mono_module.rs +++ b/crates/compiler/specialize_types/src/mono_module.rs @@ -4,7 +4,6 @@ use roc_types::subs::Subs; use crate::{foreign_symbol::ForeignSymbols, mono_type::MonoTypes, DebugInfo}; -#[allow(dead_code)] pub struct MonoModule { mono_types: MonoTypes, foreign_symbols: ForeignSymbols, @@ -12,7 +11,6 @@ pub struct MonoModule { debug_info: DebugInfo, } -#[allow(dead_code)] impl MonoModule { pub fn from_typed_can_module(_subs: &Solved) -> Self { Self { @@ -29,7 +27,7 @@ impl MonoModule { pub struct InternedStrId(u32); /// TODO move this to its own crate -#[derive(Debug)] +#[derive(Debug, Default)] pub struct Interns<'a> { interned: Vec<&'a str>, } @@ -71,13 +69,9 @@ impl<'a> Interns<'a> { } pub fn try_get_id(&self, _arena: &'a Bump, string: &'a str) -> Option { - match self - .interned + self.interned .iter() .position(|&interned| interned == string) - { - Some(index) => Some(InternedStrId(index as u32)), - None => None, - } + .map(|index| InternedStrId(index as u32)) } } diff --git a/crates/compiler/specialize_types/src/mono_type.rs b/crates/compiler/specialize_types/src/mono_type.rs index 107f933b8a..111fae81f5 100644 --- a/crates/compiler/specialize_types/src/mono_type.rs +++ b/crates/compiler/specialize_types/src/mono_type.rs @@ -74,16 +74,13 @@ impl MonoTypeId { } } -#[derive(Debug)] +#[derive(Debug, Default)] pub struct MonoTypes { entries: Vec, - #[allow(dead_code)] ids: Vec, - #[allow(dead_code)] slices: Vec<(NonZeroU16, MonoTypeId)>, // TODO make this a Vec2 } -#[allow(dead_code)] impl MonoTypes { pub fn new() -> Self { Self { @@ -132,7 +129,7 @@ impl MonoTypes { } } - pub(crate) fn add_primitive(&mut self, primitive: Primitive) -> MonoTypeId { + pub(crate) fn add_primitive(&mut self, _primitive: Primitive) -> MonoTypeId { todo!("if it's one of the hardcoded ones, find the associated MonoTypeId; otherwise, store it etc."); } diff --git a/crates/compiler/specialize_types/src/specialize_type.rs b/crates/compiler/specialize_types/src/specialize_type.rs index 5a51d1d6be..0a747330a0 100644 --- a/crates/compiler/specialize_types/src/specialize_type.rs +++ b/crates/compiler/specialize_types/src/specialize_type.rs @@ -89,14 +89,10 @@ impl MonoTypeCache { struct Env<'a, 'c, 'd, 'e, 'f, 'm, 'p, P> { arena: &'a Bump, cache: &'c mut MonoTypeCache, - #[allow(dead_code)] mono_types: &'m mut MonoTypes, - #[allow(dead_code)] field_ids: &'f mut RecordFieldIds, - #[allow(dead_code)] elem_ids: &'e mut TupleElemIds, problems: &'p mut P, - #[allow(dead_code)] debug_info: &'d mut Option, } @@ -130,7 +126,8 @@ impl<'a, 'c, 'd, 'e, 'f, 'm, 'p, P: Push> Env<'a, 'c, 'd, 'e, 'f, 'm, ' subs: &Subs, arg_vars: SubsSlice, ret_var: Variable, - fx_var: Variable, + // TODO [mono2] + _fx_var: Variable, ) -> MonoTypeId { let func = self.lower_var(subs, ret_var); let mut mono_args = Vec::with_capacity_in(arg_vars.len(), self.arena); @@ -141,7 +138,7 @@ impl<'a, 'c, 'd, 'e, 'f, 'm, 'p, P: Push> Env<'a, 'c, 'd, 'e, 'f, 'm, ' .flat_map(|var_index| self.lower_var(subs, subs[var_index])), ); - let todo = (); // TODO populate debuginfo as appropriate + // TODO [mono2] populate debuginfo as appropriate self.mono_types.add_function(func, mono_args) } @@ -293,10 +290,8 @@ impl<'a, 'c, 'd, 'e, 'f, 'm, 'p, P: Push> Env<'a, 'c, 'd, 'e, 'f, 'm, ' self.lower_builtin(subs, symbol, args_slice) } _ => { - let mono_id = self.lower_var(subs, real)?; - let todo = (); // TODO record in debuginfo the alias name for whatever we're lowering. - - mono_id + // TODO [mono2] record in debuginfo the alias name for whatever we're lowering. + self.lower_var(subs, real)? } } } @@ -424,7 +419,7 @@ fn num_num_args_to_mono_id( } } } - Content::RangedNumber(numeric_range) => todo!(), + Content::RangedNumber(_numeric_range) => todo!(), _ => { // This is an invalid number type, so break out of // the alias-unrolling loop in order to return an error. @@ -485,7 +480,7 @@ fn number_args_to_mono_id( } } } - Content::RangedNumber(numeric_range) => todo!(), + Content::RangedNumber(_numeric_range) => todo!(), _ => { // This is an invalid number type, so break out of // the alias-unrolling loop in order to return an error. diff --git a/crates/compiler/specialize_types/tests/helpers/mod.rs b/crates/compiler/specialize_types/tests/helpers/mod.rs index 9056a66753..e65093ecd0 100644 --- a/crates/compiler/specialize_types/tests/helpers/mod.rs +++ b/crates/compiler/specialize_types/tests/helpers/mod.rs @@ -1,3 +1,6 @@ +// TODO [mono2]: re-enable when ready +#![allow(dead_code)] + use bumpalo::Bump; use core::fmt::Write; use roc_load::LoadedModule; @@ -29,7 +32,7 @@ fn specialize_expr<'a>( }, src, ) = run_load_and_infer( - trim_and_deindent(&arena, src), + trim_and_deindent(arena, src), [], false, FunctionKind::LambdaSet, @@ -64,7 +67,7 @@ fn specialize_expr<'a>( let mut mono_exprs = MonoExprs::new(); let mut env = Env::new( - &arena, + arena, &mut solved, &mut types_cache, &mut mono_types, @@ -96,7 +99,6 @@ fn specialize_expr<'a>( } } -#[allow(dead_code)] #[track_caller] pub fn expect_no_expr(input: impl AsRef) { let arena = Bump::new(); @@ -107,13 +109,11 @@ pub fn expect_no_expr(input: impl AsRef) { assert_eq!(None, actual, "This input expr should have specialized to being dicarded as zero-sized, but it didn't: {:?}", input.as_ref()); } -#[allow(dead_code)] #[track_caller] pub fn expect_mono_expr(input: impl AsRef, mono_expr: MonoExpr) { expect_mono_expr_with_interns(input, |_, _| mono_expr); } -#[allow(dead_code)] #[track_caller] pub fn expect_mono_expr_str(input: impl AsRef, expr_str: impl AsRef) { expect_mono_expr_custom( diff --git a/crates/compiler/specialize_types/tests/specialize_primitives.rs b/crates/compiler/specialize_types/tests/specialize_primitives.rs index 71c7b8b5e2..f24054c9c7 100644 --- a/crates/compiler/specialize_types/tests/specialize_primitives.rs +++ b/crates/compiler/specialize_types/tests/specialize_primitives.rs @@ -164,7 +164,7 @@ mod specialize_primitives { #[test] fn unbound_f64() { - let expected = 3.14159265359; + let expected = std::f64::consts::PI; expect_mono_expr( format!("{expected}"), MonoExpr::Number(Number::Dec(expected)), diff --git a/crates/soa/src/soa_slice.rs b/crates/soa/src/soa_slice.rs index 881063939a..1a9652249e 100644 --- a/crates/soa/src/soa_slice.rs +++ b/crates/soa/src/soa_slice.rs @@ -223,6 +223,9 @@ impl NonEmptySlice { } } + /// # Safety + /// + /// The caller must ensure that the length is nonzero pub const unsafe fn new_unchecked(start: u32, length: u16) -> Self { Self { inner: Slice { @@ -241,6 +244,9 @@ impl NonEmptySlice { } } + /// # Safety + /// + /// The caller must ensure that the length is nonzero pub const unsafe fn from_slice_unchecked(slice: Slice) -> Self { Self::new(slice.start, NonZeroU16::new_unchecked(slice.length)) } diff --git a/crates/test_compile/src/deindent.rs b/crates/test_compile/src/deindent.rs index 434d63e413..90f67cbd2d 100644 --- a/crates/test_compile/src/deindent.rs +++ b/crates/test_compile/src/deindent.rs @@ -60,7 +60,7 @@ pub fn trim_and_deindent<'a>(arena: &'a Bump, input: &'a str) -> &'a str { let mut final_str_len = 0; lines.iter_mut().for_each(|line| { - if line.starts_with(" ") { + if line.starts_with(' ') { *line = line.get(smallest_indent..).unwrap_or(""); } final_str_len += line.len() + 1; // +1 for the newline that will be added to the end of this line. diff --git a/crates/test_compile/src/help_can.rs b/crates/test_compile/src/help_can.rs index 4c59a470db..8b42b069ee 100644 --- a/crates/test_compile/src/help_can.rs +++ b/crates/test_compile/src/help_can.rs @@ -64,7 +64,7 @@ impl CanExpr { let dep_idents = IdentIds::exposed_builtins(0); let mut env = Env::new( - &self.arena(), + self.arena(), input, home, Path::new("Test.roc"), @@ -133,7 +133,7 @@ impl CanExpr { } pub fn arena(&self) -> &Bump { - &self.parse_expr.arena() + self.parse_expr.arena() } pub fn home(&self) -> ModuleId { diff --git a/crates/test_compile/src/help_constrain.rs b/crates/test_compile/src/help_constrain.rs index e73c5d315a..223c8d77fe 100644 --- a/crates/test_compile/src/help_constrain.rs +++ b/crates/test_compile/src/help_constrain.rs @@ -72,7 +72,7 @@ impl ConstrainedExpr { } pub fn arena(&self) -> &Bump { - &self.can_expr.arena() + self.can_expr.arena() } pub fn home(&self) -> ModuleId { diff --git a/crates/test_compile/src/help_solve.rs b/crates/test_compile/src/help_solve.rs index e3495f47a4..192a1ef24c 100644 --- a/crates/test_compile/src/help_solve.rs +++ b/crates/test_compile/src/help_solve.rs @@ -75,7 +75,7 @@ impl SolvedExpr { } pub fn arena(&self) -> &Bump { - &self.constrained_expr.arena() + self.constrained_expr.arena() } pub fn home(&self) -> ModuleId { diff --git a/crates/test_compile/src/help_specialize.rs b/crates/test_compile/src/help_specialize.rs index c1ea189b0a..b750ec7cc4 100644 --- a/crates/test_compile/src/help_specialize.rs +++ b/crates/test_compile/src/help_specialize.rs @@ -65,6 +65,6 @@ impl SpecializedExpr { } pub fn arena(&self) -> &Bump { - &self.solved_expr.arena() + self.solved_expr.arena() } } diff --git a/crates/test_compile/src/lib.rs b/crates/test_compile/src/lib.rs index 69e28ce482..f483aa5142 100644 --- a/crates/test_compile/src/lib.rs +++ b/crates/test_compile/src/lib.rs @@ -11,10 +11,10 @@ pub use help_parse::ParseExpr; pub use help_solve::{SolvedExpr, SolvedExprOut}; pub use help_specialize::{SpecializedExpr, SpecializedExprOut}; -pub fn can_expr<'a>(input: &'a str) -> CanExprOut { +pub fn can_expr(input: &str) -> CanExprOut { CanExpr::default().can_expr(input) } -pub fn solve_expr<'a>(input: &'a str) -> SolvedExprOut { +pub fn solve_expr(input: &str) -> SolvedExprOut { SolvedExpr::default().solve_expr(input) } From def52edad505bbdf604c4b45890154e9d03ccd7a Mon Sep 17 00:00:00 2001 From: Agus Zubiaga Date: Sat, 23 Nov 2024 03:15:35 -0300 Subject: [PATCH 37/65] Fix "parameter" typo --- crates/compiler/specialize_types/src/specialize_type.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/compiler/specialize_types/src/specialize_type.rs b/crates/compiler/specialize_types/src/specialize_type.rs index 0a747330a0..794bdf269e 100644 --- a/crates/compiler/specialize_types/src/specialize_type.rs +++ b/crates/compiler/specialize_types/src/specialize_type.rs @@ -24,7 +24,7 @@ pub enum Problem { RecordExtWasNotRecord, TupleExtWasNotTuple, /// This can be either an integer specializing to a fractional number type (or vice versa), - /// or the type paramter specializing to a non-numeric type (e.g. Num Str), which should + /// or the type parameter specializing to a non-numeric type (e.g. Num Str), which should /// have been caught during type-checking and changed to an Error type. NumSpecializedToWrongType( Option, // `None` means it specialized to Unit From 13fc87a52a2b1ffbd8c99cf3e3a8871727b6ae9a Mon Sep 17 00:00:00 2001 From: Agus Zubiaga Date: Sat, 23 Nov 2024 10:32:43 -0300 Subject: [PATCH 38/65] Remove test_solve_expr since it can't work without load --- crates/compiler/solve/tests/test_solve_expr.rs | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 crates/compiler/solve/tests/test_solve_expr.rs diff --git a/crates/compiler/solve/tests/test_solve_expr.rs b/crates/compiler/solve/tests/test_solve_expr.rs deleted file mode 100644 index 8330d5d0a8..0000000000 --- a/crates/compiler/solve/tests/test_solve_expr.rs +++ /dev/null @@ -1,18 +0,0 @@ -#[cfg(test)] -mod test_solve_expr { - use roc_can::expr::Expr; - use roc_types::subs::Content; - use test_compile::solve_expr; - - #[test] - fn solve_empty_record() { - let mut output = solve_expr("{}"); - - assert_eq!(output.problems, Vec::new()); - assert!(matches!(output.expr, Expr::EmptyRecord)); - assert_eq!( - Content::Structure(roc_types::subs::FlatType::EmptyRecord), - output.subs.inner_mut().get(output.var).content - ); - } -} From 5384b969793d4b22acef60e2cc2f2e40eaf7fd9e Mon Sep 17 00:00:00 2001 From: Agus Zubiaga Date: Sat, 23 Nov 2024 11:03:44 -0300 Subject: [PATCH 39/65] Remove unnecessary test_compile dep in solve --- Cargo.lock | 1 - crates/compiler/solve/Cargo.toml | 2 -- 2 files changed, 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 74cf30db81..622ccfb31c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3118,7 +3118,6 @@ dependencies = [ "roc_unify", "soa", "tempfile", - "test_compile", "test_solve_helpers", ] diff --git a/crates/compiler/solve/Cargo.toml b/crates/compiler/solve/Cargo.toml index cc059d1aa3..19ba538961 100644 --- a/crates/compiler/solve/Cargo.toml +++ b/crates/compiler/solve/Cargo.toml @@ -45,5 +45,3 @@ lazy_static.workspace = true pretty_assertions.workspace = true regex.workspace = true tempfile.workspace = true - -test_compile.workspace = true From 5d6c47b25fb58bd58b0a6fcc16b01ffacca6cc6c Mon Sep 17 00:00:00 2001 From: Agus Zubiaga Date: Sat, 23 Nov 2024 11:05:09 -0300 Subject: [PATCH 40/65] Update test_syntax snapshot after new Slice debug impl --- .../underscore_expr_in_def.expr.result-ast | 4 +- ...and_signature_is_multiline.expr.result-ast | 4 +- .../pass/ability_multi_line.expr.result-ast | 4 +- .../pass/ability_single_line.expr.result-ast | 4 +- .../pass/ability_two_in_a_row.expr.result-ast | 8 +-- .../pass/ann_closed_union.expr.result-ast | 4 +- .../pass/ann_effectful_fn.expr.result-ast | 4 +- .../pass/ann_open_union.expr.result-ast | 4 +- ...notated_record_destructure.expr.result-ast | 4 +- .../annotated_tag_destructure.expr.result-ast | 4 +- ...nnotated_tuple_destructure.expr.result-ast | 4 +- .../snapshots/pass/basic_docs.expr.result-ast | 4 +- ...a_prefixed_indented_record.expr.result-ast | 4 +- .../comment_after_annotation.expr.result-ast | 4 +- .../comment_after_def.moduledefs.result-ast | 4 +- .../comment_before_colon_def.expr.result-ast | 4 +- .../comment_before_equals_def.expr.result-ast | 4 +- .../snapshots/pass/crash.expr.result-ast | 24 ++++----- .../pass/dbg_stmt_two_exprs.expr.result-ast | 4 +- .../snapshots/pass/def_bang.expr.result-ast | 4 +- ...middle_extra_indents.moduledefs.result-ast | 20 +++---- ...destructure_tag_assignment.expr.result-ast | 4 +- .../tests/snapshots/pass/docs.expr.result-ast | 4 +- ...fectful_closure_statements.expr.result-ast | 16 +++--- .../snapshots/pass/expect.expr.result-ast | 4 +- .../pass/expect_defs.moduledefs.result-ast | 16 +++--- .../pass/expect_single_line.expr.result-ast | 12 ++--- .../pass/extra_newline.expr.result-ast | 4 +- .../extra_newline_in_parens.expr.result-ast | 4 +- .../pass/fn_with_record_arg.expr.result-ast | 4 +- ...nction_with_tuple_ext_type.expr.result-ast | 4 +- .../function_with_tuple_type.expr.result-ast | 4 +- .../snapshots/pass/if_def.expr.result-ast | 4 +- .../pass/import.moduledefs.result-ast | 4 +- .../import_from_package.moduledefs.result-ast | 16 +++--- .../import_with_alias.moduledefs.result-ast | 8 +-- ...import_with_comments.moduledefs.result-ast | 52 +++++++++---------- .../import_with_exposed.moduledefs.result-ast | 12 ++--- .../import_with_params.moduledefs.result-ast | 16 +++--- ...ed_after_multi_backpassing.expr.result-ast | 8 +-- .../pass/ingested_file.moduledefs.result-ast | 8 +-- .../pass/inline_import.expr.result-ast | 8 +-- .../pass/inline_ingested_file.expr.result-ast | 4 +- ...nline_ingested_file_no_ann.expr.result-ast | 4 +- ..._closing_indent_not_enough.expr.result-ast | 4 +- ...e_indent_no_trailing_comma.expr.result-ast | 4 +- ...indent_with_trailing_comma.expr.result-ast | 4 +- .../snapshots/pass/mixed_docs.expr.result-ast | 4 +- .../module_def_newline.moduledefs.result-ast | 8 +-- ...i_backpassing_in_def.moduledefs.result-ast | 4 +- .../pass/multiline_str_in_pat.expr.result-ast | 4 +- .../pass/multiline_string.expr.result-ast | 12 ++--- .../multiline_type_signature.expr.result-ast | 4 +- ...ype_signature_with_comment.expr.result-ast | 4 +- .../negative_in_apply_def.expr.result-ast | 4 +- ...ested_def_annotation.moduledefs.result-ast | 8 +-- .../pass/newline_after_equals.expr.result-ast | 4 +- ...nd_spaces_before_less_than.expr.result-ast | 4 +- .../pass/newline_in_packages.full.result-ast | 4 +- ..._in_type_alias_application.expr.result-ast | 4 +- .../pass/newline_in_type_def.expr.result-ast | 4 +- .../pass/old_app_header.full.result-ast | 8 +-- .../snapshots/pass/one_def.expr.result-ast | 4 +- .../pass/one_spaced_def.expr.result-ast | 4 +- ...ructure_first_item_in_body.expr.result-ast | 4 +- .../pass/opaque_has_abilities.expr.result-ast | 40 +++++++------- .../pass/opaque_simple.moduledefs.result-ast | 4 +- ..._with_type_arguments.moduledefs.result-ast | 4 +- .../outdented_app_with_record.expr.result-ast | 4 +- .../outdented_colon_in_record.expr.result-ast | 4 +- .../pass/outdented_list.expr.result-ast | 4 +- .../pass/outdented_record.expr.result-ast | 4 +- .../parens_in_type_def_apply.expr.result-ast | 4 +- .../parenthesized_type_def.expr.result-ast | 4 +- ...ized_type_def_space_before.expr.result-ast | 4 +- .../pass/parse_alias.expr.result-ast | 4 +- .../pass/parse_as_ann.expr.result-ast | 4 +- .../pass/pizza_question.moduledefs.result-ast | 4 +- .../record_destructure_def.expr.result-ast | 8 +-- ...ord_destructure_field_bang.expr.result-ast | 4 +- .../record_func_type_decl.expr.result-ast | 4 +- .../record_type_with_function.expr.result-ast | 4 +- ...cord_updater_literal_apply.expr.result-ast | 4 +- .../pass/return_in_if.expr.result-ast | 8 +-- .../pass/return_in_static_def.expr.result-ast | 12 ++--- .../pass/return_in_when.expr.result-ast | 8 +-- .../return_only_statement.expr.result-ast | 4 +- .../pass/separate_defs.moduledefs.result-ast | 24 ++++----- .../pass/space_before_colon.full.result-ast | 4 +- ...andalone_module_defs.moduledefs.result-ast | 12 ++--- ...estion_multiple_defs.moduledefs.result-ast | 12 ++--- .../suffixed_question_one_def.full.result-ast | 16 +++--- ...xed_question_optional_last.full.result-ast | 4 +- .../pass/tag_destructure_bang.expr.result-ast | 4 +- ..._destructure_bang_no_space.expr.result-ast | 4 +- .../tuple_access_after_ident.expr.result-ast | 4 +- .../tuple_destructure_bang.expr.result-ast | 4 +- .../snapshots/pass/tuple_type.expr.result-ast | 4 +- .../pass/tuple_type_ext.expr.result-ast | 4 +- .../pass/two_spaced_def.expr.result-ast | 8 +-- .../type_decl_with_underscore.expr.result-ast | 4 +- .../pass/type_signature_def.expr.result-ast | 4 +- ...ype_signature_function_def.expr.result-ast | 4 +- ...core_in_assignment_pattern.expr.result-ast | 20 +++---- .../pass/value_def_confusion.expr.result-ast | 8 +-- .../pass/when_in_assignment.expr.result-ast | 4 +- .../pass/when_in_function.expr.result-ast | 4 +- ...nction_python_style_indent.expr.result-ast | 4 +- .../where_clause_function.expr.result-ast | 4 +- ...e_multiple_bound_abilities.expr.result-ast | 8 +-- .../where_clause_multiple_has.expr.result-ast | 4 +- ...ltiple_has_across_newlines.expr.result-ast | 4 +- .../where_clause_non_function.expr.result-ast | 4 +- .../where_clause_on_newline.expr.result-ast | 4 +- .../pass/where_ident.expr.result-ast | 4 +- 115 files changed, 390 insertions(+), 390 deletions(-) diff --git a/crates/compiler/test_syntax/tests/snapshots/malformed/underscore_expr_in_def.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/malformed/underscore_expr_in_def.expr.result-ast index 43f45640ee..c292137433 100644 --- a/crates/compiler/test_syntax/tests/snapshots/malformed/underscore_expr_in_def.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/malformed/underscore_expr_in_def.expr.result-ast @@ -7,10 +7,10 @@ Defs( @0-3, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], spaces: [], type_defs: [ diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/ability_demand_signature_is_multiline.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/ability_demand_signature_is_multiline.expr.result-ast index ede6aa85bb..d926638def 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/ability_demand_signature_is_multiline.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/ability_demand_signature_is_multiline.expr.result-ast @@ -8,10 +8,10 @@ SpaceAfter( @0-43, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], spaces: [], type_defs: [ diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/ability_multi_line.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/ability_multi_line.expr.result-ast index 1cb962fc21..09ef23f942 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/ability_multi_line.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/ability_multi_line.expr.result-ast @@ -8,10 +8,10 @@ SpaceAfter( @0-52, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], spaces: [], type_defs: [ diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/ability_single_line.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/ability_single_line.expr.result-ast index 017412f9d8..5dd22a2163 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/ability_single_line.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/ability_single_line.expr.result-ast @@ -8,10 +8,10 @@ SpaceAfter( @0-55, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], spaces: [], type_defs: [ diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/ability_two_in_a_row.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/ability_two_in_a_row.expr.result-ast index 7e21cc2acb..25d88f1d00 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/ability_two_in_a_row.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/ability_two_in_a_row.expr.result-ast @@ -10,12 +10,12 @@ SpaceAfter( @53-104, ], space_before: [ - Slice { start: 0, length: 0 }, - Slice { start: 0, length: 2 }, + Slice { start: 0, length: 0 }, + Slice { start: 0, length: 2 }, ], space_after: [ - Slice { start: 0, length: 0 }, - Slice { start: 2, length: 0 }, + Slice { start: 0, length: 0 }, + Slice { start: 2, length: 0 }, ], spaces: [ Newline, diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/ann_closed_union.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/ann_closed_union.expr.result-ast index 12f2249a2b..14c8962d0d 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/ann_closed_union.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/ann_closed_union.expr.result-ast @@ -7,10 +7,10 @@ Defs( @0-38, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], spaces: [], type_defs: [], diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/ann_effectful_fn.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/ann_effectful_fn.expr.result-ast index 998b247a5c..d0e20356a4 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/ann_effectful_fn.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/ann_effectful_fn.expr.result-ast @@ -8,10 +8,10 @@ SpaceAfter( @0-89, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], spaces: [], type_defs: [], diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/ann_open_union.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/ann_open_union.expr.result-ast index 3d78d0c00e..6258a99a03 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/ann_open_union.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/ann_open_union.expr.result-ast @@ -7,10 +7,10 @@ Defs( @0-39, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], spaces: [], type_defs: [], diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/annotated_record_destructure.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/annotated_record_destructure.expr.result-ast index 30bd3a25b3..548b3a8cb6 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/annotated_record_destructure.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/annotated_record_destructure.expr.result-ast @@ -8,10 +8,10 @@ SpaceAfter( @0-49, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], spaces: [], type_defs: [], diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/annotated_tag_destructure.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/annotated_tag_destructure.expr.result-ast index a737af53a1..2cc93df84f 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/annotated_tag_destructure.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/annotated_tag_destructure.expr.result-ast @@ -8,10 +8,10 @@ SpaceAfter( @0-46, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], spaces: [], type_defs: [], diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/annotated_tuple_destructure.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/annotated_tuple_destructure.expr.result-ast index 6c548a4d9d..d0b6667d56 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/annotated_tuple_destructure.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/annotated_tuple_destructure.expr.result-ast @@ -8,10 +8,10 @@ SpaceAfter( @0-41, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], spaces: [], type_defs: [], diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/basic_docs.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/basic_docs.expr.result-ast index c6dd36f721..d284b5583c 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/basic_docs.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/basic_docs.expr.result-ast @@ -9,10 +9,10 @@ SpaceBefore( @107-112, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], spaces: [], type_defs: [], diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/comma_prefixed_indented_record.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/comma_prefixed_indented_record.expr.result-ast index 0ee368ce19..46c57cca11 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/comma_prefixed_indented_record.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/comma_prefixed_indented_record.expr.result-ast @@ -8,10 +8,10 @@ SpaceAfter( @0-164, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], spaces: [], type_defs: [ diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/comment_after_annotation.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/comment_after_annotation.expr.result-ast index 54b7042d72..75702a0339 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/comment_after_annotation.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/comment_after_annotation.expr.result-ast @@ -7,10 +7,10 @@ Defs( @0-3, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], spaces: [], type_defs: [ diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/comment_after_def.moduledefs.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/comment_after_def.moduledefs.result-ast index de7670cd3b..0af08c9c5a 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/comment_after_def.moduledefs.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/comment_after_def.moduledefs.result-ast @@ -6,10 +6,10 @@ Defs { @0-7, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 1 }, + Slice { start: 0, length: 1 }, ], spaces: [ LineComment( diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/comment_before_colon_def.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/comment_before_colon_def.expr.result-ast index fe68d890c5..7bb65ad6d3 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/comment_before_colon_def.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/comment_before_colon_def.expr.result-ast @@ -7,10 +7,10 @@ Defs( @0-5, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], spaces: [], type_defs: [], diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/comment_before_equals_def.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/comment_before_equals_def.expr.result-ast index 9aa645ec20..e31c6457c8 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/comment_before_equals_def.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/comment_before_equals_def.expr.result-ast @@ -7,10 +7,10 @@ Defs( @0-5, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], spaces: [], type_defs: [], diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/crash.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/crash.expr.result-ast index ec23824430..f8b2794f15 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/crash.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/crash.expr.result-ast @@ -16,18 +16,18 @@ SpaceAfter( @75-101, ], space_before: [ - Slice { start: 0, length: 0 }, - Slice { start: 0, length: 1 }, - Slice { start: 1, length: 1 }, - Slice { start: 2, length: 1 }, - Slice { start: 3, length: 1 }, + Slice { start: 0, length: 0 }, + Slice { start: 0, length: 1 }, + Slice { start: 1, length: 1 }, + Slice { start: 2, length: 1 }, + Slice { start: 3, length: 1 }, ], space_after: [ - Slice { start: 0, length: 0 }, - Slice { start: 1, length: 0 }, - Slice { start: 2, length: 0 }, - Slice { start: 3, length: 0 }, - Slice { start: 4, length: 0 }, + Slice { start: 0, length: 0 }, + Slice { start: 1, length: 0 }, + Slice { start: 2, length: 0 }, + Slice { start: 3, length: 0 }, + Slice { start: 4, length: 0 }, ], spaces: [ Newline, @@ -140,10 +140,10 @@ SpaceAfter( @81-93, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], spaces: [], type_defs: [], diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/dbg_stmt_two_exprs.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/dbg_stmt_two_exprs.expr.result-ast index 12532d964a..400b544c8d 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/dbg_stmt_two_exprs.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/dbg_stmt_two_exprs.expr.result-ast @@ -10,10 +10,10 @@ SpaceAfter( @5-6, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], spaces: [], type_defs: [], diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/def_bang.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/def_bang.expr.result-ast index dc5947f6a5..d67a2bce27 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/def_bang.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/def_bang.expr.result-ast @@ -8,10 +8,10 @@ SpaceAfter( @0-33, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], spaces: [], type_defs: [], diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/defs_suffixed_middle_extra_indents.moduledefs.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/defs_suffixed_middle_extra_indents.moduledefs.result-ast index 5d64a16465..e215ef27e2 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/defs_suffixed_middle_extra_indents.moduledefs.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/defs_suffixed_middle_extra_indents.moduledefs.result-ast @@ -8,12 +8,12 @@ Defs { @176-266, ], space_before: [ - Slice { start: 0, length: 0 }, - Slice { start: 0, length: 2 }, + Slice { start: 0, length: 0 }, + Slice { start: 0, length: 2 }, ], space_after: [ - Slice { start: 0, length: 0 }, - Slice { start: 2, length: 1 }, + Slice { start: 0, length: 0 }, + Slice { start: 2, length: 1 }, ], spaces: [ Newline, @@ -38,12 +38,12 @@ Defs { @97-111, ], space_before: [ - Slice { start: 0, length: 0 }, - Slice { start: 0, length: 1 }, + Slice { start: 0, length: 0 }, + Slice { start: 0, length: 1 }, ], space_after: [ - Slice { start: 0, length: 0 }, - Slice { start: 1, length: 0 }, + Slice { start: 0, length: 0 }, + Slice { start: 1, length: 0 }, ], spaces: [ Newline, @@ -113,10 +113,10 @@ Defs { @215-224, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], spaces: [], type_defs: [], diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/destructure_tag_assignment.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/destructure_tag_assignment.expr.result-ast index 55d2f2eece..50bf0bf832 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/destructure_tag_assignment.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/destructure_tag_assignment.expr.result-ast @@ -8,10 +8,10 @@ SpaceAfter( @0-36, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], spaces: [], type_defs: [], diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/docs.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/docs.expr.result-ast index 7f377d15d7..8716e6de4a 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/docs.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/docs.expr.result-ast @@ -9,10 +9,10 @@ SpaceBefore( @48-53, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], spaces: [], type_defs: [], diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/effectful_closure_statements.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/effectful_closure_statements.expr.result-ast index 6fe182b7bb..0f7df5fc3e 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/effectful_closure_statements.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/effectful_closure_statements.expr.result-ast @@ -19,14 +19,14 @@ SpaceAfter( @61-154, ], space_before: [ - Slice { start: 0, length: 0 }, - Slice { start: 0, length: 1 }, - Slice { start: 1, length: 2 }, + Slice { start: 0, length: 0 }, + Slice { start: 0, length: 1 }, + Slice { start: 1, length: 2 }, ], space_after: [ - Slice { start: 0, length: 0 }, - Slice { start: 1, length: 0 }, - Slice { start: 3, length: 0 }, + Slice { start: 0, length: 0 }, + Slice { start: 1, length: 0 }, + Slice { start: 3, length: 0 }, ], spaces: [ Newline, @@ -89,10 +89,10 @@ SpaceAfter( @86-119, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], spaces: [], type_defs: [], diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/expect.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/expect.expr.result-ast index db5f8952bd..9947f3b3ac 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/expect.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/expect.expr.result-ast @@ -8,10 +8,10 @@ SpaceAfter( @0-13, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], spaces: [], type_defs: [], diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/expect_defs.moduledefs.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/expect_defs.moduledefs.result-ast index de5d80d2fe..8312e3ef28 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/expect_defs.moduledefs.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/expect_defs.moduledefs.result-ast @@ -6,10 +6,10 @@ Defs { @0-644, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 1 }, + Slice { start: 0, length: 1 }, ], spaces: [ Newline, @@ -31,14 +31,14 @@ Defs { @258-556, ], space_before: [ - Slice { start: 0, length: 0 }, - Slice { start: 0, length: 2 }, - Slice { start: 2, length: 2 }, + Slice { start: 0, length: 0 }, + Slice { start: 0, length: 2 }, + Slice { start: 2, length: 2 }, ], space_after: [ - Slice { start: 0, length: 0 }, - Slice { start: 2, length: 0 }, - Slice { start: 4, length: 0 }, + Slice { start: 0, length: 0 }, + Slice { start: 2, length: 0 }, + Slice { start: 4, length: 0 }, ], spaces: [ Newline, diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/expect_single_line.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/expect_single_line.expr.result-ast index 80966f1dfe..a130879059 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/expect_single_line.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/expect_single_line.expr.result-ast @@ -12,14 +12,14 @@ SpaceAfter( @22-35, ], space_before: [ - Slice { start: 0, length: 0 }, - Slice { start: 0, length: 2 }, - Slice { start: 2, length: 2 }, + Slice { start: 0, length: 0 }, + Slice { start: 0, length: 2 }, + Slice { start: 2, length: 2 }, ], space_after: [ - Slice { start: 0, length: 0 }, - Slice { start: 2, length: 0 }, - Slice { start: 4, length: 0 }, + Slice { start: 0, length: 0 }, + Slice { start: 2, length: 0 }, + Slice { start: 4, length: 0 }, ], spaces: [ Newline, diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/extra_newline.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/extra_newline.expr.result-ast index 955f2619d5..c3ad86957f 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/extra_newline.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/extra_newline.expr.result-ast @@ -17,10 +17,10 @@ SpaceAfter( @16-21, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], spaces: [], type_defs: [], diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/extra_newline_in_parens.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/extra_newline_in_parens.expr.result-ast index c2b4a8250f..d59e99b4bb 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/extra_newline_in_parens.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/extra_newline_in_parens.expr.result-ast @@ -7,10 +7,10 @@ Defs( @0-4, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], spaces: [], type_defs: [ diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/fn_with_record_arg.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/fn_with_record_arg.expr.result-ast index 475051e8bc..54e53a7555 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/fn_with_record_arg.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/fn_with_record_arg.expr.result-ast @@ -7,10 +7,10 @@ Defs( @0-89, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], spaces: [], type_defs: [], diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/function_with_tuple_ext_type.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/function_with_tuple_ext_type.expr.result-ast index 6f68ac97a2..bba976aa01 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/function_with_tuple_ext_type.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/function_with_tuple_ext_type.expr.result-ast @@ -8,10 +8,10 @@ SpaceAfter( @0-32, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], spaces: [], type_defs: [], diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/function_with_tuple_type.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/function_with_tuple_type.expr.result-ast index 4ffd212055..c8e7b05d8d 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/function_with_tuple_type.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/function_with_tuple_type.expr.result-ast @@ -8,10 +8,10 @@ SpaceAfter( @0-42, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], spaces: [], type_defs: [], diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/if_def.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/if_def.expr.result-ast index 71d7b1e87d..66a2edd326 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/if_def.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/if_def.expr.result-ast @@ -8,10 +8,10 @@ SpaceAfter( @0-6, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], spaces: [], type_defs: [], diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/import.moduledefs.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/import.moduledefs.result-ast index b179c81e2c..c6d341c857 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/import.moduledefs.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/import.moduledefs.result-ast @@ -6,10 +6,10 @@ Defs { @0-12, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 1 }, + Slice { start: 0, length: 1 }, ], spaces: [ Newline, diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/import_from_package.moduledefs.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/import_from_package.moduledefs.result-ast index 45619356f0..7bc25c6801 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/import_from_package.moduledefs.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/import_from_package.moduledefs.result-ast @@ -12,16 +12,16 @@ Defs { @93-146, ], space_before: [ - Slice { start: 0, length: 0 }, - Slice { start: 0, length: 1 }, - Slice { start: 1, length: 1 }, - Slice { start: 2, length: 1 }, + Slice { start: 0, length: 0 }, + Slice { start: 0, length: 1 }, + Slice { start: 1, length: 1 }, + Slice { start: 2, length: 1 }, ], space_after: [ - Slice { start: 0, length: 0 }, - Slice { start: 1, length: 0 }, - Slice { start: 2, length: 0 }, - Slice { start: 3, length: 1 }, + Slice { start: 0, length: 0 }, + Slice { start: 1, length: 0 }, + Slice { start: 2, length: 0 }, + Slice { start: 3, length: 1 }, ], spaces: [ Newline, diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/import_with_alias.moduledefs.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/import_with_alias.moduledefs.result-ast index 91a6090607..8bcacd569f 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/import_with_alias.moduledefs.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/import_with_alias.moduledefs.result-ast @@ -8,12 +8,12 @@ Defs { @24-50, ], space_before: [ - Slice { start: 0, length: 0 }, - Slice { start: 0, length: 1 }, + Slice { start: 0, length: 0 }, + Slice { start: 0, length: 1 }, ], space_after: [ - Slice { start: 0, length: 0 }, - Slice { start: 1, length: 1 }, + Slice { start: 0, length: 0 }, + Slice { start: 1, length: 1 }, ], spaces: [ Newline, diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/import_with_comments.moduledefs.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/import_with_comments.moduledefs.result-ast index 201c30aa33..af18a838e6 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/import_with_comments.moduledefs.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/import_with_comments.moduledefs.result-ast @@ -30,34 +30,34 @@ Defs { @710-718, ], space_before: [ - Slice { start: 0, length: 0 }, - Slice { start: 0, length: 2 }, - Slice { start: 2, length: 2 }, - Slice { start: 4, length: 2 }, - Slice { start: 6, length: 2 }, - Slice { start: 8, length: 2 }, - Slice { start: 10, length: 2 }, - Slice { start: 12, length: 2 }, - Slice { start: 14, length: 2 }, - Slice { start: 16, length: 2 }, - Slice { start: 18, length: 2 }, - Slice { start: 20, length: 3 }, - Slice { start: 23, length: 2 }, + Slice { start: 0, length: 0 }, + Slice { start: 0, length: 2 }, + Slice { start: 2, length: 2 }, + Slice { start: 4, length: 2 }, + Slice { start: 6, length: 2 }, + Slice { start: 8, length: 2 }, + Slice { start: 10, length: 2 }, + Slice { start: 12, length: 2 }, + Slice { start: 14, length: 2 }, + Slice { start: 16, length: 2 }, + Slice { start: 18, length: 2 }, + Slice { start: 20, length: 3 }, + Slice { start: 23, length: 2 }, ], space_after: [ - Slice { start: 0, length: 0 }, - Slice { start: 2, length: 0 }, - Slice { start: 4, length: 0 }, - Slice { start: 6, length: 0 }, - Slice { start: 8, length: 0 }, - Slice { start: 10, length: 0 }, - Slice { start: 12, length: 0 }, - Slice { start: 14, length: 0 }, - Slice { start: 16, length: 0 }, - Slice { start: 18, length: 0 }, - Slice { start: 20, length: 0 }, - Slice { start: 23, length: 0 }, - Slice { start: 25, length: 1 }, + Slice { start: 0, length: 0 }, + Slice { start: 2, length: 0 }, + Slice { start: 4, length: 0 }, + Slice { start: 6, length: 0 }, + Slice { start: 8, length: 0 }, + Slice { start: 10, length: 0 }, + Slice { start: 12, length: 0 }, + Slice { start: 14, length: 0 }, + Slice { start: 16, length: 0 }, + Slice { start: 18, length: 0 }, + Slice { start: 20, length: 0 }, + Slice { start: 23, length: 0 }, + Slice { start: 25, length: 1 }, ], spaces: [ Newline, diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/import_with_exposed.moduledefs.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/import_with_exposed.moduledefs.result-ast index e0341a157b..88a2f4e00e 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/import_with_exposed.moduledefs.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/import_with_exposed.moduledefs.result-ast @@ -10,14 +10,14 @@ Defs { @76-99, ], space_before: [ - Slice { start: 0, length: 0 }, - Slice { start: 0, length: 1 }, - Slice { start: 1, length: 1 }, + Slice { start: 0, length: 0 }, + Slice { start: 0, length: 1 }, + Slice { start: 1, length: 1 }, ], space_after: [ - Slice { start: 0, length: 0 }, - Slice { start: 1, length: 0 }, - Slice { start: 2, length: 1 }, + Slice { start: 0, length: 0 }, + Slice { start: 1, length: 0 }, + Slice { start: 2, length: 1 }, ], spaces: [ Newline, diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/import_with_params.moduledefs.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/import_with_params.moduledefs.result-ast index 79a680706b..ba685b3494 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/import_with_params.moduledefs.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/import_with_params.moduledefs.result-ast @@ -12,16 +12,16 @@ Defs { @97-157, ], space_before: [ - Slice { start: 0, length: 0 }, - Slice { start: 0, length: 1 }, - Slice { start: 1, length: 1 }, - Slice { start: 2, length: 1 }, + Slice { start: 0, length: 0 }, + Slice { start: 0, length: 1 }, + Slice { start: 1, length: 1 }, + Slice { start: 2, length: 1 }, ], space_after: [ - Slice { start: 0, length: 0 }, - Slice { start: 1, length: 0 }, - Slice { start: 2, length: 0 }, - Slice { start: 3, length: 1 }, + Slice { start: 0, length: 0 }, + Slice { start: 1, length: 0 }, + Slice { start: 2, length: 0 }, + Slice { start: 3, length: 1 }, ], spaces: [ Newline, diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/indented_after_multi_backpassing.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/indented_after_multi_backpassing.expr.result-ast index 2eeb237996..5a6ee4c3a6 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/indented_after_multi_backpassing.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/indented_after_multi_backpassing.expr.result-ast @@ -8,10 +8,10 @@ SpaceAfter( @0-288, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], spaces: [], type_defs: [], @@ -78,10 +78,10 @@ SpaceAfter( @123-192, ], space_before: [ - Slice { start: 0, length: 1 }, + Slice { start: 0, length: 1 }, ], space_after: [ - Slice { start: 1, length: 0 }, + Slice { start: 1, length: 0 }, ], spaces: [ Newline, diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/ingested_file.moduledefs.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/ingested_file.moduledefs.result-ast index ca1c1ab5ef..024af56c6c 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/ingested_file.moduledefs.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/ingested_file.moduledefs.result-ast @@ -8,12 +8,12 @@ Defs { @40-85, ], space_before: [ - Slice { start: 0, length: 0 }, - Slice { start: 0, length: 1 }, + Slice { start: 0, length: 0 }, + Slice { start: 0, length: 1 }, ], space_after: [ - Slice { start: 0, length: 0 }, - Slice { start: 1, length: 1 }, + Slice { start: 0, length: 0 }, + Slice { start: 1, length: 1 }, ], spaces: [ Newline, diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/inline_import.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/inline_import.expr.result-ast index 550cf67e37..d3302ee7a0 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/inline_import.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/inline_import.expr.result-ast @@ -10,12 +10,12 @@ SpaceAfter( @27-51, ], space_before: [ - Slice { start: 0, length: 0 }, - Slice { start: 0, length: 1 }, + Slice { start: 0, length: 0 }, + Slice { start: 0, length: 1 }, ], space_after: [ - Slice { start: 0, length: 0 }, - Slice { start: 1, length: 0 }, + Slice { start: 0, length: 0 }, + Slice { start: 1, length: 0 }, ], spaces: [ Newline, diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/inline_ingested_file.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/inline_ingested_file.expr.result-ast index fdbf17719c..ee3defb0f8 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/inline_ingested_file.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/inline_ingested_file.expr.result-ast @@ -8,10 +8,10 @@ SpaceAfter( @0-33, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], spaces: [], type_defs: [], diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/inline_ingested_file_no_ann.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/inline_ingested_file_no_ann.expr.result-ast index 2d9bc22969..e273ec4ea6 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/inline_ingested_file_no_ann.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/inline_ingested_file_no_ann.expr.result-ast @@ -8,10 +8,10 @@ SpaceAfter( @0-27, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], spaces: [], type_defs: [], diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/list_closing_indent_not_enough.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/list_closing_indent_not_enough.expr.result-ast index 3f26b5dc6e..54c53baf31 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/list_closing_indent_not_enough.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/list_closing_indent_not_enough.expr.result-ast @@ -8,10 +8,10 @@ SpaceAfter( @0-57, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], spaces: [], type_defs: [], diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/list_closing_same_indent_no_trailing_comma.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/list_closing_same_indent_no_trailing_comma.expr.result-ast index 4481a36b0f..b161793b60 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/list_closing_same_indent_no_trailing_comma.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/list_closing_same_indent_no_trailing_comma.expr.result-ast @@ -8,10 +8,10 @@ SpaceAfter( @0-25, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], spaces: [], type_defs: [], diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/list_closing_same_indent_with_trailing_comma.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/list_closing_same_indent_with_trailing_comma.expr.result-ast index 8cbf9fd03a..3bdfd3d1a6 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/list_closing_same_indent_with_trailing_comma.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/list_closing_same_indent_with_trailing_comma.expr.result-ast @@ -8,10 +8,10 @@ SpaceAfter( @0-26, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], spaces: [], type_defs: [], diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/mixed_docs.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/mixed_docs.expr.result-ast index 80c9380434..ff97e32cbe 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/mixed_docs.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/mixed_docs.expr.result-ast @@ -9,10 +9,10 @@ SpaceBefore( @113-118, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], spaces: [], type_defs: [], diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/module_def_newline.moduledefs.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/module_def_newline.moduledefs.result-ast index a8583eeb5d..c42dc87e69 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/module_def_newline.moduledefs.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/module_def_newline.moduledefs.result-ast @@ -6,10 +6,10 @@ Defs { @0-24, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 1 }, + Slice { start: 0, length: 1 }, ], spaces: [ Newline, @@ -30,10 +30,10 @@ Defs { @11-17, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], spaces: [], type_defs: [], diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/multi_backpassing_in_def.moduledefs.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/multi_backpassing_in_def.moduledefs.result-ast index f73c014f1c..570b112baf 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/multi_backpassing_in_def.moduledefs.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/multi_backpassing_in_def.moduledefs.result-ast @@ -6,10 +6,10 @@ Defs { @0-50, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 1 }, + Slice { start: 0, length: 1 }, ], spaces: [ Newline, diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/multiline_str_in_pat.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/multiline_str_in_pat.expr.result-ast index 4e67398afb..b49f2a0d0f 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/multiline_str_in_pat.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/multiline_str_in_pat.expr.result-ast @@ -8,10 +8,10 @@ SpaceAfter( @0-13, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], spaces: [], type_defs: [], diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/multiline_string.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/multiline_string.expr.result-ast index f81cf1dea7..90dcb06779 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/multiline_string.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/multiline_string.expr.result-ast @@ -12,14 +12,14 @@ SpaceAfter( @50-92, ], space_before: [ - Slice { start: 0, length: 0 }, - Slice { start: 0, length: 1 }, - Slice { start: 1, length: 1 }, + Slice { start: 0, length: 0 }, + Slice { start: 0, length: 1 }, + Slice { start: 1, length: 1 }, ], space_after: [ - Slice { start: 0, length: 0 }, - Slice { start: 1, length: 0 }, - Slice { start: 2, length: 0 }, + Slice { start: 0, length: 0 }, + Slice { start: 1, length: 0 }, + Slice { start: 2, length: 0 }, ], spaces: [ Newline, diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/multiline_type_signature.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/multiline_type_signature.expr.result-ast index b63b9570b9..46cf7e0a00 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/multiline_type_signature.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/multiline_type_signature.expr.result-ast @@ -7,10 +7,10 @@ Defs( @0-10, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], spaces: [], type_defs: [], diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/multiline_type_signature_with_comment.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/multiline_type_signature_with_comment.expr.result-ast index 238eeb51bc..65f707ec00 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/multiline_type_signature_with_comment.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/multiline_type_signature_with_comment.expr.result-ast @@ -7,10 +7,10 @@ Defs( @0-19, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], spaces: [], type_defs: [], diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/negative_in_apply_def.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/negative_in_apply_def.expr.result-ast index b86b0ccf9b..fdc544916b 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/negative_in_apply_def.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/negative_in_apply_def.expr.result-ast @@ -7,10 +7,10 @@ Defs( @0-9, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], spaces: [], type_defs: [], diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/nested_def_annotation.moduledefs.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/nested_def_annotation.moduledefs.result-ast index 5fc26b7458..b6828bfaff 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/nested_def_annotation.moduledefs.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/nested_def_annotation.moduledefs.result-ast @@ -6,10 +6,10 @@ Defs { @0-115, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 1 }, + Slice { start: 0, length: 1 }, ], spaces: [ Newline, @@ -30,10 +30,10 @@ Defs { @11-93, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], spaces: [], type_defs: [], diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/newline_after_equals.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/newline_after_equals.expr.result-ast index dd2a7dee12..a46baaea9a 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/newline_after_equals.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/newline_after_equals.expr.result-ast @@ -8,10 +8,10 @@ SpaceAfter( @0-9, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], spaces: [], type_defs: [], diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/newline_and_spaces_before_less_than.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/newline_and_spaces_before_less_than.expr.result-ast index 35c80b7943..58e60e2b64 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/newline_and_spaces_before_less_than.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/newline_and_spaces_before_less_than.expr.result-ast @@ -7,10 +7,10 @@ Defs( @0-13, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], spaces: [], type_defs: [], diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/newline_in_packages.full.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/newline_in_packages.full.result-ast index 16b7434654..4fc7385b18 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/newline_in_packages.full.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/newline_in_packages.full.result-ast @@ -34,10 +34,10 @@ Full( @53-100, ], space_before: [ - Slice { start: 0, length: 2 }, + Slice { start: 0, length: 2 }, ], space_after: [ - Slice { start: 2, length: 1 }, + Slice { start: 2, length: 1 }, ], spaces: [ Newline, diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/newline_in_type_alias_application.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/newline_in_type_alias_application.expr.result-ast index ebd27bd9d5..0fda46101e 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/newline_in_type_alias_application.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/newline_in_type_alias_application.expr.result-ast @@ -7,10 +7,10 @@ Defs( @0-6, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], spaces: [], type_defs: [ diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/newline_in_type_def.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/newline_in_type_def.expr.result-ast index d705f6304b..c09c9c5d37 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/newline_in_type_def.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/newline_in_type_def.expr.result-ast @@ -7,10 +7,10 @@ Defs( @0-4, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], spaces: [], type_defs: [ diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/old_app_header.full.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/old_app_header.full.result-ast index 9ad363d54d..0ca8b776fa 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/old_app_header.full.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/old_app_header.full.result-ast @@ -52,12 +52,12 @@ Full( @157-187, ], space_before: [ - Slice { start: 0, length: 3 }, - Slice { start: 6, length: 2 }, + Slice { start: 0, length: 3 }, + Slice { start: 6, length: 2 }, ], space_after: [ - Slice { start: 3, length: 3 }, - Slice { start: 8, length: 2 }, + Slice { start: 3, length: 3 }, + Slice { start: 8, length: 2 }, ], spaces: [ Newline, diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/one_def.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/one_def.expr.result-ast index fbaced4ce3..c0d9727db3 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/one_def.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/one_def.expr.result-ast @@ -9,10 +9,10 @@ SpaceBefore( @18-21, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], spaces: [], type_defs: [], diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/one_spaced_def.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/one_spaced_def.expr.result-ast index 89666c0613..ef05105d25 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/one_spaced_def.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/one_spaced_def.expr.result-ast @@ -9,10 +9,10 @@ SpaceBefore( @18-23, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], spaces: [], type_defs: [], diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/opaque_destructure_first_item_in_body.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/opaque_destructure_first_item_in_body.expr.result-ast index 21726b803b..903afc5203 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/opaque_destructure_first_item_in_body.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/opaque_destructure_first_item_in_body.expr.result-ast @@ -8,10 +8,10 @@ SpaceAfter( @0-22, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], spaces: [], type_defs: [], diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/opaque_has_abilities.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/opaque_has_abilities.expr.result-ast index 40a51299c4..5f771725bd 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/opaque_has_abilities.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/opaque_has_abilities.expr.result-ast @@ -26,28 +26,28 @@ SpaceAfter( @401-427, ], space_before: [ - Slice { start: 0, length: 0 }, - Slice { start: 0, length: 2 }, - Slice { start: 2, length: 2 }, - Slice { start: 4, length: 2 }, - Slice { start: 6, length: 2 }, - Slice { start: 8, length: 2 }, - Slice { start: 10, length: 2 }, - Slice { start: 12, length: 2 }, - Slice { start: 14, length: 2 }, - Slice { start: 16, length: 2 }, + Slice { start: 0, length: 0 }, + Slice { start: 0, length: 2 }, + Slice { start: 2, length: 2 }, + Slice { start: 4, length: 2 }, + Slice { start: 6, length: 2 }, + Slice { start: 8, length: 2 }, + Slice { start: 10, length: 2 }, + Slice { start: 12, length: 2 }, + Slice { start: 14, length: 2 }, + Slice { start: 16, length: 2 }, ], space_after: [ - Slice { start: 0, length: 0 }, - Slice { start: 2, length: 0 }, - Slice { start: 4, length: 0 }, - Slice { start: 6, length: 0 }, - Slice { start: 8, length: 0 }, - Slice { start: 10, length: 0 }, - Slice { start: 12, length: 0 }, - Slice { start: 14, length: 0 }, - Slice { start: 16, length: 0 }, - Slice { start: 18, length: 0 }, + Slice { start: 0, length: 0 }, + Slice { start: 2, length: 0 }, + Slice { start: 4, length: 0 }, + Slice { start: 6, length: 0 }, + Slice { start: 8, length: 0 }, + Slice { start: 10, length: 0 }, + Slice { start: 12, length: 0 }, + Slice { start: 14, length: 0 }, + Slice { start: 16, length: 0 }, + Slice { start: 18, length: 0 }, ], spaces: [ Newline, diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/opaque_simple.moduledefs.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/opaque_simple.moduledefs.result-ast index ecb948e64d..642c75f5ca 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/opaque_simple.moduledefs.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/opaque_simple.moduledefs.result-ast @@ -6,10 +6,10 @@ Defs { @0-9, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 1 }, + Slice { start: 0, length: 1 }, ], spaces: [ Newline, diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/opaque_with_type_arguments.moduledefs.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/opaque_with_type_arguments.moduledefs.result-ast index 1d9a396404..5fe49a3ff1 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/opaque_with_type_arguments.moduledefs.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/opaque_with_type_arguments.moduledefs.result-ast @@ -6,10 +6,10 @@ Defs { @0-53, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 1 }, + Slice { start: 0, length: 1 }, ], spaces: [ Newline, diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/outdented_app_with_record.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/outdented_app_with_record.expr.result-ast index 59d6eb1675..d971a44f86 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/outdented_app_with_record.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/outdented_app_with_record.expr.result-ast @@ -8,10 +8,10 @@ SpaceAfter( @0-29, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], spaces: [], type_defs: [], diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/outdented_colon_in_record.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/outdented_colon_in_record.expr.result-ast index 4cced72b81..fe627e2b54 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/outdented_colon_in_record.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/outdented_colon_in_record.expr.result-ast @@ -8,10 +8,10 @@ SpaceAfter( @0-22, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], spaces: [], type_defs: [], diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/outdented_list.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/outdented_list.expr.result-ast index 3e94afad7a..453657569d 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/outdented_list.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/outdented_list.expr.result-ast @@ -8,10 +8,10 @@ SpaceAfter( @0-17, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], spaces: [], type_defs: [], diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/outdented_record.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/outdented_record.expr.result-ast index 3315ef9ddc..8a5e2bfa59 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/outdented_record.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/outdented_record.expr.result-ast @@ -8,10 +8,10 @@ SpaceAfter( @0-23, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], spaces: [], type_defs: [], diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/parens_in_type_def_apply.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/parens_in_type_def_apply.expr.result-ast index 4a42d096a8..64f9667ea3 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/parens_in_type_def_apply.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/parens_in_type_def_apply.expr.result-ast @@ -7,10 +7,10 @@ Defs( @0-8, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], spaces: [], type_defs: [ diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/parenthesized_type_def.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/parenthesized_type_def.expr.result-ast index 15d8c5f845..7b6b3ded59 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/parenthesized_type_def.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/parenthesized_type_def.expr.result-ast @@ -7,10 +7,10 @@ Defs( @0-5, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], spaces: [], type_defs: [ diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/parenthesized_type_def_space_before.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/parenthesized_type_def_space_before.expr.result-ast index ee5f397811..9b37ffe977 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/parenthesized_type_def_space_before.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/parenthesized_type_def_space_before.expr.result-ast @@ -7,10 +7,10 @@ Defs( @0-6, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], spaces: [], type_defs: [ diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/parse_alias.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/parse_alias.expr.result-ast index bf1236033e..2f51d31a2e 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/parse_alias.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/parse_alias.expr.result-ast @@ -8,10 +8,10 @@ SpaceAfter( @0-26, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], spaces: [], type_defs: [ diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/parse_as_ann.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/parse_as_ann.expr.result-ast index 0f2e29f45c..4ad9db97d6 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/parse_as_ann.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/parse_as_ann.expr.result-ast @@ -8,10 +8,10 @@ SpaceAfter( @0-33, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], spaces: [], type_defs: [], diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/pizza_question.moduledefs.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/pizza_question.moduledefs.result-ast index 5b551e9d95..521b4e9399 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/pizza_question.moduledefs.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/pizza_question.moduledefs.result-ast @@ -6,10 +6,10 @@ Defs { @0-166, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 1 }, + Slice { start: 0, length: 1 }, ], spaces: [ Newline, diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/record_destructure_def.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/record_destructure_def.expr.result-ast index 38ed746233..20e2e46135 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/record_destructure_def.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/record_destructure_def.expr.result-ast @@ -11,12 +11,12 @@ SpaceBefore( @31-36, ], space_before: [ - Slice { start: 0, length: 0 }, - Slice { start: 0, length: 1 }, + Slice { start: 0, length: 0 }, + Slice { start: 0, length: 1 }, ], space_after: [ - Slice { start: 0, length: 0 }, - Slice { start: 1, length: 0 }, + Slice { start: 0, length: 0 }, + Slice { start: 1, length: 0 }, ], spaces: [ Newline, diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/record_destructure_field_bang.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/record_destructure_field_bang.expr.result-ast index 8a76150ddf..b8c1cc89ce 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/record_destructure_field_bang.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/record_destructure_field_bang.expr.result-ast @@ -8,10 +8,10 @@ SpaceAfter( @0-32, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], spaces: [], type_defs: [], diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/record_func_type_decl.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/record_func_type_decl.expr.result-ast index 12ba5102e7..eb35714507 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/record_func_type_decl.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/record_func_type_decl.expr.result-ast @@ -8,10 +8,10 @@ SpaceAfter( @0-122, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], spaces: [], type_defs: [], diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/record_type_with_function.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/record_type_with_function.expr.result-ast index 7eaa142894..5f3ed09000 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/record_type_with_function.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/record_type_with_function.expr.result-ast @@ -8,10 +8,10 @@ SpaceAfter( @0-77, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], spaces: [], type_defs: [], diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/record_updater_literal_apply.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/record_updater_literal_apply.expr.result-ast index 643a2023cc..c320411f28 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/record_updater_literal_apply.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/record_updater_literal_apply.expr.result-ast @@ -8,10 +8,10 @@ SpaceAfter( @0-42, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], spaces: [], type_defs: [], diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/return_in_if.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/return_in_if.expr.result-ast index 410e650571..9b0bf5e3c0 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/return_in_if.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/return_in_if.expr.result-ast @@ -8,10 +8,10 @@ SpaceAfter( @0-127, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], spaces: [], type_defs: [], @@ -36,10 +36,10 @@ SpaceAfter( @29-110, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], spaces: [], type_defs: [], diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/return_in_static_def.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/return_in_static_def.expr.result-ast index 94e49d5f7f..f4a4a61040 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/return_in_static_def.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/return_in_static_def.expr.result-ast @@ -8,10 +8,10 @@ SpaceAfter( @0-142, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], spaces: [], type_defs: [], @@ -30,10 +30,10 @@ SpaceAfter( @21-125, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], spaces: [], type_defs: [], @@ -70,10 +70,10 @@ SpaceAfter( @67-72, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], spaces: [], type_defs: [], diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/return_in_when.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/return_in_when.expr.result-ast index ffca41041b..b563809d59 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/return_in_when.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/return_in_when.expr.result-ast @@ -8,10 +8,10 @@ SpaceAfter( @0-154, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], spaces: [], type_defs: [], @@ -36,10 +36,10 @@ SpaceAfter( @29-136, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], spaces: [], type_defs: [], diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/return_only_statement.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/return_only_statement.expr.result-ast index 0041535b2d..98d7aa6820 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/return_only_statement.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/return_only_statement.expr.result-ast @@ -8,10 +8,10 @@ SpaceAfter( @0-33, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], spaces: [], type_defs: [], diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/separate_defs.moduledefs.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/separate_defs.moduledefs.result-ast index 7b5bc06924..6bf06e0708 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/separate_defs.moduledefs.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/separate_defs.moduledefs.result-ast @@ -16,20 +16,20 @@ Defs { @234-266, ], space_before: [ - Slice { start: 0, length: 0 }, - Slice { start: 0, length: 1 }, - Slice { start: 1, length: 1 }, - Slice { start: 2, length: 2 }, - Slice { start: 4, length: 1 }, - Slice { start: 5, length: 1 }, + Slice { start: 0, length: 0 }, + Slice { start: 0, length: 1 }, + Slice { start: 1, length: 1 }, + Slice { start: 2, length: 2 }, + Slice { start: 4, length: 1 }, + Slice { start: 5, length: 1 }, ], space_after: [ - Slice { start: 0, length: 0 }, - Slice { start: 1, length: 0 }, - Slice { start: 2, length: 0 }, - Slice { start: 4, length: 0 }, - Slice { start: 5, length: 0 }, - Slice { start: 6, length: 1 }, + Slice { start: 0, length: 0 }, + Slice { start: 1, length: 0 }, + Slice { start: 2, length: 0 }, + Slice { start: 4, length: 0 }, + Slice { start: 5, length: 0 }, + Slice { start: 6, length: 1 }, ], spaces: [ Newline, diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/space_before_colon.full.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/space_before_colon.full.result-ast index 3a4876afaa..4d53368401 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/space_before_colon.full.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/space_before_colon.full.result-ast @@ -36,10 +36,10 @@ Full( @39-65, ], space_before: [ - Slice { start: 0, length: 2 }, + Slice { start: 0, length: 2 }, ], space_after: [ - Slice { start: 2, length: 1 }, + Slice { start: 2, length: 1 }, ], spaces: [ Newline, diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/standalone_module_defs.moduledefs.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/standalone_module_defs.moduledefs.result-ast index 361490271c..d944ce8e1d 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/standalone_module_defs.moduledefs.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/standalone_module_defs.moduledefs.result-ast @@ -10,14 +10,14 @@ Defs { @44-57, ], space_before: [ - Slice { start: 0, length: 1 }, - Slice { start: 1, length: 3 }, - Slice { start: 4, length: 1 }, + Slice { start: 0, length: 1 }, + Slice { start: 1, length: 3 }, + Slice { start: 4, length: 1 }, ], space_after: [ - Slice { start: 1, length: 0 }, - Slice { start: 4, length: 0 }, - Slice { start: 5, length: 2 }, + Slice { start: 1, length: 0 }, + Slice { start: 4, length: 0 }, + Slice { start: 5, length: 2 }, ], spaces: [ LineComment( diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/suffixed_question_multiple_defs.moduledefs.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/suffixed_question_multiple_defs.moduledefs.result-ast index a22cd1c19f..fa36420396 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/suffixed_question_multiple_defs.moduledefs.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/suffixed_question_multiple_defs.moduledefs.result-ast @@ -6,10 +6,10 @@ Defs { @0-49, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 1 }, + Slice { start: 0, length: 1 }, ], spaces: [ Newline, @@ -32,12 +32,12 @@ Defs { @26-39, ], space_before: [ - Slice { start: 0, length: 0 }, - Slice { start: 0, length: 1 }, + Slice { start: 0, length: 0 }, + Slice { start: 0, length: 1 }, ], space_after: [ - Slice { start: 0, length: 0 }, - Slice { start: 1, length: 0 }, + Slice { start: 0, length: 0 }, + Slice { start: 1, length: 0 }, ], spaces: [ Newline, diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/suffixed_question_one_def.full.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/suffixed_question_one_def.full.result-ast index 61036c551b..95de78c1a0 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/suffixed_question_one_def.full.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/suffixed_question_one_def.full.result-ast @@ -46,12 +46,12 @@ Full( @76-220, ], space_before: [ - Slice { start: 0, length: 2 }, - Slice { start: 2, length: 2 }, + Slice { start: 0, length: 2 }, + Slice { start: 2, length: 2 }, ], space_after: [ - Slice { start: 2, length: 0 }, - Slice { start: 4, length: 2 }, + Slice { start: 2, length: 0 }, + Slice { start: 4, length: 2 }, ], spaces: [ Newline, @@ -95,12 +95,12 @@ Full( @162-205, ], space_before: [ - Slice { start: 0, length: 0 }, - Slice { start: 0, length: 3 }, + Slice { start: 0, length: 0 }, + Slice { start: 0, length: 3 }, ], space_after: [ - Slice { start: 0, length: 0 }, - Slice { start: 3, length: 0 }, + Slice { start: 0, length: 0 }, + Slice { start: 3, length: 0 }, ], spaces: [ Newline, diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/suffixed_question_optional_last.full.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/suffixed_question_optional_last.full.result-ast index eb059a6798..ffc0bce369 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/suffixed_question_optional_last.full.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/suffixed_question_optional_last.full.result-ast @@ -50,10 +50,10 @@ Full( @88-204, ], space_before: [ - Slice { start: 0, length: 2 }, + Slice { start: 0, length: 2 }, ], space_after: [ - Slice { start: 2, length: 1 }, + Slice { start: 2, length: 1 }, ], spaces: [ Newline, diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/tag_destructure_bang.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/tag_destructure_bang.expr.result-ast index c544346d5d..45b354596a 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/tag_destructure_bang.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/tag_destructure_bang.expr.result-ast @@ -8,10 +8,10 @@ SpaceAfter( @0-33, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], spaces: [], type_defs: [], diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/tag_destructure_bang_no_space.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/tag_destructure_bang_no_space.expr.result-ast index 303e59b8ad..f926dbc12f 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/tag_destructure_bang_no_space.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/tag_destructure_bang_no_space.expr.result-ast @@ -8,10 +8,10 @@ SpaceAfter( @0-32, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], spaces: [], type_defs: [], diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/tuple_access_after_ident.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/tuple_access_after_ident.expr.result-ast index f3a6dbb1d5..5272a96f27 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/tuple_access_after_ident.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/tuple_access_after_ident.expr.result-ast @@ -7,10 +7,10 @@ Defs( @0-15, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], spaces: [], type_defs: [], diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/tuple_destructure_bang.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/tuple_destructure_bang.expr.result-ast index f98f394b2d..ac20ecc5ca 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/tuple_destructure_bang.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/tuple_destructure_bang.expr.result-ast @@ -8,10 +8,10 @@ SpaceAfter( @0-32, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], spaces: [], type_defs: [], diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/tuple_type.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/tuple_type.expr.result-ast index 07482202ab..b5a12c87c4 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/tuple_type.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/tuple_type.expr.result-ast @@ -7,10 +7,10 @@ Defs( @0-39, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], spaces: [], type_defs: [], diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/tuple_type_ext.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/tuple_type_ext.expr.result-ast index 92c3f3e03f..d744a7f198 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/tuple_type_ext.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/tuple_type_ext.expr.result-ast @@ -7,10 +7,10 @@ Defs( @0-41, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], spaces: [], type_defs: [], diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/two_spaced_def.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/two_spaced_def.expr.result-ast index 4cb12e863e..08c99c9fb3 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/two_spaced_def.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/two_spaced_def.expr.result-ast @@ -11,12 +11,12 @@ SpaceBefore( @24-29, ], space_before: [ - Slice { start: 0, length: 0 }, - Slice { start: 0, length: 1 }, + Slice { start: 0, length: 0 }, + Slice { start: 0, length: 1 }, ], space_after: [ - Slice { start: 0, length: 0 }, - Slice { start: 1, length: 0 }, + Slice { start: 0, length: 0 }, + Slice { start: 1, length: 0 }, ], spaces: [ Newline, diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/type_decl_with_underscore.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/type_decl_with_underscore.expr.result-ast index f85036bbe2..20f71709d4 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/type_decl_with_underscore.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/type_decl_with_underscore.expr.result-ast @@ -7,10 +7,10 @@ Defs( @0-30, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], spaces: [], type_defs: [], diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/type_signature_def.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/type_signature_def.expr.result-ast index 3c2f8f24b3..34da00ded4 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/type_signature_def.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/type_signature_def.expr.result-ast @@ -7,10 +7,10 @@ Defs( @0-17, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], spaces: [], type_defs: [], diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/type_signature_function_def.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/type_signature_function_def.expr.result-ast index 36bcc2decb..cdde5c91f9 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/type_signature_function_def.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/type_signature_function_def.expr.result-ast @@ -7,10 +7,10 @@ Defs( @0-42, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], spaces: [], type_defs: [], diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/underscore_in_assignment_pattern.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/underscore_in_assignment_pattern.expr.result-ast index 1206669bdd..855fff207a 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/underscore_in_assignment_pattern.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/underscore_in_assignment_pattern.expr.result-ast @@ -16,18 +16,18 @@ SpaceAfter( @73-128, ], space_before: [ - Slice { start: 0, length: 0 }, - Slice { start: 0, length: 1 }, - Slice { start: 1, length: 1 }, - Slice { start: 2, length: 1 }, - Slice { start: 3, length: 1 }, + Slice { start: 0, length: 0 }, + Slice { start: 0, length: 1 }, + Slice { start: 1, length: 1 }, + Slice { start: 2, length: 1 }, + Slice { start: 3, length: 1 }, ], space_after: [ - Slice { start: 0, length: 0 }, - Slice { start: 1, length: 0 }, - Slice { start: 2, length: 0 }, - Slice { start: 3, length: 0 }, - Slice { start: 4, length: 0 }, + Slice { start: 0, length: 0 }, + Slice { start: 1, length: 0 }, + Slice { start: 2, length: 0 }, + Slice { start: 3, length: 0 }, + Slice { start: 4, length: 0 }, ], spaces: [ Newline, diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/value_def_confusion.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/value_def_confusion.expr.result-ast index e345a31d94..006ae8efa6 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/value_def_confusion.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/value_def_confusion.expr.result-ast @@ -9,12 +9,12 @@ Defs( @4-8, ], space_before: [ - Slice { start: 0, length: 0 }, - Slice { start: 0, length: 1 }, + Slice { start: 0, length: 0 }, + Slice { start: 0, length: 1 }, ], space_after: [ - Slice { start: 0, length: 0 }, - Slice { start: 1, length: 0 }, + Slice { start: 0, length: 0 }, + Slice { start: 1, length: 0 }, ], spaces: [ Newline, diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/when_in_assignment.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/when_in_assignment.expr.result-ast index e8e18b170a..ae141c0cc6 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/when_in_assignment.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/when_in_assignment.expr.result-ast @@ -7,10 +7,10 @@ Defs( @0-25, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], spaces: [], type_defs: [], diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/when_in_function.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/when_in_function.expr.result-ast index e1b78fcc9c..b7b59bf869 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/when_in_function.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/when_in_function.expr.result-ast @@ -7,10 +7,10 @@ Defs( @0-43, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], spaces: [], type_defs: [], diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/when_in_function_python_style_indent.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/when_in_function_python_style_indent.expr.result-ast index 0671a83da0..d06a42df79 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/when_in_function_python_style_indent.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/when_in_function_python_style_indent.expr.result-ast @@ -7,10 +7,10 @@ Defs( @0-33, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], spaces: [], type_defs: [], diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/where_clause_function.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/where_clause_function.expr.result-ast index 9d0c667000..e0cfd980ca 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/where_clause_function.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/where_clause_function.expr.result-ast @@ -8,10 +8,10 @@ SpaceAfter( @0-38, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], spaces: [], type_defs: [], diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/where_clause_multiple_bound_abilities.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/where_clause_multiple_bound_abilities.expr.result-ast index 0bc4dc2d01..f77a9a351b 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/where_clause_multiple_bound_abilities.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/where_clause_multiple_bound_abilities.expr.result-ast @@ -10,12 +10,12 @@ SpaceAfter( @75-154, ], space_before: [ - Slice { start: 0, length: 0 }, - Slice { start: 0, length: 2 }, + Slice { start: 0, length: 0 }, + Slice { start: 0, length: 2 }, ], space_after: [ - Slice { start: 0, length: 0 }, - Slice { start: 2, length: 0 }, + Slice { start: 0, length: 0 }, + Slice { start: 2, length: 0 }, ], spaces: [ Newline, diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/where_clause_multiple_has.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/where_clause_multiple_has.expr.result-ast index 66b7a5b3cf..2b6ecdf721 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/where_clause_multiple_has.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/where_clause_multiple_has.expr.result-ast @@ -8,10 +8,10 @@ SpaceAfter( @0-73, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], spaces: [], type_defs: [], diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/where_clause_multiple_has_across_newlines.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/where_clause_multiple_has_across_newlines.expr.result-ast index 0a6edab08b..c001f7d74f 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/where_clause_multiple_has_across_newlines.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/where_clause_multiple_has_across_newlines.expr.result-ast @@ -8,10 +8,10 @@ SpaceAfter( @0-92, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], spaces: [], type_defs: [], diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/where_clause_non_function.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/where_clause_non_function.expr.result-ast index 4cfc12e509..86afbf5631 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/where_clause_non_function.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/where_clause_non_function.expr.result-ast @@ -8,10 +8,10 @@ SpaceAfter( @0-26, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], spaces: [], type_defs: [], diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/where_clause_on_newline.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/where_clause_on_newline.expr.result-ast index ff2dbc064e..bfa825be89 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/where_clause_on_newline.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/where_clause_on_newline.expr.result-ast @@ -8,10 +8,10 @@ SpaceAfter( @0-40, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], spaces: [], type_defs: [], diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/where_ident.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/where_ident.expr.result-ast index 09d1f93c6b..88031feb51 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/where_ident.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/where_ident.expr.result-ast @@ -8,10 +8,10 @@ SpaceAfter( @0-39, ], space_before: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], space_after: [ - Slice { start: 0, length: 0 }, + Slice { start: 0, length: 0 }, ], spaces: [], type_defs: [], From 5370a273703d319e783fccadc16307cede12b9b8 Mon Sep 17 00:00:00 2001 From: Agus Zubiaga Date: Sat, 23 Nov 2024 11:17:12 -0300 Subject: [PATCH 41/65] Use debug_assert! insetead of expect --- crates/compiler/specialize_types/src/mono_type.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/crates/compiler/specialize_types/src/mono_type.rs b/crates/compiler/specialize_types/src/mono_type.rs index 111fae81f5..c66d0d9ad2 100644 --- a/crates/compiler/specialize_types/src/mono_type.rs +++ b/crates/compiler/specialize_types/src/mono_type.rs @@ -118,12 +118,8 @@ impl MonoTypes { let opt = self.entries.get(id.inner.index()); - #[cfg(debug_assertions)] - { - opt.expect("A MonoTypeId corresponded to an index that wasn't in MonoTypes. This should never happen!") - } + debug_assert!(opt.is_some(), "A MonoTypeId corresponded to an index that wasn't in MonoTypes. This should never happen!"); - #[cfg(not(debug_assertions))] unsafe { opt.unwrap_unchecked() } From a98acff0b91100485faed652225c1e55f2669807 Mon Sep 17 00:00:00 2001 From: shua Date: Wed, 20 Nov 2024 22:38:19 +0100 Subject: [PATCH 42/65] gen-dev: impl Num.neg for Dec,F32,F64 Dec negation was implemented across gen-dev, gen-llvm, gen-wasm as a call to the compiled zig function `bitcode::DEC_NEGATE`. f32 and f64 negation were implemented already for gen-llvm, gen-wasm. for gen-dev x86_64, float negation is implemented by flipping the sign bit, which means `xorps` for f32, and `xorpd` for f64 for gen-dev aarch64, there is conveniently a `fneg` instruction --- .../compiler/gen_dev/src/generic64/aarch64.rs | 38 +++++++++++ crates/compiler/gen_dev/src/generic64/mod.rs | 31 ++++++++- .../compiler/gen_dev/src/generic64/x86_64.rs | 65 +++++++++++++++++++ crates/compiler/gen_llvm/src/llvm/lowlevel.rs | 1 + crates/compiler/gen_wasm/src/low_level.rs | 1 + crates/compiler/test_gen/src/gen_num.rs | 22 ++++++- 6 files changed, 156 insertions(+), 2 deletions(-) diff --git a/crates/compiler/gen_dev/src/generic64/aarch64.rs b/crates/compiler/gen_dev/src/generic64/aarch64.rs index 71c937a3e4..6a01e5524b 100644 --- a/crates/compiler/gen_dev/src/generic64/aarch64.rs +++ b/crates/compiler/gen_dev/src/generic64/aarch64.rs @@ -1846,6 +1846,26 @@ impl Assembler for AArch64Assembler { neg_reg64_reg64(buf, dst, src); } + #[inline(always)] + fn neg_freg64_freg64( + buf: &mut Vec<'_, u8>, + _relocs: &mut Vec<'_, Relocation>, + dst: AArch64FloatReg, + src: AArch64FloatReg, + ) { + fneg_freg_freg(buf, FloatWidth::F64, dst, src); + } + + #[inline(always)] + fn neg_freg32_freg32( + buf: &mut Vec<'_, u8>, + _relocs: &mut Vec<'_, Relocation>, + dst: AArch64FloatReg, + src: AArch64FloatReg, + ) { + fneg_freg_freg(buf, FloatWidth::F32, dst, src); + } + #[inline(always)] fn sub_reg64_reg64_imm32( buf: &mut Vec<'_, u8>, @@ -3953,6 +3973,24 @@ fn fsub_freg_freg_freg( buf.extend(inst.bytes()); } +/// `FNEG Sd/Dd, Sn/Dn` +#[inline(always)] +fn fneg_freg_freg( + buf: &mut Vec<'_, u8>, + ftype: FloatWidth, + dst: AArch64FloatReg, + src: AArch64FloatReg, +) { + let inst = + FloatingPointDataProcessingOneSource::new(FloatingPointDataProcessingOneSourceParams { + ptype: ftype, + opcode: 0b00010, + rn: src, + rd: dst, + }); + buf.extend(inst.bytes()); +} + /// `FCMP Sn/Dn, Sm/Dm` -> Compare Sn/Dn and Sm/Dm, setting condition flags. #[inline(always)] fn fcmp_freg_freg( diff --git a/crates/compiler/gen_dev/src/generic64/mod.rs b/crates/compiler/gen_dev/src/generic64/mod.rs index 8d6482a5d3..a73beadb24 100644 --- a/crates/compiler/gen_dev/src/generic64/mod.rs +++ b/crates/compiler/gen_dev/src/generic64/mod.rs @@ -557,6 +557,18 @@ pub trait Assembler: Sized + Copy { fn sqrt_freg32_freg32(buf: &mut Vec<'_, u8>, dst: FloatReg, src: FloatReg); fn neg_reg64_reg64(buf: &mut Vec<'_, u8>, dst: GeneralReg, src: GeneralReg); + fn neg_freg64_freg64( + buf: &mut Vec<'_, u8>, + relocs: &mut Vec<'_, Relocation>, + dst: FloatReg, + src: FloatReg, + ); + fn neg_freg32_freg32( + buf: &mut Vec<'_, u8>, + relocs: &mut Vec<'_, Relocation>, + dst: FloatReg, + src: FloatReg, + ); fn mul_freg32_freg32_freg32( buf: &mut Vec<'_, u8>, dst: FloatReg, @@ -1791,7 +1803,24 @@ impl< let src_reg = self.storage_manager.load_to_general_reg(&mut self.buf, src); ASM::neg_reg64_reg64(&mut self.buf, dst_reg, src_reg); } - x => todo!("NumNeg: layout, {:?}", x), + LayoutRepr::F32 => { + let dst_reg = self.storage_manager.claim_float_reg(&mut self.buf, dst); + let src_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src); + ASM::neg_freg32_freg32(&mut self.buf, &mut self.relocs, dst_reg, src_reg); + } + LayoutRepr::F64 => { + let dst_reg = self.storage_manager.claim_float_reg(&mut self.buf, dst); + let src_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src); + ASM::neg_freg64_freg64(&mut self.buf, &mut self.relocs, dst_reg, src_reg); + } + LayoutRepr::DEC => self.build_fn_call( + dst, + bitcode::DEC_NEGATE.to_string(), + &[*src], + &[Layout::DEC], + &Layout::DEC, + ), + other => internal_error!("unreachable: NumNeg for layout, {:?}", other), } } diff --git a/crates/compiler/gen_dev/src/generic64/x86_64.rs b/crates/compiler/gen_dev/src/generic64/x86_64.rs index f03310886a..73cb0f418c 100644 --- a/crates/compiler/gen_dev/src/generic64/x86_64.rs +++ b/crates/compiler/gen_dev/src/generic64/x86_64.rs @@ -2602,6 +2602,28 @@ impl Assembler for X86_64Assembler { neg_reg64(buf, dst); } + #[inline(always)] + fn neg_freg64_freg64( + buf: &mut Vec<'_, u8>, + relocs: &mut Vec<'_, Relocation>, + dst: X86_64FloatReg, + src: X86_64FloatReg, + ) { + Self::mov_freg64_imm64(buf, relocs, dst, f64::from_bits(0x8000_0000_0000_0000)); + xorpd_freg64_freg64(buf, dst, src); + } + + #[inline(always)] + fn neg_freg32_freg32( + buf: &mut Vec<'_, u8>, + relocs: &mut Vec<'_, Relocation>, + dst: X86_64FloatReg, + src: X86_64FloatReg, + ) { + Self::mov_freg32_imm32(buf, relocs, dst, f32::from_bits(0x8000_0000)); + xorps_freg32_freg32(buf, dst, src); + } + #[inline(always)] fn sub_reg64_reg64_imm32( buf: &mut Vec<'_, u8>, @@ -3352,6 +3374,49 @@ fn sqrtss_freg32_freg32(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, src: X86_64F } } +/// `XORPD xmm1, xmm2/m128` -> Bitwise exclusive-OR of xmm2/m128 and xmm1. +#[inline(always)] +fn xorpd_freg64_freg64(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, src: X86_64FloatReg) { + let dst_high = dst as u8 > 7; + let dst_mod = dst as u8 % 8; + + let src_high = src as u8 > 7; + let src_mod = src as u8 % 8; + + if dst_high || src_high { + buf.extend([ + 0x66, + 0x40 | ((dst_high as u8) << 2) | (src_high as u8), + 0x0F, + 0x57, + 0xC0 | (dst_mod << 3) | src_mod, + ]) + } else { + buf.extend([0x66, 0x0F, 0x57, 0xC0 | (dst_mod << 3) | src_mod]); + } +} + +/// `XORPS xmm1,xmm2/m128` -> Bitwise exclusive-OR of xmm2/m128 and xmm1. +#[inline(always)] +fn xorps_freg32_freg32(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, src: X86_64FloatReg) { + let dst_high = dst as u8 > 7; + let dst_mod = dst as u8 % 8; + + let src_high = src as u8 > 7; + let src_mod = src as u8 % 8; + + if dst_high || src_high { + buf.extend([ + 0x40 | ((dst_high as u8) << 2) | (src_high as u8), + 0x0F, + 0x57, + 0xC0 | (dst_mod << 3) | src_mod, + ]); + } else { + buf.extend([0x0F, 0x57, 0xC0 | (dst_mod << 3) | src_mod]); + } +} + /// `TEST r/m64,r64` -> AND r64 with r/m64; set SF, ZF, PF according to result. #[allow(dead_code)] #[inline(always)] diff --git a/crates/compiler/gen_llvm/src/llvm/lowlevel.rs b/crates/compiler/gen_llvm/src/llvm/lowlevel.rs index 5580832b44..936cc0d9b4 100644 --- a/crates/compiler/gen_llvm/src/llvm/lowlevel.rs +++ b/crates/compiler/gen_llvm/src/llvm/lowlevel.rs @@ -2215,6 +2215,7 @@ fn build_dec_unary_op<'a, 'ctx>( match op { NumAbs => dec_unary_op(env, bitcode::DEC_ABS, arg), + NumNeg => dec_unary_op(env, bitcode::DEC_NEGATE, arg), NumAcos => dec_unary_op(env, bitcode::DEC_ACOS, arg), NumAsin => dec_unary_op(env, bitcode::DEC_ASIN, arg), NumAtan => dec_unary_op(env, bitcode::DEC_ATAN, arg), diff --git a/crates/compiler/gen_wasm/src/low_level.rs b/crates/compiler/gen_wasm/src/low_level.rs index 16fa30bb5d..a17f97e93c 100644 --- a/crates/compiler/gen_wasm/src/low_level.rs +++ b/crates/compiler/gen_wasm/src/low_level.rs @@ -1624,6 +1624,7 @@ impl<'a> LowLevelCall<'a> { } F32 => backend.code_builder.f32_neg(), F64 => backend.code_builder.f64_neg(), + Decimal => self.load_args_and_call_zig(backend, bitcode::DEC_NEGATE), _ => todo!("{:?} for {:?}", self.lowlevel, self.ret_layout), } } diff --git a/crates/compiler/test_gen/src/gen_num.rs b/crates/compiler/test_gen/src/gen_num.rs index 388e402413..6ab46c0252 100644 --- a/crates/compiler/test_gen/src/gen_num.rs +++ b/crates/compiler/test_gen/src/gen_num.rs @@ -1607,7 +1607,7 @@ fn tail_call_elimination() { #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] -fn int_negate() { +fn num_negate() { assert_evals_to!("Num.neg 123i8", -123, i8); assert_evals_to!("Num.neg Num.maxI8", -i8::MAX, i8); assert_evals_to!("Num.neg (Num.minI8 + 1)", i8::MAX, i8); @@ -1623,6 +1623,26 @@ fn int_negate() { assert_evals_to!("Num.neg 123", -123, i64); assert_evals_to!("Num.neg Num.maxI64", -i64::MAX, i64); assert_evals_to!("Num.neg (Num.minI64 + 1)", i64::MAX, i64); + + assert_evals_to!("Num.neg 12.3f32", -12.3, f32); + assert_evals_to!("Num.neg 0.0f32", -0.0, f32); + assert_evals_to!("Num.neg Num.maxF32", -f32::MAX, f32); + assert_evals_to!("Num.neg Num.minF32", -f32::MIN, f32); + assert_evals_to!("Num.neg Num.infinityF32", -f32::INFINITY, f32); + // can't test equality for nan + assert_evals_to!("Num.isNaN (Num.neg Num.nanF32)", true, bool); + + assert_evals_to!("Num.neg 12.3f64", -12.3, f64); + assert_evals_to!("Num.neg 0.0f64", -0.0, f64); + assert_evals_to!("Num.neg Num.maxF64", -f64::MAX, f64); + assert_evals_to!("Num.neg Num.minF64", -f64::MIN, f64); + assert_evals_to!("Num.neg Num.infinityF64", -f64::INFINITY, f64); + // can't test equality for nan + assert_evals_to!("Num.isNaN (Num.neg Num.nanF64)", true, bool); + + assert_evals_to!("Num.neg 123dec", RocDec::from(-123), RocDec); + // 0 is signless, unlike f32/f64 + assert_evals_to!("Num.neg 0dec", RocDec::from(0), RocDec); } #[test] From 205c78e2df71b090dd6a42ef53e2f4f0dde302be Mon Sep 17 00:00:00 2001 From: Anton-4 <17049058+Anton-4@users.noreply.github.com> Date: Sat, 23 Nov 2024 19:26:59 +0100 Subject: [PATCH 43/65] run glue_cli_tests single threaded --- .github/workflows/nix_linux_x86_64.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/nix_linux_x86_64.yml b/.github/workflows/nix_linux_x86_64.yml index a536a1b740..53fb41e109 100644 --- a/.github/workflows/nix_linux_x86_64.yml +++ b/.github/workflows/nix_linux_x86_64.yml @@ -24,7 +24,12 @@ jobs: run: nix develop -c ./ci/roc_test_builtins.sh - name: test wasm32 cli_tests - run: nix develop -c cargo test --locked --release --features="wasm32-cli-run" + # skipping glue tests due to difficult multithreading bug, we run them single threaded in the next step + run: nix develop -c cargo test --locked --release --features="wasm32-cli-run" -- --skip glue_cli_tests + + - name: wasm32 glue_cli_tests + # single threaded due to difficult bug when multithreading + run: nix develop -c cargo test --locked --release --features="wasm32-cli-run" glue_cli_tests -- --test-threads=1 - name: test the dev backend # these tests require an explicit feature flag run: nix develop -c cargo nextest run --locked --release --package test_gen --no-default-features --features gen-dev --no-fail-fast From b4fdb0a4c2a7449f540ce6fbf789371c366cab67 Mon Sep 17 00:00:00 2001 From: Joshua Warner Date: Sun, 24 Nov 2024 13:37:28 -0800 Subject: [PATCH 44/65] Make dbg parse strictly as an Apply This is important in order to fix some formatting bugs found in fuzzing --- ...st_suffixed__suffixed_tests__dbg_expr.snap | 66 ++++++++-------- crates/compiler/can/tests/test_suffixed.rs | 2 +- crates/compiler/fmt/src/expr.rs | 43 ++--------- crates/compiler/parse/src/expr.rs | 75 +++++++++---------- crates/compiler/parse/src/normalize.rs | 1 + crates/compiler/parse/src/parser.rs | 1 + .../tests/snapshots/pass/dbg.expr.result-ast | 2 +- .../pass/dbg_stmt.expr.formatted.roc | 2 +- .../snapshots/pass/dbg_stmt.expr.result-ast | 22 +++--- .../tests/snapshots/pass/dbg_stmt.expr.roc | 2 +- .../dbg_stmt_two_exprs.expr.formatted.roc | 6 +- .../pass/dbg_stmt_two_exprs.expr.result-ast | 51 +++++-------- .../pass/dbg_stmt_two_exprs.expr.roc | 4 +- crates/reporting/src/error/parse.rs | 1 + 14 files changed, 122 insertions(+), 156 deletions(-) diff --git a/crates/compiler/can/tests/snapshots/test_suffixed__suffixed_tests__dbg_expr.snap b/crates/compiler/can/tests/snapshots/test_suffixed__suffixed_tests__dbg_expr.snap index 73b5f70e41..c909ed7082 100644 --- a/crates/compiler/can/tests/snapshots/test_suffixed__suffixed_tests__dbg_expr.snap +++ b/crates/compiler/can/tests/snapshots/test_suffixed__suffixed_tests__dbg_expr.snap @@ -7,7 +7,7 @@ Defs { EitherIndex(2147483648), ], regions: [ - @0-26, + @0-28, ], space_before: [ Slice { start: 0, length: 0 }, @@ -24,13 +24,13 @@ Defs { @0-4 Identifier { ident: "main", }, - @11-26 Defs( + @11-28 Defs( Defs { tags: [ EitherIndex(2147483648), ], regions: [ - @15-26, + @16-27, ], space_before: [ Slice { start: 0, length: 0 }, @@ -42,17 +42,17 @@ Defs { type_defs: [], value_defs: [ Body( - @15-26 Identifier { + @16-27 Identifier { ident: "1", }, - @15-26 ParensAround( + @16-27 ParensAround( Defs( Defs { tags: [ EitherIndex(2147483648), ], regions: [ - @20-25, + @21-26, ], space_before: [ Slice { start: 0, length: 0 }, @@ -64,48 +64,50 @@ Defs { type_defs: [], value_defs: [ Body( - @20-25 Identifier { + @21-26 Identifier { ident: "0", }, - @20-25 Apply( - @22-23 Var { - module_name: "Num", - ident: "add", - }, - [ - @20-21 Num( - "1", + @21-26 ParensAround( + Apply( + @23-24 Var { + module_name: "Num", + ident: "add", + }, + [ + @21-22 Num( + "1", + ), + @25-26 Num( + "1", + ), + ], + BinOp( + Plus, ), - @24-25 Num( - "1", - ), - ], - BinOp( - Plus, ), ), ), ], }, - @15-26 LowLevelDbg( + @16-27 LowLevelDbg( ( "test.roc:3", " ", ), - @20-25 Apply( - @20-25 Var { + @21-26 Apply( + @21-26 Var { module_name: "Inspect", ident: "toStr", }, [ - @20-25 Var { + @21-26 Var { module_name: "", ident: "0", }, ], Space, ), - @20-25 Var { + @21-26 Var { module_name: "", ident: "0", }, @@ -115,25 +117,25 @@ Defs { ), ], }, - @11-26 LowLevelDbg( + @11-28 LowLevelDbg( ( "test.roc:2", - "in =\n ", + "n =\n ", ), - @15-26 Apply( - @15-26 Var { + @16-27 Apply( + @16-27 Var { module_name: "Inspect", ident: "toStr", }, [ - @15-26 Var { + @16-27 Var { module_name: "", ident: "1", }, ], Space, ), - @15-26 Var { + @16-27 Var { module_name: "", ident: "1", }, diff --git a/crates/compiler/can/tests/test_suffixed.rs b/crates/compiler/can/tests/test_suffixed.rs index 2a30c718d0..df260403cb 100644 --- a/crates/compiler/can/tests/test_suffixed.rs +++ b/crates/compiler/can/tests/test_suffixed.rs @@ -464,7 +464,7 @@ mod suffixed_tests { run_test!( r#" main = - dbg (dbg 1 + 1) + dbg (dbg (1 + 1)) "# ); } diff --git a/crates/compiler/fmt/src/expr.rs b/crates/compiler/fmt/src/expr.rs index 8f7d92d84f..d4a2a999b8 100644 --- a/crates/compiler/fmt/src/expr.rs +++ b/crates/compiler/fmt/src/expr.rs @@ -447,7 +447,7 @@ impl<'a> Formattable for Expr<'a> { buf.push_str("dbg"); } DbgStmt(condition, continuation) => { - fmt_dbg_stmt(buf, condition, continuation, self.is_multiline(), indent); + fmt_dbg_stmt(buf, condition, continuation, parens, indent); } LowLevelDbg(_, _, _) => unreachable!( "LowLevelDbg should only exist after desugaring, not during formatting" @@ -1022,42 +1022,15 @@ fn fmt_dbg_stmt<'a>( buf: &mut Buf, condition: &'a Loc>, continuation: &'a Loc>, - _: bool, + parens: Parens, indent: u16, ) { - buf.ensure_ends_with_newline(); - buf.indent(indent); - buf.push_str("dbg"); - - buf.spaces(1); - - fn should_outdent(mut expr: &Expr) -> bool { - loop { - match expr { - Expr::ParensAround(_) | Expr::List(_) | Expr::Record(_) | Expr::Tuple(_) => { - return true - } - Expr::SpaceAfter(inner, _) => { - expr = inner; - } - _ => return false, - } - } - } - - let inner_indent = if should_outdent(&condition.value) { - indent - } else { - indent + INDENT - }; - - let cond_value = condition.value.extract_spaces(); - - let is_defs = matches!(cond_value.item, Expr::Defs(_, _)); - - let newlines = if is_defs { Newlines::Yes } else { Newlines::No }; - - condition.format_with_options(buf, Parens::NotNeeded, newlines, inner_indent); + Expr::Apply( + &Loc::at_zero(Expr::Dbg), + &[condition], + called_via::CalledVia::Space, + ) + .format_with_options(buf, parens, Newlines::Yes, indent); // Always put a blank line after the `dbg` line(s) buf.ensure_ends_with_blank_line(); diff --git a/crates/compiler/parse/src/expr.rs b/crates/compiler/parse/src/expr.rs index 7fd20333a8..fb9131f01b 100644 --- a/crates/compiler/parse/src/expr.rs +++ b/crates/compiler/parse/src/expr.rs @@ -545,10 +545,6 @@ fn stmt_start<'a>( EExpr::Expect, expect_help(options, preceding_comment) )), - loc(specialize_err( - EExpr::Dbg, - dbg_stmt_help(options, preceding_comment) - )), loc(specialize_err(EExpr::Return, return_help(options))), loc(specialize_err(EExpr::Import, map(import(), Stmt::ValueDef))), map( @@ -2668,34 +2664,6 @@ fn return_help<'a>(options: ExprParseOptions) -> impl Parser<'a, Stmt<'a>, ERetu .trace("return_help") } -fn dbg_stmt_help<'a>( - options: ExprParseOptions, - preceding_comment: Region, -) -> impl Parser<'a, Stmt<'a>, EExpect<'a>> { - (move |arena: &'a Bump, state: State<'a>, min_indent| { - let (_, _, state) = - parser::keyword(keyword::DBG, EExpect::Dbg).parse(arena, state, min_indent)?; - - let (_, condition, state) = parse_block( - options, - arena, - state, - true, - EExpect::IndentCondition, - EExpect::Condition, - ) - .map_err(|(_, f)| (MadeProgress, f))?; - - let stmt = Stmt::ValueDef(ValueDef::Dbg { - condition: arena.alloc(condition), - preceding_comment, - }); - - Ok((MadeProgress, stmt, state)) - }) - .trace("dbg_stmt_help") -} - fn dbg_kw<'a>() -> impl Parser<'a, Expr<'a>, EExpect<'a>> { (move |arena: &'a Bump, state: State<'a>, min_indent: u32| { let (_, _, next_state) = @@ -3110,12 +3078,43 @@ fn stmts_to_defs<'a>( } Stmt::Expr(e) => { if i + 1 < stmts.len() { - defs.push_value_def( - ValueDef::Stmt(arena.alloc(Loc::at(sp_stmt.item.region, e))), - sp_stmt.item.region, - sp_stmt.before, - &[], - ); + if let Expr::Apply( + Loc { + value: Expr::Dbg, .. + }, + args, + _, + ) = e + { + if args.len() != 1 { + // TODO: this should be done in can, not parsing! + return Err(EExpr::Dbg( + EExpect::DbgArity(sp_stmt.item.region.start()), + sp_stmt.item.region.start(), + )); + } + let condition = &args[0]; + let rest = stmts_to_expr(&stmts[i + 1..], arena)?; + let e = Expr::DbgStmt(condition, arena.alloc(rest)); + + let e = if sp_stmt.before.is_empty() { + e + } else { + arena.alloc(e).before(sp_stmt.before) + }; + + last_expr = Some(Loc::at(sp_stmt.item.region, e)); + + // don't re-process the rest of the statements; they got consumed by the dbg expr + break; + } else { + defs.push_value_def( + ValueDef::Stmt(arena.alloc(Loc::at(sp_stmt.item.region, e))), + sp_stmt.item.region, + sp_stmt.before, + &[], + ); + } } else { let e = if sp_stmt.before.is_empty() { e diff --git a/crates/compiler/parse/src/normalize.rs b/crates/compiler/parse/src/normalize.rs index 6e79d807ba..6c5780ad8a 100644 --- a/crates/compiler/parse/src/normalize.rs +++ b/crates/compiler/parse/src/normalize.rs @@ -1465,6 +1465,7 @@ impl<'a> Normalize<'a> for EExpect<'a> { EExpect::Continuation(arena.alloc(inner_err.normalize(arena)), Position::zero()) } EExpect::IndentCondition(_) => EExpect::IndentCondition(Position::zero()), + EExpect::DbgArity(_) => EExpect::DbgArity(Position::zero()), } } } diff --git a/crates/compiler/parse/src/parser.rs b/crates/compiler/parse/src/parser.rs index 2eb972b18e..e21cfb222b 100644 --- a/crates/compiler/parse/src/parser.rs +++ b/crates/compiler/parse/src/parser.rs @@ -513,6 +513,7 @@ pub enum EExpect<'a> { Condition(&'a EExpr<'a>, Position), Continuation(&'a EExpr<'a>, Position), IndentCondition(Position), + DbgArity(Position), } #[derive(Debug, Clone, PartialEq, Eq)] diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/dbg.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/dbg.expr.result-ast index c298fa3337..b8b9e76ca5 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/dbg.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/dbg.expr.result-ast @@ -1,6 +1,6 @@ SpaceAfter( Apply( - @0-5 Dbg, + @0-3 Dbg, [ @4-5 Num( "1", diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/dbg_stmt.expr.formatted.roc b/crates/compiler/test_syntax/tests/snapshots/pass/dbg_stmt.expr.formatted.roc index 44de35e8d2..9e53fc6a9a 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/dbg_stmt.expr.formatted.roc +++ b/crates/compiler/test_syntax/tests/snapshots/pass/dbg_stmt.expr.formatted.roc @@ -1,3 +1,3 @@ -dbg 1 == 1 +dbg (1 == 1) 4 \ No newline at end of file diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/dbg_stmt.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/dbg_stmt.expr.result-ast index 895d9cf6e3..d6e409f5ed 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/dbg_stmt.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/dbg_stmt.expr.result-ast @@ -1,20 +1,22 @@ SpaceBefore( SpaceAfter( DbgStmt( - @5-11 BinOps( - [ - ( - @5-6 Num( - "1", + @6-12 ParensAround( + BinOps( + [ + ( + @6-7 Num( + "1", + ), + @8-10 Equals, ), - @7-9 Equals, + ], + @11-12 Num( + "1", ), - ], - @10-11 Num( - "1", ), ), - @13-14 SpaceBefore( + @15-16 SpaceBefore( Num( "4", ), diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/dbg_stmt.expr.roc b/crates/compiler/test_syntax/tests/snapshots/pass/dbg_stmt.expr.roc index fab415486d..a9eef8ef26 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/dbg_stmt.expr.roc +++ b/crates/compiler/test_syntax/tests/snapshots/pass/dbg_stmt.expr.roc @@ -1,4 +1,4 @@ -dbg 1 == 1 +dbg (1 == 1) 4 diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/dbg_stmt_two_exprs.expr.formatted.roc b/crates/compiler/test_syntax/tests/snapshots/pass/dbg_stmt_two_exprs.expr.formatted.roc index 92aa85cf3f..dd0ca03778 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/dbg_stmt_two_exprs.expr.formatted.roc +++ b/crates/compiler/test_syntax/tests/snapshots/pass/dbg_stmt_two_exprs.expr.formatted.roc @@ -1,6 +1,8 @@ dbg - q - qt + ( + q + qt + ) g qt \ No newline at end of file diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/dbg_stmt_two_exprs.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/dbg_stmt_two_exprs.expr.result-ast index 12532d964a..677c819ca1 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/dbg_stmt_two_exprs.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/dbg_stmt_two_exprs.expr.result-ast @@ -1,53 +1,38 @@ SpaceAfter( DbgStmt( - @5-10 SpaceBefore( - Defs( - Defs { - tags: [ - EitherIndex(2147483648), - ], - regions: [ - @5-6, - ], - space_before: [ - Slice { start: 0, length: 0 }, - ], - space_after: [ - Slice { start: 0, length: 0 }, - ], - spaces: [], - type_defs: [], - value_defs: [ - Stmt( - @5-6 Var { - module_name: "", - ident: "q", - }, - ), - ], - }, - @8-10 SpaceBefore( - Var { + @6-14 SpaceBefore( + ParensAround( + Apply( + @6-7 Var { module_name: "", - ident: "qt", + ident: "q", }, [ - Newline, + @12-14 SpaceBefore( + Var { + module_name: "", + ident: "qt", + }, + [ + Newline, + ], + ), ], + Space, ), ), [ Newline, ], ), - @11-16 SpaceBefore( + @16-21 SpaceBefore( Apply( - @11-12 Var { + @16-17 Var { module_name: "", ident: "g", }, [ - @14-16 SpaceBefore( + @19-21 SpaceBefore( Var { module_name: "", ident: "qt", diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/dbg_stmt_two_exprs.expr.roc b/crates/compiler/test_syntax/tests/snapshots/pass/dbg_stmt_two_exprs.expr.roc index 7cf4569d26..406e17a371 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/dbg_stmt_two_exprs.expr.roc +++ b/crates/compiler/test_syntax/tests/snapshots/pass/dbg_stmt_two_exprs.expr.roc @@ -1,5 +1,5 @@ dbg - q - qt + (q + qt) g qt diff --git a/crates/reporting/src/error/parse.rs b/crates/reporting/src/error/parse.rs index 62a36dda73..5e34152408 100644 --- a/crates/reporting/src/error/parse.rs +++ b/crates/reporting/src/error/parse.rs @@ -1512,6 +1512,7 @@ fn to_dbg_or_expect_report<'a>( to_space_report(alloc, lines, filename, err, *pos) } + roc_parse::parser::EExpect::DbgArity(_) => todo!(), roc_parse::parser::EExpect::Dbg(_) => unreachable!("another branch would be taken"), roc_parse::parser::EExpect::Expect(_) => unreachable!("another branch would be taken"), From e9eede7ac2cf69fb4fa98d5c403b73f8d0e43596 Mon Sep 17 00:00:00 2001 From: Anton-4 <17049058+Anton-4@users.noreply.github.com> Date: Mon, 25 Nov 2024 12:11:06 +0100 Subject: [PATCH 45/65] Update basic-cli docs link Signed-off-by: Anton-4 <17049058+Anton-4@users.noreply.github.com> --- www/content/docs.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/content/docs.md b/www/content/docs.md index 1dd26aff1e..4e2b4b1ef9 100644 --- a/www/content/docs.md +++ b/www/content/docs.md @@ -2,7 +2,7 @@ - [builtins](/builtins) - docs for modules built into the language—`Str`, `Num`, etc. - [basic-webserver](https://roc-lang.github.io/basic-webserver/) - a platform for making Web servers ([source code](https://github.com/roc-lang/basic-webserver)) -- [basic-cli](/packages/basic-cli/0.15.0) - a platform for making command-line interfaces ([source code](https://github.com/roc-lang/basic-cli)) +- [basic-cli](/packages/basic-cli/0.16.0) - a platform for making command-line interfaces ([source code](https://github.com/roc-lang/basic-cli)) - [plans](/plans) - current plans for future changes to the language In the future, a language reference will be on this page too. From b50b79fbe2c73fac4e489166ad702754e2f0bd6d Mon Sep 17 00:00:00 2001 From: Agus Zubiaga Date: Mon, 25 Nov 2024 19:01:41 -0300 Subject: [PATCH 46/65] Fix formatting in mono_type --- crates/compiler/specialize_types/src/mono_type.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/compiler/specialize_types/src/mono_type.rs b/crates/compiler/specialize_types/src/mono_type.rs index c66d0d9ad2..4a6986f424 100644 --- a/crates/compiler/specialize_types/src/mono_type.rs +++ b/crates/compiler/specialize_types/src/mono_type.rs @@ -120,9 +120,7 @@ impl MonoTypes { debug_assert!(opt.is_some(), "A MonoTypeId corresponded to an index that wasn't in MonoTypes. This should never happen!"); - unsafe { - opt.unwrap_unchecked() - } + unsafe { opt.unwrap_unchecked() } } pub(crate) fn add_primitive(&mut self, _primitive: Primitive) -> MonoTypeId { From b781b966a26efd7e42d22145fb0a952b8aa365a3 Mon Sep 17 00:00:00 2001 From: Agus Zubiaga Date: Mon, 25 Nov 2024 19:08:40 -0300 Subject: [PATCH 47/65] Remove test_compile dep on test_solve_helpers --- Cargo.lock | 1 - crates/test_compile/Cargo.toml | 1 - 2 files changed, 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 622ccfb31c..30af52c7af 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3834,7 +3834,6 @@ dependencies = [ "roc_specialize_types", "roc_target", "roc_types", - "test_solve_helpers", ] [[package]] diff --git a/crates/test_compile/Cargo.toml b/crates/test_compile/Cargo.toml index d31e04ece0..56b4a2e0ba 100644 --- a/crates/test_compile/Cargo.toml +++ b/crates/test_compile/Cargo.toml @@ -24,7 +24,6 @@ roc_constrain = { path = "../compiler/constrain" } roc_reporting = { path = "../reporting" } roc_target = { path = "../compiler/roc_target" } roc_solve = { path = "../compiler/solve" } -test_solve_helpers = { path = "../compiler/test_solve_helpers" } roc_solve_problem = { path = "../compiler/solve_problem" } roc_specialize_types.workspace = true From f8e70f87868802bb22f8d7448d8f3f29f7c51de3 Mon Sep 17 00:00:00 2001 From: Agus Zubiaga Date: Mon, 25 Nov 2024 19:26:49 -0300 Subject: [PATCH 48/65] Replace unwrap with debug_panic! --- crates/compiler/specialize_types/src/expr.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/crates/compiler/specialize_types/src/expr.rs b/crates/compiler/specialize_types/src/expr.rs index 8233b90081..c258dcf104 100644 --- a/crates/compiler/specialize_types/src/expr.rs +++ b/crates/compiler/specialize_types/src/expr.rs @@ -469,7 +469,12 @@ fn monomorphize_var(var: Variable, subs: &mut Subs) -> Variable { for (i, uls) in lambda_set.unspecialized.into_iter().enumerate() { let Uls(var, sym, region) = subs[uls]; let new_var = monomorphize_var(var, subs); - subs[new_unspecialized.into_iter().nth(i).unwrap()] = Uls(new_var, sym, region); + + if let Some(i) = new_unspecialized.into_iter().nth(i) { + subs[i] = Uls(new_var, sym, region); + } else { + debug_panic!("new_unspecialized is too short"); + } } let new_ambient_function = monomorphize_var(lambda_set.ambient_function, subs); let new_lambda_set = LambdaSet { From 0a98580277614f9a6869b2972327494bd52c404a Mon Sep 17 00:00:00 2001 From: Agus Zubiaga Date: Mon, 25 Nov 2024 19:32:06 -0300 Subject: [PATCH 49/65] Use "opaques" instead of "opaque types" in reporting --- crates/compiler/load/tests/test_reporting.rs | 4 ++-- crates/compiler/types/src/types.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/compiler/load/tests/test_reporting.rs b/crates/compiler/load/tests/test_reporting.rs index 7cb1f663a9..9d216db538 100644 --- a/crates/compiler/load/tests/test_reporting.rs +++ b/crates/compiler/load/tests/test_reporting.rs @@ -11054,8 +11054,8 @@ All branches in an `if` must have the same type! 4│ Recursive := [Infinitely Recursive] ^^^^^^^^^ - Recursion in opaques is only allowed if recursion happens behind a - tagged union, at least one variant of which is not recursive. + Recursion in opaque types is only allowed if recursion happens behind + a tagged union, at least one variant of which is not recursive. " ); diff --git a/crates/compiler/types/src/types.rs b/crates/compiler/types/src/types.rs index 2ae23a2d18..9af9f5aa28 100644 --- a/crates/compiler/types/src/types.rs +++ b/crates/compiler/types/src/types.rs @@ -3579,7 +3579,7 @@ impl AliasKind { pub fn as_str_plural(&self) -> &'static str { match self { AliasKind::Structural => "aliases", - AliasKind::Opaque => "opaques", + AliasKind::Opaque => "opaque types", } } } From 01892dcf2049f57bca4d6f0c50ce30ad7981d390 Mon Sep 17 00:00:00 2001 From: Agus Zubiaga Date: Mon, 25 Nov 2024 19:33:27 -0300 Subject: [PATCH 50/65] Rename into_nonempty_slice to as_nonempty_slice --- crates/soa/src/soa_slice.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/soa/src/soa_slice.rs b/crates/soa/src/soa_slice.rs index 1a9652249e..20540e4d80 100644 --- a/crates/soa/src/soa_slice.rs +++ b/crates/soa/src/soa_slice.rs @@ -115,7 +115,7 @@ impl Slice { } } - pub fn into_nonempty_slice(&self) -> Option> { + pub fn as_nonempty_slice(&self) -> Option> { NonZeroU16::new(self.length).map(|nonzero_len| NonEmptySlice::new(self.start, nonzero_len)) } } From c31b4574281eb6f00c2b637f7a2282b871565f6c Mon Sep 17 00:00:00 2001 From: Agus Zubiaga Date: Mon, 25 Nov 2024 19:35:26 -0300 Subject: [PATCH 51/65] Remove unnecessary soa_index Id type alias --- .../compiler/specialize_types/src/foreign_symbol.rs | 6 +++--- crates/compiler/specialize_types/src/mono_ir.rs | 12 ++++++------ crates/soa/src/soa_index.rs | 2 -- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/crates/compiler/specialize_types/src/foreign_symbol.rs b/crates/compiler/specialize_types/src/foreign_symbol.rs index b7f3dcc7e5..c668bc5ba5 100644 --- a/crates/compiler/specialize_types/src/foreign_symbol.rs +++ b/crates/compiler/specialize_types/src/foreign_symbol.rs @@ -1,5 +1,5 @@ use roc_module::ident::ForeignSymbol; -use soa::Id; +use soa::Index; #[derive(Debug, Default)] pub struct ForeignSymbols { @@ -26,12 +26,12 @@ impl ForeignSymbols { self.inner.push(entry); ForeignSymbolId { - inner: Id::new(id as u32), + inner: Index::new(id as u32), } } } #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub struct ForeignSymbolId { - inner: Id, + inner: Index, } diff --git a/crates/compiler/specialize_types/src/mono_ir.rs b/crates/compiler/specialize_types/src/mono_ir.rs index 854e8b4883..249da15a2f 100644 --- a/crates/compiler/specialize_types/src/mono_ir.rs +++ b/crates/compiler/specialize_types/src/mono_ir.rs @@ -6,7 +6,7 @@ use roc_can::expr::Recursive; use roc_module::low_level::LowLevel; use roc_module::symbol::Symbol; use roc_region::all::Region; -use soa::{Id, Index, NonEmptySlice, Slice, Slice2, Slice3}; +use soa::{Index, NonEmptySlice, Slice, Slice2, Slice3}; use std::iter; #[derive(Clone, Copy, Debug, PartialEq)] @@ -46,7 +46,7 @@ impl MonoExprs { self.regions.push(region); MonoExprId { - inner: Id::new(index), + inner: Index::new(index), } } @@ -146,22 +146,22 @@ impl MonoExprs { #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct MonoExprId { - inner: Id, + inner: Index, } impl MonoExprId { - pub(crate) unsafe fn new_unchecked(inner: Id) -> Self { + pub(crate) unsafe fn new_unchecked(inner: Index) -> Self { Self { inner } } } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct MonoStmtId { - inner: Id, + inner: Index, } impl MonoStmtId { - pub(crate) unsafe fn new_unchecked(inner: Id) -> Self { + pub(crate) unsafe fn new_unchecked(inner: Index) -> Self { Self { inner } } } diff --git a/crates/soa/src/soa_index.rs b/crates/soa/src/soa_index.rs index c01d2b6e3d..9d955ace0f 100644 --- a/crates/soa/src/soa_index.rs +++ b/crates/soa/src/soa_index.rs @@ -8,8 +8,6 @@ use core::{ use crate::soa_slice::Slice; -pub type Id = Index; - /// An index into an array of values, based /// on an offset into the array rather than a pointer. /// From 6e5f9eb5109bfb6220d780c8a72a391628ba131a Mon Sep 17 00:00:00 2001 From: Agus Zubiaga Date: Mon, 25 Nov 2024 19:46:19 -0300 Subject: [PATCH 52/65] Ignore whitespace-only lines in test_compile deindent --- crates/test_compile/src/deindent.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/test_compile/src/deindent.rs b/crates/test_compile/src/deindent.rs index 90f67cbd2d..9630d9052f 100644 --- a/crates/test_compile/src/deindent.rs +++ b/crates/test_compile/src/deindent.rs @@ -51,7 +51,7 @@ pub fn trim_and_deindent<'a>(arena: &'a Bump, input: &'a str) -> &'a str { // Find the smallest indent of the remaining lines. That's our indentation amount. let smallest_indent = lines .iter() - .filter(|line| !line.is_empty()) + .filter(|line| !line.trim().is_empty()) .map(|line| line.chars().take_while(|&ch| ch == ' ').count()) .min() .unwrap_or(0); From 4dd61e5c3c07239f16f8b6d3195927dec94ab093 Mon Sep 17 00:00:00 2001 From: Agus Zubiaga Date: Mon, 25 Nov 2024 19:50:02 -0300 Subject: [PATCH 53/65] Remove inaccurate comment --- crates/test_compile/src/deindent.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/test_compile/src/deindent.rs b/crates/test_compile/src/deindent.rs index 9630d9052f..9e7d23eae5 100644 --- a/crates/test_compile/src/deindent.rs +++ b/crates/test_compile/src/deindent.rs @@ -17,8 +17,6 @@ use bumpalo::Bump; /// (e.g. `" x = 1"` here) and trimming that many spaces from the beginning /// of each subsequent line. The end of the string is then trimmed normally, and /// any remaining empty lines are left empty. -/// -/// This function is a no-op on single-line strings. pub fn trim_and_deindent<'a>(arena: &'a Bump, input: &'a str) -> &'a str { let newline_count = input.chars().filter(|&ch| ch == '\n').count(); From 11f37eeb591ee4a0062c8d57ba3e2957679baf02 Mon Sep 17 00:00:00 2001 From: Agus Zubiaga Date: Mon, 25 Nov 2024 19:53:34 -0300 Subject: [PATCH 54/65] Add missing thrid item to soa_slice3 iter --- crates/soa/src/soa_slice3.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/crates/soa/src/soa_slice3.rs b/crates/soa/src/soa_slice3.rs index 75fda00d4e..ec0c554b76 100644 --- a/crates/soa/src/soa_slice3.rs +++ b/crates/soa/src/soa_slice3.rs @@ -103,7 +103,7 @@ impl Slice3 { } impl IntoIterator for Slice3 { - type Item = (Index, Index); + type Item = (Index, Index, Index); type IntoIter = SliceIterator; fn into_iter(self) -> Self::IntoIter { @@ -120,7 +120,7 @@ pub struct SliceIterator { } impl Iterator for SliceIterator { - type Item = (Index, Index); + type Item = (Index, Index, Index); fn next(&mut self) -> Option { let offset = self.offset; @@ -134,10 +134,14 @@ impl Iterator for SliceIterator { index: self.slice.start2 + offset, _marker: PhantomData, }; + let index3 = Index { + index: self.slice.start3 + offset, + _marker: PhantomData, + }; self.offset += 1; - Some((index1, index2)) + Some((index1, index2, index3)) } else { None } From 29b4ca034038a0315a5fbaea118792d8430afe0e Mon Sep 17 00:00:00 2001 From: Agus Zubiaga Date: Mon, 25 Nov 2024 19:54:43 -0300 Subject: [PATCH 55/65] Add TODO to use interner --- crates/compiler/specialize_types/src/mono_module.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/compiler/specialize_types/src/mono_module.rs b/crates/compiler/specialize_types/src/mono_module.rs index dc10d9ca89..b6ed6ee0ae 100644 --- a/crates/compiler/specialize_types/src/mono_module.rs +++ b/crates/compiler/specialize_types/src/mono_module.rs @@ -7,6 +7,7 @@ use crate::{foreign_symbol::ForeignSymbols, mono_type::MonoTypes, DebugInfo}; pub struct MonoModule { mono_types: MonoTypes, foreign_symbols: ForeignSymbols, + // TODO [mono2]: interner type interned_strings: Vec, debug_info: DebugInfo, } From 1b2832534f09d102bc08c5d1cbac7a8d0aed37bc Mon Sep 17 00:00:00 2001 From: shua Date: Tue, 26 Nov 2024 19:18:01 +0100 Subject: [PATCH 56/65] git ignore helix config using helix editor, it's useful to specify workspace-lsp-roots with all the crate roots in a repo-local directory '.helix' --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 9497a4f7c9..04a6c6e09c 100644 --- a/.gitignore +++ b/.gitignore @@ -59,6 +59,7 @@ metadata #editors .idea/ .vscode/ +.helix/ .ignore .exrc .vimrc From 621bd177e609fd1bb6d48b2d54c692f5cfe8bb3c Mon Sep 17 00:00:00 2001 From: shua Date: Tue, 26 Nov 2024 21:18:38 +0100 Subject: [PATCH 57/65] rm unused dependencies left in perfcnt and criterion-perf-events because they are used in nightly_benches, which is not included in the workspace so cargo doesn't know about it. All the others are not currently used anywhere in the repository. --- Cargo.lock | 2 -- Cargo.toml | 20 -------------------- crates/glue/Cargo.toml | 1 - crates/repl_cli/Cargo.toml | 1 - 4 files changed, 24 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 287ce67f37..adfe32105a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2630,7 +2630,6 @@ dependencies = [ "cli_test_utils", "dircpy", "fnv", - "indexmap", "indoc", "libc", "libloading", @@ -2954,7 +2953,6 @@ dependencies = [ "rustyline-derive", "target-lexicon", "tempfile", - "unicode-segmentation", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index dc6e58b7b1..739b885edd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -79,7 +79,6 @@ inkwell = { git = "https://github.com/roc-lang/inkwell", branch = "inkwell-llvm- soa = { path = "crates/soa" } arrayvec = "0.7.2" # update roc_std/Cargo.toml on change -backtrace = "0.3.67" base64-url = "1.4.13" bincode = "1.3.3" bitflags = "1.3.2" @@ -87,9 +86,7 @@ bitvec = "1.0.1" blake3 = "1.3.3" brotli = "3.3.4" # used for decompressing tarballs over HTTPS, if the server supports brotli bumpalo = { version = "3.12.0", features = ["collections"] } -bytemuck = { version = "1.13.1", features = ["derive"] } capstone = { version = "0.11.0", default-features = false } -cgmath = "0.18.0" chrono = "0.4.26" clap = { version = "4.2.7", default-features = false, features = [ "std", @@ -99,10 +96,8 @@ clap = { version = "4.2.7", default-features = false, features = [ "usage", "error-context", ] } -colored = "2.0.0" console_error_panic_hook = "0.1.7" const_format = { version = "0.2.30", features = ["const_generics"] } -copypasta = "0.8.2" criterion = { git = "https://github.com/Anton-4/criterion.rs", features = [ "html_reports", ], rev = "30ea0c5" } @@ -116,7 +111,6 @@ flate2 = "1.0.25" fnv = "1.0.7" fs_extra = "1.3.0" futures = "0.3.26" -glyph_brush = "0.7.7" hashbrown = { version = "0.14.3" } iced-x86 = { version = "1.18.0", default-features = false, features = [ "std", @@ -126,13 +120,11 @@ iced-x86 = { version = "1.18.0", default-features = false, features = [ ] } im = "15.1.0" im-rc = "15.1.0" -indexmap = "2.1.0" indoc = "1.0.9" insta = "1.28.0" js-sys = "0.3.61" lazy_static = "1.4.0" libc = "0.2.139" # update roc_std/Cargo.toml on change -libfuzzer-sys = "0.4" libloading = "0.7.4" libtest-mimic = "0.6.0" log = "0.4.17" @@ -140,18 +132,13 @@ mach_object = "0.1" maplit = "1.0.2" memmap2 = "0.5.10" mimalloc = { version = "0.1.34", default-features = false } -nonempty = "0.8.1" object = { version = "0.32.2", default-features = false, features = [ "read", "write", ] } packed_struct = "0.10.1" -page_size = "0.5.0" -palette = "0.6.1" parking_lot = "0.12" perfcnt = "0.8.0" -pest = "2.5.6" -pest_derive = "2.5.6" pretty_assertions = "1.3.0" # update roc_std/Cargo.toml on change proc-macro2 = "1.0.63" proptest = "1.1.0" @@ -185,19 +172,12 @@ syn = { version = "1.0.109", features = ["full", "extra-traits"] } tar = "0.4.38" target-lexicon = "0.12.6" tempfile = "=3.2.0" -threadpool = "1.8.1" tracing = { version = "0.1.40", features = ["release_max_level_off"] } tracing-appender = "0.2.2" tracing-subscriber = { version = "0.3.16", features = ["env-filter"] } -unicode-segmentation = "1.10.1" -uuid = { version = "1.3.0", features = ["v4"] } walkdir = "2.3.2" wasm-bindgen = "0.2.84" wasm-bindgen-futures = "0.4.34" -wgpu = "0.12.0" -wgpu_glyph = "0.16.0" -winapi = { version = "0.3.9", features = ["memoryapi"] } -winit = "0.26.1" wyhash = "0.5.0" # Optimizations based on https://deterministic.space/high-performance-rust.html diff --git a/crates/glue/Cargo.toml b/crates/glue/Cargo.toml index 2f3d92d40c..6e491f5169 100644 --- a/crates/glue/Cargo.toml +++ b/crates/glue/Cargo.toml @@ -27,7 +27,6 @@ roc_types = { path = "../compiler/types" } bumpalo.workspace = true fnv.workspace = true -indexmap.workspace = true libc.workspace = true libloading.workspace = true strum.workspace = true diff --git a/crates/repl_cli/Cargo.toml b/crates/repl_cli/Cargo.toml index c69ba39883..3ada03b64c 100644 --- a/crates/repl_cli/Cargo.toml +++ b/crates/repl_cli/Cargo.toml @@ -41,7 +41,6 @@ libloading.workspace = true rustyline-derive.workspace = true rustyline.workspace = true target-lexicon.workspace = true -unicode-segmentation.workspace = true [lib] name = "roc_repl_cli" From fc10883c47e7c8e92d24fbba81f2e92fd11ad66f Mon Sep 17 00:00:00 2001 From: shua Date: Tue, 26 Nov 2024 23:03:54 +0100 Subject: [PATCH 58/65] PR: add libfuzzer-sys back, test_syntax-fuzz to ws --- Cargo.lock | 37 +++++++++++++++++++++ Cargo.toml | 2 ++ crates/compiler/test_syntax/fuzz/Cargo.toml | 8 ++--- 3 files changed, 41 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index adfe32105a..03468c1dc7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -131,6 +131,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "arbitrary" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" + [[package]] name = "arrayref" version = "0.3.7" @@ -388,6 +394,8 @@ version = "1.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57b6a275aa2903740dc87da01c62040406b8812552e97129a63ea8850a17c6e6" dependencies = [ + "jobserver", + "libc", "shlex", ] @@ -1374,6 +1382,15 @@ version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +[[package]] +name = "jobserver" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" +dependencies = [ + "libc", +] + [[package]] name = "js-sys" version = "0.3.64" @@ -1405,6 +1422,16 @@ version = "0.2.158" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" +[[package]] +name = "libfuzzer-sys" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b9569d2f74e257076d8c6bfa73fb505b46b851e51ddaecc825944aa3bed17fa" +dependencies = [ + "arbitrary", + "cc", +] + [[package]] name = "libloading" version = "0.7.4" @@ -3916,6 +3943,16 @@ dependencies = [ "walkdir", ] +[[package]] +name = "test_syntax-fuzz" +version = "0.0.0" +dependencies = [ + "bumpalo", + "libfuzzer-sys", + "roc_parse", + "test_syntax", +] + [[package]] name = "textwrap" version = "0.11.0" diff --git a/Cargo.toml b/Cargo.toml index 739b885edd..2e4b8f1f1d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,6 +31,7 @@ members = [ "crates/wasm_interp", "crates/language_server", "crates/roc_std_heap", + "crates/compiler/test_syntax/fuzz", ] exclude = [ @@ -125,6 +126,7 @@ insta = "1.28.0" js-sys = "0.3.61" lazy_static = "1.4.0" libc = "0.2.139" # update roc_std/Cargo.toml on change +libfuzzer-sys = "0.4" libloading = "0.7.4" libtest-mimic = "0.6.0" log = "0.4.17" diff --git a/crates/compiler/test_syntax/fuzz/Cargo.toml b/crates/compiler/test_syntax/fuzz/Cargo.toml index 42e896950a..8e67cede4d 100644 --- a/crates/compiler/test_syntax/fuzz/Cargo.toml +++ b/crates/compiler/test_syntax/fuzz/Cargo.toml @@ -13,12 +13,8 @@ cargo-fuzz = true test_syntax = { path = "../../test_syntax" } roc_parse = { path = "../../parse" } -bumpalo = { version = "3.12.0", features = ["collections"] } -libfuzzer-sys = "0.4" - -# Prevent this from interfering with workspaces -[workspace] -members = ["."] +bumpalo.workspace = true +libfuzzer-sys.workspace = true [[bin]] name = "fuzz_expr" From 2649716e4a46550c9c5f5f6f77a52bf8d6fd61ec Mon Sep 17 00:00:00 2001 From: shua Date: Wed, 27 Nov 2024 00:42:56 +0100 Subject: [PATCH 59/65] PR: old code not fmt'd --- crates/compiler/test_syntax/fuzz/fuzz_targets/fuzz_module.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/compiler/test_syntax/fuzz/fuzz_targets/fuzz_module.rs b/crates/compiler/test_syntax/fuzz/fuzz_targets/fuzz_module.rs index 034974911f..406b571fa7 100644 --- a/crates/compiler/test_syntax/fuzz/fuzz_targets/fuzz_module.rs +++ b/crates/compiler/test_syntax/fuzz/fuzz_targets/fuzz_module.rs @@ -1,6 +1,6 @@ #![no_main] -use libfuzzer_sys::fuzz_target; use bumpalo::Bump; +use libfuzzer_sys::fuzz_target; use test_syntax::test_helpers::Input; fuzz_target!(|data: &[u8]| { From 171ddde9989ca04180e493c45a7c6a0e5f4b41f8 Mon Sep 17 00:00:00 2001 From: Luke Boswell Date: Wed, 27 Nov 2024 11:25:14 +1100 Subject: [PATCH 60/65] restore fuzzer --- crates/compiler/test_syntax/fuzz/Cargo.toml | 8 ++++++-- crates/compiler/test_syntax/fuzz/README.md | 2 +- .../compiler/test_syntax/fuzz/fuzz_targets/fuzz_module.rs | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/crates/compiler/test_syntax/fuzz/Cargo.toml b/crates/compiler/test_syntax/fuzz/Cargo.toml index 8e67cede4d..42e896950a 100644 --- a/crates/compiler/test_syntax/fuzz/Cargo.toml +++ b/crates/compiler/test_syntax/fuzz/Cargo.toml @@ -13,8 +13,12 @@ cargo-fuzz = true test_syntax = { path = "../../test_syntax" } roc_parse = { path = "../../parse" } -bumpalo.workspace = true -libfuzzer-sys.workspace = true +bumpalo = { version = "3.12.0", features = ["collections"] } +libfuzzer-sys = "0.4" + +# Prevent this from interfering with workspaces +[workspace] +members = ["."] [[bin]] name = "fuzz_expr" diff --git a/crates/compiler/test_syntax/fuzz/README.md b/crates/compiler/test_syntax/fuzz/README.md index ce3fd835d7..ff96def8b1 100644 --- a/crates/compiler/test_syntax/fuzz/README.md +++ b/crates/compiler/test_syntax/fuzz/README.md @@ -4,7 +4,7 @@ To setup fuzzing you will need to install cargo-fuzz and run with rust nightly: ```sh $ cargo install cargo-fuzz -$ cargo +nightly fuzz run -j -- -dict=../parse/fuzz/dict.txt +$ cargo +nightly fuzz run -j -- -dict=../dict.txt ``` The different targets can be found by running `cargo fuzz list`. diff --git a/crates/compiler/test_syntax/fuzz/fuzz_targets/fuzz_module.rs b/crates/compiler/test_syntax/fuzz/fuzz_targets/fuzz_module.rs index 406b571fa7..034974911f 100644 --- a/crates/compiler/test_syntax/fuzz/fuzz_targets/fuzz_module.rs +++ b/crates/compiler/test_syntax/fuzz/fuzz_targets/fuzz_module.rs @@ -1,6 +1,6 @@ #![no_main] -use bumpalo::Bump; use libfuzzer_sys::fuzz_target; +use bumpalo::Bump; use test_syntax::test_helpers::Input; fuzz_target!(|data: &[u8]| { From 67e6f1f7fd81e64451bb18e73075e0b6e0a449f7 Mon Sep 17 00:00:00 2001 From: Luke Boswell Date: Wed, 27 Nov 2024 11:33:21 +1100 Subject: [PATCH 61/65] fmt & clippy --- Cargo.lock | 37 ------------------------------------- Cargo.toml | 1 - 2 files changed, 38 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 03468c1dc7..adfe32105a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -131,12 +131,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "arbitrary" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" - [[package]] name = "arrayref" version = "0.3.7" @@ -394,8 +388,6 @@ version = "1.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57b6a275aa2903740dc87da01c62040406b8812552e97129a63ea8850a17c6e6" dependencies = [ - "jobserver", - "libc", "shlex", ] @@ -1382,15 +1374,6 @@ version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" -[[package]] -name = "jobserver" -version = "0.1.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" -dependencies = [ - "libc", -] - [[package]] name = "js-sys" version = "0.3.64" @@ -1422,16 +1405,6 @@ version = "0.2.158" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" -[[package]] -name = "libfuzzer-sys" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b9569d2f74e257076d8c6bfa73fb505b46b851e51ddaecc825944aa3bed17fa" -dependencies = [ - "arbitrary", - "cc", -] - [[package]] name = "libloading" version = "0.7.4" @@ -3943,16 +3916,6 @@ dependencies = [ "walkdir", ] -[[package]] -name = "test_syntax-fuzz" -version = "0.0.0" -dependencies = [ - "bumpalo", - "libfuzzer-sys", - "roc_parse", - "test_syntax", -] - [[package]] name = "textwrap" version = "0.11.0" diff --git a/Cargo.toml b/Cargo.toml index 2e4b8f1f1d..bed03a5b73 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,7 +31,6 @@ members = [ "crates/wasm_interp", "crates/language_server", "crates/roc_std_heap", - "crates/compiler/test_syntax/fuzz", ] exclude = [ From 568525ad7e472904160535e92bb86fb5d1ebaf41 Mon Sep 17 00:00:00 2001 From: Agus Zubiaga Date: Tue, 26 Nov 2024 21:47:17 -0300 Subject: [PATCH 62/65] Allow panics in test_compile since it's only used in tests --- crates/test_compile/src/lib.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/test_compile/src/lib.rs b/crates/test_compile/src/lib.rs index f483aa5142..724e753f81 100644 --- a/crates/test_compile/src/lib.rs +++ b/crates/test_compile/src/lib.rs @@ -1,3 +1,6 @@ +// This crate is only used in tests, so panic is fine +#![allow(clippy::panic)] + mod deindent; mod help_can; mod help_constrain; From 6ffc8a507b2b24dbd95ae6fb4c674f13142ee8e3 Mon Sep 17 00:00:00 2001 From: Agus Zubiaga Date: Wed, 27 Nov 2024 14:02:13 -0300 Subject: [PATCH 63/65] Allow suffixed pure functions that are exposed to the host --- crates/compiler/can/src/constraint.rs | 7 +++ crates/compiler/load/tests/helpers/mod.rs | 5 ++- crates/compiler/load/tests/test_reporting.rs | 45 +++----------------- crates/compiler/load_internal/src/file.rs | 19 +++++++++ crates/compiler/solve/src/module.rs | 4 +- crates/compiler/solve/src/solve.rs | 20 +++++++-- crates/compiler/test_derive/src/util.rs | 1 + crates/test_compile/src/help_solve.rs | 1 + 8 files changed, 57 insertions(+), 45 deletions(-) diff --git a/crates/compiler/can/src/constraint.rs b/crates/compiler/can/src/constraint.rs index dd9700a4ab..d71d56f0c0 100644 --- a/crates/compiler/can/src/constraint.rs +++ b/crates/compiler/can/src/constraint.rs @@ -957,6 +957,13 @@ impl FxSuffixKind { Self::UnsuffixedRecordField => IdentSuffix::None, } } + + pub fn symbol(&self) -> Option<&Symbol> { + match self { + Self::Let(symbol) | Self::Pattern(symbol) => Some(symbol), + Self::UnsuffixedRecordField => None, + } + } } #[derive(Debug, Clone, Copy, PartialEq)] diff --git a/crates/compiler/load/tests/helpers/mod.rs b/crates/compiler/load/tests/helpers/mod.rs index b5d2cc9518..89a3c6236e 100644 --- a/crates/compiler/load/tests/helpers/mod.rs +++ b/crates/compiler/load/tests/helpers/mod.rs @@ -51,10 +51,11 @@ pub fn infer_expr( exposed_by_module: &Default::default(), derived_module, function_kind: FunctionKind::LambdaSet, - #[cfg(debug_assertions)] - checkmate: None, module_params: None, module_params_vars: Default::default(), + host_exposed_symbols: None, + #[cfg(debug_assertions)] + checkmate: None, }; let RunSolveOutput { solved, .. } = diff --git a/crates/compiler/load/tests/test_reporting.rs b/crates/compiler/load/tests/test_reporting.rs index 9d216db538..541e3a04be 100644 --- a/crates/compiler/load/tests/test_reporting.rs +++ b/crates/compiler/load/tests/test_reporting.rs @@ -14812,7 +14812,7 @@ All branches in an `if` must have the same type! Str.trim msg "# ), - @r###" + @r#" ── EFFECT IN PURE FUNCTION in /code/proj/Main.roc ────────────────────────────── This call to `Effect.putLine!` might produce an effect: @@ -14829,18 +14829,7 @@ All branches in an `if` must have the same type! You can still run the program with this error, which can be helpful when you're debugging. - - ── UNNECESSARY EXCLAMATION in /code/proj/Main.roc ────────────────────────────── - - This function is pure, but its name suggests otherwise: - - 5│ main! = \{} -> - ^^^^^ - - The exclamation mark at the end is reserved for effectful functions. - - Hint: Did you forget to run an effect? Is the type annotation wrong? - "### + "# ); test_report!( @@ -15423,7 +15412,7 @@ All branches in an `if` must have the same type! pureHigherOrder = \f, x -> f x "# ), - @r###" + @r#" ── TYPE MISMATCH in /code/proj/Main.roc ──────────────────────────────────────── This 1st argument to `pureHigherOrder` has an unexpected type: @@ -15438,18 +15427,7 @@ All branches in an `if` must have the same type! But `pureHigherOrder` needs its 1st argument to be: Str -> {} - - ── UNNECESSARY EXCLAMATION in /code/proj/Main.roc ────────────────────────────── - - This function is pure, but its name suggests otherwise: - - 5│ main! = \{} -> - ^^^^^ - - The exclamation mark at the end is reserved for effectful functions. - - Hint: Did you forget to run an effect? Is the type annotation wrong? - "### + "# ); test_report!( @@ -15467,7 +15445,7 @@ All branches in an `if` must have the same type! pureHigherOrder = \f, x -> f x "# ), - @r###" + @r#" ── TYPE MISMATCH in /code/proj/Main.roc ──────────────────────────────────────── This 1st argument to `pureHigherOrder` has an unexpected type: @@ -15482,17 +15460,6 @@ All branches in an `if` must have the same type! But `pureHigherOrder` needs its 1st argument to be: Str -> {} - - ── UNNECESSARY EXCLAMATION in /code/proj/Main.roc ────────────────────────────── - - This function is pure, but its name suggests otherwise: - - 5│ main! = \{} -> - ^^^^^ - - The exclamation mark at the end is reserved for effectful functions. - - Hint: Did you forget to run an effect? Is the type annotation wrong? - "### + "# ); } diff --git a/crates/compiler/load_internal/src/file.rs b/crates/compiler/load_internal/src/file.rs index ff7ce3a50c..1899315113 100644 --- a/crates/compiler/load_internal/src/file.rs +++ b/crates/compiler/load_internal/src/file.rs @@ -350,6 +350,8 @@ fn start_phase<'a>( None }; + let is_host_exposed = state.root_id == module.module_id; + BuildTask::solve_module( module, ident_ids, @@ -367,6 +369,7 @@ fn start_phase<'a>( state.cached_types.clone(), derived_module, state.exec_mode, + is_host_exposed, // #[cfg(debug_assertions)] checkmate, @@ -922,6 +925,7 @@ enum BuildTask<'a> { cached_subs: CachedTypeState, derived_module: SharedDerivedModule, exec_mode: ExecutionMode, + is_host_exposed: bool, #[cfg(debug_assertions)] checkmate: Option, @@ -4331,6 +4335,7 @@ impl<'a> BuildTask<'a> { cached_subs: CachedTypeState, derived_module: SharedDerivedModule, exec_mode: ExecutionMode, + is_host_exposed: bool, #[cfg(debug_assertions)] checkmate: Option, ) -> Self { @@ -4355,6 +4360,7 @@ impl<'a> BuildTask<'a> { cached_subs, derived_module, exec_mode, + is_host_exposed, #[cfg(debug_assertions)] checkmate, @@ -4661,6 +4667,7 @@ fn run_solve_solve( var_store: VarStore, module: Module, derived_module: SharedDerivedModule, + is_host_exposed: bool, #[cfg(debug_assertions)] checkmate: Option, ) -> SolveResult { @@ -4711,6 +4718,12 @@ fn run_solve_solve( let (solve_output, solved_implementations, exposed_vars_by_symbol) = { let module_id = module.module_id; + let host_exposed_idents = if is_host_exposed { + Some(&exposed_symbols) + } else { + None + }; + let solve_config = SolveConfig { home: module_id, types, @@ -4724,6 +4737,7 @@ fn run_solve_solve( checkmate, module_params, module_params_vars: imported_param_vars, + host_exposed_symbols: host_exposed_idents, }; let solve_output = roc_solve::module::run_solve( @@ -4800,6 +4814,7 @@ fn run_solve<'a>( cached_types: CachedTypeState, derived_module: SharedDerivedModule, exec_mode: ExecutionMode, + is_host_exposed: bool, #[cfg(debug_assertions)] checkmate: Option, ) -> Msg<'a> { @@ -4831,6 +4846,7 @@ fn run_solve<'a>( var_store, module, derived_module, + is_host_exposed, // #[cfg(debug_assertions)] checkmate, @@ -4863,6 +4879,7 @@ fn run_solve<'a>( var_store, module, derived_module, + is_host_exposed, // #[cfg(debug_assertions)] checkmate, @@ -6256,6 +6273,7 @@ fn run_task<'a>( cached_subs, derived_module, exec_mode, + is_host_exposed, #[cfg(debug_assertions)] checkmate, @@ -6275,6 +6293,7 @@ fn run_task<'a>( cached_subs, derived_module, exec_mode, + is_host_exposed, // #[cfg(debug_assertions)] checkmate, diff --git a/crates/compiler/solve/src/module.rs b/crates/compiler/solve/src/module.rs index e8a5e3a0b6..ea5a7b0b6d 100644 --- a/crates/compiler/solve/src/module.rs +++ b/crates/compiler/solve/src/module.rs @@ -6,7 +6,7 @@ use roc_can::constraint::{Constraint, Constraints}; use roc_can::expr::PendingDerives; use roc_can::module::{ExposedByModule, ModuleParams, ResolvedImplementations, RigidVariables}; use roc_collections::all::MutMap; -use roc_collections::VecMap; +use roc_collections::{VecMap, VecSet}; use roc_derive::SharedDerivedModule; use roc_error_macros::internal_error; use roc_module::symbol::{ModuleId, Symbol}; @@ -76,6 +76,8 @@ pub struct SolveConfig<'a> { /// Needed during solving to resolve lambda sets from derived implementations that escape into /// the user module. pub derived_module: SharedDerivedModule, + /// + pub host_exposed_symbols: Option<&'a VecSet>, #[cfg(debug_assertions)] /// The checkmate collector for this module. diff --git a/crates/compiler/solve/src/solve.rs b/crates/compiler/solve/src/solve.rs index ca0df7cabe..e45907f8ca 100644 --- a/crates/compiler/solve/src/solve.rs +++ b/crates/compiler/solve/src/solve.rs @@ -19,7 +19,7 @@ use roc_can::constraint::{ }; use roc_can::expected::{Expected, PExpected}; use roc_can::module::ModuleParams; -use roc_collections::VecMap; +use roc_collections::{VecMap, VecSet}; use roc_debug_flags::dbg_do; #[cfg(debug_assertions)] use roc_debug_flags::ROC_VERIFY_RIGID_LET_GENERALIZED; @@ -136,6 +136,7 @@ fn run_help( function_kind, module_params, module_params_vars, + host_exposed_symbols, .. } = config; @@ -190,6 +191,7 @@ fn run_help( &mut awaiting_specializations, module_params, module_params_vars, + host_exposed_symbols, ); RunSolveOutput { @@ -249,6 +251,7 @@ fn solve( awaiting_specializations: &mut AwaitingSpecializations, module_params: Option, module_params_vars: VecMap, + host_exposed_symbols: Option<&VecSet>, ) -> State { let scope = Scope::new(module_params); @@ -455,6 +458,7 @@ fn solve( solve_suffix_fx( env, problems, + host_exposed_symbols, FxSuffixKind::Let(*symbol), loc_var.value, &loc_var.region, @@ -853,7 +857,7 @@ fn solve( *type_index, ); - solve_suffix_fx(env, problems, *kind, actual, region); + solve_suffix_fx(env, problems, host_exposed_symbols, *kind, actual, region); state } ExpectEffectful(variable, reason, region) => { @@ -1625,6 +1629,7 @@ fn solve( fn solve_suffix_fx( env: &mut InferenceEnv<'_>, problems: &mut Vec, + host_exposed_symbols: Option<&VecSet>, kind: FxSuffixKind, variable: Variable, region: &Region, @@ -1651,7 +1656,16 @@ fn solve_suffix_fx( let fx = *fx; match env.subs.get_content_without_compacting(fx) { Content::Pure => { - problems.push(TypeError::SuffixedPureFunction(*region, kind)); + match (kind.symbol(), host_exposed_symbols) { + (Some(sym), Some(host_exposed)) if host_exposed.contains(sym) => { + // If exposed to the platform, it's allowed to be suffixed but pure + // The platform might require a `main!` function that could perform + // effects, but that's not a requirement. + } + _ => { + problems.push(TypeError::SuffixedPureFunction(*region, kind)); + } + } } Content::FlexVar(_) => { env.subs.set_content(fx, Content::Effectful); diff --git a/crates/compiler/test_derive/src/util.rs b/crates/compiler/test_derive/src/util.rs index 811e6b117e..d5897ec660 100644 --- a/crates/compiler/test_derive/src/util.rs +++ b/crates/compiler/test_derive/src/util.rs @@ -439,6 +439,7 @@ fn check_derived_typechecks_and_golden( derived_module: Default::default(), module_params: None, module_params_vars: imported_param_vars, + host_exposed_symbols: None, #[cfg(debug_assertions)] checkmate: None, diff --git a/crates/test_compile/src/help_solve.rs b/crates/test_compile/src/help_solve.rs index 192a1ef24c..7002e36534 100644 --- a/crates/test_compile/src/help_solve.rs +++ b/crates/test_compile/src/help_solve.rs @@ -49,6 +49,7 @@ impl SolvedExpr { derived_module: SharedDerivedModule::default(), module_params: None, module_params_vars: VecMap::default(), + host_exposed_symbols: None, #[cfg(debug_assertions)] checkmate: None, }; From 2e7e67019f302c66dbfdbf7ae2cc2c393d6c4499 Mon Sep 17 00:00:00 2001 From: Agus Zubiaga Date: Wed, 27 Nov 2024 14:15:08 -0300 Subject: [PATCH 64/65] Add doc comment to SolveConfig.host_exposed_symbols --- crates/compiler/solve/src/module.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/compiler/solve/src/module.rs b/crates/compiler/solve/src/module.rs index ea5a7b0b6d..721e4b6c25 100644 --- a/crates/compiler/solve/src/module.rs +++ b/crates/compiler/solve/src/module.rs @@ -76,7 +76,7 @@ pub struct SolveConfig<'a> { /// Needed during solving to resolve lambda sets from derived implementations that escape into /// the user module. pub derived_module: SharedDerivedModule, - /// + /// Symbols that are exposed to the host which might need special treatment. pub host_exposed_symbols: Option<&'a VecSet>, #[cfg(debug_assertions)] From 5d2781ec44e38950b0ea6ab9e7d80316d9eeaa1c Mon Sep 17 00:00:00 2001 From: Anton-4 <17049058+Anton-4@users.noreply.github.com> Date: Thu, 28 Nov 2024 12:02:35 +0100 Subject: [PATCH 65/65] glue_cli_tests single threaded Looks like this also happens without --features="wasm32-cli-run" Signed-off-by: Anton-4 <17049058+Anton-4@users.noreply.github.com> --- .github/workflows/nix_linux_x86_64.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/nix_linux_x86_64.yml b/.github/workflows/nix_linux_x86_64.yml index 53fb41e109..5c49c9b379 100644 --- a/.github/workflows/nix_linux_x86_64.yml +++ b/.github/workflows/nix_linux_x86_64.yml @@ -18,7 +18,12 @@ jobs: run: nix-build - name: execute tests with --release - run: nix develop -c cargo test --locked --release + # skipping glue tests due to difficult multithreading bug, we run them single threaded in the next step + run: nix develop -c cargo test --locked --release -- --skip glue_cli_tests + + - name: glue_cli_tests + # single threaded due to difficult bug when multithreading + run: nix develop -c cargo test --locked --release glue_cli_tests -- --test-threads=1 - name: roc test all builtins run: nix develop -c ./ci/roc_test_builtins.sh