diff --git a/crates/compiler/module/src/symbol.rs b/crates/compiler/module/src/symbol.rs index 6984d6fdf5..097a8fd72c 100644 --- a/crates/compiler/module/src/symbol.rs +++ b/crates/compiler/module/src/symbol.rs @@ -50,6 +50,7 @@ const PRETTY_PRINT_DEBUG_SYMBOLS: bool = true; pub const DERIVABLE_ABILITIES: &[(Symbol, &[Symbol])] = &[ (Symbol::ENCODE_ENCODING, &[Symbol::ENCODE_TO_ENCODER]), (Symbol::DECODE_DECODING, &[Symbol::DECODE_DECODER]), + (Symbol::HASH_HASH_ABILITY, &[Symbol::HASH_HASH]), ]; /// In Debug builds only, Symbol has a name() method that lets diff --git a/crates/compiler/solve/src/ability.rs b/crates/compiler/solve/src/ability.rs index d25fe688e5..77b6ae7f2e 100644 --- a/crates/compiler/solve/src/ability.rs +++ b/crates/compiler/solve/src/ability.rs @@ -272,6 +272,10 @@ impl ObligationCache { var, )), + Symbol::HASH_HASH_ABILITY => { + Some(DeriveHash::is_derivable(self, abilities_store, subs, var)) + } + _ => None, }; @@ -893,6 +897,95 @@ impl DerivableVisitor for DeriveDecoding { } } +struct DeriveHash; +impl DerivableVisitor for DeriveHash { + const ABILITY: Symbol = Symbol::HASH_HASH_ABILITY; + + #[inline(always)] + fn is_derivable_builtin_opaque(symbol: Symbol) -> bool { + is_builtin_number_alias(symbol) + } + + #[inline(always)] + fn visit_recursion(_var: Variable) -> Result { + Ok(Descend(true)) + } + + #[inline(always)] + fn visit_apply(var: Variable, symbol: Symbol) -> Result { + if matches!( + symbol, + Symbol::LIST_LIST | Symbol::SET_SET | Symbol::DICT_DICT | Symbol::STR_STR, + ) { + Ok(Descend(true)) + } else { + Err(NotDerivable { + var, + context: NotDerivableContext::NoContext, + }) + } + } + + #[inline(always)] + fn visit_record( + subs: &Subs, + var: Variable, + fields: RecordFields, + ) -> Result { + for (field_name, _, field) in fields.iter_all() { + if subs[field].is_optional() { + return Err(NotDerivable { + var, + context: NotDerivableContext::Decode(NotDerivableDecode::OptionalRecordField( + subs[field_name].clone(), + )), + }); + } + } + + Ok(Descend(true)) + } + + #[inline(always)] + fn visit_tag_union(_var: Variable) -> Result { + Ok(Descend(true)) + } + + #[inline(always)] + fn visit_recursive_tag_union(_var: Variable) -> Result { + Ok(Descend(true)) + } + + #[inline(always)] + fn visit_function_or_tag_union(_var: Variable) -> Result { + Ok(Descend(true)) + } + + #[inline(always)] + fn visit_empty_record(_var: Variable) -> Result<(), NotDerivable> { + Ok(()) + } + + #[inline(always)] + fn visit_empty_tag_union(_var: Variable) -> Result<(), NotDerivable> { + Ok(()) + } + + #[inline(always)] + fn visit_alias(_var: Variable, symbol: Symbol) -> Result { + if is_builtin_number_alias(symbol) { + Ok(Descend(false)) + } else { + Ok(Descend(true)) + } + } + + #[inline(always)] + fn visit_ranged_number(_var: Variable, _range: NumericRange) -> Result<(), NotDerivable> { + Ok(()) + } +} + /// Determines what type implements an ability member of a specialized signature, given the /// [MustImplementAbility] constraints of the signature. pub fn type_implementing_specialization( diff --git a/crates/compiler/types/src/subs.rs b/crates/compiler/types/src/subs.rs index b966adbc4d..b0e4042c01 100644 --- a/crates/compiler/types/src/subs.rs +++ b/crates/compiler/types/src/subs.rs @@ -3868,11 +3868,11 @@ fn flat_type_to_err_type( ErrorType::TagUnion(sub_tags.union(err_tags), sub_ext) } - ErrorType::FlexVar(var) => { + ErrorType::FlexVar(var) | ErrorType::FlexAbleVar(var, _) => { ErrorType::TagUnion(err_tags, TypeExt::FlexOpen(var)) } - ErrorType::RigidVar(var) => { + ErrorType::RigidVar(var) | ErrorType::RigidAbleVar(var, _)=> { ErrorType::TagUnion(err_tags, TypeExt::RigidOpen(var)) } @@ -3896,11 +3896,11 @@ fn flat_type_to_err_type( ErrorType::TagUnion(sub_tags.union(err_tags), sub_ext) } - ErrorType::FlexVar(var) => { + ErrorType::FlexVar(var) | ErrorType::FlexAbleVar(var, _) => { ErrorType::TagUnion(err_tags, TypeExt::FlexOpen(var)) } - ErrorType::RigidVar(var) => { + ErrorType::RigidVar(var) | ErrorType::RigidAbleVar(var, _)=> { ErrorType::TagUnion(err_tags, TypeExt::RigidOpen(var)) } diff --git a/crates/reporting/tests/test_reporting.rs b/crates/reporting/tests/test_reporting.rs index 142e7f2b36..c3f6daf23e 100644 --- a/crates/reporting/tests/test_reporting.rs +++ b/crates/reporting/tests/test_reporting.rs @@ -10848,4 +10848,150 @@ All branches in an `if` must have the same type! Tip: Did you mean to use `Bool.false` rather than `False`? "### ); + + test_report!( + derive_hash_for_function, + indoc!( + r#" + app "test" provides [A] to "./platform" + + A a := a -> a has [Hash] + "# + ), + @r###" + ── INCOMPLETE ABILITY IMPLEMENTATION ───────────────────── /code/proj/Main.roc ─ + + Roc can't derive an implementation of the `Hash.Hash` for `A`: + + 3│ A a := a -> a has [Hash] + ^^^^ + + Note: `Hash` cannot be generated for functions. + + Tip: You can define a custom implementation of `Hash.Hash` for `A`. + "### + ); + + test_report!( + derive_hash_for_non_hash_opaque, + indoc!( + r#" + app "test" provides [A] to "./platform" + + A := B has [Hash] + + B := {} + "# + ), + @r###" + ── INCOMPLETE ABILITY IMPLEMENTATION ───────────────────── /code/proj/Main.roc ─ + + Roc can't derive an implementation of the `Hash.Hash` for `A`: + + 3│ A := B has [Hash] + ^^^^ + + Tip: `B` does not implement `Hash`. Consider adding a custom + implementation or `has Hash.Hash` to the definition of `B`. + + Tip: You can define a custom implementation of `Hash.Hash` for `A`. + "### + ); + + test_report!( + derive_hash_for_other_has_hash, + indoc!( + r#" + app "test" provides [A] to "./platform" + + A := B has [Hash] + + B := {} has [Hash] + "# + ), + @"" // no error + ); + + test_report!( + derive_hash_for_recursive_deriving, + indoc!( + r#" + app "test" provides [MyNat] to "./platform" + + MyNat := [S MyNat, Z] has [Hash] + "# + ), + @"" // no error + ); + + test_report!( + derive_hash_for_record, + indoc!( + r#" + app "test" provides [main] to "./platform" + + foo : a -> {} | a has Hash + + main = foo {a: "", b: 1} + "# + ), + @"" // no error + ); + + test_report!( + derive_hash_for_tag, + indoc!( + r#" + app "test" provides [main] to "./platform" + + foo : a -> {} | a has Hash + + t : [A {}, B U8 U64, C Str] + + main = foo t + "# + ), + @"" // no error + ); + + test_report!( + cannot_derive_hash_for_function, + indoc!( + r#" + app "test" provides [main] to "./platform" + + foo : a -> {} | a has Hash + + main = foo (\x -> x) + "# + ), + @r###" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + + This expression has a type that does not implement the abilities it's expected to: + + 5│ main = foo (\x -> x) + ^^^^^^^ + + Roc can't generate an implementation of the `Hash.Hash` ability for + + a -> a + + Note: `Hash` cannot be generated for functions. + "### + ); + + test_report!( + cannot_derive_hash_for_structure_containing_function, + indoc!( + r#" + app "test" provides [main] to "./platform" + + foo : a -> {} | a has Hash + + main = foo (A (\x -> x) B) + "# + ), + @"" + ); }