mirror of
https://github.com/roc-lang/roc.git
synced 2024-09-22 00:09:33 +03:00
Merge pull request #4199 from roc-lang/hash-obligation-checking
Obligation checking for the `Hash` ability
This commit is contained in:
commit
7bd6523175
@ -22,7 +22,7 @@ Hash has
|
||||
## Hashes a value into a [Hasher].
|
||||
## Note that [hash] does not produce a hash value itself; the hasher must be
|
||||
## [complete]d in order to extract the hash value.
|
||||
hash : a, hasher -> hasher | a has Hash, hasher has Hasher
|
||||
hash : hasher, a -> hasher | a has Hash, hasher has Hasher
|
||||
|
||||
## Describes a hashing algorithm that is fed bytes and produces an integer hash.
|
||||
##
|
||||
|
@ -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
|
||||
|
@ -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<Descend, NotDerivable> {
|
||||
Ok(Descend(true))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_apply(var: Variable, symbol: Symbol) -> Result<Descend, NotDerivable> {
|
||||
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<Descend, NotDerivable> {
|
||||
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<Descend, NotDerivable> {
|
||||
Ok(Descend(true))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_recursive_tag_union(_var: Variable) -> Result<Descend, NotDerivable> {
|
||||
Ok(Descend(true))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_function_or_tag_union(_var: Variable) -> Result<Descend, NotDerivable> {
|
||||
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<Descend, NotDerivable> {
|
||||
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(
|
||||
|
@ -7833,9 +7833,9 @@ mod solve_expr {
|
||||
|
||||
Noop := {} has [Hash {hash}]
|
||||
|
||||
hash = \@Noop {}, hasher -> hasher
|
||||
hash = \hasher, @Noop {} -> hasher
|
||||
|
||||
main = \hasher -> hash (@Noop {}) hasher
|
||||
main = \hasher -> hash hasher (@Noop {})
|
||||
"#
|
||||
),
|
||||
"hasher -> hasher | hasher has Hasher",
|
||||
|
@ -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))
|
||||
}
|
||||
|
||||
|
@ -10848,4 +10848,169 @@ 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)
|
||||
"#
|
||||
),
|
||||
@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 (A (\x -> x) B)
|
||||
^^^^^^^^^^^^^
|
||||
|
||||
Roc can't generate an implementation of the `Hash.Hash` ability for
|
||||
|
||||
[A (a -> a) [B]a]b
|
||||
|
||||
In particular, an implementation for
|
||||
|
||||
a -> a
|
||||
|
||||
cannot be generated.
|
||||
|
||||
Note: `Hash` cannot be generated for functions.
|
||||
"###
|
||||
);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user