diff --git a/BUILDING_FROM_SOURCE.md b/BUILDING_FROM_SOURCE.md index bb1e70733c..4182939e77 100644 --- a/BUILDING_FROM_SOURCE.md +++ b/BUILDING_FROM_SOURCE.md @@ -109,7 +109,7 @@ Alternatively, you can use `cargo test --no-fail-fast` or `cargo test -p specifi For debugging LLVM IR, we use [DebugIR](https://github.com/vaivaswatha/debugir). This dependency is only required to build with the `--debug` flag, and for normal developtment you should be fine without it. -### libcxb libraries +### libxcb libraries You may see an error like this during builds: diff --git a/Cargo.lock b/Cargo.lock index 27c0e7bf0f..71b9a3e355 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3351,6 +3351,7 @@ dependencies = [ "roc_parse", "roc_problem", "roc_region", + "roc_reporting", "roc_target", "roc_types", "roc_unify", @@ -3519,6 +3520,7 @@ dependencies = [ "roc_module", "roc_parse", "roc_region", + "roc_reporting", "roc_target", "roc_types", "snafu", @@ -3703,6 +3705,7 @@ dependencies = [ "roc_constrain", "roc_load_internal", "roc_module", + "roc_reporting", "roc_target", "roc_types", ] @@ -3734,7 +3737,6 @@ dependencies = [ "roc_target", "roc_types", "roc_unify", - "strip-ansi-escapes", "tempfile", "ven_pretty", ] @@ -3893,6 +3895,7 @@ dependencies = [ "roc_collections", "roc_constrain", "roc_exhaustive", + "roc_load", "roc_module", "roc_mono", "roc_parse", @@ -3902,6 +3905,7 @@ dependencies = [ "roc_target", "roc_test_utils", "roc_types", + "tempfile", "ven_pretty", ] @@ -3916,11 +3920,13 @@ dependencies = [ "roc_builtins", "roc_can", "roc_collections", + "roc_error_macros", "roc_load", "roc_module", "roc_parse", "roc_problem", "roc_region", + "roc_reporting", "roc_solve", "roc_target", "roc_types", @@ -3968,6 +3974,7 @@ version = "0.1.0" dependencies = [ "bitflags", "roc_collections", + "roc_error_macros", "roc_module", "roc_types", ] @@ -4518,6 +4525,7 @@ dependencies = [ "roc_load", "roc_module", "roc_mono", + "roc_reporting", "roc_target", "test_mono_macros", ] diff --git a/ast/Cargo.toml b/ast/Cargo.toml index 86d203cee4..d1384b96ee 100644 --- a/ast/Cargo.toml +++ b/ast/Cargo.toml @@ -19,6 +19,7 @@ roc_unify = { path = "../compiler/unify"} roc_load = { path = "../compiler/load" } roc_target = { path = "../compiler/roc_target" } roc_error_macros = { path = "../error_macros" } +roc_reporting = { path = "../reporting" } arrayvec = "0.7.2" bumpalo = { version = "3.8.0", features = ["collections"] } page_size = "0.4.2" diff --git a/ast/src/module.rs b/ast/src/module.rs index 55b324b35d..769df5d162 100644 --- a/ast/src/module.rs +++ b/ast/src/module.rs @@ -19,6 +19,7 @@ pub fn load_module(src_file: &Path) -> LoadedModule { }), subs_by_module, TargetInfo::default_x86_64(), + roc_reporting::report::RenderTarget::ColorTerminal, ); match loaded { diff --git a/ast/src/solve_type.rs b/ast/src/solve_type.rs index dc2e53f3a8..f15b128827 100644 --- a/ast/src/solve_type.rs +++ b/ast/src/solve_type.rs @@ -227,12 +227,16 @@ fn solve<'a>( ); match unify(subs, actual, expected, Mode::EQ) { - Success(vars) => { + Success { + vars, + must_implement_ability: _, + } => { + // TODO(abilities) record deferred ability checks introduce(subs, rank, pools, &vars); state } - Failure(vars, actual_type, expected_type) => { + Failure(vars, actual_type, expected_type, _bad_impl) => { introduce(subs, rank, pools, &vars); let problem = TypeError::BadExpr( @@ -267,7 +271,7 @@ fn solve<'a>( // // state // } - // Failure(vars, _actual_type, _expected_type) => { + // Failure(vars, _actual_type, _expected_type, _bad_impl) => { // introduce(subs, rank, pools, &vars); // // // ERROR NOT REPORTED @@ -320,13 +324,17 @@ fn solve<'a>( ); match unify(subs, actual, expected, Mode::EQ) { - Success(vars) => { + Success { + vars, + must_implement_ability: _, + } => { + // TODO(abilities) record deferred ability checks introduce(subs, rank, pools, &vars); state } - Failure(vars, actual_type, expected_type) => { + Failure(vars, actual_type, expected_type, _bad_impl) => { introduce(subs, rank, pools, &vars); let problem = TypeError::BadExpr( @@ -391,12 +399,16 @@ fn solve<'a>( // TODO(ayazhafiz): presence constraints for Expr2/Type2 match unify(subs, actual, expected, Mode::EQ) { - Success(vars) => { + Success { + vars, + must_implement_ability: _, + } => { + // TODO(abilities) record deferred ability checks introduce(subs, rank, pools, &vars); state } - Failure(vars, actual_type, expected_type) => { + Failure(vars, actual_type, expected_type, _bad_impl) => { introduce(subs, rank, pools, &vars); let problem = TypeError::BadPattern( @@ -699,12 +711,16 @@ fn solve<'a>( let includes = type_to_var(arena, mempool, subs, rank, pools, cached_aliases, &tag_ty); match unify(subs, actual, includes, Mode::PRESENT) { - Success(vars) => { + Success { + vars, + must_implement_ability: _, + } => { + // TODO(abilities) record deferred ability checks introduce(subs, rank, pools, &vars); state } - Failure(vars, actual_type, expected_type) => { + Failure(vars, actual_type, expected_type, _bad_impl) => { introduce(subs, rank, pools, &vars); // TODO: do we need a better error type here? @@ -1281,7 +1297,7 @@ fn adjust_rank_content( use roc_types::subs::FlatType::*; match content { - FlexVar(_) | RigidVar(_) | Error => group_rank, + FlexVar(_) | RigidVar(_) | FlexAbleVar(..) | RigidAbleVar(..) | Error => group_rank, RecursionVar { .. } => group_rank, @@ -1536,7 +1552,7 @@ fn instantiate_rigids_help( }; } - FlexVar(_) | Error => {} + FlexVar(_) | FlexAbleVar(_, _) | Error => {} RecursionVar { structure, .. } => { instantiate_rigids_help(subs, max_rank, pools, structure); @@ -1547,6 +1563,11 @@ fn instantiate_rigids_help( subs.set(copy, make_descriptor(FlexVar(Some(name)))); } + RigidAbleVar(name, ability) => { + // what it's all about: convert the rigid var into a flex var + subs.set(copy, make_descriptor(FlexAbleVar(Some(name), ability))); + } + Alias(_, args, real_type_var, _) => { for var_index in args.all_variables() { let var = subs[var_index]; @@ -1772,7 +1793,7 @@ fn deep_copy_var_help( copy } - FlexVar(_) | Error => copy, + FlexVar(_) | FlexAbleVar(_, _) | Error => copy, RecursionVar { opt_name, @@ -1797,6 +1818,12 @@ fn deep_copy_var_help( copy } + RigidAbleVar(name, ability) => { + subs.set(copy, make_descriptor(FlexAbleVar(Some(name), ability))); + + copy + } + Alias(symbol, mut args, real_type_var, kind) => { let mut new_args = Vec::with_capacity(args.all_variables().len()); diff --git a/cli/src/build.rs b/cli/src/build.rs index ab3d7d0c42..c39b55f043 100644 --- a/cli/src/build.rs +++ b/cli/src/build.rs @@ -6,6 +6,7 @@ use roc_build::{ use roc_builtins::bitcode; use roc_load::LoadingProblem; use roc_mono::ir::OptLevel; +use roc_reporting::report::RenderTarget; use roc_target::TargetInfo; use std::path::PathBuf; use std::time::{Duration, SystemTime}; @@ -68,6 +69,8 @@ pub fn build_file<'a>( src_dir.as_path(), subs_by_module, target_info, + // TODO: expose this from CLI? + RenderTarget::ColorTerminal, )?; use target_lexicon::Architecture; @@ -374,6 +377,8 @@ pub fn check_file( src_dir.as_path(), subs_by_module, target_info, + // TODO: expose this from CLI? + RenderTarget::ColorTerminal, )?; let buf = &mut String::with_capacity(1024); diff --git a/compiler/builtins/README.md b/compiler/builtins/README.md index ceaf3007e3..2ab95cd260 100644 --- a/compiler/builtins/README.md +++ b/compiler/builtins/README.md @@ -60,7 +60,7 @@ Its one thing to actually write these functions, its _another_ thing to let the ## Specifying how we pass args to the function ### builtins/mono/src/borrow.rs -After we have all of this, we need to specify if the arguments we're passing are owned, borrowed or irrelevant. Towards the bottom of this file, add a new case for you builtin and specify each arg. Be sure to read the comment, as it explains this in more detail. +After we have all of this, we need to specify if the arguments we're passing are owned, borrowed or irrelevant. Towards the bottom of this file, add a new case for your builtin and specify each arg. Be sure to read the comment, as it explains this in more detail. ## Testing it ### solve/tests/solve_expr.rs @@ -87,7 +87,7 @@ In this directory, there are a couple files like `gen_num.rs`, `gen_str.rs`, etc fn atan() { assert_evals_to!("Num.atan 10", 1.4711276743037347, f64); } - ``` +``` But replace `Num.atan`, the return value, and the return type with your new builtin. # Mistakes that are easy to make!! diff --git a/compiler/builtins/bitcode/src/dec.zig b/compiler/builtins/bitcode/src/dec.zig index 2f0dcb6abe..b9eba9c0cc 100644 --- a/compiler/builtins/bitcode/src/dec.zig +++ b/compiler/builtins/bitcode/src/dec.zig @@ -310,9 +310,7 @@ pub const RocDec = extern struct { // (n / 0) is an error if (denominator_i128 == 0) { - // The compiler frontend does the `denominator == 0` check for us, - // therefore this case is unreachable from roc user code - unreachable; + @panic("TODO runtime exception for dividing by 0!"); } // If they're both negative, or if neither is negative, the final answer diff --git a/compiler/builtins/bitcode/src/num.zig b/compiler/builtins/bitcode/src/num.zig index 41d93df976..54bcf8ff5a 100644 --- a/compiler/builtins/bitcode/src/num.zig +++ b/compiler/builtins/bitcode/src/num.zig @@ -102,7 +102,7 @@ pub fn exportRound(comptime T: type, comptime name: []const u8) void { pub fn exportDivCeil(comptime T: type, comptime name: []const u8) void { comptime var f = struct { fn func(a: T, b: T) callconv(.C) T { - return math.divCeil(T, a, b) catch unreachable; + return math.divCeil(T, a, b) catch @panic("TODO runtime exception for dividing by 0!"); } }.func; @export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong }); diff --git a/compiler/builtins/roc/Num.roc b/compiler/builtins/roc/Num.roc index 2a30529e3e..3038842caa 100644 --- a/compiler/builtins/roc/Num.roc +++ b/compiler/builtins/roc/Num.roc @@ -69,6 +69,7 @@ interface Num isNegative, rem, div, + divChecked, modInt, modFloat, sqrt, @@ -97,7 +98,9 @@ interface Num bytesToU16, bytesToU32, divCeil, + divCeilChecked, divFloor, + divFloorChecked, toStr, isMultipleOf, minI8, @@ -229,10 +232,13 @@ atan : Float a -> Float a sqrt : Float a -> Result (Float a) [ SqrtOfNegative ]* log : Float a -> Result (Float a) [ LogNeedsPositive ]* -div : Float a, Float a -> Result (Float a) [ DivByZero ]* +div : Float a, Float a -> Float a +divChecked : Float a, Float a -> Result (Float a) [ DivByZero ]* -divCeil: Int a, Int a -> Result (Int a) [ DivByZero ]* -divFloor: Int a, Int a -> Result (Int a) [ DivByZero ]* +divCeil : Int a, Int a -> Int a +divCeilChecked : Int a, Int a -> Result (Int a) [ DivByZero ]* +divFloor : Int a, Int a -> Int a +divFloorChecked : Int a, Int a -> Result (Int a) [ DivByZero ]* # mod : Float a, Float a -> Result (Float a) [ DivByZero ]* rem : Int a, Int a -> Result (Int a) [ DivByZero ]* diff --git a/compiler/builtins/src/std.rs b/compiler/builtins/src/std.rs index 0dc322ae15..cc0fd16ef0 100644 --- a/compiler/builtins/src/std.rs +++ b/compiler/builtins/src/std.rs @@ -316,17 +316,31 @@ pub fn types() -> MutMap { Box::new(SolvedType::Wildcard), ); - // divInt : Int a, Int a -> Result (Int a) [ DivByZero ]* + // divFloor : Int a, Int a -> Int a add_top_level_function_type!( - Symbol::NUM_DIV_INT, + Symbol::NUM_DIV_FLOOR, + vec![int_type(flex(TVAR1)), int_type(flex(TVAR1))], + Box::new(int_type(flex(TVAR1))) + ); + + // divFloorChecked : Int a, Int a -> Result (Int a) [ DivByZero ]* + add_top_level_function_type!( + Symbol::NUM_DIV_FLOOR_CHECKED, vec![int_type(flex(TVAR1)), int_type(flex(TVAR1))], Box::new(result_type(int_type(flex(TVAR1)), div_by_zero.clone())), ); - //divCeil: Int a, Int a -> Result (Int a) [ DivByZero ]* + // divCeil : Int a, Int a -> Int a add_top_level_function_type!( Symbol::NUM_DIV_CEIL, vec![int_type(flex(TVAR1)), int_type(flex(TVAR1))], + Box::new(int_type(flex(TVAR1))) + ); + + // divCeilChecked : Int a, Int a -> Result (Int a) [ DivByZero ]* + add_top_level_function_type!( + Symbol::NUM_DIV_CEIL_CHECKED, + vec![int_type(flex(TVAR1)), int_type(flex(TVAR1))], Box::new(result_type(int_type(flex(TVAR1)), div_by_zero.clone())), ); @@ -659,6 +673,13 @@ pub fn types() -> MutMap { add_top_level_function_type!( Symbol::NUM_DIV_FLOAT, vec![float_type(flex(TVAR1)), float_type(flex(TVAR1))], + Box::new(float_type(flex(TVAR1))) + ); + + // divChecked : Float a, Float a -> Result (Float a) [ DivByZero ]* + add_top_level_function_type!( + Symbol::NUM_DIV_FLOAT_CHECKED, + vec![float_type(flex(TVAR1)), float_type(flex(TVAR1))], Box::new(result_type(float_type(flex(TVAR1)), div_by_zero.clone())), ); diff --git a/compiler/can/src/abilities.rs b/compiler/can/src/abilities.rs index 5998f9da8e..4132456a21 100644 --- a/compiler/can/src/abilities.rs +++ b/compiler/can/src/abilities.rs @@ -1,55 +1,63 @@ -use roc_collections::all::{MutMap, MutSet}; +use roc_collections::all::MutMap; use roc_module::symbol::Symbol; +use roc_region::all::Region; use roc_types::types::Type; -use crate::annotation::HasClause; - /// Stores information about an ability member definition, including the parent ability, the /// defining type, and what type variables need to be instantiated with instances of the ability. -#[derive(Debug)] -struct AbilityMemberData { - #[allow(unused)] - parent_ability: Symbol, - #[allow(unused)] - signature: Type, - #[allow(unused)] - bound_has_clauses: Vec, +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct AbilityMemberData { + pub parent_ability: Symbol, + pub signature: Type, + pub region: Region, +} + +/// A particular specialization of an ability member. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct MemberSpecialization { + pub symbol: Symbol, + pub region: Region, } /// Stores information about what abilities exist in a scope, what it means to implement an /// ability, and what types implement them. // TODO(abilities): this should probably go on the Scope, I don't put it there for now because we // are only dealing with inter-module abilities for now. -#[derive(Default, Debug)] +#[derive(Default, Debug, Clone, PartialEq, Eq)] pub struct AbilitiesStore { /// Maps an ability to the members defining it. - #[allow(unused)] members_of_ability: MutMap>, /// Information about all members composing abilities. ability_members: MutMap, - /// Tuples of (type, member) specifying that `type` declares an implementation of an ability - /// member `member`. - #[allow(unused)] - declared_implementations: MutSet<(Symbol, Symbol)>, + /// Map of symbols that specialize an ability member to the root ability symbol name. + /// For example, for the program + /// Hash has hash : a -> U64 | a has Hash + /// ^^^^ gets the symbol "#hash" + /// hash = \@Id n -> n + /// ^^^^ gets the symbol "#hash1" + /// + /// We keep the mapping #hash1->#hash + specialization_to_root: MutMap, + + /// Maps a tuple (member, type) specifying that `type` declares an implementation of an ability + /// member `member`, to the exact symbol that implements the ability. + declared_specializations: MutMap<(Symbol, Symbol), MemberSpecialization>, } impl AbilitiesStore { - pub fn register_ability( - &mut self, - ability: Symbol, - members: Vec<(Symbol, Type, Vec)>, - ) { + /// Records the definition of an ability, including its members. + pub fn register_ability(&mut self, ability: Symbol, members: Vec<(Symbol, Region, Type)>) { let mut members_vec = Vec::with_capacity(members.len()); - for (member, signature, bound_has_clauses) in members.into_iter() { + for (member, region, signature) in members.into_iter() { members_vec.push(member); let old_member = self.ability_members.insert( member, AbilityMemberData { parent_ability: ability, signature, - bound_has_clauses, + region, }, ); debug_assert!(old_member.is_none(), "Replacing existing member definition"); @@ -61,14 +69,83 @@ impl AbilitiesStore { ); } - pub fn register_implementation(&mut self, implementing_type: Symbol, ability_member: Symbol) { - let old_impl = self - .declared_implementations - .insert((implementing_type, ability_member)); - debug_assert!(!old_impl, "Replacing existing implementation"); + /// Records a specialization of `ability_member` with specialized type `implementing_type`. + /// Entries via this function are considered a source of truth. It must be ensured that a + /// specialization is validated before being registered here. + pub fn register_specialization_for_type( + &mut self, + ability_member: Symbol, + implementing_type: Symbol, + specialization: MemberSpecialization, + ) { + let old_spec = self + .declared_specializations + .insert((ability_member, implementing_type), specialization); + debug_assert!(old_spec.is_none(), "Replacing existing specialization"); } + /// Checks if `name` is a root ability member symbol name. + /// Note that this will return `false` for specializations of an ability member, which have + /// different symbols from the root. pub fn is_ability_member_name(&self, name: Symbol) -> bool { self.ability_members.contains_key(&name) } + + /// Returns information about all known ability members and their root symbols. + pub fn root_ability_members(&self) -> &MutMap { + &self.ability_members + } + + /// Records that the symbol `specializing_symbol` claims to specialize `ability_member`; for + /// example the symbol of `hash : Id -> U64` specializing `hash : a -> U64 | a has Hash`. + pub fn register_specializing_symbol( + &mut self, + specializing_symbol: Symbol, + ability_member: Symbol, + ) { + self.specialization_to_root + .insert(specializing_symbol, ability_member); + } + + /// Returns whether a symbol is declared to specialize an ability member. + pub fn is_specialization_name(&self, symbol: Symbol) -> bool { + self.specialization_to_root.contains_key(&symbol) + } + + /// Finds the symbol name and ability member definition for a symbol specializing the ability + /// member, if it specializes any. + /// For example, suppose `hash : Id -> U64` has symbol #hash1 and specializes + /// `hash : a -> U64 | a has Hash` with symbol #hash. Calling this with #hash1 would retrieve + /// the ability member data for #hash. + pub fn root_name_and_def( + &self, + specializing_symbol: Symbol, + ) -> Option<(Symbol, &AbilityMemberData)> { + let root_symbol = self.specialization_to_root.get(&specializing_symbol)?; + debug_assert!(self.ability_members.contains_key(root_symbol)); + let root_data = self.ability_members.get(root_symbol).unwrap(); + Some((*root_symbol, root_data)) + } + + /// Finds the ability member definition for a member name. + pub fn member_def(&self, member: Symbol) -> Option<&AbilityMemberData> { + self.ability_members.get(&member) + } + + /// Returns an iterator over pairs (ability member, type) specifying that + /// "ability member" has a specialization with type "type". + pub fn get_known_specializations(&self) -> impl Iterator + '_ { + self.declared_specializations.keys().copied() + } + + /// Retrieves the specialization of `member` for `typ`, if it exists. + pub fn get_specialization(&self, member: Symbol, typ: Symbol) -> Option { + self.declared_specializations.get(&(member, typ)).copied() + } + + /// Returns pairs of (type, ability member) specifying that "ability member" has a + /// specialization with type "type". + pub fn members_of_ability(&self, ability: Symbol) -> Option<&[Symbol]> { + self.members_of_ability.get(&ability).map(|v| v.as_ref()) + } } diff --git a/compiler/can/src/annotation.rs b/compiler/can/src/annotation.rs index c8af0d3e0a..c5d1ea3f23 100644 --- a/compiler/can/src/annotation.rs +++ b/compiler/can/src/annotation.rs @@ -19,6 +19,22 @@ pub struct Annotation { pub aliases: SendMap, } +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub enum NamedOrAbleVariable<'a> { + Named(&'a NamedVariable), + Able(&'a AbleVariable), +} + +impl<'a> NamedOrAbleVariable<'a> { + pub fn first_seen(&self) -> Region { + match self { + NamedOrAbleVariable::Named(nv) => nv.first_seen, + NamedOrAbleVariable::Able(av) => av.first_seen, + } + } +} + +/// A named type variable, not bound to an ability. #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct NamedVariable { pub variable: Variable, @@ -27,21 +43,40 @@ pub struct NamedVariable { pub first_seen: Region, } +/// A type variable bound to an ability, like "a has Hash". +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct AbleVariable { + pub variable: Variable, + pub name: Lowercase, + pub ability: Symbol, + // NB: there may be multiple occurrences of a variable + pub first_seen: Region, +} + #[derive(Clone, Debug, PartialEq, Default)] pub struct IntroducedVariables { pub wildcards: Vec>, pub lambda_sets: Vec, pub inferred: Vec>, pub named: Vec, + pub able: Vec, pub host_exposed_aliases: MutMap, } impl IntroducedVariables { + #[inline(always)] + fn debug_assert_not_already_present(&self, var: Variable) { + debug_assert!((self.wildcards.iter().map(|v| &v.value)) + .chain(self.lambda_sets.iter()) + .chain(self.inferred.iter().map(|v| &v.value)) + .chain(self.named.iter().map(|nv| &nv.variable)) + .chain(self.able.iter().map(|av| &av.variable)) + .chain(self.host_exposed_aliases.values()) + .all(|&v| v != var)); + } + pub fn insert_named(&mut self, name: Lowercase, var: Loc) { - debug_assert!(!self - .named - .iter() - .any(|nv| nv.name == name || nv.variable == var.value)); + self.debug_assert_not_already_present(var.value); let named_variable = NamedVariable { name, @@ -52,19 +87,36 @@ impl IntroducedVariables { self.named.push(named_variable); } + pub fn insert_able(&mut self, name: Lowercase, var: Loc, ability: Symbol) { + self.debug_assert_not_already_present(var.value); + + let able_variable = AbleVariable { + name, + ability, + variable: var.value, + first_seen: var.region, + }; + + self.able.push(able_variable); + } + pub fn insert_wildcard(&mut self, var: Loc) { + self.debug_assert_not_already_present(var.value); self.wildcards.push(var); } pub fn insert_inferred(&mut self, var: Loc) { + self.debug_assert_not_already_present(var.value); self.inferred.push(var); } fn insert_lambda_set(&mut self, var: Variable) { + self.debug_assert_not_already_present(var); self.lambda_sets.push(var); } pub fn insert_host_exposed_alias(&mut self, symbol: Symbol, var: Variable) { + self.debug_assert_not_already_present(var); self.host_exposed_aliases.insert(symbol, var); } @@ -78,6 +130,10 @@ impl IntroducedVariables { self.named.extend(other.named.iter().cloned()); self.named.sort(); self.named.dedup(); + + self.able.extend(other.able.iter().cloned()); + self.able.sort(); + self.able.dedup(); } pub fn union_owned(&mut self, other: Self) { @@ -91,22 +147,26 @@ impl IntroducedVariables { self.named.dedup(); } - pub fn var_by_name(&self, name: &Lowercase) -> Option<&Variable> { - self.named + pub fn var_by_name(&self, name: &Lowercase) -> Option { + (self.named.iter().map(|nv| (&nv.name, nv.variable))) + .chain(self.able.iter().map(|av| (&av.name, av.variable))) + .find(|(cand, _)| cand == &name) + .map(|(_, var)| var) + } + + pub fn named_var_by_name(&self, name: &Lowercase) -> Option { + if let Some(nav) = self + .named .iter() .find(|nv| &nv.name == name) - .map(|nv| &nv.variable) - } - - pub fn name_by_var(&self, var: Variable) -> Option<&Lowercase> { - self.named + .map(NamedOrAbleVariable::Named) + { + return Some(nav); + } + self.able .iter() - .find(|nv| nv.variable == var) - .map(|nv| &nv.name) - } - - pub fn named_var_by_name(&self, name: &Lowercase) -> Option<&NamedVariable> { - self.named.iter().find(|nv| &nv.name == name) + .find(|av| &av.name == name) + .map(NamedOrAbleVariable::Able) } } @@ -147,13 +207,6 @@ pub fn canonicalize_annotation( } } -#[derive(Clone, Debug)] -pub struct HasClause { - pub var_name: Lowercase, - pub var: Variable, - pub ability: Symbol, -} - pub fn canonicalize_annotation_with_possible_clauses( env: &mut Env, scope: &mut Scope, @@ -161,16 +214,17 @@ pub fn canonicalize_annotation_with_possible_clauses( region: Region, var_store: &mut VarStore, abilities_in_scope: &[Symbol], -) -> (Annotation, Vec>) { +) -> Annotation { let mut introduced_variables = IntroducedVariables::default(); let mut references = MutSet::default(); let mut aliases = SendMap::default(); - let (annotation, region, clauses) = match annotation { + let (annotation, region) = match annotation { TypeAnnotation::Where(annotation, clauses) => { - let mut can_clauses = Vec::with_capacity(clauses.len()); + // Add each "has" clause. The association of a variable to an ability will be saved on + // `introduced_variables`, which we'll process later. for clause in clauses.iter() { - match canonicalize_has_clause( + let opt_err = canonicalize_has_clause( env, scope, var_store, @@ -178,24 +232,19 @@ pub fn canonicalize_annotation_with_possible_clauses( clause, abilities_in_scope, &mut references, - ) { - Ok(result) => can_clauses.push(Loc::at(clause.region, result)), - Err(err_type) => { - return ( - Annotation { - typ: err_type, - introduced_variables, - references, - aliases, - }, - can_clauses, - ) - } - }; + ); + if let Err(err_type) = opt_err { + return Annotation { + typ: err_type, + introduced_variables, + references, + aliases, + }; + } } - (&annotation.value, annotation.region, can_clauses) + (&annotation.value, annotation.region) } - annot => (annot, region, vec![]), + annot => (annot, region), }; let typ = can_annotation_help( @@ -209,14 +258,12 @@ pub fn canonicalize_annotation_with_possible_clauses( &mut references, ); - let annot = Annotation { + Annotation { typ, introduced_variables, references, aliases, - }; - - (annot, clauses) + } } fn make_apply_symbol( @@ -502,7 +549,7 @@ fn can_annotation_help( let name = Lowercase::from(*v); match introduced_variables.var_by_name(&name) { - Some(var) => Type::Variable(*var), + Some(var) => Type::Variable(var), None => { let var = var_store.fresh(); @@ -566,8 +613,8 @@ fn can_annotation_help( let var_name = Lowercase::from(var); if let Some(var) = introduced_variables.var_by_name(&var_name) { - vars.push((var_name.clone(), Type::Variable(*var))); - lowercase_vars.push(Loc::at(loc_var.region, (var_name, *var))); + vars.push((var_name.clone(), Type::Variable(var))); + lowercase_vars.push(Loc::at(loc_var.region, (var_name, var))); } else { let var = var_store.fresh(); @@ -799,7 +846,7 @@ fn canonicalize_has_clause( clause: &Loc>, abilities_in_scope: &[Symbol], references: &mut MutSet, -) -> Result { +) -> Result<(), Type> { let Loc { region, value: roc_parse::ast::HasClause { var, ability }, @@ -836,25 +883,21 @@ fn canonicalize_has_clause( let var_name_ident = var_name.to_string().into(); let shadow = Loc::at(region, var_name_ident); env.problem(roc_problem::can::Problem::Shadowing { - original_region: shadowing.first_seen, + original_region: shadowing.first_seen(), shadow: shadow.clone(), kind: ShadowKind::Variable, }); return Err(Type::Erroneous(Problem::Shadowed( - shadowing.first_seen, + shadowing.first_seen(), shadow, ))); } let var = var_store.fresh(); - introduced_variables.insert_named(var_name.clone(), Loc::at(region, var)); + introduced_variables.insert_able(var_name, Loc::at(region, var), ability); - Ok(HasClause { - var_name, - var, - ability, - }) + Ok(()) } #[allow(clippy::too_many_arguments)] @@ -1105,7 +1148,7 @@ fn can_assigned_fields<'a>( let field_name = Lowercase::from(loc_field_name.value); let field_type = { if let Some(var) = introduced_variables.var_by_name(&field_name) { - Type::Variable(*var) + Type::Variable(var) } else { let field_var = var_store.fresh(); introduced_variables.insert_named( diff --git a/compiler/can/src/builtins.rs b/compiler/can/src/builtins.rs index 48bfdf64b2..d194966b8b 100644 --- a/compiler/can/src/builtins.rs +++ b/compiler/can/src/builtins.rs @@ -195,8 +195,11 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option NUM_COS => num_cos, NUM_TAN => num_tan, NUM_DIV_FLOAT => num_div_float, - NUM_DIV_INT => num_div_int, + NUM_DIV_FLOAT_CHECKED => num_div_float_checked, + NUM_DIV_FLOOR => num_div_floor, + NUM_DIV_FLOOR_CHECKED => num_div_floor_checked, NUM_DIV_CEIL => num_div_ceil, + NUM_DIV_CEIL_CHECKED => num_div_ceil_checked, NUM_ABS => num_abs, NUM_NEG => num_neg, NUM_REM => num_rem, @@ -4295,8 +4298,13 @@ fn num_abs(symbol: Symbol, var_store: &mut VarStore) -> Def { ) } -/// Num.div : Float, Float -> Result Float [ DivByZero ]* +/// Num.div : Float, Float -> Float fn num_div_float(symbol: Symbol, var_store: &mut VarStore) -> Def { + num_binop(symbol, var_store, LowLevel::NumDivUnchecked) +} + +/// Num.divChecked : Float, Float -> Result Float [ DivByZero ]* +fn num_div_float_checked(symbol: Symbol, var_store: &mut VarStore) -> Def { let bool_var = var_store.fresh(); let num_var = var_store.fresh(); let unbound_zero_var = var_store.fresh(); @@ -4361,8 +4369,13 @@ fn num_div_float(symbol: Symbol, var_store: &mut VarStore) -> Def { ) } -/// Num.div : Int a , Int a -> Result (Int a) [ DivByZero ]* -fn num_div_int(symbol: Symbol, var_store: &mut VarStore) -> Def { +/// Num.divFloor : Int a, Int a -> Int a +fn num_div_floor(symbol: Symbol, var_store: &mut VarStore) -> Def { + num_binop(symbol, var_store, LowLevel::NumDivUnchecked) +} + +/// Num.divFloorChecked : Int a , Int a -> Result (Int a) [ DivByZero ]* +fn num_div_floor_checked(symbol: Symbol, var_store: &mut VarStore) -> Def { let bool_var = var_store.fresh(); let num_var = var_store.fresh(); let unbound_zero_var = var_store.fresh(); @@ -4432,8 +4445,13 @@ fn num_div_int(symbol: Symbol, var_store: &mut VarStore) -> Def { ) } -/// Num.divCeil : Int a , Int a -> Result (Int a) [ DivByZero ]* +/// Num.divCeil : Int a, Int a -> Int a fn num_div_ceil(symbol: Symbol, var_store: &mut VarStore) -> Def { + num_binop(symbol, var_store, LowLevel::NumDivCeilUnchecked) +} + +/// Num.divCeilChecked : Int a , Int a -> Result (Int a) [ DivByZero ]* +fn num_div_ceil_checked(symbol: Symbol, var_store: &mut VarStore) -> Def { let bool_var = var_store.fresh(); let num_var = var_store.fresh(); let unbound_zero_var = var_store.fresh(); diff --git a/compiler/can/src/def.rs b/compiler/can/src/def.rs index 460b8a5efc..2a8a6d1880 100644 --- a/compiler/can/src/def.rs +++ b/compiler/can/src/def.rs @@ -1,4 +1,3 @@ -use crate::abilities::AbilitiesStore; use crate::annotation::canonicalize_annotation; use crate::annotation::canonicalize_annotation_with_possible_clauses; use crate::annotation::IntroducedVariables; @@ -430,12 +429,11 @@ pub fn canonicalize_defs<'a>( } // Now we can go through and resolve all pending abilities, to add them to scope. - let mut abilities_store = AbilitiesStore::default(); for (loc_ability_name, members) in abilities.into_values() { let mut can_members = Vec::with_capacity(members.len()); for member in members { - let (member_annot, clauses) = canonicalize_annotation_with_possible_clauses( + let member_annot = canonicalize_annotation_with_possible_clauses( env, &mut scope, &member.typ.value, @@ -450,13 +448,14 @@ pub fn canonicalize_defs<'a>( output.references.referenced_type_defs.insert(symbol); } + let name_region = member.name.region; let member_name = member.name.extract_spaces().item; let member_sym = match scope.introduce( member_name.into(), &env.exposed_ident_ids, &mut env.ident_ids, - member.name.region, + name_region, ) { Ok(sym) => sym, Err((original_region, shadow, _new_symbol)) => { @@ -473,9 +472,11 @@ pub fn canonicalize_defs<'a>( // What variables in the annotation are bound to the parent ability, and what variables // are bound to some other ability? let (variables_bound_to_ability, variables_bound_to_other_abilities): (Vec<_>, Vec<_>) = - clauses - .into_iter() - .partition(|has_clause| has_clause.value.ability == loc_ability_name.value); + member_annot + .introduced_variables + .able + .iter() + .partition(|av| av.ability == loc_ability_name.value); let mut bad_has_clauses = false; @@ -485,18 +486,38 @@ pub fn canonicalize_defs<'a>( env.problem(Problem::AbilityMemberMissingHasClause { member: member_sym, ability: loc_ability_name.value, - region: member.name.region, + region: name_region, + }); + bad_has_clauses = true; + } + + if variables_bound_to_ability.len() > 1 { + // There is more than one variable bound to the member signature, so something like + // Eq has eq : a, b -> Bool | a has Eq, b has Eq + // We have no way of telling what type implements a particular instance of Eq in + // this case (a or b?), so disallow it. + let span_has_clauses = + Region::across_all(variables_bound_to_ability.iter().map(|v| &v.first_seen)); + let bound_var_names = variables_bound_to_ability + .iter() + .map(|v| v.name.clone()) + .collect(); + env.problem(Problem::AbilityMemberMultipleBoundVars { + member: member_sym, + ability: loc_ability_name.value, + span_has_clauses, + bound_var_names, }); bad_has_clauses = true; } if !variables_bound_to_other_abilities.is_empty() { // Disallow variables bound to other abilities, for now. - for bad_clause in variables_bound_to_other_abilities.iter() { + for bad_variable in variables_bound_to_other_abilities.iter() { env.problem(Problem::AbilityMemberBindsExternalAbility { member: member_sym, ability: loc_ability_name.value, - region: bad_clause.region, + region: bad_variable.first_seen, }); } bad_has_clauses = true; @@ -507,15 +528,18 @@ pub fn canonicalize_defs<'a>( continue; } - let has_clauses = variables_bound_to_ability - .into_iter() - .map(|clause| clause.value) - .collect(); - can_members.push((member_sym, member_annot.typ, has_clauses)); + // The introduced variables are good; add them to the output. + output + .introduced_variables + .union(&member_annot.introduced_variables); + + can_members.push((member_sym, name_region, member_annot.typ)); } // Store what symbols a type must define implementations for to have this ability. - abilities_store.register_ability(loc_ability_name.value, can_members); + scope + .abilities_store + .register_ability(loc_ability_name.value, can_members); } // Now that we have the scope completely assembled, and shadowing resolved, @@ -526,14 +550,7 @@ pub fn canonicalize_defs<'a>( // once we've finished assembling the entire scope. let mut pending_value_defs = Vec::with_capacity(value_defs.len()); for loc_def in value_defs.into_iter() { - match to_pending_value_def( - env, - var_store, - loc_def.value, - &mut scope, - &abilities_store, - pattern_type, - ) { + match to_pending_value_def(env, var_store, loc_def.value, &mut scope, pattern_type) { None => { /* skip */ } Some((new_output, pending_def)) => { // store the top-level defs, used to ensure that closures won't capture them @@ -1561,7 +1578,9 @@ pub fn can_defs_with_return<'a>( // Now that we've collected all the references, check to see if any of the new idents // we defined went unused by the return expression. If any were unused, report it. for (symbol, region) in symbols_introduced { - if !output.references.has_value_lookup(symbol) && !output.references.has_type_lookup(symbol) + if !output.references.has_value_lookup(symbol) + && !output.references.has_type_lookup(symbol) + && !scope.abilities_store.is_specialization_name(symbol) { env.problem(Problem::UnusedDef(symbol, region)); } @@ -1772,7 +1791,6 @@ fn to_pending_value_def<'a>( var_store: &mut VarStore, def: &'a ast::ValueDef<'a>, scope: &mut Scope, - abilities_store: &AbilitiesStore, pattern_type: PatternType, ) -> Option<(Output, PendingValueDef<'a>)> { use ast::ValueDef::*; @@ -1784,7 +1802,6 @@ fn to_pending_value_def<'a>( env, var_store, scope, - abilities_store, pattern_type, &loc_pattern.value, loc_pattern.region, @@ -1801,7 +1818,6 @@ fn to_pending_value_def<'a>( env, var_store, scope, - abilities_store, pattern_type, &loc_pattern.value, loc_pattern.region, @@ -1832,7 +1848,6 @@ fn to_pending_value_def<'a>( env, var_store, scope, - abilities_store, pattern_type, &body_pattern.value, body_pattern.region, diff --git a/compiler/can/src/expr.rs b/compiler/can/src/expr.rs index b021dc2192..d9262ceab1 100644 --- a/compiler/can/src/expr.rs +++ b/compiler/can/src/expr.rs @@ -1083,6 +1083,7 @@ fn canonicalize_when_branch<'a>( && !branch_output.references.has_value_lookup(symbol) && !branch_output.references.has_type_lookup(symbol) && !original_scope.contains_symbol(symbol) + && !scope.abilities_store.is_specialization_name(symbol) { env.problem(Problem::UnusedDef(symbol, *region)); } diff --git a/compiler/can/src/module.rs b/compiler/can/src/module.rs index c0b01b8888..01e6664d14 100644 --- a/compiler/can/src/module.rs +++ b/compiler/can/src/module.rs @@ -1,3 +1,4 @@ +use crate::abilities::AbilitiesStore; use crate::def::{canonicalize_defs, sort_can_defs, Declaration, Def}; use crate::effect_module::HostedGeneratedFunctions; use crate::env::Env; @@ -28,11 +29,13 @@ pub struct Module { /// all aliases. `bool` indicates whether it is exposed pub aliases: MutMap, pub rigid_variables: RigidVariables, + pub abilities_store: AbilitiesStore, } #[derive(Debug, Default)] pub struct RigidVariables { pub named: MutMap, + pub able: MutMap, pub wildcards: MutSet, } @@ -250,6 +253,7 @@ pub fn canonicalize_module_defs<'a>( if !output.references.has_value_lookup(symbol) && !output.references.has_type_lookup(symbol) && !exposed_symbols.contains(&symbol) + && !scope.abilities_store.is_specialization_name(symbol) { env.problem(Problem::UnusedDef(symbol, region)); } @@ -259,6 +263,12 @@ pub fn canonicalize_module_defs<'a>( rigid_variables.named.insert(named.variable, named.name); } + for able in output.introduced_variables.able { + rigid_variables + .able + .insert(able.variable, (able.name, able.ability)); + } + for var in output.introduced_variables.wildcards { rigid_variables.wildcards.insert(var.value); } @@ -444,6 +454,10 @@ pub fn canonicalize_module_defs<'a>( aliases.insert(symbol, alias); } + for member in scope.abilities_store.root_ability_members().keys() { + exposed_but_not_defined.remove(member); + } + // By this point, all exposed symbols should have been removed from // exposed_symbols and added to exposed_vars_by_symbol. If any were // not, that means they were declared as exposed but there was diff --git a/compiler/can/src/pattern.rs b/compiler/can/src/pattern.rs index 251c889a0c..e620f41f00 100644 --- a/compiler/can/src/pattern.rs +++ b/compiler/can/src/pattern.rs @@ -1,4 +1,3 @@ -use crate::abilities::AbilitiesStore; use crate::annotation::freshen_opaque_def; use crate::env::Env; use crate::expr::{canonicalize_expr, unescape_char, Expr, IntValue, Output}; @@ -157,7 +156,6 @@ pub fn canonicalize_def_header_pattern<'a>( env: &mut Env<'a>, var_store: &mut VarStore, scope: &mut Scope, - abilities_store: &AbilitiesStore, pattern_type: PatternType, pattern: &ast::Pattern<'a>, region: Region, @@ -172,7 +170,6 @@ pub fn canonicalize_def_header_pattern<'a>( &env.exposed_ident_ids, &mut env.ident_ids, region, - abilities_store, ) { Ok((symbol, shadowing_ability_member)) => { output.references.bound_symbols.insert(symbol); diff --git a/compiler/can/src/scope.rs b/compiler/can/src/scope.rs index 179f15bdf0..f4cb75e1be 100644 --- a/compiler/can/src/scope.rs +++ b/compiler/can/src/scope.rs @@ -22,7 +22,7 @@ pub struct Scope { pub aliases: SendMap, /// The abilities currently in scope, and their implementors. - pub abilities: SendMap, + pub abilities_store: AbilitiesStore, /// The current module being processed. This will be used to turn /// unqualified idents into Symbols. @@ -68,7 +68,7 @@ impl Scope { symbols: SendMap::default(), aliases, // TODO(abilities): default abilities in scope - abilities: SendMap::default(), + abilities_store: AbilitiesStore::default(), } } @@ -247,7 +247,6 @@ impl Scope { exposed_ident_ids: &IdentIds, all_ident_ids: &mut IdentIds, region: Region, - abilities_store: &AbilitiesStore, ) -> Result<(Symbol, Option), (Region, Loc, Symbol)> { match self.idents.get(&ident) { Some(&(original_symbol, original_region)) => { @@ -256,7 +255,9 @@ impl Scope { self.symbols.insert(shadow_symbol, region); - if abilities_store.is_ability_member_name(original_symbol) { + if self.abilities_store.is_ability_member_name(original_symbol) { + self.abilities_store + .register_specializing_symbol(shadow_symbol, original_symbol); // Add a symbol for the shadow, but don't re-associate the member name. Ok((shadow_symbol, Some(original_symbol))) } else { diff --git a/compiler/constrain/src/module.rs b/compiler/constrain/src/module.rs index e9b622cbee..566b15cd88 100644 --- a/compiler/constrain/src/module.rs +++ b/compiler/constrain/src/module.rs @@ -1,4 +1,5 @@ use roc_builtins::std::StdLib; +use roc_can::abilities::AbilitiesStore; use roc_can::constraint::{Constraint, Constraints}; use roc_can::def::Declaration; use roc_collections::all::MutMap; @@ -100,10 +101,26 @@ pub enum ExposedModuleTypes { pub fn constrain_module( constraints: &mut Constraints, + abilities_store: &AbilitiesStore, declarations: &[Declaration], home: ModuleId, ) -> Constraint { - crate::expr::constrain_decls(constraints, home, declarations) + let mut constraint = crate::expr::constrain_decls(constraints, home, declarations); + + for (member_name, member_data) in abilities_store.root_ability_members().iter() { + constraint = constraints.let_constraint( + [], + [], + [(*member_name, Loc::at_zero(member_data.signature.clone()))], + Constraint::True, + constraint, + ); + } + + // The module constraint should always save the environment at the end. + debug_assert!(constraints.contains_save_the_environment(&constraint)); + + constraint } #[derive(Debug, Clone)] diff --git a/compiler/constrain/src/pattern.rs b/compiler/constrain/src/pattern.rs index a66dcb03a5..2672602b7b 100644 --- a/compiler/constrain/src/pattern.rs +++ b/compiler/constrain/src/pattern.rs @@ -188,9 +188,23 @@ pub fn constrain_pattern( // Erroneous patterns don't add any constraints. } - Identifier(symbol) | Shadowed(_, _, symbol) - // TODO(abilities): handle linking the member def to the specialization ident - | AbilityMemberSpecialization { + Identifier(symbol) | Shadowed(_, _, symbol) => { + if could_be_a_tag_union(expected.get_type_ref()) { + state + .constraints + .push(constraints.is_open_type(expected.get_type_ref().clone())); + } + + state.headers.insert( + *symbol, + Loc { + region, + value: expected.get_type(), + }, + ); + } + + AbilityMemberSpecialization { ident: symbol, specializes: _, } => { diff --git a/compiler/load/Cargo.toml b/compiler/load/Cargo.toml index 4f7632fb60..f3d1d4c30f 100644 --- a/compiler/load/Cargo.toml +++ b/compiler/load/Cargo.toml @@ -13,10 +13,12 @@ roc_constrain= { path = "../constrain" } roc_types = { path = "../types" } roc_module = { path = "../module" } roc_collections = { path = "../collections" } +roc_reporting = { path = "../../reporting" } [build-dependencies] roc_load_internal = { path = "../load_internal" } roc_builtins = { path = "../builtins" } roc_module = { path = "../module" } +roc_reporting = { path = "../../reporting" } roc_target = { path = "../roc_target" } bumpalo = { version = "3.8.0", features = ["collections"] } diff --git a/compiler/load/build.rs b/compiler/load/build.rs index dc7b09cca9..3484b412d1 100644 --- a/compiler/load/build.rs +++ b/compiler/load/build.rs @@ -36,6 +36,7 @@ fn write_subs_for_module(module_id: ModuleId, filename: &str) { &src_dir, Default::default(), target_info, + roc_reporting::report::RenderTarget::ColorTerminal, ); let module = res_module.unwrap(); diff --git a/compiler/load/src/lib.rs b/compiler/load/src/lib.rs index 3b05a22bdf..b867228cca 100644 --- a/compiler/load/src/lib.rs +++ b/compiler/load/src/lib.rs @@ -2,6 +2,7 @@ use bumpalo::Bump; use roc_collections::all::MutMap; use roc_constrain::module::ExposedByModule; use roc_module::symbol::{ModuleId, Symbol}; +use roc_reporting::report::RenderTarget; use roc_target::TargetInfo; use roc_types::subs::{Subs, Variable}; use std::path::{Path, PathBuf}; @@ -18,6 +19,7 @@ fn load<'a>( exposed_types: ExposedByModule, goal_phase: Phase, target_info: TargetInfo, + render: RenderTarget, ) -> Result, LoadingProblem<'a>> { let cached_subs = read_cached_subs(); @@ -29,6 +31,7 @@ fn load<'a>( goal_phase, target_info, cached_subs, + render, ) } @@ -39,6 +42,7 @@ pub fn load_and_monomorphize_from_str<'a>( src_dir: &Path, exposed_types: ExposedByModule, target_info: TargetInfo, + render: RenderTarget, ) -> Result, LoadingProblem<'a>> { use LoadResult::*; @@ -51,6 +55,7 @@ pub fn load_and_monomorphize_from_str<'a>( exposed_types, Phase::MakeSpecializations, target_info, + render, )? { Monomorphized(module) => Ok(module), TypeChecked(_) => unreachable!(""), @@ -63,10 +68,11 @@ pub fn load_and_monomorphize<'a>( src_dir: &Path, exposed_types: ExposedByModule, target_info: TargetInfo, + render: RenderTarget, ) -> Result, LoadingProblem<'a>> { use LoadResult::*; - let load_start = LoadStart::from_path(arena, filename)?; + let load_start = LoadStart::from_path(arena, filename, render)?; match load( arena, @@ -75,6 +81,7 @@ pub fn load_and_monomorphize<'a>( exposed_types, Phase::MakeSpecializations, target_info, + render, )? { Monomorphized(module) => Ok(module), TypeChecked(_) => unreachable!(""), @@ -87,10 +94,11 @@ pub fn load_and_typecheck<'a>( src_dir: &Path, exposed_types: ExposedByModule, target_info: TargetInfo, + render: RenderTarget, ) -> Result> { use LoadResult::*; - let load_start = LoadStart::from_path(arena, filename)?; + let load_start = LoadStart::from_path(arena, filename, render)?; match load( arena, @@ -99,6 +107,7 @@ pub fn load_and_typecheck<'a>( exposed_types, Phase::SolveTypes, target_info, + render, )? { Monomorphized(_) => unreachable!(""), TypeChecked(module) => Ok(module), diff --git a/compiler/load_internal/Cargo.toml b/compiler/load_internal/Cargo.toml index 5381fc94b9..3d876623f5 100644 --- a/compiler/load_internal/Cargo.toml +++ b/compiler/load_internal/Cargo.toml @@ -33,4 +33,3 @@ tempfile = "3.2.0" pretty_assertions = "1.0.0" maplit = "1.0.2" indoc = "1.0.3" -strip-ansi-escapes = "0.1.1" diff --git a/compiler/load_internal/src/file.rs b/compiler/load_internal/src/file.rs index 27f8cb8c76..5287d7721c 100644 --- a/compiler/load_internal/src/file.rs +++ b/compiler/load_internal/src/file.rs @@ -5,6 +5,7 @@ use crossbeam::deque::{Injector, Stealer, Worker}; use crossbeam::thread; use parking_lot::Mutex; use roc_builtins::std::borrow_stdlib; +use roc_can::abilities::AbilitiesStore; use roc_can::constraint::{Constraint as ConstraintSoa, Constraints}; use roc_can::def::Declaration; use roc_can::module::{canonicalize_module_defs, Module}; @@ -31,6 +32,7 @@ use roc_parse::ident::UppercaseIdent; use roc_parse::module::module_defs; use roc_parse::parser::{FileError, Parser, SyntaxError}; use roc_region::all::{LineInfo, Loc, Region}; +use roc_reporting::report::RenderTarget; use roc_solve::module::SolvedModule; use roc_solve::solve; use roc_target::TargetInfo; @@ -347,6 +349,7 @@ pub struct LoadedModule { pub sources: MutMap)>, pub timings: MutMap, pub documentation: MutMap, + pub abilities_store: AbilitiesStore, } impl LoadedModule { @@ -508,6 +511,7 @@ enum Msg<'a> { decls: Vec, dep_idents: MutMap, module_timing: ModuleTiming, + abilities_store: AbilitiesStore, }, FinishedAllTypeChecking { solved_subs: Solved, @@ -515,6 +519,7 @@ enum Msg<'a> { exposed_aliases_by_symbol: MutMap, dep_idents: MutMap, documentation: MutMap, + abilities_store: AbilitiesStore, }, FoundSpecializations { module_id: ModuleId, @@ -604,6 +609,8 @@ struct State<'a> { // (Granted, this has not been attempted or measured!) pub layout_caches: std::vec::Vec>, + pub render: RenderTarget, + // cached subs (used for builtin modules, could include packages in the future too) cached_subs: CachedSubs, } @@ -611,6 +618,7 @@ struct State<'a> { type CachedSubs = Arc)>>>; impl<'a> State<'a> { + #[allow(clippy::too_many_arguments)] fn new( root_id: ModuleId, target_info: TargetInfo, @@ -619,6 +627,7 @@ impl<'a> State<'a> { arc_modules: Arc>>, ident_ids_by_module: Arc>>, cached_subs: MutMap)>, + render: RenderTarget, ) -> Self { let arc_shorthands = Arc::new(Mutex::new(MutMap::default())); @@ -643,6 +652,7 @@ impl<'a> State<'a> { timings: MutMap::default(), layout_caches: std::vec::Vec::with_capacity(num_cpus::get()), cached_subs: Arc::new(Mutex::new(cached_subs)), + render, } } } @@ -824,6 +834,7 @@ pub fn load_and_typecheck_str<'a>( src_dir: &Path, exposed_types: ExposedByModule, target_info: TargetInfo, + render: RenderTarget, ) -> Result> { use LoadResult::*; @@ -841,12 +852,19 @@ pub fn load_and_typecheck_str<'a>( Phase::SolveTypes, target_info, cached_subs, + render, )? { Monomorphized(_) => unreachable!(""), TypeChecked(module) => Ok(module), } } +#[derive(Clone, Copy)] +pub enum PrintTarget { + ColorTerminal, + Generic, +} + pub struct LoadStart<'a> { arc_modules: Arc>>, ident_ids_by_module: Arc>>, @@ -855,7 +873,11 @@ pub struct LoadStart<'a> { } impl<'a> LoadStart<'a> { - pub fn from_path(arena: &'a Bump, filename: PathBuf) -> Result> { + pub fn from_path( + arena: &'a Bump, + filename: PathBuf, + render: RenderTarget, + ) -> Result> { let arc_modules = Arc::new(Mutex::new(PackageModuleIds::default())); let root_exposed_ident_ids = IdentIds::exposed_builtins(0); let ident_ids_by_module = Arc::new(Mutex::new(root_exposed_ident_ids)); @@ -887,7 +909,12 @@ impl<'a> LoadStart<'a> { // if parsing failed, this module did not add any identifiers let root_exposed_ident_ids = IdentIds::exposed_builtins(0); - let buf = to_parse_problem_report(problem, module_ids, root_exposed_ident_ids); + let buf = to_parse_problem_report( + problem, + module_ids, + root_exposed_ident_ids, + render, + ); return Err(LoadingProblem::FormattedReport(buf)); } Err(LoadingProblem::FileProblem { filename, error }) => { @@ -995,6 +1022,7 @@ pub fn load<'a>( goal_phase: Phase, target_info: TargetInfo, cached_subs: MutMap)>, + render: RenderTarget, ) -> Result, LoadingProblem<'a>> { // When compiling to wasm, we cannot spawn extra threads // so we have a single-threaded implementation @@ -1007,6 +1035,7 @@ pub fn load<'a>( goal_phase, target_info, cached_subs, + render, ) } else { load_multi_threaded( @@ -1017,6 +1046,7 @@ pub fn load<'a>( goal_phase, target_info, cached_subs, + render, ) } } @@ -1031,12 +1061,14 @@ fn load_single_threaded<'a>( goal_phase: Phase, target_info: TargetInfo, cached_subs: MutMap)>, + render: RenderTarget, ) -> Result, LoadingProblem<'a>> { let LoadStart { arc_modules, ident_ids_by_module, root_id, root_msg, + .. } = load_start; let (msg_tx, msg_rx) = bounded(1024); @@ -1053,6 +1085,7 @@ fn load_single_threaded<'a>( arc_modules, ident_ids_by_module, cached_subs, + render, ); // We'll add tasks to this, and then worker threads will take tasks from it. @@ -1115,6 +1148,7 @@ fn state_thread_step<'a>( exposed_aliases_by_symbol, dep_idents, documentation, + abilities_store, } => { // We're done! There should be no more messages pending. debug_assert!(msg_rx.is_empty()); @@ -1131,6 +1165,7 @@ fn state_thread_step<'a>( exposed_vars_by_symbol, dep_idents, documentation, + abilities_store, ); Ok(ControlFlow::Break(LoadResult::TypeChecked(typechecked))) @@ -1153,8 +1188,12 @@ fn state_thread_step<'a>( Msg::FailedToParse(problem) => { let module_ids = (*state.arc_modules).lock().clone().into_module_ids(); - let buf = - to_parse_problem_report(problem, module_ids, state.constrained_ident_ids); + let buf = to_parse_problem_report( + problem, + module_ids, + state.constrained_ident_ids, + state.render, + ); Err(LoadingProblem::FormattedReport(buf)) } msg => { @@ -1164,6 +1203,8 @@ fn state_thread_step<'a>( let constrained_ident_ids = state.constrained_ident_ids.clone(); let arc_modules = state.arc_modules.clone(); + let render = state.render; + let res_state = update( state, msg, @@ -1185,8 +1226,12 @@ fn state_thread_step<'a>( .into_inner() .into_module_ids(); - let buf = - to_parse_problem_report(problem, module_ids, constrained_ident_ids); + let buf = to_parse_problem_report( + problem, + module_ids, + constrained_ident_ids, + render, + ); Err(LoadingProblem::FormattedReport(buf)) } Err(e) => Err(e), @@ -1210,12 +1255,14 @@ fn load_multi_threaded<'a>( goal_phase: Phase, target_info: TargetInfo, cached_subs: MutMap)>, + render: RenderTarget, ) -> Result, LoadingProblem<'a>> { let LoadStart { arc_modules, ident_ids_by_module, root_id, root_msg, + .. } = load_start; let mut state = State::new( @@ -1226,6 +1273,7 @@ fn load_multi_threaded<'a>( arc_modules, ident_ids_by_module, cached_subs, + render, ); let (msg_tx, msg_rx) = bounded(1024); @@ -1746,6 +1794,7 @@ fn update<'a>( decls, dep_idents, mut module_timing, + abilities_store, } => { log!("solved types for {:?}", module_id); module_timing.end_time = SystemTime::now(); @@ -1798,6 +1847,7 @@ fn update<'a>( exposed_aliases_by_symbol: solved_module.aliases, dep_idents, documentation, + abilities_store, }) .map_err(|_| LoadingProblem::MsgChannelDied)?; @@ -2126,6 +2176,7 @@ fn finish( exposed_vars_by_symbol: Vec<(Symbol, Variable)>, dep_idents: MutMap, documentation: MutMap, + abilities_store: AbilitiesStore, ) -> LoadedModule { let module_ids = Arc::try_unwrap(state.arc_modules) .unwrap_or_else(|_| panic!("There were still outstanding Arc references to module_ids")) @@ -2160,6 +2211,7 @@ fn finish( sources, timings: state.timings, documentation, + abilities_store, } } @@ -3102,6 +3154,10 @@ fn add_imports( rigid_vars.extend(copied_import.rigid); rigid_vars.extend(copied_import.flex); + // Rigid vars bound to abilities are also treated like rigids. + rigid_vars.extend(copied_import.rigid_able); + rigid_vars.extend(copied_import.flex_able); + import_variables.extend(copied_import.registered); def_types.push(( @@ -3119,6 +3175,7 @@ fn add_imports( import_variables } +#[allow(clippy::complexity)] fn run_solve_solve( imported_builtins: Vec, exposed_for_module: ExposedForModule, @@ -3126,11 +3183,17 @@ fn run_solve_solve( constraint: ConstraintSoa, mut var_store: VarStore, module: Module, -) -> (Solved, Vec<(Symbol, Variable)>, Vec) { +) -> ( + Solved, + Vec<(Symbol, Variable)>, + Vec, + AbilitiesStore, +) { let Module { exposed_symbols, aliases, rigid_variables, + abilities_store, .. } = module; @@ -3155,12 +3218,13 @@ fn run_solve_solve( solve_aliases.insert(*name, alias.clone()); } - let (solved_subs, solved_env, problems) = roc_solve::module::run_solve( + let (solved_subs, solved_env, problems, abilities_store) = roc_solve::module::run_solve( &constraints, actual_constraint, rigid_variables, subs, solve_aliases, + abilities_store, ); let solved_subs = if true { @@ -3179,7 +3243,12 @@ fn run_solve_solve( .filter(|(k, _)| exposed_symbols.contains(k)) .collect(); - (solved_subs, exposed_vars_by_symbol, problems) + ( + solved_subs, + exposed_vars_by_symbol, + problems, + abilities_store, + ) } #[allow(clippy::too_many_arguments)] @@ -3203,7 +3272,7 @@ fn run_solve<'a>( // TODO remove when we write builtins in roc let aliases = module.aliases.clone(); - let (solved_subs, exposed_vars_by_symbol, problems) = { + let (solved_subs, exposed_vars_by_symbol, problems, abilities_store) = { if module_id.is_builtin() { match cached_subs.lock().remove(&module_id) { None => { @@ -3217,9 +3286,13 @@ fn run_solve<'a>( module, ) } - Some((subs, exposed_vars_by_symbol)) => { - (Solved(subs), exposed_vars_by_symbol.to_vec(), vec![]) - } + Some((subs, exposed_vars_by_symbol)) => ( + Solved(subs), + exposed_vars_by_symbol.to_vec(), + vec![], + // TODO(abilities) replace when we have abilities for builtins + AbilitiesStore::default(), + ), } } else { run_solve_solve( @@ -3258,6 +3331,7 @@ fn run_solve<'a>( dep_idents, solved_module, module_timing, + abilities_store, } } @@ -3385,8 +3459,12 @@ fn canonicalize_and_constrain<'a>( let mut constraints = Constraints::new(); - let constraint = - constrain_module(&mut constraints, &module_output.declarations, module_id); + let constraint = constrain_module( + &mut constraints, + &module_output.scope.abilities_store, + &module_output.declarations, + module_id, + ); let after = roc_types::types::get_type_clone_count(); @@ -3426,6 +3504,7 @@ fn canonicalize_and_constrain<'a>( referenced_types: module_output.referenced_types, aliases, rigid_variables: module_output.rigid_variables, + abilities_store: module_output.scope.abilities_store, }; let constrained_module = ConstrainedModule { @@ -3859,6 +3938,7 @@ fn add_def_to_module<'a>( // This is a top-level definition, so it cannot capture anything captured_symbols: CapturedSymbols::None, body, + body_var: def.expr_var, // This is a 0-arity thunk, so it cannot be recursive is_self_recursive: false, }; @@ -4068,6 +4148,7 @@ fn to_parse_problem_report<'a>( problem: FileError<'a, SyntaxError<'a>>, mut module_ids: ModuleIds, all_ident_ids: MutMap, + render: RenderTarget, ) -> String { use roc_reporting::report::{parse_problem, RocDocAllocator, DEFAULT_PALETTE}; @@ -4102,7 +4183,7 @@ fn to_parse_problem_report<'a>( let mut buf = String::new(); let palette = DEFAULT_PALETTE; - report.render_color_terminal(&mut buf, &alloc, &palette); + report.render(render, &mut buf, &alloc, &palette); buf } diff --git a/compiler/load_internal/tests/test_load.rs b/compiler/load_internal/tests/test_load.rs index f78802553c..9cc4a46721 100644 --- a/compiler/load_internal/tests/test_load.rs +++ b/compiler/load_internal/tests/test_load.rs @@ -25,6 +25,7 @@ mod test_load { use roc_problem::can::Problem; use roc_region::all::LineInfo; use roc_reporting::report::can_problem; + use roc_reporting::report::RenderTarget; use roc_reporting::report::RocDocAllocator; use roc_target::TargetInfo; use roc_types::pretty_print::{content_to_string, name_all_type_vars}; @@ -41,7 +42,7 @@ mod test_load { ) -> Result> { use LoadResult::*; - let load_start = LoadStart::from_path(arena, filename)?; + let load_start = LoadStart::from_path(arena, filename, RenderTarget::Generic)?; match roc_load_internal::file::load( arena, @@ -51,6 +52,7 @@ mod test_load { Phase::SolveTypes, target_info, Default::default(), // these tests will re-compile the builtins + RenderTarget::Generic, )? { Monomorphized(_) => unreachable!(""), TypeChecked(module) => Ok(module), @@ -88,8 +90,6 @@ mod test_load { } fn multiple_modules(files: Vec<(&str, &str)>) -> Result { - use roc_load_internal::file::LoadingProblem; - let arena = Bump::new(); let arena = &arena; @@ -426,12 +426,12 @@ mod test_load { loaded_module, hashmap! { "floatTest" => "Float *", - "divisionFn" => "Float a, Float a -> Result (Float a) [ DivByZero ]*", - "divisionTest" => "Result (Float *) [ DivByZero ]*", + "divisionFn" => "Float a, Float a -> Float a", + "divisionTest" => "Float *", "intTest" => "I64", "x" => "Float *", "constantNum" => "Num *", - "divDep1ByDep2" => "Result (Float *) [ DivByZero ]*", + "divDep1ByDep2" => "Float *", "fromDep2" => "Float *", }, ); @@ -589,18 +589,18 @@ mod test_load { report, indoc!( " - \u{1b}[36m── UNFINISHED LIST ─────────────────────────────────────────────────────────────\u{1b}[0m + ── UNFINISHED LIST ───────────────────────────────────────────────────────────── - I cannot find the end of this list: + I cannot find the end of this list: - \u{1b}[36m3\u{1b}[0m\u{1b}[36m│\u{1b}[0m \u{1b}[37mmain = [\u{1b}[0m - \u{1b}[31m^\u{1b}[0m + 3│ main = [ + ^ - You could change it to something like \u{1b}[33m[ 1, 2, 3 ]\u{1b}[0m or even just \u{1b}[33m[]\u{1b}[0m. - Anything where there is an open and a close square bracket, and where - the elements of the list are separated by commas. + You could change it to something like [ 1, 2, 3 ] or even just []. + Anything where there is an open and a close square bracket, and where + the elements of the list are separated by commas. - \u{1b}[4mNote\u{1b}[0m: I may be confused by indentation" + Note: I may be confused by indentation" ) ), Ok(_) => unreachable!("we expect failure here"), @@ -769,8 +769,6 @@ mod test_load { ]; let err = multiple_modules(modules).unwrap_err(); - let err = strip_ansi_escapes::strip(err).unwrap(); - let err = String::from_utf8(err).unwrap(); assert_eq!( err, indoc!( diff --git a/compiler/module/src/low_level.rs b/compiler/module/src/low_level.rs index 3876411440..ef888bc878 100644 --- a/compiler/module/src/low_level.rs +++ b/compiler/module/src/low_level.rs @@ -289,8 +289,10 @@ impl LowLevelWrapperType { Symbol::NUM_LT => CanBeReplacedBy(NumLt), Symbol::NUM_LTE => CanBeReplacedBy(NumLte), Symbol::NUM_COMPARE => CanBeReplacedBy(NumCompare), - Symbol::NUM_DIV_FLOAT => WrapperIsRequired, - Symbol::NUM_DIV_CEIL => WrapperIsRequired, + Symbol::NUM_DIV_FLOAT => CanBeReplacedBy(NumDivUnchecked), + Symbol::NUM_DIV_FLOAT_CHECKED => WrapperIsRequired, + Symbol::NUM_DIV_CEIL => CanBeReplacedBy(NumDivCeilUnchecked), + Symbol::NUM_DIV_CEIL_CHECKED => WrapperIsRequired, Symbol::NUM_REM => WrapperIsRequired, Symbol::NUM_IS_MULTIPLE_OF => CanBeReplacedBy(NumIsMultipleOf), Symbol::NUM_ABS => CanBeReplacedBy(NumAbs), diff --git a/compiler/module/src/symbol.rs b/compiler/module/src/symbol.rs index 86ccb79309..5be160e1d8 100644 --- a/compiler/module/src/symbol.rs +++ b/compiler/module/src/symbol.rs @@ -945,118 +945,126 @@ define_builtins! { 36 NUM_IS_POSITIVE: "isPositive" 37 NUM_IS_NEGATIVE: "isNegative" 38 NUM_REM: "rem" - 39 NUM_DIV_FLOAT: "div" - 40 NUM_DIV_INT: "divFloor" - 41 NUM_MOD_INT: "modInt" - 42 NUM_MOD_FLOAT: "modFloat" - 43 NUM_SQRT: "sqrt" - 44 NUM_LOG: "log" - 45 NUM_ROUND: "round" - 46 NUM_COMPARE: "compare" - 47 NUM_POW: "pow" - 48 NUM_CEILING: "ceiling" - 49 NUM_POW_INT: "powInt" - 50 NUM_FLOOR: "floor" - 51 NUM_ADD_WRAP: "addWrap" - 52 NUM_ADD_CHECKED: "addChecked" - 53 NUM_ADD_SATURATED: "addSaturated" - 54 NUM_ATAN: "atan" - 55 NUM_ACOS: "acos" - 56 NUM_ASIN: "asin" - 57 NUM_AT_SIGNED128: "@Signed128" - 58 NUM_SIGNED128: "Signed128" imported - 59 NUM_AT_SIGNED64: "@Signed64" - 60 NUM_SIGNED64: "Signed64" imported - 61 NUM_AT_SIGNED32: "@Signed32" - 62 NUM_SIGNED32: "Signed32" imported - 63 NUM_AT_SIGNED16: "@Signed16" - 64 NUM_SIGNED16: "Signed16" imported - 65 NUM_AT_SIGNED8: "@Signed8" - 66 NUM_SIGNED8: "Signed8" imported - 67 NUM_AT_UNSIGNED128: "@Unsigned128" - 68 NUM_UNSIGNED128: "Unsigned128" imported - 69 NUM_AT_UNSIGNED64: "@Unsigned64" - 70 NUM_UNSIGNED64: "Unsigned64" imported - 71 NUM_AT_UNSIGNED32: "@Unsigned32" - 72 NUM_UNSIGNED32: "Unsigned32" imported - 73 NUM_AT_UNSIGNED16: "@Unsigned16" - 74 NUM_UNSIGNED16: "Unsigned16" imported - 75 NUM_AT_UNSIGNED8: "@Unsigned8" - 76 NUM_UNSIGNED8: "Unsigned8" imported - 77 NUM_AT_BINARY64: "@Binary64" - 78 NUM_BINARY64: "Binary64" imported - 79 NUM_AT_BINARY32: "@Binary32" - 80 NUM_BINARY32: "Binary32" imported - 81 NUM_BITWISE_AND: "bitwiseAnd" - 82 NUM_BITWISE_XOR: "bitwiseXor" - 83 NUM_BITWISE_OR: "bitwiseOr" - 84 NUM_SHIFT_LEFT: "shiftLeftBy" - 85 NUM_SHIFT_RIGHT: "shiftRightBy" - 86 NUM_SHIFT_RIGHT_ZERO_FILL: "shiftRightZfBy" - 87 NUM_SUB_WRAP: "subWrap" - 88 NUM_SUB_CHECKED: "subChecked" - 89 NUM_SUB_SATURATED: "subSaturated" - 90 NUM_MUL_WRAP: "mulWrap" - 91 NUM_MUL_CHECKED: "mulChecked" - 92 NUM_INT: "Int" imported - 93 NUM_FLOAT: "Float" imported - 94 NUM_AT_NATURAL: "@Natural" - 95 NUM_NATURAL: "Natural" imported - 96 NUM_NAT: "Nat" imported - 97 NUM_INT_CAST: "intCast" - 98 NUM_IS_MULTIPLE_OF: "isMultipleOf" - 99 NUM_AT_DECIMAL: "@Decimal" - 100 NUM_DECIMAL: "Decimal" imported - 101 NUM_DEC: "Dec" imported // the Num.Dectype alias - 102 NUM_BYTES_TO_U16: "bytesToU16" - 103 NUM_BYTES_TO_U32: "bytesToU32" - 104 NUM_CAST_TO_NAT: "#castToNat" - 105 NUM_DIV_CEIL: "divCeil" - 106 NUM_TO_STR: "toStr" - 107 NUM_MIN_I8: "minI8" - 108 NUM_MAX_I8: "maxI8" - 109 NUM_MIN_U8: "minU8" - 110 NUM_MAX_U8: "maxU8" - 111 NUM_MIN_I16: "minI16" - 112 NUM_MAX_I16: "maxI16" - 113 NUM_MIN_U16: "minU16" - 114 NUM_MAX_U16: "maxU16" - 115 NUM_MIN_I32: "minI32" - 116 NUM_MAX_I32: "maxI32" - 117 NUM_MIN_U32: "minU32" - 118 NUM_MAX_U32: "maxU32" - 119 NUM_MIN_I64: "minI64" - 120 NUM_MAX_I64: "maxI64" - 121 NUM_MIN_U64: "minU64" - 122 NUM_MAX_U64: "maxU64" - 123 NUM_MIN_I128: "minI128" - 124 NUM_MAX_I128: "maxI128" - 125 NUM_TO_I8: "toI8" - 126 NUM_TO_I8_CHECKED: "toI8Checked" - 127 NUM_TO_I16: "toI16" - 128 NUM_TO_I16_CHECKED: "toI16Checked" - 129 NUM_TO_I32: "toI32" - 130 NUM_TO_I32_CHECKED: "toI32Checked" - 131 NUM_TO_I64: "toI64" - 132 NUM_TO_I64_CHECKED: "toI64Checked" - 133 NUM_TO_I128: "toI128" - 134 NUM_TO_I128_CHECKED: "toI128Checked" - 135 NUM_TO_U8: "toU8" - 136 NUM_TO_U8_CHECKED: "toU8Checked" - 137 NUM_TO_U16: "toU16" - 138 NUM_TO_U16_CHECKED: "toU16Checked" - 139 NUM_TO_U32: "toU32" - 140 NUM_TO_U32_CHECKED: "toU32Checked" - 141 NUM_TO_U64: "toU64" - 142 NUM_TO_U64_CHECKED: "toU64Checked" - 143 NUM_TO_U128: "toU128" - 144 NUM_TO_U128_CHECKED: "toU128Checked" - 145 NUM_TO_NAT: "toNat" - 146 NUM_TO_NAT_CHECKED: "toNatChecked" - 147 NUM_TO_F32: "toF32" - 148 NUM_TO_F32_CHECKED: "toF32Checked" - 149 NUM_TO_F64: "toF64" - 150 NUM_TO_F64_CHECKED: "toF64Checked" + 39 NUM_REM_CHECKED: "remChecked" + 40 NUM_DIV_FLOAT: "div" + 41 NUM_DIV_FLOAT_CHECKED: "divChecked" + 42 NUM_DIV_FLOOR: "divFloor" + 43 NUM_DIV_FLOOR_CHECKED: "divFloorChecked" + 44 NUM_MOD_INT: "modInt" + 45 NUM_MOD_INT_CHECKED: "modIntChecked" + 46 NUM_MOD_FLOAT: "modFloat" + 47 NUM_MOD_FLOAT_CHECKED: "modFloatChecked" + 48 NUM_SQRT: "sqrt" + 49 NUM_SQRT_CHECKED: "sqrtChecked" + 50 NUM_LOG: "log" + 51 NUM_LOG_CHECKED: "logChecked" + 52 NUM_ROUND: "round" + 53 NUM_COMPARE: "compare" + 54 NUM_POW: "pow" + 55 NUM_CEILING: "ceiling" + 56 NUM_POW_INT: "powInt" + 57 NUM_FLOOR: "floor" + 58 NUM_ADD_WRAP: "addWrap" + 59 NUM_ADD_CHECKED: "addChecked" + 60 NUM_ADD_SATURATED: "addSaturated" + 61 NUM_ATAN: "atan" + 62 NUM_ACOS: "acos" + 63 NUM_ASIN: "asin" + 64 NUM_AT_SIGNED128: "@Signed128" + 65 NUM_SIGNED128: "Signed128" imported + 66 NUM_AT_SIGNED64: "@Signed64" + 67 NUM_SIGNED64: "Signed64" imported + 68 NUM_AT_SIGNED32: "@Signed32" + 69 NUM_SIGNED32: "Signed32" imported + 70 NUM_AT_SIGNED16: "@Signed16" + 71 NUM_SIGNED16: "Signed16" imported + 72 NUM_AT_SIGNED8: "@Signed8" + 73 NUM_SIGNED8: "Signed8" imported + 74 NUM_AT_UNSIGNED128: "@Unsigned128" + 75 NUM_UNSIGNED128: "Unsigned128" imported + 76 NUM_AT_UNSIGNED64: "@Unsigned64" + 77 NUM_UNSIGNED64: "Unsigned64" imported + 78 NUM_AT_UNSIGNED32: "@Unsigned32" + 79 NUM_UNSIGNED32: "Unsigned32" imported + 80 NUM_AT_UNSIGNED16: "@Unsigned16" + 81 NUM_UNSIGNED16: "Unsigned16" imported + 82 NUM_AT_UNSIGNED8: "@Unsigned8" + 83 NUM_UNSIGNED8: "Unsigned8" imported + 84 NUM_AT_BINARY64: "@Binary64" + 85 NUM_BINARY64: "Binary64" imported + 86 NUM_AT_BINARY32: "@Binary32" + 87 NUM_BINARY32: "Binary32" imported + 88 NUM_BITWISE_AND: "bitwiseAnd" + 89 NUM_BITWISE_XOR: "bitwiseXor" + 90 NUM_BITWISE_OR: "bitwiseOr" + 91 NUM_SHIFT_LEFT: "shiftLeftBy" + 92 NUM_SHIFT_RIGHT: "shiftRightBy" + 93 NUM_SHIFT_RIGHT_ZERO_FILL: "shiftRightZfBy" + 94 NUM_SUB_WRAP: "subWrap" + 95 NUM_SUB_CHECKED: "subChecked" + 96 NUM_SUB_SATURATED: "subSaturated" + 97 NUM_MUL_WRAP: "mulWrap" + 98 NUM_MUL_CHECKED: "mulChecked" + 99 NUM_INT: "Int" imported + 100 NUM_FLOAT: "Float" imported + 101 NUM_AT_NATURAL: "@Natural" + 102 NUM_NATURAL: "Natural" imported + 103 NUM_NAT: "Nat" imported + 104 NUM_INT_CAST: "intCast" + 105 NUM_IS_MULTIPLE_OF: "isMultipleOf" + 106 NUM_AT_DECIMAL: "@Decimal" + 107 NUM_DECIMAL: "Decimal" imported + 108 NUM_DEC: "Dec" imported // the Num.Dectype alias + 109 NUM_BYTES_TO_U16: "bytesToU16" + 110 NUM_BYTES_TO_U32: "bytesToU32" + 111 NUM_CAST_TO_NAT: "#castToNat" + 112 NUM_DIV_CEIL: "divCeil" + 113 NUM_DIV_CEIL_CHECKED: "divCeilChecked" + 114 NUM_TO_STR: "toStr" + 115 NUM_MIN_I8: "minI8" + 116 NUM_MAX_I8: "maxI8" + 117 NUM_MIN_U8: "minU8" + 118 NUM_MAX_U8: "maxU8" + 119 NUM_MIN_I16: "minI16" + 120 NUM_MAX_I16: "maxI16" + 121 NUM_MIN_U16: "minU16" + 122 NUM_MAX_U16: "maxU16" + 123 NUM_MIN_I32: "minI32" + 124 NUM_MAX_I32: "maxI32" + 125 NUM_MIN_U32: "minU32" + 126 NUM_MAX_U32: "maxU32" + 127 NUM_MIN_I64: "minI64" + 128 NUM_MAX_I64: "maxI64" + 129 NUM_MIN_U64: "minU64" + 130 NUM_MAX_U64: "maxU64" + 131 NUM_MIN_I128: "minI128" + 132 NUM_MAX_I128: "maxI128" + 133 NUM_TO_I8: "toI8" + 134 NUM_TO_I8_CHECKED: "toI8Checked" + 135 NUM_TO_I16: "toI16" + 136 NUM_TO_I16_CHECKED: "toI16Checked" + 137 NUM_TO_I32: "toI32" + 138 NUM_TO_I32_CHECKED: "toI32Checked" + 139 NUM_TO_I64: "toI64" + 140 NUM_TO_I64_CHECKED: "toI64Checked" + 141 NUM_TO_I128: "toI128" + 142 NUM_TO_I128_CHECKED: "toI128Checked" + 143 NUM_TO_U8: "toU8" + 144 NUM_TO_U8_CHECKED: "toU8Checked" + 145 NUM_TO_U16: "toU16" + 146 NUM_TO_U16_CHECKED: "toU16Checked" + 147 NUM_TO_U32: "toU32" + 148 NUM_TO_U32_CHECKED: "toU32Checked" + 149 NUM_TO_U64: "toU64" + 150 NUM_TO_U64_CHECKED: "toU64Checked" + 151 NUM_TO_U128: "toU128" + 152 NUM_TO_U128_CHECKED: "toU128Checked" + 153 NUM_TO_NAT: "toNat" + 154 NUM_TO_NAT_CHECKED: "toNatChecked" + 155 NUM_TO_F32: "toF32" + 156 NUM_TO_F32_CHECKED: "toF32Checked" + 157 NUM_TO_F64: "toF64" + 158 NUM_TO_F64_CHECKED: "toF64Checked" } 2 BOOL: "Bool" => { 0 BOOL_BOOL: "Bool" imported // the Bool.Bool type alias diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index 4ab5b2c841..8409b19b01 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -195,6 +195,7 @@ pub struct PartialProc<'a> { pub pattern_symbols: &'a [Symbol], pub captured_symbols: CapturedSymbols<'a>, pub body: roc_can::expr::Expr, + pub body_var: Variable, pub is_self_recursive: bool, } @@ -224,6 +225,7 @@ impl<'a> PartialProc<'a> { pattern_symbols, captured_symbols, body: body.value, + body_var: ret_var, is_self_recursive, } } @@ -240,6 +242,7 @@ impl<'a> PartialProc<'a> { pattern_symbols: pattern_symbols.into_bump_slice(), captured_symbols: CapturedSymbols::None, body: roc_can::expr::Expr::RuntimeError(error.value), + body_var: ret_var, is_self_recursive: false, } } @@ -902,6 +905,7 @@ impl<'a> Procs<'a> { pattern_symbols, captured_symbols, body: body.value, + body_var: ret_var, is_self_recursive, }; @@ -939,6 +943,7 @@ impl<'a> Procs<'a> { pattern_symbols, captured_symbols, body: body.value, + body_var: ret_var, is_self_recursive, }; @@ -2476,7 +2481,7 @@ fn specialize_external<'a>( }; let body = partial_proc.body.clone(); - let mut specialized_body = from_can(env, fn_var, body, procs, layout_cache); + let mut specialized_body = from_can(env, partial_proc.body_var, body, procs, layout_cache); match specialized { SpecializedLayout::FunctionPointerBody { diff --git a/compiler/mono/src/layout.rs b/compiler/mono/src/layout.rs index 07cf05ea5e..eef312cab3 100644 --- a/compiler/mono/src/layout.rs +++ b/compiler/mono/src/layout.rs @@ -3,6 +3,7 @@ use bumpalo::collections::Vec; use bumpalo::Bump; use roc_builtins::bitcode::{FloatWidth, IntWidth}; use roc_collections::all::{default_hasher, MutMap}; +use roc_error_macros::todo_abilities; use roc_module::ident::{Lowercase, TagName}; use roc_module::symbol::{Interns, Symbol}; use roc_problem::can::RuntimeError; @@ -10,7 +11,7 @@ use roc_target::{PtrWidth, TargetInfo}; use roc_types::subs::{ Content, FlatType, RecordFields, Subs, UnionTags, UnsortedUnionTags, Variable, }; -use roc_types::types::{gather_fields_unsorted_iter, RecordField}; +use roc_types::types::{gather_fields_unsorted_iter, RecordField, RecordFieldsError}; use std::collections::hash_map::{DefaultHasher, Entry}; use std::collections::HashMap; use std::hash::{Hash, Hasher}; @@ -72,6 +73,7 @@ impl<'a> RawFunctionLayout<'a> { use roc_types::subs::Content::*; match content { FlexVar(_) | RigidVar(_) => Err(LayoutProblem::UnresolvedTypeVar(var)), + FlexAbleVar(_, _) | RigidAbleVar(_, _) => todo_abilities!("Not reachable yet"), RecursionVar { structure, .. } => { let structure_content = env.subs.get_content_without_compacting(structure); Self::new_help(env, structure, *structure_content) @@ -952,6 +954,7 @@ impl<'a> Layout<'a> { use roc_types::subs::Content::*; match content { FlexVar(_) | RigidVar(_) => Err(LayoutProblem::UnresolvedTypeVar(var)), + FlexAbleVar(_, _) | RigidAbleVar(_, _) => todo_abilities!("Not reachable yet"), RecursionVar { structure, .. } => { let structure_content = env.subs.get_content_without_compacting(structure); Self::new_help(env, structure, *structure_content) @@ -1683,7 +1686,11 @@ fn layout_from_flat_type<'a>( // extract any values from the ext_var let mut pairs = Vec::with_capacity_in(fields.len(), arena); - for (label, field) in fields.unsorted_iterator(subs, ext_var) { + let it = match fields.unsorted_iterator(subs, ext_var) { + Ok(it) => it, + Err(RecordFieldsError) => return Err(LayoutProblem::Erroneous), + }; + for (label, field) in it { // drop optional fields let var = match field { RecordField::Optional(_) => continue, @@ -2657,6 +2664,7 @@ fn layout_from_num_content<'a>( // (e.g. for (5 + 5) assume both 5s are 64-bit integers.) Ok(Layout::default_integer()) } + FlexAbleVar(_, _) | RigidAbleVar(_, _) => todo_abilities!("Not reachable yet"), Structure(Apply(symbol, args)) => match *symbol { // Ints Symbol::NUM_NAT => Ok(Layout::usize(target_info)), diff --git a/compiler/mono/src/layout_soa.rs b/compiler/mono/src/layout_soa.rs index b4f9bd1a72..ed813d0c0a 100644 --- a/compiler/mono/src/layout_soa.rs +++ b/compiler/mono/src/layout_soa.rs @@ -137,8 +137,10 @@ impl FunctionLayout { use LayoutError::*; match content { - Content::FlexVar(_) => Err(UnresolvedVariable(var)), - Content::RigidVar(_) => Err(UnresolvedVariable(var)), + Content::FlexVar(_) + | Content::RigidVar(_) + | Content::FlexAbleVar(_, _) + | Content::RigidAbleVar(_, _) => Err(UnresolvedVariable(var)), Content::RecursionVar { .. } => Err(TypeError(())), Content::Structure(flat_type) => Self::from_flat_type(layouts, subs, flat_type), Content::Alias(_, _, actual, _) => Self::from_var_help(layouts, subs, *actual), @@ -243,8 +245,10 @@ impl LambdaSet { use LayoutError::*; match content { - Content::FlexVar(_) => Err(UnresolvedVariable(var)), - Content::RigidVar(_) => Err(UnresolvedVariable(var)), + Content::FlexVar(_) + | Content::RigidVar(_) + | Content::FlexAbleVar(_, _) + | Content::RigidAbleVar(_, _) => Err(UnresolvedVariable(var)), Content::RecursionVar { .. } => { unreachable!("lambda sets cannot currently be recursive") } @@ -627,8 +631,10 @@ impl Layout { use LayoutError::*; match content { - Content::FlexVar(_) => Err(UnresolvedVariable(var)), - Content::RigidVar(_) => Err(UnresolvedVariable(var)), + Content::FlexVar(_) + | Content::RigidVar(_) + | Content::FlexAbleVar(_, _) + | Content::RigidAbleVar(_, _) => Err(UnresolvedVariable(var)), Content::RecursionVar { structure, opt_name: _, diff --git a/compiler/problem/src/can.rs b/compiler/problem/src/can.rs index d35d93b307..f8d1684418 100644 --- a/compiler/problem/src/can.rs +++ b/compiler/problem/src/can.rs @@ -119,6 +119,13 @@ pub enum Problem { ability: Symbol, region: Region, }, + AbilityMemberMultipleBoundVars { + member: Symbol, + ability: Symbol, + span_has_clauses: Region, + bound_var_names: Vec, + }, + // TODO(abilities): remove me when ability hierarchies are supported AbilityMemberBindsExternalAbility { member: Symbol, ability: Symbol, diff --git a/compiler/solve/Cargo.toml b/compiler/solve/Cargo.toml index ebb9e4a615..b78faa8421 100644 --- a/compiler/solve/Cargo.toml +++ b/compiler/solve/Cargo.toml @@ -7,6 +7,7 @@ edition = "2018" [dependencies] roc_collections = { path = "../collections" } +roc_error_macros = { path = "../../error_macros" } roc_region = { path = "../region" } roc_module = { path = "../module" } roc_types = { path = "../types" } @@ -22,6 +23,7 @@ roc_problem = { path = "../problem" } roc_parse = { path = "../parse" } roc_solve = { path = "../solve" } roc_target = { path = "../roc_target" } +roc_reporting = { path = "../../reporting" } pretty_assertions = "1.0.0" indoc = "1.0.3" tempfile = "3.2.0" diff --git a/compiler/solve/src/module.rs b/compiler/solve/src/module.rs index c6197065a8..c0f5f7ef94 100644 --- a/compiler/solve/src/module.rs +++ b/compiler/solve/src/module.rs @@ -1,4 +1,5 @@ use crate::solve::{self, Aliases}; +use roc_can::abilities::AbilitiesStore; use roc_can::constraint::{Constraint as ConstraintSoa, Constraints}; use roc_can::module::RigidVariables; use roc_collections::all::MutMap; @@ -32,13 +33,23 @@ pub fn run_solve( rigid_variables: RigidVariables, mut subs: Subs, mut aliases: Aliases, -) -> (Solved, solve::Env, Vec) { + mut abilities_store: AbilitiesStore, +) -> ( + Solved, + solve::Env, + Vec, + AbilitiesStore, +) { let env = solve::Env::default(); for (var, name) in rigid_variables.named { subs.rigid_var(var, name); } + for (var, (name, ability)) in rigid_variables.able { + subs.rigid_able_var(var, name, ability); + } + for var in rigid_variables.wildcards { subs.rigid_var(var, "*".into()); } @@ -55,9 +66,10 @@ pub fn run_solve( subs, &mut aliases, &constraint, + &mut abilities_store, ); - (solved_subs, solved_env, problems) + (solved_subs, solved_env, problems, abilities_store) } pub fn exposed_types_storage_subs( diff --git a/compiler/solve/src/solve.rs b/compiler/solve/src/solve.rs index e546730f70..649c1e28ed 100644 --- a/compiler/solve/src/solve.rs +++ b/compiler/solve/src/solve.rs @@ -1,4 +1,5 @@ use bumpalo::Bump; +use roc_can::abilities::{AbilitiesStore, MemberSpecialization}; use roc_can::constraint::Constraint::{self, *}; use roc_can::constraint::{Constraints, LetConstraint}; use roc_can::expected::{Expected, PExpected}; @@ -14,9 +15,9 @@ use roc_types::subs::{ use roc_types::types::Type::{self, *}; use roc_types::types::{ gather_fields_unsorted_iter, AliasCommon, AliasKind, Category, ErrorType, PatternCategory, - TypeExtension, + Reason, TypeExtension, }; -use roc_unify::unify::{unify, Mode, Unified::*}; +use roc_unify::unify::{unify, Mode, MustImplementAbility, Unified::*}; // Type checking system adapted from Elm by Evan Czaplicki, BSD-3-Clause Licensed // https://github.com/elm/compiler @@ -75,6 +76,13 @@ pub enum TypeError { CircularType(Region, Symbol, ErrorType), BadType(roc_types::types::Problem), UnexposedLookup(Symbol), + IncompleteAbilityImplementation { + // TODO(abilities): have general types here, not just opaques + typ: Symbol, + ability: Symbol, + specialized_members: Vec>, + missing_members: Vec>, + }, } use roc_types::types::Alias; @@ -515,8 +523,17 @@ pub fn run( mut subs: Subs, aliases: &mut Aliases, constraint: &Constraint, + abilities_store: &mut AbilitiesStore, ) -> (Solved, Env) { - let env = run_in_place(constraints, env, problems, &mut subs, aliases, constraint); + let env = run_in_place( + constraints, + env, + problems, + &mut subs, + aliases, + constraint, + abilities_store, + ); (Solved(subs), env) } @@ -529,6 +546,7 @@ pub fn run_in_place( subs: &mut Subs, aliases: &mut Aliases, constraint: &Constraint, + abilities_store: &mut AbilitiesStore, ) -> Env { let mut pools = Pools::default(); @@ -540,6 +558,8 @@ pub fn run_in_place( let arena = Bump::new(); + let mut deferred_must_implement_abilities = Vec::new(); + let state = solve( &arena, constraints, @@ -551,8 +571,39 @@ pub fn run_in_place( aliases, subs, constraint, + abilities_store, + &mut deferred_must_implement_abilities, ); + // Now that the module has been solved, we can run through and check all + // types claimed to implement abilities. + deferred_must_implement_abilities.dedup(); + for MustImplementAbility { typ, ability } in deferred_must_implement_abilities.into_iter() { + let members_of_ability = abilities_store.members_of_ability(ability).unwrap(); + let mut specialized_members = Vec::with_capacity(members_of_ability.len()); + let mut missing_members = Vec::with_capacity(members_of_ability.len()); + for &member in members_of_ability { + match abilities_store.get_specialization(member, typ) { + None => { + let root_data = abilities_store.member_def(member).unwrap(); + missing_members.push(Loc::at(root_data.region, member)); + } + Some(specialization) => { + specialized_members.push(Loc::at(specialization.region, member)); + } + } + } + + if !missing_members.is_empty() { + problems.push(TypeError::IncompleteAbilityImplementation { + typ, + ability, + specialized_members, + missing_members, + }); + } + } + state.env } @@ -604,6 +655,8 @@ fn solve( aliases: &mut Aliases, subs: &mut Subs, constraint: &Constraint, + abilities_store: &mut AbilitiesStore, + deferred_must_implement_abilities: &mut Vec, ) -> State { let initial = Work::Constraint { env, @@ -752,6 +805,19 @@ fn solve( let mut new_env = env.clone(); for (symbol, loc_var) in local_def_vars.iter() { + check_ability_specialization( + arena, + subs, + &new_env, + pools, + rank, + abilities_store, + problems, + deferred_must_implement_abilities, + *symbol, + *loc_var, + ); + new_env.insert_symbol_var_if_vacant(*symbol, loc_var.value); } @@ -796,12 +862,15 @@ fn solve( let expected = type_to_var(subs, rank, pools, aliases, expectation.get_type_ref()); match unify(subs, actual, expected, Mode::EQ) { - Success(vars) => { + Success { + vars, + must_implement_ability: _, + } => { introduce(subs, rank, pools, &vars); state } - Failure(vars, actual_type, expected_type) => { + Failure(vars, actual_type, expected_type, _bad_impls) => { introduce(subs, rank, pools, &vars); let problem = TypeError::BadExpr( @@ -838,12 +907,15 @@ fn solve( let target = *target; match unify(subs, actual, target, Mode::EQ) { - Success(vars) => { + Success { + vars, + must_implement_ability: _, + } => { introduce(subs, rank, pools, &vars); state } - Failure(vars, _actual_type, _expected_type) => { + Failure(vars, _actual_type, _expected_type, _bad_impls) => { introduce(subs, rank, pools, &vars); // ERROR NOT REPORTED @@ -890,13 +962,16 @@ fn solve( type_to_var(subs, rank, pools, aliases, expectation.get_type_ref()); match unify(subs, actual, expected, Mode::EQ) { - Success(vars) => { + Success { + vars, + must_implement_ability: _, + } => { introduce(subs, rank, pools, &vars); state } - Failure(vars, actual_type, expected_type) => { + Failure(vars, actual_type, expected_type, _bad_impls) => { introduce(subs, rank, pools, &vars); let problem = TypeError::BadExpr( @@ -954,12 +1029,15 @@ fn solve( }; match unify(subs, actual, expected, mode) { - Success(vars) => { + Success { + vars, + must_implement_ability: _, + } => { introduce(subs, rank, pools, &vars); state } - Failure(vars, actual_type, expected_type) => { + Failure(vars, actual_type, expected_type, _bad_impls) => { introduce(subs, rank, pools, &vars); let problem = TypeError::BadPattern( @@ -1123,12 +1201,15 @@ fn solve( let includes = type_to_var(subs, rank, pools, aliases, &tag_ty); match unify(subs, actual, includes, Mode::PRESENT) { - Success(vars) => { + Success { + vars, + must_implement_ability: _, + } => { introduce(subs, rank, pools, &vars); state } - Failure(vars, actual_type, expected_to_include_type) => { + Failure(vars, actual_type, expected_to_include_type, _bad_impls) => { introduce(subs, rank, pools, &vars); let problem = TypeError::BadPattern( @@ -1156,6 +1237,136 @@ fn solve( state } +/// If a symbol claims to specialize an ability member, check that its solved type in fact +/// does specialize the ability, and record the specialization. +#[allow(clippy::too_many_arguments)] +// Aggressive but necessary - there aren't many usages. +#[inline(always)] +fn check_ability_specialization( + arena: &Bump, + subs: &mut Subs, + env: &Env, + pools: &mut Pools, + rank: Rank, + abilities_store: &mut AbilitiesStore, + problems: &mut Vec, + deferred_must_implement_abilities: &mut Vec, + symbol: Symbol, + symbol_loc_var: Loc, +) { + // If the symbol specializes an ability member, we need to make sure that the + // inferred type for the specialization actually aligns with the expected + // implementation. + if let Some((root_symbol, root_data)) = abilities_store.root_name_and_def(symbol) { + let root_signature_var = env + .get_var_by_symbol(&root_symbol) + .expect("Ability should be registered in env by now!"); + + // Check if they unify - if they don't, then the claimed specialization isn't really one, + // and that's a type error! + // This also fixes any latent type variables that need to be specialized to exactly what + // the ability signature expects. + + // We need to freshly instantiate the root signature so that all unifications are reflected + // in the specialization type, but not the original signature type. + let root_signature_var = + deep_copy_var_in(subs, Rank::toplevel(), pools, root_signature_var, arena); + let snapshot = subs.snapshot(); + let unified = unify(subs, symbol_loc_var.value, root_signature_var, Mode::EQ); + + match unified { + Success { + vars: _, + must_implement_ability, + } if must_implement_ability.is_empty() => { + // This can happen when every ability constriant on a type variable went + // through only another type variable. That means this def is not specialized + // for one type - for now, we won't admit this. + + // Rollback the snapshot so we unlink the root signature with the specialization, + // so we can have two separate error types. + subs.rollback_to(snapshot); + + let (expected_type, _problems) = subs.var_to_error_type(root_signature_var); + let (actual_type, _problems) = subs.var_to_error_type(symbol_loc_var.value); + + let reason = Reason::GeneralizedAbilityMemberSpecialization { + member_name: root_symbol, + def_region: root_data.region, + }; + + let problem = TypeError::BadExpr( + symbol_loc_var.region, + Category::AbilityMemberSpecialization(root_symbol), + actual_type, + Expected::ForReason(reason, expected_type, symbol_loc_var.region), + ); + + problems.push(problem); + } + + Success { + vars, + must_implement_ability, + } => { + subs.commit_snapshot(snapshot); + introduce(subs, rank, pools, &vars); + + // First, figure out and register for what type does this symbol specialize + // the ability member. + let mut ability_implementations_for_specialization = must_implement_ability + .iter() + .filter(|mia| mia.ability == root_data.parent_ability) + .collect::>(); + ability_implementations_for_specialization.dedup(); + + debug_assert!(ability_implementations_for_specialization.len() == 1, "Multiple variables bound to an ability - this is ambiguous and should have been caught in canonicalization"); + + // This is a valid specialization! Record it. + let specialization_type = ability_implementations_for_specialization[0].typ; + let specialization = MemberSpecialization { + symbol, + region: symbol_loc_var.region, + }; + abilities_store.register_specialization_for_type( + root_symbol, + specialization_type, + specialization, + ); + + // Store the checks for what abilities must be implemented to be checked after the + // whole module is complete. + deferred_must_implement_abilities.extend(must_implement_ability); + } + Failure(vars, actual_type, expected_type, unimplemented_abilities) => { + subs.commit_snapshot(snapshot); + introduce(subs, rank, pools, &vars); + + let reason = Reason::InvalidAbilityMemberSpecialization { + member_name: root_symbol, + def_region: root_data.region, + unimplemented_abilities, + }; + + let problem = TypeError::BadExpr( + symbol_loc_var.region, + Category::AbilityMemberSpecialization(root_symbol), + actual_type, + Expected::ForReason(reason, expected_type, symbol_loc_var.region), + ); + + problems.push(problem); + } + BadType(vars, problem) => { + subs.commit_snapshot(snapshot); + introduce(subs, rank, pools, &vars); + + problems.push(TypeError::BadType(problem)); + } + } + } +} + #[derive(Debug)] enum LocalDefVarsVec { Stack(arrayvec::ArrayVec), @@ -1288,7 +1499,7 @@ impl RegisterVariable { use RegisterVariable::*; match typ { - Variable(var) => Direct(*var), + Type::Variable(var) => Direct(*var), EmptyRec => Direct(Variable::EMPTY_RECORD), EmptyTagUnion => Direct(Variable::EMPTY_TAG_UNION), Type::DelayedAlias(AliasCommon { symbol, .. }) => { @@ -2180,7 +2391,7 @@ fn adjust_rank_content( use roc_types::subs::FlatType::*; match content { - FlexVar(_) | RigidVar(_) | Error => group_rank, + FlexVar(_) | RigidVar(_) | FlexAbleVar(_, _) | RigidAbleVar(_, _) | Error => group_rank, RecursionVar { .. } => group_rank, @@ -2396,7 +2607,14 @@ fn instantiate_rigids_help(subs: &mut Subs, max_rank: Rank, initial: Variable) { desc.mark = Mark::NONE; desc.copy = OptVariable::NONE; } - FlexVar(_) | Error => (), + &RigidAbleVar(name, ability) => { + // Same as `RigidVar` above + desc.content = FlexAbleVar(Some(name), ability); + desc.rank = max_rank; + desc.mark = Mark::NONE; + desc.copy = OptVariable::NONE; + } + FlexVar(_) | FlexAbleVar(_, _) | Error => (), RecursionVar { structure, .. } => { stack.push(*structure); @@ -2684,7 +2902,7 @@ fn deep_copy_var_help( copy } - FlexVar(_) | Error => copy, + FlexVar(_) | FlexAbleVar(_, _) | Error => copy, RecursionVar { opt_name, @@ -2709,6 +2927,12 @@ fn deep_copy_var_help( copy } + RigidAbleVar(name, ability) => { + subs.set(copy, make_descriptor(FlexAbleVar(Some(name), ability))); + + copy + } + Alias(symbol, arguments, real_type_var, kind) => { let new_variables = SubsSlice::reserve_into_subs(subs, arguments.all_variables_len as _); diff --git a/compiler/solve/tests/solve_expr.rs b/compiler/solve/tests/solve_expr.rs index ed65cfbb64..fd2f854633 100644 --- a/compiler/solve/tests/solve_expr.rs +++ b/compiler/solve/tests/solve_expr.rs @@ -10,20 +10,12 @@ mod helpers; #[cfg(test)] mod solve_expr { use crate::helpers::with_larger_debug_stack; + use roc_load::LoadedModule; use roc_types::pretty_print::{content_to_string, name_all_type_vars}; // HELPERS - fn infer_eq_help( - src: &str, - ) -> Result< - ( - Vec, - Vec, - String, - ), - std::io::Error, - > { + fn run_load_and_infer(src: &str) -> Result { use bumpalo::Bump; use std::fs::File; use std::io::Write; @@ -58,6 +50,7 @@ mod solve_expr { dir.path(), exposed_types, roc_target::TargetInfo::default_x86_64(), + roc_reporting::report::RenderTarget::Generic, ); dir.close()?; @@ -66,8 +59,19 @@ mod solve_expr { }; let loaded = loaded.expect("failed to load module"); + Ok(loaded) + } - use roc_load::LoadedModule; + fn infer_eq_help( + src: &str, + ) -> Result< + ( + Vec, + Vec, + String, + ), + std::io::Error, + > { let LoadedModule { module_id: home, mut can_problems, @@ -76,7 +80,7 @@ mod solve_expr { mut solved, exposed_to_host, .. - } = loaded; + } = run_load_and_infer(src)?; let mut can_problems = can_problems.remove(&home).unwrap_or_default(); let type_problems = type_problems.remove(&home).unwrap_or_default(); @@ -155,6 +159,51 @@ mod solve_expr { assert_eq!(actual, expected.to_string()); } + fn check_inferred_abilities<'a, I>(src: &'a str, expected_specializations: I) + where + I: IntoIterator, + { + let LoadedModule { + module_id: home, + mut can_problems, + mut type_problems, + interns, + abilities_store, + .. + } = run_load_and_infer(src).unwrap(); + + let can_problems = can_problems.remove(&home).unwrap_or_default(); + let type_problems = type_problems.remove(&home).unwrap_or_default(); + + assert_eq!(can_problems, Vec::new(), "Canonicalization problems: "); + + if !type_problems.is_empty() { + eprintln!("{:?}", type_problems); + panic!(); + } + + let known_specializations = abilities_store.get_known_specializations(); + use std::collections::HashSet; + let pretty_specializations = known_specializations + .into_iter() + .map(|(typ, member)| { + ( + typ.ident_str(&interns).as_str(), + member.ident_str(&interns).as_str(), + ) + }) + .collect::>(); + + for expected_spec in expected_specializations.into_iter() { + assert!( + pretty_specializations.contains(&expected_spec), + "{:#?} not in {:#?}", + expected_spec, + pretty_specializations, + ); + } + } + #[test] fn int_literal() { infer_eq("5", "Num *"); @@ -3296,6 +3345,30 @@ mod solve_expr { ); } + #[test] + fn div() { + infer_eq_without_problem( + indoc!( + r#" + Num.div + "# + ), + "Float a, Float a -> Float a", + ) + } + + #[test] + fn div_checked() { + infer_eq_without_problem( + indoc!( + r#" + Num.divChecked + "# + ), + "Float a, Float a -> Result (Float a) [ DivByZero ]*", + ) + } + #[test] fn div_ceil() { infer_eq_without_problem( @@ -3304,22 +3377,46 @@ mod solve_expr { 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 pow_int() { + fn div_floor() { infer_eq_without_problem( indoc!( r#" - Num.powInt + Num.divFloor "# ), "Int a, Int a -> Int a", ); } + #[test] + fn div_floor_checked() { + infer_eq_without_problem( + indoc!( + r#" + Num.divFloorChecked + "# + ), + "Int a, Int a -> Result (Int a) [ DivByZero ]*", + ); + } + #[test] fn atan() { infer_eq_without_problem( @@ -5662,4 +5759,85 @@ mod solve_expr { "a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, aa, bb -> { a : a, aa : aa, b : b, bb : bb, c : c, d : d, e : e, f : f, g : g, h : h, i : i, j : j, k : k, l : l, m : m, n : n, o : o, p : p, q : q, r : r, s : s, t : t, u : u, v : v, w : w, x : x, y : y, z : z }", ) } + + #[test] + fn exposed_ability_name() { + infer_eq_without_problem( + indoc!( + r#" + app "test" provides [ hash ] to "./platform" + + Hash has hash : a -> U64 | a has Hash + "# + ), + "a -> U64 | a has Hash", + ) + } + + #[test] + fn single_ability_single_member_specializations() { + check_inferred_abilities( + indoc!( + r#" + app "test" provides [ hash ] to "./platform" + + Hash has hash : a -> U64 | a has Hash + + Id := U64 + + hash = \$Id n -> n + "# + ), + [("hash", "Id")], + ) + } + + #[test] + fn single_ability_multiple_members_specializations() { + check_inferred_abilities( + indoc!( + r#" + app "test" provides [ hash, hash32 ] to "./platform" + + Hash has + hash : a -> U64 | a has Hash + hash32 : a -> U32 | a has Hash + + Id := U64 + + hash = \$Id n -> n + hash32 = \$Id n -> Num.toU32 n + "# + ), + [("hash", "Id"), ("hash32", "Id")], + ) + } + + #[test] + fn multiple_abilities_multiple_members_specializations() { + check_inferred_abilities( + indoc!( + r#" + app "test" provides [ hash, hash32, eq, le ] to "./platform" + + Hash has + hash : a -> U64 | a has Hash + hash32 : a -> U32 | a has Hash + + Ord has + eq : a, a -> Bool | a has Ord + le : a, a -> Bool | a has Ord + + Id := U64 + + hash = \$Id n -> n + hash32 = \$Id n -> Num.toU32 n + + eq = \$Id m, $Id n -> m == n + le = \$Id m, $Id n -> m < n + "# + ), + [("hash", "Id"), ("hash32", "Id"), ("eq", "Id"), ("le", "Id")], + ) + } } diff --git a/compiler/test_gen/src/gen_num.rs b/compiler/test_gen/src/gen_num.rs index 0ead1c308d..88480c6310 100644 --- a/compiler/test_gen/src/gen_num.rs +++ b/compiler/test_gen/src/gen_num.rs @@ -723,11 +723,24 @@ fn gen_wrap_add_nums() { #[test] #[cfg(any(feature = "gen-llvm"))] fn gen_div_f64() { - // FIXME this works with normal types, but fails when checking uniqueness types assert_evals_to!( indoc!( r#" - when 48 / 2 is + 48 / 2 + "# + ), + 24.0, + f64 + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm"))] +fn gen_div_checked_f64() { + assert_evals_to!( + indoc!( + r#" + when Num.divChecked 48 2 is Ok val -> val Err _ -> -1 "# @@ -736,6 +749,23 @@ fn gen_div_f64() { f64 ); } + +#[test] +#[cfg(any(feature = "gen-llvm"))] +fn gen_div_checked_by_zero_f64() { + assert_evals_to!( + indoc!( + r#" + when Num.divChecked 47 0 is + Ok val -> val + Err _ -> -1 + "# + ), + -1.0, + f64 + ); +} + #[test] #[cfg(any(feature = "gen-llvm"))] fn gen_div_dec() { @@ -748,7 +778,27 @@ fn gen_div_dec() { y : Dec y = 3 - when x / y is + x / y + "# + ), + RocDec::from_str_to_i128_unsafe("3.333333333333333333"), + i128 + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm"))] +fn gen_div_checked_dec() { + assert_evals_to!( + indoc!( + r#" + x : Dec + x = 10 + + y : Dec + y = 3 + + when Num.divChecked x y is Ok val -> val Err _ -> -1 "# @@ -757,6 +807,27 @@ fn gen_div_dec() { i128 ); } +#[test] +#[cfg(any(feature = "gen-llvm"))] +fn gen_div_checked_by_zero_dec() { + assert_evals_to!( + indoc!( + r#" + x : Dec + x = 10 + + y : Dec + y = 0 + + when Num.divChecked x y is + Ok val -> val + Err _ -> -1 + "# + ), + RocDec::from_str_to_i128_unsafe("-1"), + i128 + ); +} #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))] @@ -965,7 +1036,21 @@ fn gen_div_i64() { assert_evals_to!( indoc!( r#" - when 1000 // 10 is + 1000 // 10 + "# + ), + 100, + i64 + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm"))] +fn gen_div_checked_i64() { + assert_evals_to!( + indoc!( + r#" + when Num.divFloorChecked 1000 10 is Ok val -> val Err _ -> -1 "# @@ -977,11 +1062,11 @@ fn gen_div_i64() { #[test] #[cfg(any(feature = "gen-llvm"))] -fn gen_div_by_zero_i64() { +fn gen_div_checked_by_zero_i64() { assert_evals_to!( indoc!( r#" - when 1000 // 0 is + when Num.divFloorChecked 1000 0 is Err DivByZero -> 99 _ -> -24 "# diff --git a/compiler/test_gen/src/helpers/dev.rs b/compiler/test_gen/src/helpers/dev.rs index 2adcaadb90..0ecd1bb37c 100644 --- a/compiler/test_gen/src/helpers/dev.rs +++ b/compiler/test_gen/src/helpers/dev.rs @@ -54,6 +54,7 @@ pub fn helper( src_dir, Default::default(), roc_target::TargetInfo::default_x86_64(), + roc_reporting::report::RenderTarget::ColorTerminal, ); let mut loaded = loaded.expect("failed to load module"); diff --git a/compiler/test_gen/src/helpers/llvm.rs b/compiler/test_gen/src/helpers/llvm.rs index f6660f977b..03bc6b7a1e 100644 --- a/compiler/test_gen/src/helpers/llvm.rs +++ b/compiler/test_gen/src/helpers/llvm.rs @@ -7,6 +7,7 @@ use roc_collections::all::MutSet; use roc_gen_llvm::llvm::externs::add_default_roc_externs; use roc_mono::ir::OptLevel; use roc_region::all::LineInfo; +use roc_reporting::report::RenderTarget; use target_lexicon::Triple; fn promote_expr_to_module(src: &str) -> String { @@ -57,6 +58,7 @@ fn create_llvm_module<'a>( src_dir, Default::default(), target_info, + RenderTarget::ColorTerminal, ); let mut loaded = match loaded { diff --git a/compiler/test_gen/src/helpers/wasm.rs b/compiler/test_gen/src/helpers/wasm.rs index 6460cb1139..0c59df3cf5 100644 --- a/compiler/test_gen/src/helpers/wasm.rs +++ b/compiler/test_gen/src/helpers/wasm.rs @@ -91,6 +91,7 @@ fn compile_roc_to_wasm_bytes<'a, T: Wasm32Result>( src_dir, Default::default(), roc_target::TargetInfo::default_wasm32(), + roc_reporting::report::RenderTarget::ColorTerminal, ); let loaded = loaded.expect("failed to load module"); diff --git a/compiler/test_mono/Cargo.toml b/compiler/test_mono/Cargo.toml index 2f56668cb1..9f336dbe73 100644 --- a/compiler/test_mono/Cargo.toml +++ b/compiler/test_mono/Cargo.toml @@ -17,6 +17,7 @@ roc_load = { path = "../load" } roc_can = { path = "../can" } roc_mono = { path = "../mono" } roc_target = { path = "../roc_target" } +roc_reporting = { path = "../../reporting" } test_mono_macros = { path = "../test_mono_macros" } pretty_assertions = "1.0.0" bumpalo = { version = "3.8.0", features = ["collections"] } diff --git a/compiler/test_mono/src/tests.rs b/compiler/test_mono/src/tests.rs index ff9b7bfcaa..74e17f9a15 100644 --- a/compiler/test_mono/src/tests.rs +++ b/compiler/test_mono/src/tests.rs @@ -101,6 +101,7 @@ fn compiles_to_ir(test_name: &str, src: &str) { src_dir, Default::default(), TARGET_INFO, + roc_reporting::report::RenderTarget::Generic, ); let mut loaded = match loaded { @@ -274,7 +275,7 @@ fn ir_round() { #[mono_test] fn ir_when_idiv() { r#" - when 1000 // 10 is + when Num.divFloorChecked 1000 10 is Ok val -> val Err _ -> -1 "# diff --git a/compiler/types/src/pretty_print.rs b/compiler/types/src/pretty_print.rs index ce6b338282..e4e8456fe3 100644 --- a/compiler/types/src/pretty_print.rs +++ b/compiler/types/src/pretty_print.rs @@ -116,7 +116,7 @@ fn find_names_needed( } match &subs.get_content_without_compacting(variable).clone() { - RecursionVar { opt_name: None, .. } | FlexVar(None) => { + RecursionVar { opt_name: None, .. } | FlexVar(None) | FlexAbleVar(None, _) => { let root = subs.get_root_key_without_compacting(variable); // If this var is *not* its own root, then the @@ -139,7 +139,8 @@ fn find_names_needed( opt_name: Some(name_index), .. } - | FlexVar(Some(name_index)) => { + | FlexVar(Some(name_index)) + | FlexAbleVar(Some(name_index), _) => { // This root already has a name. Nothing more to do here! // User-defined names are already taken. @@ -147,7 +148,7 @@ fn find_names_needed( let name = subs.field_names[name_index.index as usize].clone(); names_taken.insert(name); } - RigidVar(name_index) => { + RigidVar(name_index) | RigidAbleVar(name_index, _) => { // User-defined names are already taken. // We must not accidentally generate names that collide with them! let name = subs.field_names[name_index.index as usize].clone(); @@ -289,6 +290,11 @@ fn set_root_name(root: Variable, name: Lowercase, subs: &mut Subs) { } } +#[derive(Default)] +struct Context<'a> { + able_variables: Vec<(&'a str, Symbol)>, +} + pub fn content_to_string( content: &Content, subs: &Subs, @@ -297,8 +303,16 @@ pub fn content_to_string( ) -> String { let mut buf = String::new(); let env = Env { home, interns }; + let mut ctx = Context::default(); - write_content(&env, content, subs, &mut buf, Parens::Unnecessary); + write_content(&env, &mut ctx, content, subs, &mut buf, Parens::Unnecessary); + + for (i, (var, ability)) in ctx.able_variables.into_iter().enumerate() { + buf.push_str(if i == 0 { " | " } else { ", " }); + buf.push_str(var); + buf.push_str(" has "); + write_symbol(&env, ability, &mut buf); + } buf } @@ -314,7 +328,14 @@ pub fn get_single_arg<'a>(subs: &'a Subs, args: &'a AliasVariables) -> &'a Conte subs.get_content_without_compacting(arg_var) } -fn write_content(env: &Env, content: &Content, subs: &Subs, buf: &mut String, parens: Parens) { +fn write_content<'a>( + env: &Env, + ctx: &mut Context<'a>, + content: &Content, + subs: &'a Subs, + buf: &mut String, + parens: Parens, +) { use crate::subs::Content::*; match content { @@ -327,6 +348,18 @@ fn write_content(env: &Env, content: &Content, subs: &Subs, buf: &mut String, pa let name = &subs.field_names[name_index.index as usize]; buf.push_str(name.as_str()) } + FlexAbleVar(opt_name_index, ability) => { + let name = opt_name_index + .map(|name_index| subs.field_names[name_index.index as usize].as_str()) + .unwrap_or(WILDCARD); + ctx.able_variables.push((name, *ability)); + buf.push_str(name); + } + RigidAbleVar(name_index, ability) => { + let name = subs.field_names[name_index.index as usize].as_str(); + ctx.able_variables.push((name, *ability)); + buf.push_str(name); + } RecursionVar { opt_name, .. } => match opt_name { Some(name_index) => { let name = &subs.field_names[name_index.index as usize]; @@ -334,7 +367,7 @@ fn write_content(env: &Env, content: &Content, subs: &Subs, buf: &mut String, pa } None => buf.push_str(WILDCARD), }, - Structure(flat_type) => write_flat_type(env, flat_type, subs, buf, parens), + Structure(flat_type) => write_flat_type(env, ctx, flat_type, subs, buf, parens), Alias(symbol, args, _actual, _kind) => { let write_parens = parens == Parens::InTypeParam && !args.is_empty(); @@ -346,6 +379,7 @@ fn write_content(env: &Env, content: &Content, subs: &Subs, buf: &mut String, pa Symbol::NUM_INTEGER => { write_integer( env, + ctx, get_single_arg(subs, &args), subs, buf, @@ -357,13 +391,13 @@ fn write_content(env: &Env, content: &Content, subs: &Subs, buf: &mut String, pa _ => write_parens!(write_parens, buf, { buf.push_str("Num "); - write_content(env, content, subs, buf, parens); + write_content(env, ctx, content, subs, buf, parens); }), }, _ => write_parens!(write_parens, buf, { buf.push_str("Num "); - write_content(env, content, subs, buf, parens); + write_content(env, ctx, content, subs, buf, parens); }), } } @@ -371,7 +405,7 @@ fn write_content(env: &Env, content: &Content, subs: &Subs, buf: &mut String, pa Symbol::NUM_INT => { let content = get_single_arg(subs, args); - write_integer(env, content, subs, buf, parens, write_parens) + write_integer(env, ctx, content, subs, buf, parens, write_parens) } Symbol::NUM_FLOAT => { @@ -390,7 +424,7 @@ fn write_content(env: &Env, content: &Content, subs: &Subs, buf: &mut String, pa Alias(Symbol::NUM_DECIMAL, _, _, _) => buf.push_str("Dec"), _ => write_parens!(write_parens, buf, { buf.push_str("Float "); - write_content(env, content, subs, buf, parens); + write_content(env, ctx, content, subs, buf, parens); }), } } @@ -403,6 +437,7 @@ fn write_content(env: &Env, content: &Content, subs: &Subs, buf: &mut String, pa buf.push(' '); write_content( env, + ctx, subs.get_content_without_compacting(var), subs, buf, @@ -414,7 +449,7 @@ fn write_content(env: &Env, content: &Content, subs: &Subs, buf: &mut String, pa if false { buf.push_str("[[ but really "); let content = subs.get_content_without_compacting(*_actual); - write_content(env, content, subs, buf, parens); + write_content(env, ctx, content, subs, buf, parens); buf.push_str("]]"); } }), @@ -422,6 +457,7 @@ fn write_content(env: &Env, content: &Content, subs: &Subs, buf: &mut String, pa } RangedNumber(typ, _range_vars) => write_content( env, + ctx, subs.get_content_without_compacting(*typ), subs, buf, @@ -431,10 +467,11 @@ fn write_content(env: &Env, content: &Content, subs: &Subs, buf: &mut String, pa } } -fn write_integer( +fn write_integer<'a>( env: &Env, + ctx: &mut Context<'a>, content: &Content, - subs: &Subs, + subs: &'a Subs, buf: &mut String, parens: Parens, write_parens: bool, @@ -454,7 +491,7 @@ fn write_integer( )* actual => { buf.push_str("Int "); - write_content(env, actual, subs, buf, parens); + write_content(env, ctx, actual, subs, buf, parens); } } ) @@ -497,6 +534,7 @@ impl<'a> ExtContent<'a> { fn write_ext_content<'a>( env: &Env, + ctx: &mut Context<'a>, subs: &'a Subs, buf: &mut String, ext_content: ExtContent<'a>, @@ -508,12 +546,13 @@ fn write_ext_content<'a>( // // e.g. the "*" at the end of `{ x: I64 }*` // or the "r" at the end of `{ x: I64 }r` - write_content(env, content, subs, buf, parens) + write_content(env, ctx, content, subs, buf, parens) } } fn write_sorted_tags2<'a>( env: &Env, + ctx: &mut Context<'a>, subs: &'a Subs, buf: &mut String, tags: &UnionTags, @@ -546,6 +585,7 @@ fn write_sorted_tags2<'a>( buf.push(' '); write_content( env, + ctx, subs.get_content_without_compacting(*var), subs, buf, @@ -559,6 +599,7 @@ fn write_sorted_tags2<'a>( fn write_sorted_tags<'a>( env: &Env, + ctx: &mut Context<'a>, subs: &'a Subs, buf: &mut String, tags: &MutMap>, @@ -603,6 +644,7 @@ fn write_sorted_tags<'a>( buf.push(' '); write_content( env, + ctx, subs.get_content_without_compacting(*var), subs, buf, @@ -614,18 +656,37 @@ fn write_sorted_tags<'a>( ExtContent::from_var(subs, ext_var) } -fn write_flat_type(env: &Env, flat_type: &FlatType, subs: &Subs, buf: &mut String, parens: Parens) { +fn write_flat_type<'a>( + env: &Env, + ctx: &mut Context<'a>, + flat_type: &FlatType, + subs: &'a Subs, + buf: &mut String, + parens: Parens, +) { use crate::subs::FlatType::*; match flat_type { - Apply(symbol, args) => { - write_apply(env, *symbol, subs.get_subs_slice(*args), subs, buf, parens) - } + Apply(symbol, args) => write_apply( + env, + ctx, + *symbol, + subs.get_subs_slice(*args), + subs, + buf, + parens, + ), EmptyRecord => buf.push_str(EMPTY_RECORD), EmptyTagUnion => buf.push_str(EMPTY_TAG_UNION), - Func(args, _closure, ret) => { - write_fn(env, subs.get_subs_slice(*args), *ret, subs, buf, parens) - } + Func(args, _closure, ret) => write_fn( + env, + ctx, + subs.get_subs_slice(*args), + *ret, + subs, + buf, + parens, + ), Record(fields, ext_var) => { use crate::types::{gather_fields, RecordStructure}; @@ -664,6 +725,7 @@ fn write_flat_type(env: &Env, flat_type: &FlatType, subs: &Subs, buf: &mut Strin write_content( env, + ctx, subs.get_content_without_compacting(var), subs, buf, @@ -684,18 +746,18 @@ fn write_flat_type(env: &Env, flat_type: &FlatType, subs: &Subs, buf: &mut Strin // // e.g. the "*" at the end of `{ x: I64 }*` // or the "r" at the end of `{ x: I64 }r` - write_content(env, content, subs, buf, parens) + write_content(env, ctx, content, subs, buf, parens) } } } TagUnion(tags, ext_var) => { buf.push_str("[ "); - let ext_content = write_sorted_tags2(env, subs, buf, tags, *ext_var); + let ext_content = write_sorted_tags2(env, ctx, subs, buf, tags, *ext_var); buf.push_str(" ]"); - write_ext_content(env, subs, buf, ext_content, parens) + write_ext_content(env, ctx, subs, buf, ext_content, parens) } FunctionOrTagUnion(tag_name, _, ext_var) => { @@ -703,25 +765,26 @@ fn write_flat_type(env: &Env, flat_type: &FlatType, subs: &Subs, buf: &mut Strin let mut tags: MutMap = MutMap::default(); tags.insert(subs[*tag_name].clone(), vec![]); - let ext_content = write_sorted_tags(env, subs, buf, &tags, *ext_var); + let ext_content = write_sorted_tags(env, ctx, subs, buf, &tags, *ext_var); buf.push_str(" ]"); - write_ext_content(env, subs, buf, ext_content, parens) + write_ext_content(env, ctx, subs, buf, ext_content, parens) } RecursiveTagUnion(rec_var, tags, ext_var) => { buf.push_str("[ "); - let ext_content = write_sorted_tags2(env, subs, buf, tags, *ext_var); + let ext_content = write_sorted_tags2(env, ctx, subs, buf, tags, *ext_var); buf.push_str(" ]"); - write_ext_content(env, subs, buf, ext_content, parens); + write_ext_content(env, ctx, subs, buf, ext_content, parens); buf.push_str(" as "); write_content( env, + ctx, subs.get_content_without_compacting(*rec_var), subs, buf, @@ -777,11 +840,12 @@ pub fn chase_ext_tag_union<'a>( } } -fn write_apply( +fn write_apply<'a>( env: &Env, + ctx: &mut Context<'a>, symbol: Symbol, args: &[Variable], - subs: &Subs, + subs: &'a Subs, buf: &mut String, parens: Parens, ) { @@ -805,7 +869,7 @@ fn write_apply( buf.push('('); } - write_content(env, content, subs, &mut arg_param, Parens::InTypeParam); + write_content(env, ctx, content, subs, &mut arg_param, Parens::InTypeParam); buf.push_str("Num "); buf.push_str(&arg_param); @@ -838,6 +902,7 @@ fn write_apply( buf.push(' '); write_content( env, + ctx, subs.get_content_without_compacting(*arg), subs, buf, @@ -852,11 +917,12 @@ fn write_apply( } } -fn write_fn( +fn write_fn<'a>( env: &Env, + ctx: &mut Context<'a>, args: &[Variable], ret: Variable, - subs: &Subs, + subs: &'a Subs, buf: &mut String, parens: Parens, ) { @@ -876,6 +942,7 @@ fn write_fn( write_content( env, + ctx, subs.get_content_without_compacting(*arg), subs, buf, @@ -886,6 +953,7 @@ fn write_fn( buf.push_str(" -> "); write_content( env, + ctx, subs.get_content_without_compacting(ret), subs, buf, diff --git a/compiler/types/src/subs.rs b/compiler/types/src/subs.rs index 36f0ac811d..827bf03fb9 100644 --- a/compiler/types/src/subs.rs +++ b/compiler/types/src/subs.rs @@ -1,5 +1,7 @@ #![deny(unsafe_op_in_unsafe_fn)] -use crate::types::{name_type_var, AliasKind, ErrorType, Problem, RecordField, TypeExt}; +use crate::types::{ + name_type_var, AliasKind, ErrorType, Problem, RecordField, RecordFieldsError, TypeExt, +}; use roc_collections::all::{ImMap, ImSet, MutSet, SendMap}; use roc_module::ident::{Lowercase, TagName, Uppercase}; use roc_module::symbol::Symbol; @@ -754,7 +756,9 @@ impl<'a> fmt::Debug for SubsFmtContent<'a> { fn subs_fmt_content(this: &Content, subs: &Subs, f: &mut fmt::Formatter) -> fmt::Result { match this { Content::FlexVar(name) => write!(f, "Flex({:?})", name), + Content::FlexAbleVar(name, symbol) => write!(f, "FlexAble({:?}, {:?})", name, symbol), Content::RigidVar(name) => write!(f, "Rigid({:?})", name), + Content::RigidAbleVar(name, symbol) => write!(f, "RigidAble({:?}, {:?})", name, symbol), Content::RecursionVar { structure, opt_name, @@ -794,7 +798,19 @@ fn subs_fmt_flat_type(this: &FlatType, subs: &Subs, f: &mut fmt::Formatter) -> f } FlatType::Func(arguments, lambda_set, result) => { let slice = subs.get_subs_slice(*arguments); - write!(f, "Func({:?}, {:?}, {:?})", slice, lambda_set, result) + write!(f, "Func([")?; + for var in slice { + let content = subs.get_content_without_compacting(*var); + write!(f, "<{:?}>{:?},", *var, SubsFmtContent(content, subs))?; + } + let result_content = subs.get_content_without_compacting(*result); + write!( + f, + "], {:?}, <{:?}>{:?})", + lambda_set, + *result, + SubsFmtContent(result_content, subs) + ) } FlatType::Record(fields, ext) => { write!(f, "{{ ")?; @@ -1737,6 +1753,14 @@ impl Subs { self.set(var, desc); } + pub fn rigid_able_var(&mut self, var: Variable, name: Lowercase, ability: Symbol) { + let name_index = SubsIndex::push_new(&mut self.field_names, name); + let content = Content::RigidAbleVar(name_index, ability); + let desc = Descriptor::from(content); + + self.set(var, desc); + } + /// Unions two keys without the possibility of failure. pub fn union(&mut self, left: Variable, right: Variable, desc: Descriptor) { let l_root = self.utable.inlined_get_root_key(left); @@ -2118,6 +2142,12 @@ pub enum Content { FlexVar(Option>), /// name given in a user-written annotation RigidVar(SubsIndex), + /// Like a [Self::FlexVar], but is also bound to an ability. + /// This can only happen when unified with a [Self::RigidAbleVar]. + FlexAbleVar(Option>, Symbol), + /// Like a [Self::RigidVar], but is also bound to an ability. + /// For example, "a has Hash". + RigidAbleVar(SubsIndex, Symbol), /// name given to a recursion variable RecursionVar { structure: Variable, @@ -2713,11 +2743,11 @@ impl RecordFields { &'a self, subs: &'a Subs, ext: Variable, - ) -> impl Iterator)> + 'a { - let (it, _) = crate::types::gather_fields_unsorted_iter(subs, *self, ext) - .expect("Something weird ended up in a record type"); + ) -> Result)> + 'a, RecordFieldsError> + { + let (it, _) = crate::types::gather_fields_unsorted_iter(subs, *self, ext)?; - it + Ok(it) } #[inline(always)] @@ -2838,7 +2868,12 @@ fn occurs( Err((root_var, vec![])) } else { match subs.get_content_without_compacting(root_var) { - FlexVar(_) | RigidVar(_) | RecursionVar { .. } | Error => Ok(()), + FlexVar(_) + | RigidVar(_) + | FlexAbleVar(_, _) + | RigidAbleVar(_, _) + | RecursionVar { .. } + | Error => Ok(()), Structure(flat_type) => { let mut new_seen = seen.to_owned(); @@ -2966,7 +3001,12 @@ fn explicit_substitute( to } else { match subs.get(in_var).content { - FlexVar(_) | RigidVar(_) | RecursionVar { .. } | Error => in_var, + FlexVar(_) + | RigidVar(_) + | FlexAbleVar(_, _) + | RigidAbleVar(_, _) + | RecursionVar { .. } + | Error => in_var, Structure(flat_type) => { match flat_type { @@ -3134,9 +3174,9 @@ fn get_var_names( subs.set_mark(var, Mark::GET_VAR_NAMES); match desc.content { - Error | FlexVar(None) => taken_names, + Error | FlexVar(None) | FlexAbleVar(None, _) => taken_names, - FlexVar(Some(name_index)) => add_name( + FlexVar(Some(name_index)) | FlexAbleVar(Some(name_index), _) => add_name( subs, 0, name_index, @@ -3163,7 +3203,9 @@ fn get_var_names( None => taken_names, }, - RigidVar(name_index) => add_name(subs, 0, name_index, var, RigidVar, taken_names), + RigidVar(name_index) | RigidAbleVar(name_index, _) => { + add_name(subs, 0, name_index, var, RigidVar, taken_names) + } Alias(_, args, _, _) => args.into_iter().fold(taken_names, |answer, arg_var| { get_var_names(subs, subs[arg_var], answer) @@ -3329,11 +3371,6 @@ fn content_to_err_type( match content { Structure(flat_type) => flat_type_to_err_type(subs, state, flat_type), - FlexVar(Some(name_index)) => { - let name = subs.field_names[name_index.index as usize].clone(); - ErrorType::FlexVar(name) - } - FlexVar(opt_name) => { let name = match opt_name { Some(name_index) => subs.field_names[name_index.index as usize].clone(), @@ -3356,6 +3393,28 @@ fn content_to_err_type( ErrorType::RigidVar(name) } + FlexAbleVar(opt_name, ability) => { + let name = match opt_name { + Some(name_index) => subs.field_names[name_index.index as usize].clone(), + None => { + // set the name so when this variable occurs elsewhere in the type it gets the same name + let name = get_fresh_var_name(state); + let name_index = SubsIndex::push_new(&mut subs.field_names, name.clone()); + + subs.set_content(var, FlexVar(Some(name_index))); + + name + } + }; + + ErrorType::FlexAbleVar(name, ability) + } + + RigidAbleVar(name_index, ability) => { + let name = subs.field_names[name_index.index as usize].clone(); + ErrorType::RigidAbleVar(name, ability) + } + RecursionVar { opt_name, .. } => { let name = match opt_name { Some(name_index) => subs.field_names[name_index.index as usize].clone(), @@ -3628,7 +3687,7 @@ fn restore_help(subs: &mut Subs, initial: Variable) { use FlatType::*; match &desc.content { - FlexVar(_) | RigidVar(_) | Error => (), + FlexVar(_) | RigidVar(_) | FlexAbleVar(_, _) | RigidAbleVar(_, _) | Error => (), RecursionVar { structure, .. } => { stack.push(*structure); @@ -3857,6 +3916,8 @@ impl StorageSubs { match content { FlexVar(opt_name) => FlexVar(*opt_name), RigidVar(name) => RigidVar(*name), + FlexAbleVar(opt_name, ability) => FlexAbleVar(*opt_name, *ability), + RigidAbleVar(name, ability) => RigidAbleVar(*name, *ability), RecursionVar { structure, opt_name, @@ -4253,6 +4314,29 @@ fn deep_copy_var_to_help(env: &mut DeepCopyVarToEnv<'_>, var: Variable) -> Varia copy } + FlexAbleVar(opt_name_index, ability) => { + let new_name_index = opt_name_index.map(|name_index| { + let name = env.source.field_names[name_index.index as usize].clone(); + SubsIndex::push_new(&mut env.target.field_names, name) + }); + + let content = FlexAbleVar(new_name_index, ability); + env.target.set_content(copy, content); + + copy + } + + RigidAbleVar(name_index, ability) => { + let name = env.source.field_names[name_index.index as usize].clone(); + let new_name_index = SubsIndex::push_new(&mut env.target.field_names, name); + env.target.set( + copy, + make_descriptor(FlexAbleVar(Some(new_name_index), ability)), + ); + + copy + } + Alias(symbol, arguments, real_type_var, kind) => { let new_variables = SubsSlice::reserve_into_subs(env.target, arguments.all_variables_len as _); @@ -4312,6 +4396,8 @@ pub struct CopiedImport { pub variable: Variable, pub flex: Vec, pub rigid: Vec, + pub flex_able: Vec, + pub rigid_able: Vec, pub translations: Vec<(Variable, Variable)>, pub registered: Vec, } @@ -4322,6 +4408,8 @@ struct CopyImportEnv<'a> { target: &'a mut Subs, flex: Vec, rigid: Vec, + flex_able: Vec, + rigid_able: Vec, translations: Vec<(Variable, Variable)>, registered: Vec, } @@ -4343,6 +4431,8 @@ pub fn copy_import_to( target, flex: Vec::new(), rigid: Vec::new(), + flex_able: Vec::new(), + rigid_able: Vec::new(), translations: Vec::new(), registered: Vec::new(), }; @@ -4354,6 +4444,8 @@ pub fn copy_import_to( source, flex, rigid, + flex_able, + rigid_able, translations, registered, target: _, @@ -4376,6 +4468,8 @@ pub fn copy_import_to( variable: copy, flex, rigid, + flex_able, + rigid_able, translations, registered, } @@ -4393,7 +4487,10 @@ pub fn copy_import_to( /// standard variables fn is_registered(content: &Content) -> bool { match content { - Content::FlexVar(_) | Content::RigidVar(_) => false, + Content::FlexVar(_) + | Content::RigidVar(_) + | Content::FlexAbleVar(..) + | Content::RigidAbleVar(..) => false, Content::Structure(FlatType::EmptyRecord | FlatType::EmptyTagUnion) => false, Content::Structure(_) @@ -4631,6 +4728,20 @@ fn copy_import_to_help(env: &mut CopyImportEnv<'_>, max_rank: Rank, var: Variabl copy } + FlexAbleVar(opt_name_index, ability) => { + if let Some(name_index) = opt_name_index { + let name = env.source.field_names[name_index.index as usize].clone(); + let new_name_index = SubsIndex::push_new(&mut env.target.field_names, name); + + let content = FlexAbleVar(Some(new_name_index), ability); + env.target.set_content(copy, content); + } + + env.flex_able.push(copy); + + copy + } + Error => { // Open question: should this return Error, or a Flex var? @@ -4653,6 +4764,20 @@ fn copy_import_to_help(env: &mut CopyImportEnv<'_>, max_rank: Rank, var: Variabl copy } + RigidAbleVar(name_index, ability) => { + let name = env.source.field_names[name_index.index as usize].clone(); + let new_name_index = SubsIndex::push_new(&mut env.target.field_names, name); + + env.target + .set(copy, make_descriptor(RigidAbleVar(new_name_index, ability))); + + env.rigid_able.push(copy); + + env.translations.push((var, copy)); + + copy + } + RecursionVar { opt_name, structure, @@ -4746,7 +4871,7 @@ where use Content::*; use FlatType::*; match content { - FlexVar(_) | RigidVar(_) => {} + FlexVar(_) | RigidVar(_) | FlexAbleVar(_, _) | RigidAbleVar(_, _) => {} RecursionVar { structure, opt_name: _, diff --git a/compiler/types/src/types.rs b/compiler/types/src/types.rs index eca1b3bc66..8f5f68636e 100644 --- a/compiler/types/src/types.rs +++ b/compiler/types/src/types.rs @@ -1744,6 +1744,15 @@ pub enum Reason { RecordUpdateKeys(Symbol, SendMap), RecordDefaultField(Lowercase), NumericLiteralSuffix, + InvalidAbilityMemberSpecialization { + member_name: Symbol, + def_region: Region, + unimplemented_abilities: DoesNotImplementAbility, + }, + GeneralizedAbilityMemberSpecialization { + member_name: Symbol, + def_region: Region, + }, } #[derive(PartialEq, Debug, Clone)] @@ -1783,6 +1792,8 @@ pub enum Category { Accessor(Lowercase), Access(Lowercase), DefaultValue(Lowercase), // for setting optional fields + + AbilityMemberSpecialization(Symbol), } #[derive(Debug, Clone, PartialEq, Eq)] @@ -1867,14 +1878,19 @@ pub enum Mismatch { InconsistentWhenBranches, CanonicalizationProblem, TypeNotInRange, + DoesNotImplementAbiity(Variable, Symbol), } +pub type DoesNotImplementAbility = Vec<(ErrorType, Symbol)>; + #[derive(PartialEq, Eq, Clone, Hash)] pub enum ErrorType { Infinite, Type(Symbol, Vec), FlexVar(Lowercase), RigidVar(Lowercase), + FlexAbleVar(Lowercase, Symbol), + RigidAbleVar(Lowercase, Symbol), Record(SendMap>, TypeExt), TagUnion(SendMap>, TypeExt), RecursiveTagUnion(Box, SendMap>, TypeExt), @@ -1905,10 +1921,7 @@ impl ErrorType { match self { Infinite => {} Type(_, ts) => ts.iter().for_each(|t| t.add_names(taken)), - FlexVar(v) => { - taken.insert(v.clone()); - } - RigidVar(v) => { + FlexVar(v) | RigidVar(v) | FlexAbleVar(v, _) | RigidAbleVar(v, _) => { taken.insert(v.clone()); } Record(fields, ext) => { @@ -2087,8 +2100,18 @@ fn write_debug_error_type_help(error_type: ErrorType, buf: &mut String, parens: match error_type { Infinite => buf.push('∞'), Error => buf.push('?'), - FlexVar(name) => buf.push_str(name.as_str()), - RigidVar(name) => buf.push_str(name.as_str()), + FlexVar(name) | RigidVar(name) => buf.push_str(name.as_str()), + FlexAbleVar(name, symbol) | RigidAbleVar(name, symbol) => { + let write_parens = parens == Parens::InTypeParam; + if write_parens { + buf.push('('); + } + buf.push_str(name.as_str()); + buf.push_str(&format!(" has {:?}", symbol)); + if write_parens { + buf.push(')'); + } + } Type(symbol, arguments) => { let write_parens = parens == Parens::InTypeParam && !arguments.is_empty(); diff --git a/compiler/unify/Cargo.toml b/compiler/unify/Cargo.toml index e15626603c..d8622ed928 100644 --- a/compiler/unify/Cargo.toml +++ b/compiler/unify/Cargo.toml @@ -11,6 +11,9 @@ bitflags = "1.3.2" [dependencies.roc_collections] path = "../collections" +[dependencies.roc_error_macros] +path = "../../error_macros" + [dependencies.roc_module] path = "../module" diff --git a/compiler/unify/src/unify.rs b/compiler/unify/src/unify.rs index 82a96045b5..a586cdaed7 100644 --- a/compiler/unify/src/unify.rs +++ b/compiler/unify/src/unify.rs @@ -1,4 +1,5 @@ use bitflags::bitflags; +use roc_error_macros::todo_abilities; use roc_module::ident::{Lowercase, TagName}; use roc_module::symbol::Symbol; use roc_types::subs::Content::{self, *}; @@ -6,7 +7,7 @@ use roc_types::subs::{ AliasVariables, Descriptor, ErrorTypeContext, FlatType, GetSubsSlice, Mark, OptVariable, RecordFields, Subs, SubsIndex, SubsSlice, UnionTags, Variable, VariableSubsSlice, }; -use roc_types::types::{AliasKind, ErrorType, Mismatch, RecordField}; +use roc_types::types::{AliasKind, DoesNotImplementAbility, ErrorType, Mismatch, RecordField}; macro_rules! mismatch { () => {{ @@ -19,7 +20,10 @@ macro_rules! mismatch { ); } - vec![Mismatch::TypeMismatch] + Outcome { + mismatches: vec![Mismatch::TypeMismatch], + ..Outcome::default() + } }}; ($msg:expr) => {{ if cfg!(debug_assertions) && std::env::var("ROC_PRINT_MISMATCHES").is_ok() { @@ -34,7 +38,10 @@ macro_rules! mismatch { } - vec![Mismatch::TypeMismatch] + Outcome { + mismatches: vec![Mismatch::TypeMismatch], + ..Outcome::default() + } }}; ($msg:expr,) => {{ mismatch!($msg) @@ -51,8 +58,28 @@ macro_rules! mismatch { println!(""); } - vec![Mismatch::TypeMismatch] + Outcome { + mismatches: vec![Mismatch::TypeMismatch], + ..Outcome::default() + } }}; + (%not_able, $var:expr, $ability:expr, $msg:expr, $($arg:tt)*) => {{ + if cfg!(debug_assertions) && std::env::var("ROC_PRINT_MISMATCHES").is_ok() { + println!( + "Mismatch in {} Line {} Column {}", + file!(), + line!(), + column!() + ); + println!($msg, $($arg)*); + println!(""); + } + + Outcome { + mismatches: vec![Mismatch::TypeMismatch, Mismatch::DoesNotImplementAbiity($var, $ability)], + ..Outcome::default() + } + }} } type Pool = Vec; @@ -105,20 +132,52 @@ pub struct Context { #[derive(Debug)] pub enum Unified { - Success(Pool), - Failure(Pool, ErrorType, ErrorType), + Success { + vars: Pool, + must_implement_ability: Vec, + }, + Failure(Pool, ErrorType, ErrorType, DoesNotImplementAbility), BadType(Pool, roc_types::types::Problem), } -type Outcome = Vec; +/// Specifies that `type` must implement the ability `ability`. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct MustImplementAbility { + // This only points to opaque type names currently. + // TODO(abilities) support structural types in general + pub typ: Symbol, + pub ability: Symbol, +} + +#[derive(Debug, Default)] +pub struct Outcome { + mismatches: Vec, + /// We defer these checks until the end of a solving phase. + /// NOTE: this vector is almost always empty! + must_implement_ability: Vec, +} + +impl Outcome { + fn union(&mut self, other: Self) { + self.mismatches.extend(other.mismatches); + self.must_implement_ability + .extend(other.must_implement_ability); + } +} #[inline(always)] pub fn unify(subs: &mut Subs, var1: Variable, var2: Variable, mode: Mode) -> Unified { let mut vars = Vec::new(); - let mismatches = unify_pool(subs, &mut vars, var1, var2, mode); + let Outcome { + mismatches, + must_implement_ability, + } = unify_pool(subs, &mut vars, var1, var2, mode); if mismatches.is_empty() { - Unified::Success(vars) + Unified::Success { + vars, + must_implement_ability, + } } else { let error_context = if mismatches.contains(&Mismatch::TypeNotInRange) { ErrorTypeContext::ExpandRanges @@ -136,7 +195,19 @@ pub fn unify(subs: &mut Subs, var1: Variable, var2: Variable, mode: Mode) -> Uni if !problems.is_empty() { Unified::BadType(vars, problems.remove(0)) } else { - Unified::Failure(vars, type1, type2) + let do_not_implement_ability = mismatches + .into_iter() + .filter_map(|mismatch| match mismatch { + Mismatch::DoesNotImplementAbiity(var, ab) => { + let (err_type, _new_problems) = + subs.var_to_error_type_contextual(var, error_context); + Some((err_type, ab)) + } + _ => None, + }) + .collect(); + + Unified::Failure(vars, type1, type2, do_not_implement_ability) } } } @@ -150,7 +221,7 @@ pub fn unify_pool( mode: Mode, ) -> Outcome { if subs.equivalent(var1, var2) { - Vec::new() + Outcome::default() } else { let ctx = Context { first: var1, @@ -191,7 +262,14 @@ fn unify_context(subs: &mut Subs, pool: &mut Pool, ctx: Context) -> Outcome { ); } match &ctx.first_desc.content { - FlexVar(opt_name) => unify_flex(subs, &ctx, opt_name, &ctx.second_desc.content), + FlexVar(opt_name) => unify_flex(subs, &ctx, opt_name, None, &ctx.second_desc.content), + FlexAbleVar(opt_name, ability) => unify_flex( + subs, + &ctx, + opt_name, + Some(*ability), + &ctx.second_desc.content, + ), RecursionVar { opt_name, structure, @@ -203,7 +281,10 @@ fn unify_context(subs: &mut Subs, pool: &mut Pool, ctx: Context) -> Outcome { *structure, &ctx.second_desc.content, ), - RigidVar(name) => unify_rigid(subs, &ctx, name, &ctx.second_desc.content), + RigidVar(name) => unify_rigid(subs, &ctx, name, None, &ctx.second_desc.content), + RigidAbleVar(name, ability) => { + unify_rigid(subs, &ctx, name, Some(*ability), &ctx.second_desc.content) + } Structure(flat_type) => { unify_structure(subs, pool, &ctx, flat_type, &ctx.second_desc.content) } @@ -238,7 +319,7 @@ fn unify_ranged_number( } &RangedNumber(other_real_var, other_range_vars) => { let outcome = unify_pool(subs, pool, real_var, other_real_var, ctx.mode); - if outcome.is_empty() { + if outcome.mismatches.is_empty() { check_valid_range(subs, pool, ctx.first, other_range_vars, ctx.mode) } else { outcome @@ -246,9 +327,12 @@ fn unify_ranged_number( // TODO: We should probably check that "range_vars" and "other_range_vars" intersect } Error => merge(subs, ctx, Error), + FlexAbleVar(..) | RigidAbleVar(..) => { + todo_abilities!("I don't think this can be reached yet") + } }; - if !outcome.is_empty() { + if !outcome.mismatches.is_empty() { return outcome; } @@ -269,11 +353,11 @@ fn check_valid_range( let snapshot = subs.snapshot(); let old_pool = pool.clone(); let outcome = unify_pool(subs, pool, var, possible_var, mode | Mode::RIGID_AS_FLEX); - if outcome.is_empty() { + if outcome.mismatches.is_empty() { // Okay, we matched some type in the range. subs.rollback_to(snapshot); *pool = old_pool; - return vec![]; + return Outcome::default(); } else if it.peek().is_some() { // We failed to match something in the range, but there are still things we can try. subs.rollback_to(snapshot); @@ -283,7 +367,10 @@ fn check_valid_range( } } - return vec![Mismatch::TypeNotInRange]; + Outcome { + mismatches: vec![Mismatch::TypeNotInRange], + ..Outcome::default() + } } #[inline(always)] @@ -310,13 +397,19 @@ fn unify_alias( unify_pool(subs, pool, real_var, *structure, ctx.mode) } RigidVar(_) => unify_pool(subs, pool, real_var, ctx.second, ctx.mode), + RigidAbleVar (_, ability) | FlexAbleVar(_, ability) if kind == AliasKind::Opaque && args.is_empty() => { + // Opaque type wins + let mut outcome = merge(subs, ctx, Alias(symbol, args, real_var, kind)); + outcome.must_implement_ability.push(MustImplementAbility { typ: symbol, ability: *ability }); + outcome + } Alias(other_symbol, other_args, other_real_var, _) // Opaques types are only equal if the opaque symbols are equal! if !either_is_opaque || symbol == *other_symbol => { if symbol == *other_symbol { if args.len() == other_args.len() { - let mut problems = Vec::new(); + let mut outcome = Outcome::default(); let it = args .all_variables() .into_iter() @@ -327,23 +420,23 @@ fn unify_alias( for (l, r) in it { let l_var = subs[l]; let r_var = subs[r]; - problems.extend(unify_pool(subs, pool, l_var, r_var, ctx.mode)); + outcome.union(unify_pool(subs, pool, l_var, r_var, ctx.mode)); } - if problems.is_empty() { - problems.extend(merge(subs, ctx, *other_content)); + if outcome.mismatches.is_empty() { + outcome.union(merge(subs, ctx, *other_content)); } let args_unification_had_changes = !subs.vars_since_snapshot(&args_unification_snapshot).is_empty(); subs.commit_snapshot(args_unification_snapshot); - if !args.is_empty() && args_unification_had_changes && problems.is_empty() { + if !args.is_empty() && args_unification_had_changes && outcome.mismatches.is_empty() { // We need to unify the real vars because unification of type variables // may have made them larger, which then needs to be reflected in the `real_var`. - problems.extend(unify_pool(subs, pool, real_var, *other_real_var, ctx.mode)); + outcome.union(unify_pool(subs, pool, real_var, *other_real_var, ctx.mode)); } - problems + outcome } else { dbg!(args.len(), other_args.len()); mismatch!("{:?}", symbol) @@ -355,7 +448,7 @@ fn unify_alias( Structure(_) if !either_is_opaque => unify_pool(subs, pool, real_var, ctx.second, ctx.mode), RangedNumber(other_real_var, other_range_vars) if !either_is_opaque => { let outcome = unify_pool(subs, pool, real_var, *other_real_var, ctx.mode); - if outcome.is_empty() { + if outcome.mismatches.is_empty() { check_valid_range(subs, pool, real_var, *other_range_vars, ctx.mode) } else { outcome @@ -448,13 +541,31 @@ fn unify_structure( }, RangedNumber(other_real_var, other_range_vars) => { let outcome = unify_pool(subs, pool, ctx.first, *other_real_var, ctx.mode); - if outcome.is_empty() { + if outcome.mismatches.is_empty() { check_valid_range(subs, pool, ctx.first, *other_range_vars, ctx.mode) } else { outcome } } Error => merge(subs, ctx, Error), + + FlexAbleVar(_, ability) => { + // TODO(abilities) support structural types in ability bounds + mismatch!( + %not_able, ctx.first, *ability, + "trying to unify {:?} with FlexAble {:?}", + &flat_type, + &other + ) + } + RigidAbleVar(_, ability) => { + mismatch!( + %not_able, ctx.first, *ability, + "trying to unify {:?} with RigidAble {:?}", + &flat_type, + &other + ) + } } } @@ -474,29 +585,29 @@ fn unify_record( if separate.only_in_1.is_empty() { if separate.only_in_2.is_empty() { // these variable will be the empty record, but we must still unify them - let ext_problems = unify_pool(subs, pool, ext1, ext2, ctx.mode); + let ext_outcome = unify_pool(subs, pool, ext1, ext2, ctx.mode); - if !ext_problems.is_empty() { - return ext_problems; + if !ext_outcome.mismatches.is_empty() { + return ext_outcome; } - let mut field_problems = + let mut field_outcome = unify_shared_fields(subs, pool, ctx, shared_fields, OtherFields::None, ext1); - field_problems.extend(ext_problems); + field_outcome.union(ext_outcome); - field_problems + field_outcome } else { let only_in_2 = RecordFields::insert_into_subs(subs, separate.only_in_2); let flat_type = FlatType::Record(only_in_2, ext2); let sub_record = fresh(subs, pool, ctx, Structure(flat_type)); - let ext_problems = unify_pool(subs, pool, ext1, sub_record, ctx.mode); + let ext_outcome = unify_pool(subs, pool, ext1, sub_record, ctx.mode); - if !ext_problems.is_empty() { - return ext_problems; + if !ext_outcome.mismatches.is_empty() { + return ext_outcome; } - let mut field_problems = unify_shared_fields( + let mut field_outcome = unify_shared_fields( subs, pool, ctx, @@ -505,21 +616,21 @@ fn unify_record( sub_record, ); - field_problems.extend(ext_problems); + field_outcome.union(ext_outcome); - field_problems + field_outcome } } else if separate.only_in_2.is_empty() { let only_in_1 = RecordFields::insert_into_subs(subs, separate.only_in_1); let flat_type = FlatType::Record(only_in_1, ext1); let sub_record = fresh(subs, pool, ctx, Structure(flat_type)); - let ext_problems = unify_pool(subs, pool, sub_record, ext2, ctx.mode); + let ext_outcome = unify_pool(subs, pool, sub_record, ext2, ctx.mode); - if !ext_problems.is_empty() { - return ext_problems; + if !ext_outcome.mismatches.is_empty() { + return ext_outcome; } - let mut field_problems = unify_shared_fields( + let mut field_outcome = unify_shared_fields( subs, pool, ctx, @@ -528,9 +639,9 @@ fn unify_record( sub_record, ); - field_problems.extend(ext_problems); + field_outcome.union(ext_outcome); - field_problems + field_outcome } else { let only_in_1 = RecordFields::insert_into_subs(subs, separate.only_in_1); let only_in_2 = RecordFields::insert_into_subs(subs, separate.only_in_2); @@ -544,24 +655,26 @@ fn unify_record( let sub1 = fresh(subs, pool, ctx, Structure(flat_type1)); let sub2 = fresh(subs, pool, ctx, Structure(flat_type2)); - let rec1_problems = unify_pool(subs, pool, ext1, sub2, ctx.mode); - if !rec1_problems.is_empty() { - return rec1_problems; + let rec1_outcome = unify_pool(subs, pool, ext1, sub2, ctx.mode); + if !rec1_outcome.mismatches.is_empty() { + return rec1_outcome; } - let rec2_problems = unify_pool(subs, pool, sub1, ext2, ctx.mode); - if !rec2_problems.is_empty() { - return rec2_problems; + let rec2_outcome = unify_pool(subs, pool, sub1, ext2, ctx.mode); + if !rec2_outcome.mismatches.is_empty() { + return rec2_outcome; } - let mut field_problems = + let mut field_outcome = unify_shared_fields(subs, pool, ctx, shared_fields, other_fields, ext); - field_problems.reserve(rec1_problems.len() + rec2_problems.len()); - field_problems.extend(rec1_problems); - field_problems.extend(rec2_problems); + field_outcome + .mismatches + .reserve(rec1_outcome.mismatches.len() + rec2_outcome.mismatches.len()); + field_outcome.union(rec1_outcome); + field_outcome.union(rec2_outcome); - field_problems + field_outcome } } @@ -584,7 +697,7 @@ fn unify_shared_fields( let num_shared_fields = shared_fields.len(); for (name, (actual, expected)) in shared_fields { - let local_problems = unify_pool( + let local_outcome = unify_pool( subs, pool, actual.into_inner(), @@ -592,7 +705,7 @@ fn unify_shared_fields( ctx.mode, ); - if local_problems.is_empty() { + if local_outcome.mismatches.is_empty() { use RecordField::*; // Unification of optional fields @@ -856,18 +969,18 @@ fn unify_tag_union_new( if separate.only_in_1.is_empty() { if separate.only_in_2.is_empty() { - let ext_problems = if ctx.mode.is_eq() { + let ext_outcome = if ctx.mode.is_eq() { unify_pool(subs, pool, ext1, ext2, ctx.mode) } else { // In a presence context, we don't care about ext2 being equal to ext1 - vec![] + Outcome::default() }; - if !ext_problems.is_empty() { - return ext_problems; + if !ext_outcome.mismatches.is_empty() { + return ext_outcome; } - let mut tag_problems = unify_shared_tags_new( + let mut shared_tags_outcome = unify_shared_tags_new( subs, pool, ctx, @@ -877,20 +990,20 @@ fn unify_tag_union_new( recursion_var, ); - tag_problems.extend(ext_problems); + shared_tags_outcome.union(ext_outcome); - tag_problems + shared_tags_outcome } else { let unique_tags2 = UnionTags::insert_slices_into_subs(subs, separate.only_in_2); let flat_type = FlatType::TagUnion(unique_tags2, ext2); let sub_record = fresh(subs, pool, ctx, Structure(flat_type)); - let ext_problems = unify_pool(subs, pool, ext1, sub_record, ctx.mode); + let ext_outcome = unify_pool(subs, pool, ext1, sub_record, ctx.mode); - if !ext_problems.is_empty() { - return ext_problems; + if !ext_outcome.mismatches.is_empty() { + return ext_outcome; } - let mut tag_problems = unify_shared_tags_new( + let mut shared_tags_outcome = unify_shared_tags_new( subs, pool, ctx, @@ -900,9 +1013,9 @@ fn unify_tag_union_new( recursion_var, ); - tag_problems.extend(ext_problems); + shared_tags_outcome.union(ext_outcome); - tag_problems + shared_tags_outcome } } else if separate.only_in_2.is_empty() { let unique_tags1 = UnionTags::insert_slices_into_subs(subs, separate.only_in_1); @@ -911,10 +1024,10 @@ fn unify_tag_union_new( // In a presence context, we don't care about ext2 being equal to tags1 if ctx.mode.is_eq() { - let ext_problems = unify_pool(subs, pool, sub_record, ext2, ctx.mode); + let ext_outcome = unify_pool(subs, pool, sub_record, ext2, ctx.mode); - if !ext_problems.is_empty() { - return ext_problems; + if !ext_outcome.mismatches.is_empty() { + return ext_outcome; } } @@ -961,17 +1074,17 @@ fn unify_tag_union_new( let snapshot = subs.snapshot(); - let ext1_problems = unify_pool(subs, pool, ext1, sub2, ctx.mode); - if !ext1_problems.is_empty() { + let ext1_outcome = unify_pool(subs, pool, ext1, sub2, ctx.mode); + if !ext1_outcome.mismatches.is_empty() { subs.rollback_to(snapshot); - return ext1_problems; + return ext1_outcome; } if ctx.mode.is_eq() { - let ext2_problems = unify_pool(subs, pool, sub1, ext2, ctx.mode); - if !ext2_problems.is_empty() { + let ext2_outcome = unify_pool(subs, pool, sub1, ext2, ctx.mode); + if !ext2_outcome.mismatches.is_empty() { subs.rollback_to(snapshot); - return ext2_problems; + return ext2_outcome; } } @@ -1063,17 +1176,17 @@ fn unify_shared_tags_new( maybe_mark_tag_union_recursive(subs, actual); maybe_mark_tag_union_recursive(subs, expected); - let mut problems = Vec::new(); + let mut outcome = Outcome::default(); - problems.extend(unify_pool(subs, pool, actual, expected, ctx.mode)); + outcome.union(unify_pool(subs, pool, actual, expected, ctx.mode)); // clearly, this is very suspicious: these variables have just been unified. And yet, // not doing this leads to stack overflows if let Rec::Right(_) = recursion_var { - if problems.is_empty() { + if outcome.mismatches.is_empty() { matching_vars.push(expected); } - } else if problems.is_empty() { + } else if outcome.mismatches.is_empty() { matching_vars.push(actual); } } @@ -1215,39 +1328,43 @@ fn unify_flat_type( debug_assert!(is_recursion_var(subs, *rec2)); let rec = Rec::Both(*rec1, *rec2); - let mut problems = + let mut outcome = unify_tag_union_new(subs, pool, ctx, *tags1, *ext1, *tags2, *ext2, rec); - problems.extend(unify_pool(subs, pool, *rec1, *rec2, ctx.mode)); + outcome.union(unify_pool(subs, pool, *rec1, *rec2, ctx.mode)); - problems + outcome } (Apply(l_symbol, l_args), Apply(r_symbol, r_args)) if l_symbol == r_symbol => { - let problems = unify_zip_slices(subs, pool, *l_args, *r_args); + let mut outcome = unify_zip_slices(subs, pool, *l_args, *r_args); - if problems.is_empty() { - merge(subs, ctx, Structure(Apply(*r_symbol, *r_args))) - } else { - problems + if outcome.mismatches.is_empty() { + outcome.union(merge(subs, ctx, Structure(Apply(*r_symbol, *r_args)))); } + + outcome } (Func(l_args, l_closure, l_ret), Func(r_args, r_closure, r_ret)) if l_args.len() == r_args.len() => { - let arg_problems = unify_zip_slices(subs, pool, *l_args, *r_args); - let ret_problems = unify_pool(subs, pool, *l_ret, *r_ret, ctx.mode); - let closure_problems = unify_pool(subs, pool, *l_closure, *r_closure, ctx.mode); + let arg_outcome = unify_zip_slices(subs, pool, *l_args, *r_args); + let ret_outcome = unify_pool(subs, pool, *l_ret, *r_ret, ctx.mode); + let closure_outcome = unify_pool(subs, pool, *l_closure, *r_closure, ctx.mode); - if arg_problems.is_empty() && closure_problems.is_empty() && ret_problems.is_empty() { - merge(subs, ctx, Structure(Func(*r_args, *r_closure, *r_ret))) - } else { - let mut problems = ret_problems; + let mut outcome = ret_outcome; - problems.extend(closure_problems); - problems.extend(arg_problems); + outcome.union(closure_outcome); + outcome.union(arg_outcome); - problems + if outcome.mismatches.is_empty() { + outcome.union(merge( + subs, + ctx, + Structure(Func(*r_args, *r_closure, *r_ret)), + )); } + + outcome } (FunctionOrTagUnion(tag_name, tag_symbol, ext), Func(args, closure, ret)) => { unify_function_or_tag_union_and_func( @@ -1282,12 +1399,12 @@ fn unify_flat_type( let tag_name_2_ref = &subs[*tag_name_2]; if tag_name_1_ref == tag_name_2_ref { - let problems = unify_pool(subs, pool, *ext1, *ext2, ctx.mode); - if problems.is_empty() { + let outcome = unify_pool(subs, pool, *ext1, *ext2, ctx.mode); + if outcome.mismatches.is_empty() { let content = *subs.get_content_without_compacting(ctx.second); merge(subs, ctx, content) } else { - problems + outcome } } else { let tags1 = UnionTags::from_tag_name_index(*tag_name_1); @@ -1343,7 +1460,7 @@ fn unify_zip_slices( left: SubsSlice, right: SubsSlice, ) -> Outcome { - let mut problems = Vec::new(); + let mut outcome = Outcome::default(); let it = left.into_iter().zip(right.into_iter()); @@ -1351,10 +1468,10 @@ fn unify_zip_slices( let l_var = subs[l_index]; let r_var = subs[r_index]; - problems.extend(unify_pool(subs, pool, l_var, r_var, Mode::EQ)); + outcome.union(unify_pool(subs, pool, l_var, r_var, Mode::EQ)); } - problems + outcome } #[inline(always)] @@ -1362,6 +1479,7 @@ fn unify_rigid( subs: &mut Subs, ctx: &Context, name: &SubsIndex, + opt_able_bound: Option, other: &Content, ) -> Outcome { match other { @@ -1369,16 +1487,76 @@ fn unify_rigid( // If the other is flex, rigid wins! merge(subs, ctx, RigidVar(*name)) } - RigidVar(_) | RecursionVar { .. } | Structure(_) | Alias(_, _, _, _) | RangedNumber(..) => { - if !ctx.mode.contains(Mode::RIGID_AS_FLEX) { - // Type mismatch! Rigid can only unify with flex, even if the - // rigid names are the same. - mismatch!("Rigid {:?} with {:?}", ctx.first, &other) - } else { - // We are treating rigid vars as flex vars; admit this - merge(subs, ctx, *other) + FlexAbleVar(_, other_ability) => { + match opt_able_bound { + Some(ability) => { + if ability == *other_ability { + // The ability bounds are the same, so rigid wins! + merge(subs, ctx, RigidAbleVar(*name, ability)) + } else { + // Mismatch for now. + // TODO check ability hierarchies. + mismatch!( + %not_able, ctx.second, ability, + "RigidAble {:?} with ability {:?} not compatible with ability {:?}", + ctx.first, + ability, + other_ability + ) + } + } + None => { + // Mismatch - Rigid can unify with FlexAble only when the Rigid has an ability + // bound as well, otherwise the user failed to correctly annotate the bound. + mismatch!( + %not_able, ctx.first, *other_ability, + "Rigid {:?} with FlexAble {:?}", ctx.first, other + ) + } } } + + RigidVar(_) | RecursionVar { .. } | Structure(_) | Alias(_, _, _, _) | RangedNumber(..) + if ctx.mode.contains(Mode::RIGID_AS_FLEX) => + { + // Usually rigids can only unify with flex, but the mode indicates we are treating + // rigid vars as flex, so admit this. + match (opt_able_bound, other) { + (None, other) => merge(subs, ctx, *other), + (Some(ability), Alias(opaque_name, vars, _real_var, AliasKind::Opaque)) + if vars.is_empty() => + { + let mut output = merge(subs, ctx, *other); + let must_implement_ability = MustImplementAbility { + typ: *opaque_name, + ability, + }; + output.must_implement_ability.push(must_implement_ability); + output + } + (Some(ability), other) => { + // For now, only allow opaque types with no type variables to implement abilities. + mismatch!( + %not_able, ctx.second, ability, + "RigidAble {:?} with non-opaque or opaque with type variables {:?}", + ctx.first, + &other + ) + } + } + } + + RigidVar(_) + | RigidAbleVar(..) + | RecursionVar { .. } + | Structure(_) + | Alias(..) + | RangedNumber(..) => { + // Type mismatch! Rigid can only unify with flex, even if the + // rigid names are the same. + mismatch!("Rigid {:?} with {:?}", ctx.first, &other) + } + Error => { // Error propagates. merge(subs, ctx, Error) @@ -1391,16 +1569,49 @@ fn unify_flex( subs: &mut Subs, ctx: &Context, opt_name: &Option>, + opt_able_bound: Option, other: &Content, ) -> Outcome { match other { FlexVar(None) => { // If both are flex, and only left has a name, keep the name around. - merge(subs, ctx, FlexVar(*opt_name)) + match opt_able_bound { + Some(ability) => merge(subs, ctx, FlexAbleVar(*opt_name, ability)), + None => merge(subs, ctx, FlexVar(*opt_name)), + } + } + + FlexAbleVar(opt_other_name, other_ability) => { + // Prefer the right's name when possible. + let opt_name = (opt_other_name).or(*opt_name); + + match opt_able_bound { + Some(ability) => { + if ability == *other_ability { + // The ability bounds are the same! Keep the name around if it exists. + merge(subs, ctx, FlexAbleVar(opt_name, ability)) + } else { + // Ability names differ; mismatch for now. + // TODO check ability hierarchies. + mismatch!( + %not_able, ctx.second, ability, + "FlexAble {:?} with ability {:?} not compatible with ability {:?}", + ctx.first, + ability, + other_ability + ) + } + } + None => { + // Right has an ability bound, but left might have the name. Combine them. + merge(subs, ctx, FlexAbleVar(opt_name, *other_ability)) + } + } } FlexVar(Some(_)) | RigidVar(_) + | RigidAbleVar(_, _) | RecursionVar { .. } | Structure(_) | Alias(_, _, _, _) @@ -1446,7 +1657,13 @@ fn unify_recursion( // unify the structure variable with this Structure unify_pool(subs, pool, structure, ctx.second, ctx.mode) } - RigidVar(_) => mismatch!("RecursionVar {:?} with rigid {:?}", ctx.first, &other), + RigidVar(_) => { + mismatch!("RecursionVar {:?} with rigid {:?}", ctx.first, &other) + } + + FlexAbleVar(..) | RigidAbleVar(..) => { + mismatch!("RecursionVar {:?} with able var {:?}", ctx.first, &other) + } FlexVar(_) => merge( subs, @@ -1492,7 +1709,7 @@ pub fn merge(subs: &mut Subs, ctx: &Context, content: Content) -> Outcome { subs.union(ctx.first, ctx.second, desc); - Vec::new() + Outcome::default() } fn register(subs: &mut Subs, desc: Descriptor, pool: &mut Pool) -> Variable { @@ -1543,7 +1760,7 @@ fn unify_function_or_tag_union_and_func( let new_tag_union_var = fresh(subs, pool, ctx, content); - let mut problems = if left { + let mut outcome = if left { unify_pool(subs, pool, new_tag_union_var, function_return, ctx.mode) } else { unify_pool(subs, pool, function_return, new_tag_union_var, ctx.mode) @@ -1567,16 +1784,16 @@ fn unify_function_or_tag_union_and_func( pool, ); - let closure_problems = if left { + let closure_outcome = if left { unify_pool(subs, pool, tag_lambda_set, function_lambda_set, ctx.mode) } else { unify_pool(subs, pool, function_lambda_set, tag_lambda_set, ctx.mode) }; - problems.extend(closure_problems); + outcome.union(closure_outcome); } - if problems.is_empty() { + if outcome.mismatches.is_empty() { let desc = if left { subs.get(ctx.second) } else { @@ -1586,5 +1803,5 @@ fn unify_function_or_tag_union_and_func( subs.union(ctx.first, ctx.second, desc); } - problems + outcome } diff --git a/docs/Cargo.toml b/docs/Cargo.toml index d377031ac9..1fe3c231e2 100644 --- a/docs/Cargo.toml +++ b/docs/Cargo.toml @@ -21,6 +21,7 @@ roc_parse = { path = "../compiler/parse" } roc_target = { path = "../compiler/roc_target" } roc_collections = { path = "../compiler/collections" } roc_highlight = { path = "../highlight"} +roc_reporting = { path = "../reporting"} bumpalo = { version = "3.8.0", features = ["collections"] } snafu = { version = "0.6.10", features = ["backtraces"] } peg = "0.8.0" diff --git a/docs/src/lib.rs b/docs/src/lib.rs index 7a4a05f0c9..d09134d98d 100644 --- a/docs/src/lib.rs +++ b/docs/src/lib.rs @@ -424,6 +424,7 @@ pub fn load_modules_for_files(filenames: Vec) -> Vec { src_dir.as_path(), Default::default(), roc_target::TargetInfo::default_x86_64(), // This is just type-checking for docs, so "target" doesn't matter + roc_reporting::report::RenderTarget::ColorTerminal, ) { Ok(loaded) => modules.push(loaded), Err(LoadingProblem::FormattedReport(report)) => { diff --git a/examples/benchmarks/Deriv.roc b/examples/benchmarks/Deriv.roc index 2cad781765..caf08906a6 100644 --- a/examples/benchmarks/Deriv.roc +++ b/examples/benchmarks/Deriv.roc @@ -42,7 +42,7 @@ Expr : [ Val I64, Var Str, Add Expr Expr, Mul Expr Expr, Pow Expr Expr, Ln Expr divmod : I64, I64 -> Result { div : I64, mod : I64 } [ DivByZero ]* divmod = \l, r -> when Pair (l // r) (l % r) is - Pair (Ok div) (Ok mod) -> + Pair div (Ok mod) -> Ok { div, mod } _ -> diff --git a/examples/benchmarks/RBTreeDel.roc b/examples/benchmarks/RBTreeDel.roc index 24419ef84a..43fd309e5d 100644 --- a/examples/benchmarks/RBTreeDel.roc +++ b/examples/benchmarks/RBTreeDel.roc @@ -47,7 +47,7 @@ makeMapHelp = \total, n, m -> isFrequency = n |> Num.isMultipleOf 4 - key = n1 + ((total - n1) // 5 |> resultWithDefault 0) + key = n1 + ((total - n1) // 5) t2 = if isFrequency then delete t1 key else t1 makeMapHelp total n1 t2 diff --git a/examples/false-interpreter/False.roc b/examples/false-interpreter/False.roc index d7e0ac0c4f..f8306e7d8d 100644 --- a/examples/false-interpreter/False.roc +++ b/examples/false-interpreter/False.roc @@ -434,7 +434,7 @@ stepExecCtx = \ctx, char -> ( (T popCtx1 numR) <- Result.after (popNumber ctx) (T popCtx2 numL) <- Result.after (popNumber popCtx1) - res <- Result.after (Num.divFloor numL numR) + res <- Result.after (Num.divFloorChecked numL numR) Ok (Context.pushStack popCtx2 (Number res)) ) diff --git a/repl_eval/src/gen.rs b/repl_eval/src/gen.rs index a5e1fa6dbd..4e8f698c29 100644 --- a/repl_eval/src/gen.rs +++ b/repl_eval/src/gen.rs @@ -60,6 +60,7 @@ pub fn compile_to_mono<'a>( src_dir, exposed_types, target_info, + roc_reporting::report::RenderTarget::ColorTerminal, ); let mut loaded = match loaded { diff --git a/repl_test/src/tests.rs b/repl_test/src/tests.rs index 84df9024ee..9f46137f38 100644 --- a/repl_test/src/tests.rs +++ b/repl_test/src/tests.rs @@ -61,23 +61,41 @@ fn num_rem() { #[cfg(not(feature = "wasm"))] #[test] -fn num_floor_division_success() { - expect_success("Num.divFloor 4 3", "Ok 1 : Result (Int *) [ DivByZero ]*"); +fn num_floor_division() { + expect_success("Num.divFloor 4 3", "1 : Int *"); } #[cfg(not(feature = "wasm"))] #[test] -fn num_floor_division_divby_zero() { +fn num_floor_checked_division_success() { expect_success( - "Num.divFloor 4 0", + "Num.divFloorChecked 4 3", + "Ok 1 : Result (Int *) [ DivByZero ]*", + ); +} + +#[cfg(not(feature = "wasm"))] +#[test] +fn num_floor_checked_division_divby_zero() { + expect_success( + "Num.divFloorChecked 4 0", "Err DivByZero : Result (Int *) [ DivByZero ]*", ); } #[cfg(not(feature = "wasm"))] #[test] -fn num_ceil_division_success() { - expect_success("Num.divCeil 4 3", "Ok 2 : Result (Int *) [ DivByZero ]*") +fn num_ceil_division() { + expect_success("Num.divCeil 4 3", "2 : Int *") +} + +#[cfg(not(feature = "wasm"))] +#[test] +fn num_ceil_checked_division_success() { + expect_success( + "Num.divCeilChecked 4 3", + "Ok 2 : Result (Int *) [ DivByZero ]*", + ) } #[test] @@ -1103,3 +1121,20 @@ fn issue_2582_specialize_result_value() { r" : Num *, List Str -> Result Str [ ListWasEmpty ]*", ) } + +#[test] +#[cfg(not(feature = "wasm"))] +fn issue_2818() { + expect_success( + indoc!( + r#" + f : {} -> List Str + f = \_ -> + x = [] + x + f + "# + ), + r" : {} -> List Str", + ) +} diff --git a/reporting/Cargo.toml b/reporting/Cargo.toml index f2f9bf1c57..63e16ad7c3 100644 --- a/reporting/Cargo.toml +++ b/reporting/Cargo.toml @@ -23,9 +23,11 @@ bumpalo = { version = "3.8.0", features = ["collections"] } [dev-dependencies] roc_constrain = { path = "../compiler/constrain" } roc_builtins = { path = "../compiler/builtins" } +roc_load = { path = "../compiler/load" } roc_problem = { path = "../compiler/problem" } roc_parse = { path = "../compiler/parse" } roc_target = { path = "../compiler/roc_target" } roc_test_utils = { path = "../test_utils" } pretty_assertions = "1.0.0" indoc = "1.0.3" +tempfile = "3.2.0" diff --git a/reporting/src/error/canonicalize.rs b/reporting/src/error/canonicalize.rs index c804905ae2..d366baf6f2 100644 --- a/reporting/src/error/canonicalize.rs +++ b/reporting/src/error/canonicalize.rs @@ -44,6 +44,7 @@ const ALIAS_USES_ABILITY: &str = "ALIAS USES ABILITY"; const ILLEGAL_HAS_CLAUSE: &str = "ILLEGAL HAS CLAUSE"; const ABILITY_MEMBER_MISSING_HAS_CLAUSE: &str = "ABILITY MEMBER MISSING HAS CLAUSE"; const ABILITY_MEMBER_HAS_EXTRANEOUS_HAS_CLAUSE: &str = "ABILITY MEMBER HAS EXTRANEOUS HAS CLAUSE"; +const ABILITY_MEMBER_BINDS_MULTIPLE_VARIABLES: &str = "ABILITY MEMBER BINDS MULTIPLE VARIABLES"; pub fn can_problem<'b>( alloc: &'b RocDocAllocator<'b>, @@ -684,6 +685,34 @@ pub fn can_problem<'b>( severity = Severity::RuntimeError; } + Problem::AbilityMemberMultipleBoundVars { + member, + ability, + span_has_clauses, + mut bound_var_names, + } => { + doc = alloc.stack(vec![ + alloc.concat(vec![ + alloc.reflow("The definition of the ability member "), + alloc.symbol_unqualified(member), + alloc.reflow(" includes multiple variables bound to the "), + alloc.symbol_unqualified(ability), + alloc.keyword(" ability:"), + ]), + alloc.region(lines.convert_region(span_has_clauses)), + alloc.reflow("Ability members can only bind one type variable to their parent ability. Otherwise, I wouldn't know what type implements an ability by looking at specializations!"), + alloc.concat(vec![ + alloc.hint("Did you mean to only bind "), + alloc.type_variable(bound_var_names.swap_remove(0)), + alloc.reflow(" to "), + alloc.symbol_unqualified(ability), + alloc.reflow("?"), + ]) + ]); + title = ABILITY_MEMBER_BINDS_MULTIPLE_VARIABLES.to_string(); + severity = Severity::RuntimeError; + } + Problem::AbilityMemberBindsExternalAbility { member, ability, diff --git a/reporting/src/error/type.rs b/reporting/src/error/type.rs index 21ec179d6f..b1d6334d77 100644 --- a/reporting/src/error/type.rs +++ b/reporting/src/error/type.rs @@ -118,6 +118,53 @@ pub fn type_problem<'b>( other => panic!("unhandled bad type: {:?}", other), } } + IncompleteAbilityImplementation { + typ, + ability, + specialized_members, + missing_members, + } => { + let title = "INCOMPLETE ABILITY IMPLEMENTATION".to_string(); + + let mut stack = vec![alloc.concat(vec![ + alloc.reflow("The type "), + alloc.symbol_unqualified(typ), + alloc.reflow(" does not fully implement the ability "), + alloc.symbol_unqualified(ability), + alloc.reflow(". The following specializations are missing:"), + ])]; + + for member in missing_members.into_iter() { + stack.push(alloc.concat(vec![ + alloc.reflow("A specialization for "), + alloc.symbol_unqualified(member.value), + alloc.reflow(", which is defined here:"), + ])); + stack.push(alloc.region(lines.convert_region(member.region))); + } + + debug_assert!(!specialized_members.is_empty()); + + stack.push(alloc.concat(vec![ + alloc.note(""), + alloc.symbol_unqualified(typ), + alloc.reflow(" specializes the following members of "), + alloc.symbol_unqualified(ability), + alloc.reflow(":"), + ])); + + for spec in specialized_members { + stack.push(alloc.concat(vec![ + alloc.symbol_unqualified(spec.value), + alloc.reflow(", specialized here:"), + ])); + stack.push(alloc.region(lines.convert_region(spec.region))); + } + + let doc = alloc.stack(stack); + + report(title, doc, filename) + } } } @@ -950,6 +997,102 @@ fn to_expr_report<'b>( None, ), + Reason::InvalidAbilityMemberSpecialization { + member_name, + def_region: _, + unimplemented_abilities, + } => { + let problem = alloc.concat(vec![ + alloc.reflow("Something is off with this specialization of "), + alloc.symbol_unqualified(member_name), + alloc.reflow(":"), + ]); + let this_is = alloc.reflow("This value is"); + let instead_of = alloc.concat(vec![ + alloc.reflow("But the type annotation on "), + alloc.symbol_unqualified(member_name), + alloc.reflow(" says it must match:"), + ]); + + let hint = if unimplemented_abilities.is_empty() { + None + } else { + let mut stack = Vec::with_capacity(unimplemented_abilities.len()); + for (err_type, ability) in unimplemented_abilities.into_iter() { + stack.push(alloc.concat(vec![ + to_doc(alloc, Parens::Unnecessary, err_type).0, + alloc.reflow(" does not implement "), + alloc.symbol_unqualified(ability), + ])); + } + + let hint = alloc.stack(vec![ + alloc.concat(vec![ + alloc.note(""), + alloc.reflow("Some types in this specialization don't implement the abilities they are expected to. I found the following missing implementations:"), + ]), + alloc.type_block(alloc.stack(stack)), + ]); + + Some(hint) + }; + + report_mismatch( + alloc, + lines, + filename, + &category, + found, + expected_type, + region, + Some(expr_region), + problem, + this_is, + instead_of, + hint, + ) + } + + Reason::GeneralizedAbilityMemberSpecialization { + member_name, + def_region: _, + } => { + let problem = alloc.concat(vec![ + alloc.reflow("This specialization of "), + alloc.symbol_unqualified(member_name), + alloc.reflow(" is overly general:"), + ]); + let this_is = alloc.reflow("This value is"); + let instead_of = alloc.concat(vec![ + alloc.reflow("But the type annotation on "), + alloc.symbol_unqualified(member_name), + alloc.reflow(" says it must match:"), + ]); + + let note = alloc.stack(vec![ + alloc.concat(vec![ + alloc.note(""), + alloc.reflow("The specialized type is too general, and does not provide a concrete type where a type variable is bound to an ability."), + ]), + alloc.reflow("Specializations can only be made for concrete types. If you have a generic implementation for this value, perhaps you don't need an ability?"), + ]); + + report_mismatch( + alloc, + lines, + filename, + &category, + found, + expected_type, + region, + Some(expr_region), + problem, + this_is, + instead_of, + Some(note), + ) + } + Reason::LowLevelOpArg { op, arg_index } => { panic!( "Compiler bug: argument #{} to low-level operation {:?} was the wrong type!", @@ -1281,6 +1424,10 @@ fn format_category<'b>( alloc.concat(vec![this_is, alloc.text(" a default field")]), alloc.text(" of type:"), ), + AbilityMemberSpecialization(_ability_member) => ( + alloc.concat(vec![this_is, alloc.text(" a declared specialization")]), + alloc.text(" of type:"), + ), } } @@ -1526,7 +1673,7 @@ fn to_circular_report<'b>( You will see ∞ for parts of the type that repeat \ something already printed out infinitely.", ), - alloc.type_block(to_doc(alloc, Parens::Unnecessary, overall_type)), + alloc.type_block(to_doc(alloc, Parens::Unnecessary, overall_type).0), ]), ]) }, @@ -1627,10 +1774,12 @@ fn to_comparison<'b>( expected: ErrorType, ) -> Comparison<'b> { let diff = to_diff(alloc, Parens::Unnecessary, actual, expected); + let actual = type_with_able_vars(alloc, diff.left, diff.left_able); + let expected = type_with_able_vars(alloc, diff.right, diff.right_able); Comparison { - actual: alloc.type_block(diff.left), - expected: alloc.type_block(diff.right), + actual: alloc.type_block(actual), + expected: alloc.type_block(expected), problems: match diff.status { Status::Similar => vec![], Status::Different(problems) => problems, @@ -1683,6 +1832,9 @@ pub struct Diff { left: T, right: T, status: Status, + // idea: lift "able" type variables so they are shown at the top of a type. + left_able: AbleVariables, + right_able: AbleVariables, } fn ext_to_doc<'b>(alloc: &'b RocDocAllocator<'b>, ext: TypeExt) -> Option> { @@ -1694,10 +1846,30 @@ fn ext_to_doc<'b>(alloc: &'b RocDocAllocator<'b>, ext: TypeExt) -> Option; + +#[derive(Default)] +struct Context { + able_variables: AbleVariables, +} + pub fn to_doc<'b>( alloc: &'b RocDocAllocator<'b>, parens: Parens, tipe: ErrorType, +) -> (RocDocBuilder<'b>, AbleVariables) { + let mut ctx = Context::default(); + + let doc = to_doc_help(&mut ctx, alloc, parens, tipe); + + (doc, ctx.able_variables) +} + +fn to_doc_help<'b>( + ctx: &mut Context, + alloc: &'b RocDocAllocator<'b>, + parens: Parens, + tipe: ErrorType, ) -> RocDocBuilder<'b> { use ErrorType::*; @@ -1706,22 +1878,26 @@ pub fn to_doc<'b>( alloc, parens, args.into_iter() - .map(|arg| to_doc(alloc, Parens::InFn, arg)) + .map(|arg| to_doc_help(ctx, alloc, Parens::InFn, arg)) .collect(), - to_doc(alloc, Parens::InFn, *ret), + to_doc_help(ctx, alloc, Parens::InFn, *ret), ), Infinite => alloc.text("∞"), Error => alloc.text("?"), - FlexVar(lowercase) => alloc.type_variable(lowercase), - RigidVar(lowercase) => alloc.type_variable(lowercase), + FlexVar(lowercase) | RigidVar(lowercase) => alloc.type_variable(lowercase), + FlexAbleVar(lowercase, ability) | RigidAbleVar(lowercase, ability) => { + // TODO we should be putting able variables on the toplevel of the type, not here + ctx.able_variables.push((lowercase.clone(), ability)); + alloc.type_variable(lowercase) + } Type(symbol, args) => report_text::apply( alloc, parens, alloc.symbol_foreign_qualified(symbol), args.into_iter() - .map(|arg| to_doc(alloc, Parens::InTypeParam, arg)) + .map(|arg| to_doc_help(ctx, alloc, Parens::InTypeParam, arg)) .collect(), ), @@ -1730,7 +1906,7 @@ pub fn to_doc<'b>( parens, alloc.symbol_foreign_qualified(symbol), args.into_iter() - .map(|arg| to_doc(alloc, Parens::InTypeParam, arg)) + .map(|arg| to_doc_help(ctx, alloc, Parens::InTypeParam, arg)) .collect(), ), @@ -1746,15 +1922,24 @@ pub fn to_doc<'b>( ( alloc.string(k.as_str().to_string()), match value { - RecordField::Optional(v) => { - RecordField::Optional(to_doc(alloc, Parens::Unnecessary, v)) - } - RecordField::Required(v) => { - RecordField::Required(to_doc(alloc, Parens::Unnecessary, v)) - } - RecordField::Demanded(v) => { - RecordField::Demanded(to_doc(alloc, Parens::Unnecessary, v)) - } + RecordField::Optional(v) => RecordField::Optional(to_doc_help( + ctx, + alloc, + Parens::Unnecessary, + v, + )), + RecordField::Required(v) => RecordField::Required(to_doc_help( + ctx, + alloc, + Parens::Unnecessary, + v, + )), + RecordField::Demanded(v) => RecordField::Demanded(to_doc_help( + ctx, + alloc, + Parens::Unnecessary, + v, + )), }, ) }) @@ -1770,7 +1955,7 @@ pub fn to_doc<'b>( ( name, args.into_iter() - .map(|arg| to_doc(alloc, Parens::InTypeParam, arg)) + .map(|arg| to_doc_help(ctx, alloc, Parens::InTypeParam, arg)) .collect::>(), ) }) @@ -1793,7 +1978,7 @@ pub fn to_doc<'b>( ( name, args.into_iter() - .map(|arg| to_doc(alloc, Parens::InTypeParam, arg)) + .map(|arg| to_doc_help(ctx, alloc, Parens::InTypeParam, arg)) .collect::>(), ) }) @@ -1802,7 +1987,7 @@ pub fn to_doc<'b>( report_text::recursive_tag_union( alloc, - to_doc(alloc, Parens::Unnecessary, *rec_var), + to_doc_help(ctx, alloc, Parens::Unnecessary, *rec_var), tags.into_iter() .map(|(k, v)| (alloc.tag_name(k), v)) .collect(), @@ -1811,10 +1996,10 @@ pub fn to_doc<'b>( } Range(typ, range_types) => { - let typ = to_doc(alloc, parens, *typ); + let typ = to_doc_help(ctx, alloc, parens, *typ); let range_types = range_types .into_iter() - .map(|arg| to_doc(alloc, Parens::Unnecessary, arg)) + .map(|arg| to_doc_help(ctx, alloc, Parens::Unnecessary, arg)) .collect(); report_text::range(alloc, typ, range_types) } @@ -1826,15 +2011,42 @@ fn same<'b>( parens: Parens, tipe: ErrorType, ) -> Diff> { - let doc = to_doc(alloc, parens, tipe); + let (doc, able) = to_doc(alloc, parens, tipe); Diff { left: doc.clone(), right: doc, status: Status::Similar, + left_able: able.clone(), + right_able: able, } } +fn type_with_able_vars<'b>( + alloc: &'b RocDocAllocator<'b>, + typ: RocDocBuilder<'b>, + able: AbleVariables, +) -> RocDocBuilder<'b> { + if able.is_empty() { + // fast path: taken the vast majority of the time + return typ; + } + + let mut doc = Vec::with_capacity(1 + 6 * able.len()); + doc.push(typ); + + for (i, (var, ability)) in able.into_iter().enumerate() { + doc.push(alloc.string(if i == 0 { " | " } else { ", " }.to_string())); + doc.push(alloc.type_variable(var)); + doc.push(alloc.space()); + doc.push(alloc.keyword("has")); + doc.push(alloc.space()); + doc.push(alloc.symbol_foreign_qualified(ability)); + } + + alloc.concat(doc) +} + fn to_diff<'b>( alloc: &'b RocDocAllocator<'b>, parens: Parens, @@ -1863,15 +2075,21 @@ fn to_diff<'b>( let left = report_text::function(alloc, parens, arg_diff.left, ret_diff.left); let right = report_text::function(alloc, parens, arg_diff.right, ret_diff.right); + let mut left_able = arg_diff.left_able; + left_able.extend(ret_diff.left_able); + let mut right_able = arg_diff.right_able; + right_able.extend(ret_diff.right_able); Diff { left, right, status, + left_able, + right_able, } } else { - let left = to_doc(alloc, Parens::InFn, type1); - let right = to_doc(alloc, Parens::InFn, type2); + let (left, left_able) = to_doc(alloc, Parens::InFn, type1); + let (right, right_able) = to_doc(alloc, Parens::InFn, type2); Diff { left, @@ -1880,6 +2098,8 @@ fn to_diff<'b>( args1.len(), args2.len(), )]), + left_able, + right_able, } } } @@ -1902,6 +2122,8 @@ fn to_diff<'b>( left, right, status: args_diff.status, + left_able: args_diff.left_able, + right_able: args_diff.right_able, } } @@ -1924,17 +2146,21 @@ fn to_diff<'b>( left, right, status: args_diff.status, + left_able: args_diff.left_able, + right_able: args_diff.right_able, } } (Alias(_, _, _, AliasKind::Opaque), _) | (_, Alias(_, _, _, AliasKind::Opaque)) => { - let left = to_doc(alloc, Parens::InFn, type1); - let right = to_doc(alloc, Parens::InFn, type2); + let (left, left_able) = to_doc(alloc, Parens::InFn, type1); + let (right, right_able) = to_doc(alloc, Parens::InFn, type2); Diff { left, right, status: Status::Different(vec![Problem::OpaqueComparedToNonOpaque]), + left_able, + right_able, } } @@ -1961,20 +2187,22 @@ fn to_diff<'b>( (RecursiveTagUnion(_rec1, _tags1, _ext1), RecursiveTagUnion(_rec2, _tags2, _ext2)) => { // TODO do a better job here - let left = to_doc(alloc, Parens::Unnecessary, type1); - let right = to_doc(alloc, Parens::Unnecessary, type2); + let (left, left_able) = to_doc(alloc, Parens::Unnecessary, type1); + let (right, right_able) = to_doc(alloc, Parens::Unnecessary, type2); Diff { left, right, status: Status::Similar, + left_able, + right_able, } } pair => { // We hit none of the specific cases where we give more detailed information - let left = to_doc(alloc, parens, type1); - let right = to_doc(alloc, parens, type2); + let (left, left_able) = to_doc(alloc, parens, type1); + let (right, right_able) = to_doc(alloc, parens, type2); let is_int = |t: &ErrorType| match t { ErrorType::Type(Symbol::NUM_INT, _) => true, @@ -2030,6 +2258,8 @@ fn to_diff<'b>( left, right, status: Status::Different(problems), + left_able, + right_able, } } } @@ -2049,6 +2279,8 @@ where // TODO use ExactSizeIterator to pre-allocate here let mut left = Vec::new(); let mut right = Vec::new(); + let mut left_able = Vec::new(); + let mut right_able = Vec::new(); for (arg1, arg2) in args1.into_iter().zip(args2.into_iter()) { let diff = to_diff(alloc, parens, arg1, arg2); @@ -2056,12 +2288,16 @@ where left.push(diff.left); right.push(diff.right); status.merge(diff.status); + left_able.extend(diff.left_able); + right_able.extend(diff.right_able); } Diff { left, right, status, + left_able, + right_able, } } @@ -2128,6 +2364,8 @@ fn diff_record<'b>( _ => diff.status, } }, + left_able: diff.left_able, + right_able: diff.right_able, } }; @@ -2135,7 +2373,7 @@ fn diff_record<'b>( ( field.clone(), alloc.string(field.as_str().to_string()), - tipe.map(|t| to_doc(alloc, Parens::Unnecessary, t.clone())), + tipe.map(|t| to_doc(alloc, Parens::Unnecessary, t.clone()).0), ) }; let shared_keys = fields1 @@ -2193,12 +2431,16 @@ fn diff_record<'b>( left: vec![], right: vec![], status: Status::Similar, + left_able: vec![], + right_able: vec![], }; for diff in both { fields_diff.left.push(diff.left); fields_diff.right.push(diff.right); fields_diff.status.merge(diff.status); + fields_diff.left_able.extend(diff.left_able); + fields_diff.right_able.extend(diff.right_able); } if !all_fields_shared { @@ -2236,6 +2478,8 @@ fn diff_record<'b>( left: doc1, right: doc2, status: fields_diff.status, + left_able: fields_diff.left_able, + right_able: fields_diff.right_able, } } @@ -2253,16 +2497,26 @@ fn diff_tag_union<'b>( left: (field.clone(), alloc.tag_name(field.clone()), diff.left), right: (field.clone(), alloc.tag_name(field), diff.right), status: diff.status, + left_able: diff.left_able, + right_able: diff.right_able, } }; - let to_unknown_docs = |(field, args): (&TagName, &Vec)| { - ( - field.clone(), - alloc.tag_name(field.clone()), + let to_unknown_docs = |(field, args): (&TagName, &Vec)| -> ( + TagName, + RocDocBuilder<'b>, + Vec>, + AbleVariables, + ) { + let (args, able): (_, Vec) = // TODO add spaces between args args.iter() .map(|arg| to_doc(alloc, Parens::InTypeParam, arg.clone())) - .collect(), + .unzip(); + ( + field.clone(), + alloc.tag_name(field.clone()), + args, + able.into_iter().flatten().collect(), ) }; let shared_keys = fields1 @@ -2280,7 +2534,7 @@ fn diff_tag_union<'b>( let status = match (ext_has_fixed_fields(&ext1), ext_has_fixed_fields(&ext2)) { (true, true) => match left.peek() { - Some((f, _, _)) => Status::Different(vec![Problem::TagTypo( + Some((f, _, _, _)) => Status::Different(vec![Problem::TagTypo( f.clone(), fields2.keys().cloned().collect(), )]), @@ -2297,14 +2551,14 @@ fn diff_tag_union<'b>( } }, (false, true) => match left.peek() { - Some((f, _, _)) => Status::Different(vec![Problem::TagTypo( + Some((f, _, _, _)) => Status::Different(vec![Problem::TagTypo( f.clone(), fields2.keys().cloned().collect(), )]), None => Status::Similar, }, (true, false) => match right.peek() { - Some((f, _, _)) => Status::Different(vec![Problem::TagTypo( + Some((f, _, _, _)) => Status::Different(vec![Problem::TagTypo( f.clone(), fields1.keys().cloned().collect(), )]), @@ -2319,17 +2573,27 @@ fn diff_tag_union<'b>( left: vec![], right: vec![], status: Status::Similar, + left_able: vec![], + right_able: vec![], }; for diff in both { fields_diff.left.push(diff.left); fields_diff.right.push(diff.right); fields_diff.status.merge(diff.status); + fields_diff.left_able.extend(diff.left_able); + fields_diff.right_able.extend(diff.right_able); } if !all_fields_shared { - fields_diff.left.extend(left); - fields_diff.right.extend(right); + for (tag, tag_doc, args, able) in left { + fields_diff.left.push((tag, tag_doc, args)); + fields_diff.left_able.extend(able); + } + for (tag, tag_doc, args, able) in right { + fields_diff.right.push((tag, tag_doc, args)); + fields_diff.right_able.extend(able); + } fields_diff.status.merge(Status::Different(vec![])); } @@ -2356,6 +2620,8 @@ fn diff_tag_union<'b>( left: doc1, right: doc2, status: fields_diff.status, + left_able: fields_diff.left_able, + right_able: fields_diff.right_able, } } @@ -2373,12 +2639,16 @@ fn ext_to_diff<'b>( left: ext_doc_1, right: ext_doc_2, status, + left_able: vec![], + right_able: vec![], }, Status::Different(_) => Diff { // NOTE elm colors these differently at this point left: ext_doc_1, right: ext_doc_2, status, + left_able: vec![], + right_able: vec![], }, } } @@ -2518,7 +2788,7 @@ mod report_text { let entry_to_doc = |(name, tipe): (Lowercase, RecordField)| { ( alloc.string(name.as_str().to_string()), - to_doc(alloc, Parens::Unnecessary, tipe.into_inner()), + to_doc(alloc, Parens::Unnecessary, tipe.into_inner()).0, ) }; @@ -2863,7 +3133,14 @@ fn type_problem_to_pretty<'b>( match tipe { Infinite | Error | FlexVar(_) => alloc.nil(), - RigidVar(y) => bad_double_rigid(x, y), + FlexAbleVar(_, ability) => bad_rigid_var( + x, + alloc.concat(vec![ + alloc.reflow("an instance of the ability "), + alloc.symbol_unqualified(ability), + ]), + ), + RigidVar(y) | RigidAbleVar(y, _) => bad_double_rigid(x, y), Function(_, _, _) => bad_rigid_var(x, alloc.reflow("a function value")), Record(_, _) => bad_rigid_var(x, alloc.reflow("a record value")), TagUnion(_, _) | RecursiveTagUnion(_, _, _) => { diff --git a/reporting/src/report.rs b/reporting/src/report.rs index 4dc7c46d3e..fb6de7f29b 100644 --- a/reporting/src/report.rs +++ b/reporting/src/report.rs @@ -72,6 +72,12 @@ pub enum Severity { Warning, } +#[derive(Clone, Copy, Debug)] +pub enum RenderTarget { + ColorTerminal, + Generic, +} + /// A textual report. pub struct Report<'b> { pub title: String, @@ -81,6 +87,19 @@ pub struct Report<'b> { } impl<'b> Report<'b> { + pub fn render( + self, + target: RenderTarget, + buf: &'b mut String, + alloc: &'b RocDocAllocator<'b>, + palette: &'b Palette, + ) { + match target { + RenderTarget::Generic => self.render_ci(buf, alloc), + RenderTarget::ColorTerminal => self.render_color_terminal(buf, alloc, palette), + } + } + /// Render to CI console output, where no colors are available. pub fn render_ci(self, buf: &'b mut String, alloc: &'b RocDocAllocator<'b>) { let err_msg = ""; diff --git a/reporting/tests/helpers/mod.rs b/reporting/tests/helpers/mod.rs index eda767542e..34aa95bf8f 100644 --- a/reporting/tests/helpers/mod.rs +++ b/reporting/tests/helpers/mod.rs @@ -1,6 +1,7 @@ extern crate bumpalo; use self::bumpalo::Bump; +use roc_can::abilities::AbilitiesStore; use roc_can::constraint::{Constraint, Constraints}; use roc_can::env::Env; use roc_can::expected::Expected; @@ -31,10 +32,19 @@ pub fn infer_expr( constraints: &Constraints, constraint: &Constraint, aliases: &mut Aliases, + abilities_store: &mut AbilitiesStore, expr_var: Variable, ) -> (Content, Subs) { let env = solve::Env::default(); - let (solved, _) = solve::run(constraints, &env, problems, subs, aliases, constraint); + let (solved, _) = solve::run( + constraints, + &env, + problems, + subs, + aliases, + constraint, + abilities_store, + ); let content = *solved.inner().get_content_without_compacting(expr_var); diff --git a/reporting/tests/test_reporting.rs b/reporting/tests/test_reporting.rs index 65525012f1..6585008ab9 100644 --- a/reporting/tests/test_reporting.rs +++ b/reporting/tests/test_reporting.rs @@ -8,17 +8,20 @@ mod helpers; #[cfg(test)] mod test_reporting { - use crate::helpers::test_home; - use crate::helpers::{can_expr, infer_expr, CanExprOut, ParseErrOut}; + use crate::helpers::{can_expr, infer_expr, test_home, CanExprOut, ParseErrOut}; use bumpalo::Bump; use indoc::indoc; + use roc_can::abilities::AbilitiesStore; + use roc_can::def::Declaration; + use roc_can::pattern::Pattern; + use roc_load::{self, LoadedModule, LoadingProblem}; use roc_module::symbol::{Interns, ModuleId}; use roc_mono::ir::{Procs, Stmt, UpdateModeIds}; use roc_mono::layout::LayoutCache; use roc_region::all::LineInfo; use roc_reporting::report::{ - can_problem, mono_problem, parse_problem, type_problem, Report, Severity, ANSI_STYLE_CODES, - DEFAULT_PALETTE, + can_problem, mono_problem, parse_problem, type_problem, RenderTarget, Report, Severity, + ANSI_STYLE_CODES, DEFAULT_PALETTE, }; use roc_reporting::report::{RocDocAllocator, RocDocBuilder}; use roc_solve::solve; @@ -43,6 +46,214 @@ mod test_reporting { } } + fn promote_expr_to_module(src: &str) -> String { + let mut buffer = + String::from("app \"test\" provides [ main ] to \"./platform\"\n\nmain =\n"); + + for line in src.lines() { + // indent the body! + buffer.push_str(" "); + buffer.push_str(line); + buffer.push('\n'); + } + + buffer + } + + fn run_load_and_infer<'a>( + arena: &'a Bump, + src: &'a str, + ) -> (String, Result>) { + use std::fs::File; + use std::io::Write; + use tempfile::tempdir; + + let module_src = if src.starts_with("app") { + // this is already a module + src.to_string() + } else { + // this is an expression, promote it to a module + promote_expr_to_module(src) + }; + + let exposed_types = Default::default(); + let loaded = { + let dir = tempdir().unwrap(); + let filename = PathBuf::from("Test.roc"); + let file_path = dir.path().join(filename); + let full_file_path = file_path.clone(); + let mut file = File::create(file_path).unwrap(); + writeln!(file, "{}", module_src).unwrap(); + let result = roc_load::load_and_typecheck( + arena, + full_file_path, + dir.path(), + exposed_types, + roc_target::TargetInfo::default_x86_64(), + RenderTarget::Generic, + ); + drop(file); + + dir.close().unwrap(); + + result + }; + + (module_src, loaded) + } + + fn infer_expr_help_new<'a>( + arena: &'a Bump, + expr_src: &'a str, + ) -> Result< + ( + String, + Vec, + Vec, + Vec, + ModuleId, + Interns, + ), + LoadingProblem<'a>, + > { + let (module_src, result) = run_load_and_infer(arena, expr_src); + let LoadedModule { + module_id: home, + mut can_problems, + mut type_problems, + interns, + mut solved, + exposed_to_host, + mut declarations_by_id, + .. + } = result?; + + let can_problems = can_problems.remove(&home).unwrap_or_default(); + let type_problems = type_problems.remove(&home).unwrap_or_default(); + + let subs = solved.inner_mut(); + + for var in exposed_to_host.values() { + name_all_type_vars(*var, subs); + } + + let mut mono_problems = Vec::new(); + + // MONO + + if type_problems.is_empty() && can_problems.is_empty() { + let arena = Bump::new(); + + assert!(exposed_to_host.len() == 1); + let (sym, _var) = exposed_to_host.into_iter().next().unwrap(); + + let home_decls = declarations_by_id.remove(&home).unwrap(); + let (loc_expr, var) = home_decls + .into_iter() + .find_map(|decl| match decl { + Declaration::Declare(def) => match def.loc_pattern.value { + Pattern::Identifier(s) if s == sym => Some((def.loc_expr, def.expr_var)), + _ => None, + }, + _ => None, + }) + .expect("No expression to monomorphize found!"); + + // Compile and add all the Procs before adding main + let mut procs = Procs::new_in(&arena); + let mut ident_ids = interns.all_ident_ids.get(&home).unwrap().clone(); + let mut update_mode_ids = UpdateModeIds::new(); + + // Populate Procs and Subs, and get the low-level Expr from the canonical Expr + let target_info = roc_target::TargetInfo::default_x86_64(); + let mut layout_cache = LayoutCache::new(target_info); + let mut mono_env = roc_mono::ir::Env { + arena: &arena, + subs, + problems: &mut mono_problems, + home, + ident_ids: &mut ident_ids, + update_mode_ids: &mut update_mode_ids, + target_info, + // call_specialization_counter=0 is reserved + call_specialization_counter: 1, + }; + let _mono_expr = Stmt::new( + &mut mono_env, + loc_expr.value, + var, + &mut procs, + &mut layout_cache, + ); + } + + Ok(( + module_src, + type_problems, + can_problems, + mono_problems, + home, + interns, + )) + } + + fn list_reports_new(arena: &Bump, src: &str, finalize_render: F) -> String + where + F: FnOnce(RocDocBuilder<'_>, &mut String), + { + use ven_pretty::DocAllocator; + + let filename = filename_from_string(r"\code\proj\Main.roc"); + + let mut buf = String::new(); + + match infer_expr_help_new(arena, src) { + Err(LoadingProblem::FormattedReport(fail)) => fail, + Ok((module_src, type_problems, can_problems, mono_problems, home, interns)) => { + let lines = LineInfo::new(&module_src); + let src_lines: Vec<&str> = module_src.split('\n').collect(); + let mut reports = Vec::new(); + + let alloc = RocDocAllocator::new(&src_lines, home, &interns); + + for problem in can_problems { + let report = can_problem(&alloc, &lines, filename.clone(), problem.clone()); + reports.push(report); + } + + for problem in type_problems { + if let Some(report) = + type_problem(&alloc, &lines, filename.clone(), problem.clone()) + { + reports.push(report); + } + } + + for problem in mono_problems { + let report = mono_problem(&alloc, &lines, filename.clone(), problem.clone()); + reports.push(report); + } + + let has_reports = !reports.is_empty(); + + let doc = alloc + .stack(reports.into_iter().map(|v| v.pretty(&alloc))) + .append(if has_reports { + alloc.line() + } else { + alloc.nil() + }); + + finalize_render(doc, &mut buf); + buf + } + Err(other) => { + assert!(false, "failed to load: {:?}", other); + unreachable!() + } + } + } + fn infer_expr_help<'a>( arena: &'a Bump, expr_src: &'a str, @@ -85,12 +296,14 @@ mod test_reporting { } let mut unify_problems = Vec::new(); + let mut abilities_store = AbilitiesStore::default(); let (_content, mut subs) = infer_expr( subs, &mut unify_problems, &constraints, &constraint, &mut solve_aliases, + &mut abilities_store, var, ); @@ -298,6 +511,27 @@ mod test_reporting { assert_eq!(readable, expected_rendering); } + fn new_report_problem_as(src: &str, expected_rendering: &str) { + let arena = Bump::new(); + + let finalize_render = |doc: RocDocBuilder<'_>, buf: &mut String| { + doc.1 + .render_raw(70, &mut roc_reporting::report::CiWrite::new(buf)) + .expect("list_reports") + }; + + let buf = list_reports_new(&arena, src, finalize_render); + + // convenient to copy-paste the generated message + if buf != expected_rendering { + for line in buf.split('\n') { + println!(" {}", line); + } + } + + assert_multiline_str_eq!(expected_rendering, buf.as_str()); + } + fn human_readable(str: &str) -> String { str.replace(ANSI_STYLE_CODES.red, "") .replace(ANSI_STYLE_CODES.white, "") @@ -8682,7 +8916,7 @@ I need all branches in an `if` to have the same type! #[test] fn ability_demands_not_indented_with_first() { - report_problem_as( + new_report_problem_as( indoc!( r#" Eq has @@ -8699,19 +8933,18 @@ I need all branches in an `if` to have the same type! I was partway through parsing an ability definition, but I got stuck here: - 2│ eq : a, a -> U64 | a has Eq - 3│ neq : a, a -> U64 | a has Eq - ^ + 5│ eq : a, a -> U64 | a has Eq + 6│ neq : a, a -> U64 | a has Eq + ^ - I suspect this line is indented too much (by 4 spaces) - "# + I suspect this line is indented too much (by 4 spaces)"# ), ) } #[test] fn ability_demand_value_has_args() { - report_problem_as( + new_report_problem_as( indoc!( r#" Eq has @@ -8727,12 +8960,11 @@ I need all branches in an `if` to have the same type! I was partway through parsing an ability definition, but I got stuck here: - 2│ eq b c : a, a -> U64 | a has Eq - ^ + 5│ eq b c : a, a -> U64 | a has Eq + ^ I was expecting to see a : annotating the signature of this value - next. - "# + next."# ), ) } @@ -8898,8 +9130,8 @@ I need all branches in an `if` to have the same type! } #[test] - fn bad_type_parameter_in_ability() { - report_problem_as( + fn ability_bad_type_parameter() { + new_report_problem_as( indoc!( r#" Hash a b c has @@ -8914,8 +9146,8 @@ I need all branches in an `if` to have the same type! The definition of the `Hash` ability includes type variables: - 1│ Hash a b c has - ^^^^^ + 4│ Hash a b c has + ^^^^^ Abilities cannot depend on type variables, but their member values can! @@ -8924,8 +9156,8 @@ I need all branches in an `if` to have the same type! `Hash` is not used anywhere in your code. - 1│ Hash a b c has - ^^^^ + 4│ Hash a b c has + ^^^^ If you didn't intend on using `Hash` then remove it so future readers of your code don't wonder why it is there. @@ -8936,12 +9168,12 @@ I need all branches in an `if` to have the same type! #[test] fn alias_in_has_clause() { - report_problem_as( + new_report_problem_as( indoc!( r#" - Hash has hash : a, b -> U64 | a has Hash, b has Bool + app "test" provides [ hash ] to "./platform" - 1 + Hash has hash : a, b -> U64 | a has Hash, b has Bool "# ), indoc!( @@ -8950,18 +9182,8 @@ I need all branches in an `if` to have the same type! The type referenced in this "has" clause is not an ability: - 1│ Hash has hash : a, b -> U64 | a has Hash, b has Bool + 3│ Hash has hash : a, b -> U64 | a has Hash, b has Bool ^^^^ - - ── UNUSED DEFINITION ─────────────────────────────────────────────────────────── - - `hash` is not used anywhere in your code. - - 1│ Hash has hash : a, b -> U64 | a has Hash, b has Bool - ^^^^ - - If you didn't intend on using `hash` then remove it so future readers of - your code don't wonder why it is there. "# ), ) @@ -8969,12 +9191,12 @@ I need all branches in an `if` to have the same type! #[test] fn shadowed_type_variable_in_has_clause() { - report_problem_as( + new_report_problem_as( indoc!( r#" - Ab1 has ab1 : a -> {} | a has Ab1, a has Ab1 + app "test" provides [ ab1 ] to "./platform" - 1 + Ab1 has ab1 : a -> {} | a has Ab1, a has Ab1 "# ), indoc!( @@ -8983,26 +9205,16 @@ I need all branches in an `if` to have the same type! The `a` name is first defined here: - 1│ Ab1 has ab1 : a -> {} | a has Ab1, a has Ab1 + 3│ Ab1 has ab1 : a -> {} | a has Ab1, a has Ab1 ^^^^^^^^^ But then it's defined a second time here: - 1│ Ab1 has ab1 : a -> {} | a has Ab1, a has Ab1 + 3│ Ab1 has ab1 : a -> {} | a has Ab1, a has Ab1 ^^^^^^^^^ Since these variables have the same name, it's easy to use the wrong one on accident. Give one of them a new name. - - ── UNUSED DEFINITION ─────────────────────────────────────────────────────────── - - `ab1` is not used anywhere in your code. - - 1│ Ab1 has ab1 : a -> {} | a has Ab1, a has Ab1 - ^^^ - - If you didn't intend on using `ab1` then remove it so future readers of - your code don't wonder why it is there. "# ), ) @@ -9010,7 +9222,7 @@ I need all branches in an `if` to have the same type! #[test] fn alias_using_ability() { - report_problem_as( + new_report_problem_as( indoc!( r#" Ability has ab : a -> {} | a has Ability @@ -9027,8 +9239,8 @@ I need all branches in an `if` to have the same type! The definition of the `Alias` aliases references the ability `Ability`: - 3│ Alias : Ability - ^^^^^ + 6│ Alias : Ability + ^^^^^ Abilities are not types, but you can add an ability constraint to a type variable `a` by writing @@ -9041,8 +9253,8 @@ I need all branches in an `if` to have the same type! `ab` is not used anywhere in your code. - 1│ Ability has ab : a -> {} | a has Ability - ^^ + 4│ Ability has ab : a -> {} | a has Ability + ^^ If you didn't intend on using `ab` then remove it so future readers of your code don't wonder why it is there. @@ -9053,7 +9265,7 @@ I need all branches in an `if` to have the same type! #[test] fn ability_shadows_ability() { - report_problem_as( + new_report_problem_as( indoc!( r#" Ability has ab : a -> U64 | a has Ability @@ -9069,13 +9281,13 @@ I need all branches in an `if` to have the same type! The `Ability` name is first defined here: - 1│ Ability has ab : a -> U64 | a has Ability - ^^^^^^^ + 4│ Ability has ab : a -> U64 | a has Ability + ^^^^^^^ But then it's defined a second time here: - 3│ Ability has ab1 : a -> U64 | a has Ability - ^^^^^^^ + 6│ Ability has ab1 : a -> U64 | a has Ability + ^^^^^^^ Since these abilities have the same name, it's easy to use the wrong one on accident. Give one of them a new name. @@ -9084,8 +9296,8 @@ I need all branches in an `if` to have the same type! `ab` is not used anywhere in your code. - 1│ Ability has ab : a -> U64 | a has Ability - ^^ + 4│ Ability has ab : a -> U64 | a has Ability + ^^ If you didn't intend on using `ab` then remove it so future readers of your code don't wonder why it is there. @@ -9096,12 +9308,12 @@ I need all branches in an `if` to have the same type! #[test] fn ability_member_does_not_bind_ability() { - report_problem_as( + new_report_problem_as( indoc!( r#" - Ability has ab : {} -> {} + app "test" provides [ ] to "./platform" - 1 + Ability has ab : {} -> {} "# ), indoc!( @@ -9111,7 +9323,7 @@ I need all branches in an `if` to have the same type! The definition of the ability member `ab` does not include a `has` clause binding a type variable to the ability `Ability`: - 1│ Ability has ab : {} -> {} + 3│ Ability has ab : {} -> {} ^^ Ability members must include a `has` clause binding a type variable to @@ -9125,7 +9337,7 @@ I need all branches in an `if` to have the same type! `Ability` is not used anywhere in your code. - 1│ Ability has ab : {} -> {} + 3│ Ability has ab : {} -> {} ^^^^^^^ If you didn't intend on using `Ability` then remove it so future readers @@ -9135,7 +9347,7 @@ I need all branches in an `if` to have the same type! `ab` is not used anywhere in your code. - 1│ Ability has ab : {} -> {} + 3│ Ability has ab : {} -> {} ^^ If you didn't intend on using `ab` then remove it so future readers of @@ -9147,13 +9359,13 @@ I need all branches in an `if` to have the same type! #[test] fn ability_member_binds_extra_ability() { - report_problem_as( + new_report_problem_as( indoc!( r#" + app "test" provides [ eq ] to "./platform" + Eq has eq : a, a -> Bool | a has Eq Hash has hash : a, b -> U64 | a has Eq, b has Hash - - 1 "# ), indoc!( @@ -9163,7 +9375,7 @@ I need all branches in an `if` to have the same type! The definition of the ability member `hash` includes a has clause binding an ability it is not a part of: - 2│ Hash has hash : a, b -> U64 | a has Eq, b has Hash + 4│ Hash has hash : a, b -> U64 | a has Eq, b has Hash ^^^^^^^^ Currently, ability members can only bind variables to the ability they @@ -9173,19 +9385,9 @@ I need all branches in an `if` to have the same type! ── UNUSED DEFINITION ─────────────────────────────────────────────────────────── - `eq` is not used anywhere in your code. - - 1│ Eq has eq : a, a -> Bool | a has Eq - ^^ - - If you didn't intend on using `eq` then remove it so future readers of - your code don't wonder why it is there. - - ── UNUSED DEFINITION ─────────────────────────────────────────────────────────── - `hash` is not used anywhere in your code. - 2│ Hash has hash : a, b -> U64 | a has Eq, b has Hash + 4│ Hash has hash : a, b -> U64 | a has Eq, b has Hash ^^^^ If you didn't intend on using `hash` then remove it so future readers of @@ -9196,15 +9398,55 @@ I need all branches in an `if` to have the same type! } #[test] - fn has_clause_outside_of_ability() { - report_problem_as( + fn ability_member_binds_parent_twice() { + new_report_problem_as( indoc!( r#" + app "test" provides [ ] to "./platform" + + Eq has eq : a, b -> Bool | a has Eq, b has Eq + "# + ), + indoc!( + r#" + ── ABILITY MEMBER BINDS MULTIPLE VARIABLES ───────────────────────────────────── + + The definition of the ability member `eq` includes multiple variables + bound to the `Eq`` ability:` + + 3│ Eq has eq : a, b -> Bool | a has Eq, b has Eq + ^^^^^^^^^^^^^^^^^^ + + Ability members can only bind one type variable to their parent + ability. Otherwise, I wouldn't know what type implements an ability by + looking at specializations! + + Hint: Did you mean to only bind `a` to `Eq`? + + ── UNUSED DEFINITION ─────────────────────────────────────────────────────────── + + `eq` is not used anywhere in your code. + + 3│ Eq has eq : a, b -> Bool | a has Eq, b has Eq + ^^ + + If you didn't intend on using `eq` then remove it so future readers of + your code don't wonder why it is there. + "# + ), + ) + } + + #[test] + fn has_clause_outside_of_ability() { + new_report_problem_as( + indoc!( + r#" + app "test" provides [ hash, f ] to "./platform" + Hash has hash : a -> U64 | a has Hash f : a -> U64 | a has Hash - - f "# ), indoc!( @@ -9213,21 +9455,206 @@ I need all branches in an `if` to have the same type! A `has` clause is not allowed here: - 3│ f : a -> U64 | a has Hash + 5│ f : a -> U64 | a has Hash ^^^^^^^^^^ `has` clauses can only be specified on the top-level type annotation of an ability member. + "# + ), + ) + } - ── UNUSED DEFINITION ─────────────────────────────────────────────────────────── + #[test] + fn ability_specialization_with_non_implementing_type() { + new_report_problem_as( + indoc!( + r#" + app "test" provides [ hash ] to "./platform" - `hash` is not used anywhere in your code. + Hash has hash : a -> U64 | a has Hash - 1│ Hash has hash : a -> U64 | a has Hash - ^^^^ + hash = \{} -> 0u64 + "# + ), + indoc!( + r#" + ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── - If you didn't intend on using `hash` then remove it so future readers of - your code don't wonder why it is there. + Something is off with this specialization of `hash`: + + 5│ hash = \{} -> 0u64 + ^^^^ + + This value is a declared specialization of type: + + {}a -> U64 + + But the type annotation on `hash` says it must match: + + a -> U64 | a has Hash + + Note: Some types in this specialization don't implement the abilities + they are expected to. I found the following missing implementations: + + {}a does not implement Hash + "# + ), + ) + } + + #[test] + fn ability_specialization_does_not_match_type() { + new_report_problem_as( + indoc!( + r#" + app "test" provides [ hash ] to "./platform" + + Hash has hash : a -> U64 | a has Hash + + Id := U32 + + hash = \$Id n -> n + "# + ), + indoc!( + r#" + ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + + Something is off with this specialization of `hash`: + + 7│ hash = \$Id n -> n + ^^^^ + + This value is a declared specialization of type: + + Id -> U32 + + But the type annotation on `hash` says it must match: + + Id -> U64 + "# + ), + ) + } + + #[test] + fn ability_specialization_is_incomplete() { + new_report_problem_as( + indoc!( + r#" + app "test" provides [ eq, le ] to "./platform" + + Eq has + eq : a, a -> Bool | a has Eq + le : a, a -> Bool | a has Eq + + Id := U64 + + eq = \$Id m, $Id n -> m == n + "# + ), + indoc!( + r#" + ── INCOMPLETE ABILITY IMPLEMENTATION ─────────────────────────────────────────── + + The type `Id` does not fully implement the ability `Eq`. The following + specializations are missing: + + A specialization for `le`, which is defined here: + + 5│ le : a, a -> Bool | a has Eq + ^^ + + Note: `Id` specializes the following members of `Eq`: + + `eq`, specialized here: + + 9│ eq = \$Id m, $Id n -> m == n + ^^ + "# + ), + ) + } + + #[test] + fn ability_specialization_overly_generalized() { + new_report_problem_as( + indoc!( + r#" + app "test" provides [ hash ] to "./platform" + + Hash has + hash : a -> U64 | a has Hash + + hash = \_ -> 0u64 + "# + ), + indoc!( + r#" + ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + + This specialization of `hash` is overly general: + + 6│ hash = \_ -> 0u64 + ^^^^ + + This value is a declared specialization of type: + + a -> U64 + + But the type annotation on `hash` says it must match: + + a -> U64 | a has Hash + + Note: The specialized type is too general, and does not provide a + concrete type where a type variable is bound to an ability. + + Specializations can only be made for concrete types. If you have a + generic implementation for this value, perhaps you don't need an + ability? + "# + ), + ) + } + + #[test] + fn ability_specialization_conflicting_specialization_types() { + new_report_problem_as( + indoc!( + r#" + app "test" provides [ eq ] to "./platform" + + Eq has + eq : a, a -> Bool | a has Eq + + You := {} + AndI := {} + + eq = \$You {}, $AndI {} -> False + "# + ), + indoc!( + r#" + ── TYPE MISMATCH ─────────────────────────────────────────────────────────────── + + Something is off with this specialization of `eq`: + + 9│ eq = \$You {}, $AndI {} -> False + ^^ + + This value is a declared specialization of type: + + You, AndI -> [ False, True ] + + But the type annotation on `eq` says it must match: + + You, You -> Bool + + Tip: Type comparisons between an opaque type are only ever equal if + both types are the same opaque type. Did you mean to create an opaque + type by wrapping it? If I have an opaque type Age := U32 I can create + an instance of this opaque type by doing @Age 23. "# ), ) diff --git a/roc-for-elm-programmers.md b/roc-for-elm-programmers.md index f932b10ed4..204ad125a9 100644 --- a/roc-for-elm-programmers.md +++ b/roc-for-elm-programmers.md @@ -1130,7 +1130,7 @@ so calculations involving them take longer. Roc does not let floating point calculations result in `Infinity`, `-Infinity`, or `NaN`. Any operation which would result in one of these -(such as `sqrt` or `/`) will return a `Result`. +(such as `sqrt` or `/`) will panic. Similarly to how there are different sizes of floating point numbers, there are also different sizes of integer to choose from: