Merge pull request #4290 from roc-lang/impl-eq

Add the `Eq` ability
This commit is contained in:
Ayaz 2022-10-13 16:13:51 -05:00 committed by GitHub
commit e471f4b388
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 964 additions and 309 deletions

View File

@ -20,7 +20,7 @@ initialModel = \start -> {
cameFrom: Dict.empty,
}
cheapestOpen : (position -> F64), Model position -> Result position {}
cheapestOpen : (position -> F64), Model position -> Result position {} | position has Eq
cheapestOpen = \costFn, model ->
model.openSet
|> Set.toList
@ -35,13 +35,13 @@ cheapestOpen = \costFn, model ->
|> Result.map .position
|> Result.mapErr (\_ -> {})
reconstructPath : Dict position position, position -> List position
reconstructPath : Dict position position, position -> List position | position has Eq
reconstructPath = \cameFrom, goal ->
when Dict.get cameFrom goal is
Err _ -> []
Ok next -> List.append (reconstructPath cameFrom next) goal
updateCost : position, position, Model position -> Model position
updateCost : position, position, Model position -> Model position | position has Eq
updateCost = \current, neighbor, model ->
newCameFrom =
Dict.insert model.cameFrom neighbor current
@ -70,7 +70,7 @@ updateCost = \current, neighbor, model ->
else
model
astar : (position, position -> F64), (position -> Set position), position, Model position -> Result (List position) {}
astar : (position, position -> F64), (position -> Set position), position, Model position -> Result (List position) {} | position has Eq
astar = \costFn, moveFn, goal, model ->
when cheapestOpen (\source -> costFn source goal) model is
Err {} -> Err {}

View File

@ -1,8 +1,34 @@
interface Bool
exposes [Bool, true, false, and, or, not, isEq, isNotEq]
exposes [Bool, Eq, true, false, and, or, not, isEq, isNotEq]
imports []
Bool := [True, False]
## A type that can be compared for total equality.
##
## Total equality means that all values of the type can be compared to each
## other, and two values `a`, `b` are identical if and only if `isEq a b` is
## `Bool.true`.
##
## Not all types support total equality. For example, an [F32] or [F64] can
## be a `NaN` ([not a number](https://en.wikipedia.org/wiki/NaN)), and the [IEEE-754](https://en.wikipedia.org/wiki/IEEE_754)
## floating point standard specifies that two `NaN`s are never equal to each other.
Eq has
## Returns `Bool.true` if the two values are equal, and `Bool.false` otherwise.
##
## `a == b` is shorthand for `Bool.isEq a b`.
##
## When `isEq` is derived by the Roc compiler, values are compared via
## structural equality. Structural equality works as follows:
##
## 1. Tags are equal if they have the same tag name, and also their contents (if any) are equal.
## 2. Records are equal if all their fields are equal.
## 3. Collections ([Str], [List], [Dict], and [Set]) are equal if they are the same length, and also all their corresponding elements are equal.
## 4. [Num](Num#Num) values are equal if their numbers are equal, with one exception: if both arguments to `isEq` are *NaN*, then `isEq` returns `Bool.false`. See `Num.isNaN` for more about *NaN*.
## 5. Functions can never be compared for structural equality. Roc cannot derive `isEq` for types that contain functions!
isEq : a, a -> Bool | a has Eq
Bool := [True, False] has [Eq { isEq: boolIsEq }]
boolIsEq = \@Bool b1, @Bool b2 -> structuralEq b1 b2
## The boolean true value.
true : Bool
@ -67,25 +93,19 @@ or : Bool, Bool -> Bool
## Returns `Bool.false` when given `Bool.true`, and vice versa.
not : Bool -> Bool
## Returns `Bool.true` if the two values are *structurally equal*, and `Bool.false` otherwise.
##
## `a == b` is shorthand for `Bool.isEq a b`
##
## Structural equality works as follows:
##
## 1. Tags are equal if they have the same tag name, and also their contents (if any) are equal.
## 2. Records are equal if all their fields are equal.
## 3. Collections ([Str], [List], [Dict], and [Set]) are equal if they are the same length, and also all their corresponding elements are equal.
## 4. [Num](Num#Num) values are equal if their numbers are equal, with one exception: if both arguments to `isEq` are *NaN*, then `isEq` returns `Bool.false`. See `Num.isNaN` for more about *NaN*.
##
## Note that `isEq` takes `'val` instead of `val`, which means `isEq` does not
## accept arguments whose types contain functions.
isEq : a, a -> Bool
## Calls [isEq] on the given values, then calls [not] on the result.
##
## `a != b` is shorthand for `Bool.isNotEq a b`
##
## Note that `isNotEq` takes `'val` instead of `val`, which means `isNotEq` does not
## accept arguments whose types contain functions.
isNotEq : a, a -> Bool
isNotEq : a, a -> Bool | a has Eq
isNotEq = \a, b -> structuralNotEq a b
# INTERNAL COMPILER USE ONLY: used to lower calls to `isEq` to structural
# equality via the `Eq` low-level for derived types.
structuralEq : a, a -> Bool
# INTERNAL COMPILER USE ONLY: used to lower calls to `isNotEq` to structural
# inequality via the `NotEq` low-level for derived types.
structuralNotEq : a, a -> Bool

View File

@ -18,7 +18,7 @@ interface Dict
removeAll,
]
imports [
Bool.{ Bool },
Bool.{ Bool, Eq },
Result.{ Result },
List,
Num.{ Nat },
@ -72,7 +72,9 @@ interface Dict
## When comparing two dictionaries for equality, they are `==` only if their both their contents and their
## orderings match. This preserves the property that if `dict1 == dict2`, you should be able to rely on
## `fn dict1 == fn dict2` also being `Bool.true`, even if `fn` relies on the dictionary's ordering.
Dict k v := List [Pair k v]
Dict k v := List [Pair k v] has [Eq { isEq: dictEq }]
dictEq = \@Dict l1, @Dict l2 -> l1 == l2
## An empty dictionary.
empty : Dict k v
@ -81,7 +83,7 @@ empty = @Dict []
withCapacity : Nat -> Dict k v
withCapacity = \n -> @Dict (List.withCapacity n)
get : Dict k v, k -> Result v [KeyNotFound]*
get : Dict k v, k -> Result v [KeyNotFound]* | k has Eq
get = \@Dict list, needle ->
when List.findFirst list (\Pair key _ -> key == needle) is
Ok (Pair _ v) ->
@ -94,7 +96,7 @@ walk : Dict k v, state, (state, k, v -> state) -> state
walk = \@Dict list, initialState, transform ->
List.walk list initialState (\state, Pair k v -> transform state k v)
insert : Dict k v, k, v -> Dict k v
insert : Dict k v, k, v -> Dict k v | k has Eq
insert = \@Dict list, k, v ->
when List.findFirstIndex list (\Pair key _ -> key == k) is
Err NotFound ->
@ -109,7 +111,7 @@ len : Dict k v -> Nat
len = \@Dict list ->
List.len list
remove : Dict k v, k -> Dict k v
remove : Dict k v, k -> Dict k v | k has Eq
remove = \@Dict list, key ->
when List.findFirstIndex list (\Pair k _ -> k == key) is
Err NotFound ->
@ -124,11 +126,7 @@ remove = \@Dict list, key ->
|> @Dict
## Insert or remove a value in a Dict based on its presence
update :
Dict k v,
k,
([Present v, Missing] -> [Present v, Missing])
-> Dict k v
update : Dict k v, k, ([Present v, Missing] -> [Present v, Missing]) -> Dict k v | k has Eq
update = \dict, key, alter ->
possibleValue =
get dict key
@ -151,7 +149,7 @@ expect update empty "a" alterValue == single "a" Bool.false
expect update (single "a" Bool.false) "a" alterValue == single "a" Bool.true
expect update (single "a" Bool.true) "a" alterValue == empty
contains : Dict k v, k -> Bool
contains : Dict k v, k -> Bool | k has Eq
contains = \@Dict list, needle ->
step = \_, Pair key _val ->
if key == needle then
@ -178,18 +176,18 @@ values = \@Dict list ->
List.map list (\Pair _ v -> v)
# union : Dict k v, Dict k v -> Dict k v
insertAll : Dict k v, Dict k v -> Dict k v
insertAll : Dict k v, Dict k v -> Dict k v | k has Eq
insertAll = \xs, @Dict ys ->
List.walk ys xs (\state, Pair k v -> Dict.insertIfVacant state k v)
# intersection : Dict k v, Dict k v -> Dict k v
keepShared : Dict k v, Dict k v -> Dict k v
keepShared : Dict k v, Dict k v -> Dict k v | k has Eq
keepShared = \@Dict xs, ys ->
List.keepIf xs (\Pair k _ -> Dict.contains ys k)
|> @Dict
# difference : Dict k v, Dict k v -> Dict k v
removeAll : Dict k v, Dict k v -> Dict k v
removeAll : Dict k v, Dict k v -> Dict k v | k has Eq
removeAll = \xs, @Dict ys ->
List.walk ys xs (\state, Pair k _ -> Dict.remove state k)
@ -202,7 +200,7 @@ insertFresh = \@Dict list, k, v ->
|> List.append (Pair k v)
|> @Dict
insertIfVacant : Dict k v, k, v -> Dict k v
insertIfVacant : Dict k v, k, v -> Dict k v | k has Eq
insertIfVacant = \dict, key, value ->
if Dict.contains dict key then
dict

View File

@ -33,7 +33,7 @@ interface Json
F64,
Dec,
},
Bool.{ Bool },
Bool.{ Bool, Eq },
Result,
]

View File

@ -65,7 +65,7 @@ interface List
countIf,
]
imports [
Bool.{ Bool },
Bool.{ Bool, Eq },
Result.{ Result },
Num.{ Nat, Num, Int },
]
@ -354,7 +354,7 @@ join = \lists ->
List.walk lists (List.withCapacity totalLength) (\state, list -> List.concat state list)
contains : List a, a -> Bool
contains : List a, a -> Bool | a has Eq
contains = \list, needle ->
List.any list (\x -> x == needle)
@ -903,7 +903,7 @@ intersperse = \list, sep ->
## is considered to "start with" an empty list.
##
## If the first list is empty, this only returns `Bool.true` if the second list is empty.
startsWith : List elem, List elem -> Bool
startsWith : List elem, List elem -> Bool | elem has Eq
startsWith = \list, prefix ->
# TODO once we have seamless slices, verify that this wouldn't
# have better performance with a function like List.compareSublists
@ -915,7 +915,7 @@ startsWith = \list, prefix ->
## is considered to "end with" an empty list.
##
## If the first list is empty, this only returns `Bool.true` if the second list is empty.
endsWith : List elem, List elem -> Bool
endsWith : List elem, List elem -> Bool | elem has Eq
endsWith = \list, suffix ->
# TODO once we have seamless slices, verify that this wouldn't
# have better performance with a function like List.compareSublists
@ -944,7 +944,7 @@ split = \elements, userSplitIndex ->
## remaining elements after that occurrence. If the delimiter is not found, returns `Err`.
##
## List.splitFirst [Foo, Z, Bar, Z, Baz] Z == Ok { before: [Foo], after: [Bar, Baz] }
splitFirst : List elem, elem -> Result { before : List elem, after : List elem } [NotFound]*
splitFirst : List elem, elem -> Result { before : List elem, after : List elem } [NotFound]* | elem has Eq
splitFirst = \list, delimiter ->
when List.findFirstIndex list (\elem -> elem == delimiter) is
Ok index ->
@ -959,7 +959,7 @@ splitFirst = \list, delimiter ->
## remaining elements after that occurrence. If the delimiter is not found, returns `Err`.
##
## List.splitLast [Foo, Z, Bar, Z, Baz] Z == Ok { before: [Foo, Bar], after: [Baz] }
splitLast : List elem, elem -> Result { before : List elem, after : List elem } [NotFound]*
splitLast : List elem, elem -> Result { before : List elem, after : List elem } [NotFound]* | elem has Eq
splitLast = \list, delimiter ->
when List.findLastIndex list (\elem -> elem == delimiter) is
Ok index ->

View File

@ -793,7 +793,7 @@ div : Frac a, Frac a -> Frac a
divChecked : Frac a, Frac a -> Result (Frac a) [DivByZero]*
divChecked = \a, b ->
if b == 0 then
if Num.isZero b then
Err DivByZero
else
Ok (Num.div a b)
@ -802,7 +802,7 @@ divCeil : Int a, Int a -> Int a
divCeilChecked : Int a, Int a -> Result (Int a) [DivByZero]*
divCeilChecked = \a, b ->
if b == 0 then
if Num.isZero b then
Err DivByZero
else
Ok (Num.divCeil a b)
@ -827,7 +827,7 @@ divTrunc : Int a, Int a -> Int a
divTruncChecked : Int a, Int a -> Result (Int a) [DivByZero]*
divTruncChecked = \a, b ->
if b == 0 then
if Num.isZero b then
Err DivByZero
else
Ok (Num.divTrunc a b)
@ -847,7 +847,7 @@ rem : Int a, Int a -> Int a
remChecked : Int a, Int a -> Result (Int a) [DivByZero]*
remChecked = \a, b ->
if b == 0 then
if Num.isZero b then
Err DivByZero
else
Ok (Num.rem a b)

View File

@ -14,9 +14,11 @@ interface Set
intersection,
difference,
]
imports [List, Bool.{ Bool }, Dict.{ Dict }, Num.{ Nat }]
imports [List, Bool.{ Bool, Eq }, Dict.{ Dict }, Num.{ Nat }]
Set k := Dict.Dict k {}
Set k := Dict.Dict k {} has [Eq { isEq: setEq }]
setEq = \@Set d1, @Set d2 -> d1 == d2
fromDict : Dict k {} -> Set k
fromDict = \dict -> @Set dict
@ -35,7 +37,7 @@ single = \key ->
## Make sure never to insert a *NaN* to a [Set]! Because *NaN* is defined to be
## unequal to *NaN*, adding a *NaN* results in an entry that can never be
## retrieved or removed from the [Set].
insert : Set k, k -> Set k
insert : Set k, k -> Set k | k has Eq
insert = \@Set dict, key ->
dict
|> Dict.insert key {}
@ -75,11 +77,11 @@ expect
actual == 3
## Drops the given element from the set.
remove : Set k, k -> Set k
remove : Set k, k -> Set k | k has Eq
remove = \@Set dict, key ->
@Set (Dict.remove dict key)
contains : Set k, k -> Bool
contains : Set k, k -> Bool | k has Eq
contains = \set, key ->
set
|> Set.toDict
@ -89,21 +91,21 @@ toList : Set k -> List k
toList = \@Set dict ->
Dict.keys dict
fromList : List k -> Set k
fromList : List k -> Set k | k has Eq
fromList = \list ->
initial = @Set (Dict.withCapacity (List.len list))
List.walk list initial \set, key -> Set.insert set key
union : Set k, Set k -> Set k
union : Set k, Set k -> Set k | k has Eq
union = \@Set dict1, @Set dict2 ->
@Set (Dict.insertAll dict1 dict2)
intersection : Set k, Set k -> Set k
intersection : Set k, Set k -> Set k | k has Eq
intersection = \@Set dict1, @Set dict2 ->
@Set (Dict.keepShared dict1 dict2)
difference : Set k, Set k -> Set k
difference : Set k, Set k -> Set k | k has Eq
difference = \@Set dict1, @Set dict2 ->
@Set (Dict.removeAll dict1 dict2)

View File

@ -47,7 +47,7 @@ interface Str
withPrefix,
]
imports [
Bool.{ Bool },
Bool.{ Bool, Eq },
Result.{ Result },
List,
Num.{ Nat, Num, U8, U16, U32, U64, U128, I8, I16, I32, I64, I128, F32, F64, Dec },

View File

@ -504,8 +504,12 @@ impl IAbilitiesStore<Pending> {
let old_declared_impl = self.declared_implementations.insert(impl_key, member_impl);
debug_assert!(
old_declared_impl.is_none(),
"Replacing existing declared impl!"
old_declared_impl.is_none() ||
// Can happen between we import declared implementations during canonicalization, but
// implementation information only after solving
old_declared_impl.unwrap() == member_impl,
"Replacing existing declared impl: {:?}",
(impl_key, old_declared_impl)
);
}

View File

@ -191,8 +191,8 @@ map_symbol_to_lowlevel_and_arity! {
NumShiftRightZfBy; NUM_SHIFT_RIGHT_ZERO_FILL; 2,
NumToStr; NUM_TO_STR; 1,
Eq; BOOL_EQ; 2,
NotEq; BOOL_NEQ; 2,
Eq; BOOL_STRUCTURAL_EQ; 2,
NotEq; BOOL_STRUCTURAL_NOT_EQ; 2,
And; BOOL_AND; 2,
Or; BOOL_OR; 2,
Not; BOOL_NOT; 1,

View File

@ -374,6 +374,7 @@ pub fn canonicalize_module_defs<'a>(
if !output.references.has_type_or_value_lookup(symbol)
&& !exposed_symbols.contains(&symbol)
&& !scope.abilities_store.is_specialization_name(symbol)
&& !symbol.is_exposed_for_builtin_derivers()
{
env.problem(Problem::UnusedDef(symbol, region));
}

View File

@ -1510,8 +1510,12 @@ fn constrain_function_def(
loc_symbol.value,
Loc {
region: loc_function_def.region,
// todo can we use Type::Variable(expr_var) here?
value: signature.clone(),
// NOTE: we MUST use `expr_var` here so that the correct type variable is
// associated with the function. We prefer this to the annotation type, because the
// annotation type may be instantiated into a fresh type variable that is
// disassociated fromt the rest of the program.
// Below, we'll check that the function actually matches the annotation.
value: Type::Variable(expr_var),
},
);

View File

@ -76,6 +76,7 @@ pub enum DeriveBuiltin {
ToEncoder,
Decoder,
Hash,
IsEq,
}
impl TryFrom<Symbol> for DeriveBuiltin {
@ -86,6 +87,7 @@ impl TryFrom<Symbol> for DeriveBuiltin {
Symbol::ENCODE_TO_ENCODER => Ok(DeriveBuiltin::ToEncoder),
Symbol::DECODE_DECODER => Ok(DeriveBuiltin::Decoder),
Symbol::HASH_HASH => Ok(DeriveBuiltin::Hash),
Symbol::BOOL_IS_EQ => Ok(DeriveBuiltin::IsEq),
_ => Err(value),
}
}
@ -112,6 +114,13 @@ impl Derived {
}
FlatHash::Key(repr) => Ok(Derived::Key(DeriveKey::Hash(repr))),
},
DeriveBuiltin::IsEq => {
// If obligation checking passes, we always lower derived implementations of `isEq`
// to the `Eq` low-level, to be fulfilled by the backends.
Ok(Derived::SingleLambdaSetImmediate(
Symbol::BOOL_STRUCTURAL_EQ,
))
}
}
}
}

View File

@ -14,12 +14,12 @@ const SKIP_SUBS_CACHE: bool = {
// IFTTT: crates/compiler/load/src/lib.rs
const MODULES: &[(ModuleId, &str)] = &[
(ModuleId::BOOL, "Bool.roc"),
(ModuleId::DICT, "Dict.roc"),
(ModuleId::SET, "Set.roc"),
(ModuleId::RESULT, "Result.roc"),
(ModuleId::NUM, "Num.roc"),
(ModuleId::LIST, "List.roc"),
(ModuleId::STR, "Str.roc"),
(ModuleId::DICT, "Dict.roc"),
(ModuleId::SET, "Set.roc"),
(ModuleId::BOX, "Box.roc"),
(ModuleId::ENCODE, "Encode.roc"),
(ModuleId::DECODE, "Decode.roc"),

View File

@ -158,12 +158,12 @@ pub fn load_and_typecheck_str<'a>(
// IFTTT: crates/compiler/load/build.rs
const BOOL: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/Bool.dat")) as &[_];
const DICT: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/Dict.dat")) as &[_];
const SET: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/Set.dat")) as &[_];
const RESULT: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/Result.dat")) as &[_];
const NUM: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/Num.dat")) as &[_];
const LIST: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/List.dat")) as &[_];
const STR: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/Str.dat")) as &[_];
const DICT: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/Dict.dat")) as &[_];
const SET: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/Set.dat")) as &[_];
const BOX: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/Box.dat")) as &[_];
const ENCODE: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/Encode.dat")) as &[_];
const DECODE: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/Decode.dat")) as &[_];
@ -183,16 +183,17 @@ fn read_cached_types() -> MutMap<ModuleId, TypeState> {
// That is very strange, but we can solve it separately
if !cfg!(target_family = "wasm") && !cfg!(windows) && !SKIP_SUBS_CACHE {
output.insert(ModuleId::BOOL, deserialize_help(BOOL));
output.insert(ModuleId::RESULT, deserialize_help(RESULT));
output.insert(ModuleId::NUM, deserialize_help(NUM));
output.insert(ModuleId::LIST, deserialize_help(LIST));
output.insert(ModuleId::STR, deserialize_help(STR));
output.insert(ModuleId::DICT, deserialize_help(DICT));
output.insert(ModuleId::SET, deserialize_help(SET));
output.insert(ModuleId::BOX, deserialize_help(BOX));
output.insert(ModuleId::DICT, deserialize_help(DICT));
output.insert(ModuleId::SET, deserialize_help(SET));
output.insert(ModuleId::ENCODE, deserialize_help(ENCODE));
output.insert(ModuleId::DECODE, deserialize_help(DECODE));

View File

@ -344,6 +344,9 @@ fn start_phase<'a>(
)
});
// Add the declared abilities from the modules we import;
// we may not know all their types yet since type-solving happens in
// parallel, but we'll fill that in during type-checking our module.
abilities_store
.union(import_store.closure_from_imported(exposed_symbols));
}
@ -4332,6 +4335,9 @@ pub fn add_imports(
import_variables.push(list_len_type);
}
// Fill in the implementation information of the abilities from the modules we import, which we
// now know because all imported modules should be solved by now.
//
// TODO: see if we can reduce the amount of specializations we need to import.
// One idea is to just always assume external modules fulfill their specialization obligations
// and save lambda set resolution for mono.
@ -4466,7 +4472,11 @@ fn run_solve_solve(
// ability.
let exposed_vars_by_symbol: Vec<_> = solved_env
.vars_by_symbol()
.filter(|(k, _)| exposed_symbols.contains(k) || is_specialization_symbol(*k))
.filter(|(k, _)| {
exposed_symbols.contains(k)
|| is_specialization_symbol(*k)
|| k.is_exposed_for_builtin_derivers()
})
.collect();
(

View File

@ -22,7 +22,7 @@ initialModel = \start ->
}
cheapestOpen : (position -> F64), Model position -> Result position [KeyNotFound]*
cheapestOpen : (position -> F64), Model position -> Result position [KeyNotFound]* | position has Eq
cheapestOpen = \costFunction, model ->
folder = \resSmallestSoFar, position ->
@ -47,7 +47,7 @@ cheapestOpen = \costFunction, model ->
reconstructPath : Dict position position, position -> List position
reconstructPath : Dict position position, position -> List position | position has Eq
reconstructPath = \cameFrom, goal ->
when Dict.get cameFrom goal is
Err KeyNotFound ->
@ -56,7 +56,7 @@ reconstructPath = \cameFrom, goal ->
Ok next ->
List.append (reconstructPath cameFrom next) goal
updateCost : position, position, Model position -> Model position
updateCost : position, position, Model position -> Model position | position has Eq
updateCost = \current, neighbour, model ->
newCameFrom = Dict.insert model.cameFrom neighbour current
@ -80,12 +80,12 @@ updateCost = \current, neighbour, model ->
model
findPath : { costFunction: (position, position -> F64), moveFunction: (position -> Set position), start : position, end : position } -> Result (List position) [KeyNotFound]*
findPath : { costFunction: (position, position -> F64), moveFunction: (position -> Set position), start : position, end : position } -> Result (List position) [KeyNotFound]* | position has Eq
findPath = \{ costFunction, moveFunction, start, end } ->
astar costFunction moveFunction end (initialModel start)
astar : (position, position -> F64), (position -> Set position), position, Model position -> [Err [KeyNotFound]*, Ok (List position)]*
astar : (position, position -> F64), (position -> Set position), position, Model position -> [Err [KeyNotFound]*, Ok (List position)]* | position has Eq
astar = \costFn, moveFn, goal, model ->
when cheapestOpen (\position -> costFn goal position) model is
Err _ ->

View File

@ -481,12 +481,12 @@ fn load_astar() {
expect_types(
loaded_module,
hashmap! {
"findPath" => "{ costFunction : position, position -> F64, end : position, moveFunction : position -> Set position, start : position } -> Result (List position) [KeyNotFound]*",
"findPath" => "{ costFunction : position, position -> F64, end : position, moveFunction : position -> Set position, start : position } -> Result (List position) [KeyNotFound]* | position has Eq",
"initialModel" => "position -> Model position",
"reconstructPath" => "Dict position position, position -> List position",
"updateCost" => "position, position, Model position -> Model position",
"cheapestOpen" => "(position -> F64), Model position -> Result position [KeyNotFound]*",
"astar" => "(position, position -> F64), (position -> Set position), position, Model position -> [Err [KeyNotFound]*, Ok (List position)]*",
"reconstructPath" => "Dict position position, position -> List position | position has Eq",
"updateCost" => "position, position, Model position -> Model position | position has Eq",
"cheapestOpen" => "(position -> F64), Model position -> Result position [KeyNotFound]* | position has Eq",
"astar" => "(position, position -> F64), (position -> Set position), position, Model position -> [Err [KeyNotFound]*, Ok (List position)]* | position has Eq",
},
);
}

View File

@ -309,8 +309,8 @@ map_symbol_to_lowlevel! {
NumShiftRightBy <= NUM_SHIFT_RIGHT,
NumShiftRightZfBy <= NUM_SHIFT_RIGHT_ZERO_FILL,
NumToStr <= NUM_TO_STR,
Eq <= BOOL_EQ,
NotEq <= BOOL_NEQ,
Eq <= BOOL_STRUCTURAL_EQ,
NotEq <= BOOL_STRUCTURAL_NOT_EQ,
And <= BOOL_AND,
Or <= BOOL_OR,
Not <= BOOL_NOT,

View File

@ -51,6 +51,7 @@ 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]),
(Symbol::BOOL_EQ, &[Symbol::BOOL_IS_EQ]),
];
/// In Debug builds only, Symbol has a name() method that lets
@ -98,6 +99,17 @@ impl Symbol {
DERIVABLE_ABILITIES.iter().find(|(name, _)| *name == self)
}
/// A symbol that should never be exposed to userspace, but needs to be exposed
/// to compiled modules for deriving abilities for structural types.
pub fn is_exposed_for_builtin_derivers(&self) -> bool {
matches!(
self,
// The `structuralEq` call used deriving structural equality, which will wrap the `Eq`
// low-level implementation.
&Self::BOOL_STRUCTURAL_EQ
)
}
pub fn module_string<'a>(&self, interns: &'a Interns) -> &'a ModuleName {
interns
.module_ids
@ -796,6 +808,7 @@ macro_rules! define_builtins {
$(exposed_type=$exposed_type:literal)?
$(in_scope_for_hints=$in_scope_for_hints:literal)?
)*
$(unexposed $u_ident_id:literal $u_ident_const:ident: $u_ident_name:literal)*
}
)+
num_modules: $total:literal
@ -943,6 +956,9 @@ macro_rules! define_builtins {
$(
pub const $ident_const: Symbol = Symbol::new(ModuleId::$module_const, IdentId($ident_id));
)*
$(
pub const $u_ident_const: Symbol = Symbol::new(ModuleId::$module_const, IdentId($u_ident_id));
)*
)+
/// The default `Apply` types that should be in scope,
@ -1239,8 +1255,12 @@ define_builtins! {
4 BOOL_OR: "or"
5 BOOL_NOT: "not"
6 BOOL_XOR: "xor"
7 BOOL_EQ: "isEq"
8 BOOL_NEQ: "isNotEq"
7 BOOL_NEQ: "isNotEq"
8 BOOL_EQ: "Eq" exposed_type=true
9 BOOL_IS_EQ: "isEq"
10 BOOL_IS_EQ_IMPL: "boolIsEq"
unexposed 11 BOOL_STRUCTURAL_EQ: "structuralEq"
unexposed 12 BOOL_STRUCTURAL_NOT_EQ: "structuralNotEq"
}
5 STR: "Str" => {
0 STR_STR: "Str" exposed_apply_type=true // the Str.Str type alias

View File

@ -2214,7 +2214,16 @@ impl<'a> Layout<'a> {
// completely, but for now we represent it with the empty tag union
cacheable(Ok(Layout::VOID))
}
FlexAbleVar(_, _) | RigidAbleVar(_, _) => todo_abilities!("Not reachable yet"),
FlexAbleVar(_, _) | RigidAbleVar(_, _) => {
roc_debug_flags::dbg_do!(roc_debug_flags::ROC_NO_UNBOUND_LAYOUT, {
todo_abilities!("Able var is unbound!");
});
// If we encounter an unbound type var (e.g. `*` or `a`)
// then it's zero-sized; In the future we may drop this argument
// completely, but for now we represent it with the empty tag union
cacheable(Ok(Layout::VOID))
}
RecursionVar { structure, .. } => {
let structure_content = env.subs.get_content_without_compacting(structure);
Self::new_help(env, structure, *structure_content)

View File

@ -11,7 +11,7 @@ pub fn serialize_slice<T: Copy>(
written: usize,
) -> io::Result<usize> {
let alignment = std::mem::align_of::<T>();
let padding_bytes = round_to_multiple_of(written, alignment) - written;
let padding_bytes = next_multiple_of(written, alignment) - written;
for _ in 0..padding_bytes {
writer.write_all(&[0])?;
@ -27,7 +27,7 @@ pub fn deserialize_slice<T: Copy>(bytes: &[u8], length: usize, mut offset: usize
let alignment = std::mem::align_of::<T>();
let size = std::mem::size_of::<T>();
offset = round_to_multiple_of(offset, alignment);
offset = next_multiple_of(offset, alignment);
let byte_length = length * size;
let byte_slice = &bytes[offset..][..byte_length];
@ -199,8 +199,12 @@ unsafe fn slice_as_bytes<T>(slice: &[T]) -> &[u8] {
std::slice::from_raw_parts(ptr as *const u8, byte_length)
}
fn round_to_multiple_of(value: usize, base: usize) -> usize {
(value + (base - 1)) / base * base
// TODO check on https://github.com/rust-lang/rust/issues/88581 some time in the future
pub const fn next_multiple_of(lhs: usize, rhs: usize) -> usize {
match lhs % rhs {
0 => lhs,
r => lhs + (rhs - r),
}
}
#[cfg(test)]

View File

@ -5,7 +5,8 @@ use roc_error_macros::{internal_error, todo_abilities};
use roc_module::symbol::Symbol;
use roc_region::all::{Loc, Region};
use roc_solve_problem::{
NotDerivableContext, NotDerivableDecode, TypeError, UnderivableReason, Unfulfilled,
NotDerivableContext, NotDerivableDecode, NotDerivableEq, TypeError, UnderivableReason,
Unfulfilled,
};
use roc_types::num::NumericRange;
use roc_types::subs::{
@ -276,6 +277,8 @@ impl ObligationCache {
Some(DeriveHash::is_derivable(self, abilities_store, subs, var))
}
Symbol::BOOL_EQ => Some(DeriveEq::is_derivable(self, abilities_store, subs, var)),
_ => None,
};
@ -420,7 +423,7 @@ impl ObligationCache {
#[inline(always)]
#[rustfmt::skip]
fn is_builtin_number_alias(symbol: Symbol) -> bool {
fn is_builtin_int_alias(symbol: Symbol) -> bool {
matches!(symbol,
Symbol::NUM_U8 | Symbol::NUM_UNSIGNED8
| Symbol::NUM_U16 | Symbol::NUM_UNSIGNED16
@ -433,12 +436,32 @@ fn is_builtin_number_alias(symbol: Symbol) -> bool {
| Symbol::NUM_I64 | Symbol::NUM_SIGNED64
| Symbol::NUM_I128 | Symbol::NUM_SIGNED128
| Symbol::NUM_NAT | Symbol::NUM_NATURAL
)
}
#[inline(always)]
#[rustfmt::skip]
fn is_builtin_float_alias(symbol: Symbol) -> bool {
matches!(symbol,
| Symbol::NUM_F32 | Symbol::NUM_BINARY32
| Symbol::NUM_F64 | Symbol::NUM_BINARY64
)
}
#[inline(always)]
#[rustfmt::skip]
fn is_builtin_dec_alias(symbol: Symbol) -> bool {
matches!(symbol,
| Symbol::NUM_DEC | Symbol::NUM_DECIMAL,
)
}
#[inline(always)]
#[rustfmt::skip]
fn is_builtin_number_alias(symbol: Symbol) -> bool {
is_builtin_int_alias(symbol) || is_builtin_float_alias(symbol) || is_builtin_dec_alias(symbol)
}
struct NotDerivable {
var: Variable,
context: NotDerivableContext,
@ -986,6 +1009,106 @@ impl DerivableVisitor for DeriveHash {
}
}
struct DeriveEq;
impl DerivableVisitor for DeriveEq {
const ABILITY: Symbol = Symbol::BOOL_EQ;
#[inline(always)]
fn is_derivable_builtin_opaque(symbol: Symbol) -> bool {
is_builtin_int_alias(symbol) || is_builtin_dec_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
| Symbol::BOX_BOX_TYPE,
) {
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_float_alias(symbol) {
Err(NotDerivable {
var,
context: NotDerivableContext::Eq(NotDerivableEq::FloatingPoint),
})
} else 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> {
// Ranged numbers are allowed, because they are always possibly ints - floats can not have
// `isEq` derived, but if something were to be a float, we'd see it exactly as a float.
Ok(())
}
}
/// Determines what type implements an ability member of a specialized signature, given the
/// [MustImplementAbility] constraints of the signature.
pub fn type_implementing_specialization(

View File

@ -378,6 +378,7 @@ mod solve_expr {
let known_specializations = abilities_store.iter_declared_implementations().filter_map(
|(impl_key, member_impl)| match member_impl {
MemberImpl::Impl(impl_symbol) => {
dbg!(impl_symbol);
let specialization = abilities_store.specialization_info(*impl_symbol).expect(
"declared implementations should be resolved conclusively after solving",
);
@ -3469,7 +3470,7 @@ mod solve_expr {
Dict.insert
"#
),
"Dict k v, k, v -> Dict k v",
"Dict k v, k, v -> Dict k v | k has Eq",
);
}
@ -3730,7 +3731,7 @@ mod solve_expr {
infer_eq_without_problem(
indoc!(
r#"
reconstructPath : Dict position position, position -> List position
reconstructPath : Dict position position, position -> List position | position has Eq
reconstructPath = \cameFrom, goal ->
when Dict.get cameFrom goal is
Err KeyNotFound ->
@ -3742,7 +3743,7 @@ mod solve_expr {
reconstructPath
"#
),
"Dict position position, position -> List position",
"Dict position position, position -> List position | position has Eq",
);
}
@ -3777,7 +3778,7 @@ mod solve_expr {
Model position : { openSet : Set position }
cheapestOpen : Model position -> Result position [KeyNotFound]*
cheapestOpen : Model position -> Result position [KeyNotFound]* | position has Eq
cheapestOpen = \model ->
folder = \resSmallestSoFar, position ->
@ -3792,14 +3793,14 @@ mod solve_expr {
Set.walk model.openSet (Ok { position: boom {}, cost: 0.0 }) folder
|> Result.map (\x -> x.position)
astar : Model position -> Result position [KeyNotFound]*
astar : Model position -> Result position [KeyNotFound]* | position has Eq
astar = \model -> cheapestOpen model
main =
astar
"#
),
"Model position -> Result position [KeyNotFound]*",
"Model position -> Result position [KeyNotFound]* | position has Eq",
);
}
@ -4441,7 +4442,7 @@ mod solve_expr {
Key k : Num k
removeHelpEQGT : Key k, RBTree (Key k) v -> RBTree (Key k) v
removeHelpEQGT : Key k, RBTree (Key k) v -> RBTree (Key k) v | k has Eq
removeHelpEQGT = \targetKey, dict ->
when dict is
Node color key value left right ->
@ -4555,7 +4556,7 @@ mod solve_expr {
_ ->
Empty
removeHelp : Key k, RBTree (Key k) v -> RBTree (Key k) v
removeHelp : Key k, RBTree (Key k) v -> RBTree (Key k) v | k has Eq
removeHelp = \targetKey, dict ->
when dict is
Empty ->
@ -4585,7 +4586,7 @@ mod solve_expr {
main : RBTree I64 I64
main =
removeHelp 1 Empty
removeHelp 1i64 Empty
"#
),
"RBTree I64 I64",
@ -4643,7 +4644,7 @@ mod solve_expr {
RBTree k v : [Node NodeColor k v (RBTree k v) (RBTree k v), Empty]
removeHelp : Num k, RBTree (Num k) v -> RBTree (Num k) v
removeHelp : Num k, RBTree (Num k) v -> RBTree (Num k) v | k has Eq
removeHelp = \targetKey, dict ->
when dict is
Empty ->
@ -4678,7 +4679,7 @@ mod solve_expr {
removeHelpPrepEQGT : Key k, RBTree (Key k) v, NodeColor, (Key k), v, RBTree (Key k) v, RBTree (Key k) v -> RBTree (Key k) v
removeHelpEQGT : Key k, RBTree (Key k) v -> RBTree (Key k) v
removeHelpEQGT : Key k, RBTree (Key k) v -> RBTree (Key k) v | k has Eq
removeHelpEQGT = \targetKey, dict ->
when dict is
Node color key value left right ->
@ -4701,7 +4702,7 @@ mod solve_expr {
main : RBTree I64 I64
main =
removeHelp 1 Empty
removeHelp 1i64 Empty
"#
),
"RBTree I64 I64",
@ -7972,4 +7973,22 @@ mod solve_expr {
"O",
);
}
#[test]
fn custom_implement_eq() {
infer_eq_without_problem(
indoc!(
r#"
app "test" provides [main] to "./platform"
Trivial := {} has [Eq {isEq}]
isEq = \@Trivial {}, @Trivial {} -> Bool.true
main = Bool.isEq (@Trivial {}) (@Trivial {})
"#
),
"Bool",
);
}
}

View File

@ -67,9 +67,15 @@ pub enum NotDerivableContext {
UnboundVar,
Opaque(Symbol),
Decode(NotDerivableDecode),
Eq(NotDerivableEq),
}
#[derive(PartialEq, Debug, Clone)]
pub enum NotDerivableDecode {
OptionalRecordField(Lowercase),
}
#[derive(PartialEq, Debug, Clone)]
pub enum NotDerivableEq {
FloatingPoint,
}

View File

@ -0,0 +1,58 @@
#![cfg(test)]
// Even with #[allow(non_snake_case)] on individual idents, rust-analyzer issues diagnostics.
// See https://github.com/rust-lang/rust-analyzer/issues/6541.
// For the `v!` macro we use uppercase variables when constructing tag unions.
#![allow(non_snake_case)]
use crate::{util::check_single_lset_immediate, v};
use roc_module::symbol::Symbol;
use roc_types::subs::Variable;
use roc_derive_key::DeriveBuiltin::IsEq;
#[test]
fn immediates() {
// Everything is an immediate for `Eq`.
check_single_lset_immediate(IsEq, v!(U8), Symbol::BOOL_STRUCTURAL_EQ);
check_single_lset_immediate(IsEq, v!(U16), Symbol::BOOL_STRUCTURAL_EQ);
check_single_lset_immediate(IsEq, v!(U32), Symbol::BOOL_STRUCTURAL_EQ);
check_single_lset_immediate(IsEq, v!(U64), Symbol::BOOL_STRUCTURAL_EQ);
check_single_lset_immediate(IsEq, v!(U128), Symbol::BOOL_STRUCTURAL_EQ);
check_single_lset_immediate(IsEq, v!(I8), Symbol::BOOL_STRUCTURAL_EQ);
check_single_lset_immediate(IsEq, v!(I16), Symbol::BOOL_STRUCTURAL_EQ);
check_single_lset_immediate(IsEq, v!(I32), Symbol::BOOL_STRUCTURAL_EQ);
check_single_lset_immediate(IsEq, v!(I64), Symbol::BOOL_STRUCTURAL_EQ);
check_single_lset_immediate(IsEq, v!(I128), Symbol::BOOL_STRUCTURAL_EQ);
check_single_lset_immediate(IsEq, v!(STR), Symbol::BOOL_STRUCTURAL_EQ);
check_single_lset_immediate(
IsEq,
v!(Symbol::LIST_LIST v!(U8)),
Symbol::BOOL_STRUCTURAL_EQ,
);
check_single_lset_immediate(
IsEq,
v!(Symbol::LIST_LIST v!(STR)),
Symbol::BOOL_STRUCTURAL_EQ,
);
check_single_lset_immediate(IsEq, v!({ a: v!(U8), }), Symbol::BOOL_STRUCTURAL_EQ);
check_single_lset_immediate(IsEq, v!(EMPTY_RECORD), Symbol::BOOL_STRUCTURAL_EQ);
check_single_lset_immediate(
IsEq,
v!([ A v!(U8) v!(STR), B v!(STR) ]),
Symbol::BOOL_STRUCTURAL_EQ,
);
check_single_lset_immediate(
IsEq,
v!([ A v!(U8) v!(STR), B v!(STR) ]),
Symbol::BOOL_STRUCTURAL_EQ,
);
check_single_lset_immediate(
IsEq,
v!([ Nil, Cons v!(^lst)] as lst),
Symbol::BOOL_STRUCTURAL_EQ,
);
// NOTE: despite this reaching an immediate, `F64`s will never actually be allowed to be
// compared, because obligation checking will rule them out from `isEq`!
check_single_lset_immediate(IsEq, v!(F64), Symbol::BOOL_STRUCTURAL_EQ);
}

View File

@ -2,6 +2,7 @@
mod decoding;
mod encoding;
mod eq;
mod hash;
mod pretty_print;

View File

@ -55,6 +55,11 @@ fn module_source_and_path(builtin: DeriveBuiltin) -> (ModuleId, &'static str, Pa
module_source(ModuleId::HASH),
builtins_path.join("Hash.roc"),
),
DeriveBuiltin::IsEq => (
ModuleId::BOOL,
module_source(ModuleId::BOOL),
builtins_path.join("Bool.roc"),
),
}
}

View File

@ -1602,3 +1602,56 @@ mod hash {
}
}
}
#[cfg(all(test, any(feature = "gen-llvm", feature = "gen-wasm")))]
mod eq {
#[cfg(feature = "gen-llvm")]
use crate::helpers::llvm::assert_evals_to;
#[cfg(feature = "gen-wasm")]
use crate::helpers::wasm::assert_evals_to;
use indoc::indoc;
use roc_std::RocStr;
#[test]
fn custom_eq_impl() {
assert_evals_to!(
indoc!(
r#"
app "test" provides [main] to "./platform"
LyingEq := U8 has [Eq {isEq}]
isEq = \@LyingEq m, @LyingEq n -> m != n
main =
a = @LyingEq 10
b = @LyingEq 5
c = @LyingEq 5
if Bool.isEq a b && !(Bool.isEq b c) then
"okay"
else
"fail"
"#
),
RocStr::from("okay"),
RocStr
)
}
#[test]
fn derive_structural_eq() {
assert_evals_to!(
indoc!(
r#"
app "test" provides [main] to "./platform"
main = Bool.isEq 10u8 10u8
"#
),
true,
bool
)
}
}

View File

@ -77,40 +77,6 @@ fn neq_u64() {
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn eq_f64() {
assert_evals_to!(
indoc!(
r#"
i : F64
i = 1
i == i
"#
),
true,
bool
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn neq_f64() {
assert_evals_to!(
indoc!(
r#"
i : F64
i = 1
i != i
"#
),
false,
bool
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn eq_bool_tag() {
@ -673,8 +639,8 @@ fn compare_nullable_recursive_union_same_content() {
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn boxed_eq_int() {
assert_evals_to!("Box.box 1 == Box.box 1", true, bool);
assert_evals_to!("Box.box 2 == Box.box 1", false, bool);
assert_evals_to!("Box.box 1i64 == Box.box 1", true, bool);
assert_evals_to!("Box.box 2i64 == Box.box 1", false, bool);
}
#[test]

View File

@ -917,7 +917,7 @@ fn list_walk_implements_position() {
r#"
Option a : [Some a, None]
find : List a, a -> Option Nat
find : List a, a -> Option Nat | a has Eq
find = \list, needle ->
findHelp list needle
|> .v
@ -3499,7 +3499,7 @@ fn list_walk_backwards_implements_position() {
r#"
Option a : [Some a, None]
find : List a, a -> Option Nat
find : List a, a -> Option Nat | a has Eq
find = \list, needle ->
findHelp list needle
|> .v

View File

@ -875,7 +875,7 @@ fn gen_int_eq() {
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn gen_int_neq() {
assert_evals_to!(
indoc!(
@ -948,7 +948,7 @@ fn gen_wrap_int_neq() {
assert_evals_to!(
indoc!(
r#"
wrappedNotEq : a, a -> Bool
wrappedNotEq : a, a -> Bool | a has Eq
wrappedNotEq = \num1, num2 ->
num1 != num2

View File

@ -2595,7 +2595,7 @@ fn pass_through_unresolved_type_variable() {
main : Str
main =
(accept Bool.isEq) "B"
(accept \x -> x) "B"
accept : * -> (b -> b)
@ -2878,10 +2878,10 @@ fn unresolved_tvar_when_capture_is_unused() {
main : I64
main =
r : Bool
r = Bool.false
r : U8
r = 1
p1 = (\_ -> r == (1 == 1))
p1 = (\_ -> r == 1)
oneOfResult = List.map [p1] (\p -> p Green)
when oneOfResult is

View File

@ -340,6 +340,7 @@ pub fn unify(env: &mut Env, var1: Variable, var2: Variable, mode: Mode) -> Unifi
}
#[inline(always)]
#[must_use]
pub fn unify_introduced_ability_specialization(
env: &mut Env,
ability_member_signature: Variable,
@ -350,6 +351,7 @@ pub fn unify_introduced_ability_specialization(
}
#[inline(always)]
#[must_use]
pub fn unify_with_collector<M: MetaCollector>(
env: &mut Env,
var1: Variable,
@ -360,6 +362,7 @@ pub fn unify_with_collector<M: MetaCollector>(
}
#[inline(always)]
#[must_use]
fn unify_help<M: MetaCollector>(
env: &mut Env,
var1: Variable,
@ -416,6 +419,7 @@ fn unify_help<M: MetaCollector>(
}
#[inline(always)]
#[must_use]
pub fn unify_pool<M: MetaCollector>(
env: &mut Env,
pool: &mut Pool,
@ -496,6 +500,7 @@ fn debug_print_unified_types<M: MetaCollector>(
})
}
#[must_use]
fn unify_context<M: MetaCollector>(env: &mut Env, pool: &mut Pool, ctx: Context) -> Outcome<M> {
#[cfg(debug_assertions)]
debug_print_unified_types::<M>(env, &ctx, None);
@ -555,6 +560,7 @@ fn not_in_range_mismatch<M: MetaCollector>() -> Outcome<M> {
}
#[inline(always)]
#[must_use]
fn unify_ranged_number<M: MetaCollector>(
env: &mut Env,
pool: &mut Pool,
@ -714,6 +720,7 @@ fn wrap_range_var(
#[inline(always)]
#[allow(clippy::too_many_arguments)]
#[must_use]
fn unify_two_aliases<M: MetaCollector>(
env: &mut Env,
pool: &mut Pool,
@ -812,6 +819,7 @@ fn unify_two_aliases<M: MetaCollector>(
// Unifies a structural alias
#[inline(always)]
#[must_use]
fn unify_alias<M: MetaCollector>(
env: &mut Env,
pool: &mut Pool,
@ -870,6 +878,7 @@ fn opaque_obligation(opaque: Symbol, opaque_var: Variable) -> Obligated {
}
#[inline(always)]
#[must_use]
fn unify_opaque<M: MetaCollector>(
env: &mut Env,
pool: &mut Pool,
@ -934,6 +943,7 @@ fn unify_opaque<M: MetaCollector>(
}
#[inline(always)]
#[must_use]
fn unify_structure<M: MetaCollector>(
env: &mut Env,
pool: &mut Pool,
@ -1032,6 +1042,7 @@ fn unify_structure<M: MetaCollector>(
}
#[inline(always)]
#[must_use]
fn unify_lambda_set<M: MetaCollector>(
env: &mut Env,
pool: &mut Pool,
@ -1358,6 +1369,7 @@ fn is_sorted_unspecialized_lamba_set_list(subs: &Subs, uls: &[Uls]) -> bool {
uls == sort_unspecialized_lambda_sets(subs, uls.to_vec())
}
#[must_use = "must use outcomes!"]
fn unify_unspecialized_lambdas<M: MetaCollector>(
env: &mut Env,
pool: &mut Pool,
@ -1602,6 +1614,7 @@ fn unify_unspecialized_lambdas<M: MetaCollector>(
))
}
#[must_use]
fn unify_lambda_set_help<M: MetaCollector>(
env: &mut Env,
pool: &mut Pool,
@ -1695,6 +1708,7 @@ fn unify_lambda_set_help<M: MetaCollector>(
whole_outcome
}
#[must_use]
fn unify_record<M: MetaCollector>(
env: &mut Env,
pool: &mut Pool,
@ -1801,6 +1815,7 @@ enum OtherFields {
type SharedFields = Vec<(Lowercase, (RecordField<Variable>, RecordField<Variable>))>;
#[must_use]
fn unify_shared_fields<M: MetaCollector>(
env: &mut Env,
pool: &mut Pool,
@ -2109,6 +2124,7 @@ fn should_extend_ext_with_uninhabited_type(
}
#[allow(clippy::too_many_arguments)]
#[must_use]
fn unify_tag_unions<M: MetaCollector>(
env: &mut Env,
pool: &mut Pool,
@ -2409,6 +2425,7 @@ fn choose_merged_var(subs: &Subs, var1: Variable, var2: Variable) -> Variable {
}
}
#[must_use]
fn unify_shared_tags_new<M: MetaCollector>(
env: &mut Env,
pool: &mut Pool,
@ -2539,6 +2556,7 @@ fn unify_shared_tags_new<M: MetaCollector>(
}
}
#[must_use]
fn unify_shared_tags_merge_new<M: MetaCollector>(
env: &mut Env,
ctx: &Context,
@ -2558,6 +2576,7 @@ fn unify_shared_tags_merge_new<M: MetaCollector>(
}
#[inline(always)]
#[must_use]
fn unify_flat_type<M: MetaCollector>(
env: &mut Env,
pool: &mut Pool,
@ -2757,6 +2776,7 @@ fn unify_flat_type<M: MetaCollector>(
}
}
#[must_use]
fn unify_zip_slices<M: MetaCollector>(
env: &mut Env,
pool: &mut Pool,
@ -2778,6 +2798,7 @@ fn unify_zip_slices<M: MetaCollector>(
}
#[inline(always)]
#[must_use]
fn unify_rigid<M: MetaCollector>(
env: &mut Env,
ctx: &Context,
@ -2821,6 +2842,7 @@ fn unify_rigid<M: MetaCollector>(
}
#[inline(always)]
#[must_use]
fn unify_rigid_able<M: MetaCollector>(
env: &mut Env,
ctx: &Context,
@ -2870,6 +2892,7 @@ fn unify_rigid_able<M: MetaCollector>(
}
#[inline(always)]
#[must_use]
fn unify_flex<M: MetaCollector>(
env: &mut Env,
ctx: &Context,
@ -2906,6 +2929,7 @@ fn unify_flex<M: MetaCollector>(
}
#[inline(always)]
#[must_use]
fn unify_flex_able<M: MetaCollector>(
env: &mut Env,
ctx: &Context,
@ -2981,6 +3005,7 @@ fn unify_flex_able<M: MetaCollector>(
}
}
#[must_use]
fn merge_flex_able_with_concrete<M: MetaCollector>(
env: &mut Env,
ctx: &Context,
@ -3014,6 +3039,7 @@ fn merge_flex_able_with_concrete<M: MetaCollector>(
}
#[inline(always)]
#[must_use]
fn unify_recursion<M: MetaCollector>(
env: &mut Env,
pool: &mut Pool,
@ -3100,6 +3126,7 @@ fn unify_recursion<M: MetaCollector>(
}
}
#[must_use]
pub fn merge<M: MetaCollector>(env: &mut Env, ctx: &Context, content: Content) -> Outcome<M> {
let mut outcome: Outcome<M> = Outcome::default();
@ -3154,6 +3181,7 @@ fn is_recursion_var(subs: &Subs, var: Variable) -> bool {
}
#[allow(clippy::too_many_arguments)]
#[must_use]
fn unify_function_or_tag_union_and_func<M: MetaCollector>(
env: &mut Env,
pool: &mut Pool,

View File

@ -1397,6 +1397,7 @@ balance = \color ->
}
#[test]
#[ignore = "Does not yet know about ability syntax"]
fn test_astar() {
let tokens = tokenize(&cli_testing_path("benchmarks/AStar.roc"));

View File

@ -1009,7 +1009,9 @@ pub fn can_problem<'b>(
fn list_builtin_abilities<'a>(alloc: &'a RocDocAllocator<'a>) -> RocDocBuilder<'a> {
alloc.intersperse(
[alloc.symbol_qualified(DERIVABLE_ABILITIES[0].0)],
DERIVABLE_ABILITIES
.iter()
.map(|(ab, _)| alloc.symbol_unqualified(*ab)),
alloc.reflow(", "),
)
}

View File

@ -9,7 +9,8 @@ use roc_module::ident::{Ident, IdentStr, Lowercase, TagName};
use roc_module::symbol::Symbol;
use roc_region::all::{LineInfo, Loc, Region};
use roc_solve_problem::{
NotDerivableContext, NotDerivableDecode, TypeError, UnderivableReason, Unfulfilled,
NotDerivableContext, NotDerivableDecode, NotDerivableEq, TypeError, UnderivableReason,
Unfulfilled,
};
use roc_std::RocDec;
use roc_types::pretty_print::{Parens, WILDCARD};
@ -283,8 +284,8 @@ fn report_unfulfilled_ability<'a>(
let reason = report_underivable_reason(alloc, reason, ability, &typ);
let stack = [
alloc.concat([
alloc.reflow("Roc can't generate an implementation of the "),
alloc.symbol_qualified(ability),
alloc.reflow("I can't generate an implementation of the "),
alloc.symbol_foreign_qualified(ability),
alloc.reflow(" ability for"),
]),
alloc.type_block(error_type_to_doc(alloc, typ)),
@ -304,10 +305,10 @@ fn report_unfulfilled_ability<'a>(
let reason = report_underivable_reason(alloc, reason, ability, &typ);
let stack = [
alloc.concat([
alloc.reflow("Roc can't derive an implementation of the "),
alloc.symbol_qualified(ability),
alloc.reflow(" for "),
alloc.symbol_unqualified(opaque),
alloc.reflow("I can't derive an implementation of the "),
alloc.symbol_foreign_qualified(ability),
alloc.reflow(" ability for "),
alloc.symbol_foreign_qualified(opaque),
alloc.reflow(":"),
]),
alloc.region(lines.convert_region(derive_region)),
@ -316,7 +317,7 @@ fn report_unfulfilled_ability<'a>(
.chain(reason)
.chain(std::iter::once(alloc.tip().append(alloc.concat([
alloc.reflow("You can define a custom implementation of "),
alloc.symbol_qualified(ability),
alloc.symbol_unqualified(ability),
alloc.reflow(" for "),
alloc.symbol_unqualified(opaque),
alloc.reflow("."),
@ -433,6 +434,18 @@ fn underivable_hint<'b>(
])))
}
},
NotDerivableContext::Eq(reason) => match reason {
NotDerivableEq::FloatingPoint => {
Some(alloc.note("").append(alloc.concat([
alloc.reflow("I can't derive "),
alloc.symbol_qualified(Symbol::BOOL_IS_EQ),
alloc.reflow(" for floating-point types. That's because Roc's floating-point numbers cannot be compared for total equality - in Roc, `NaN` is never comparable to `NaN`."),
alloc.reflow(" If a type doesn't support total equality, it cannot support the "),
alloc.symbol_unqualified(Symbol::BOOL_EQ),
alloc.reflow(" ability!"),
])))
}
},
}
}

View File

@ -2098,7 +2098,7 @@ mod test_reporting {
Ok
U8
Box
f
Eq
"###
);
@ -6266,7 +6266,7 @@ All branches in an `if` must have the same type!
inference_var_conflict_in_rigid_links,
indoc!(
r#"
f : a -> (_ -> b)
f : a -> (_ -> b) | a has Eq
f = \x -> \y -> if x == y then x else y
f
"#
@ -6277,19 +6277,19 @@ All branches in an `if` must have the same type!
Something is off with the body of the `f` definition:
4 f : a -> (_ -> b)
4 f : a -> (_ -> b) | a has Eq
5 f = \x -> \y -> if x == y then x else y
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The body is an anonymous function of type:
a -> a
a -> a | a has Eq, a has Eq
But the type annotation on `f` says it should be:
a -> b
a -> b | a has Eq
Tip: Your type annotation uses `a` and `b` as separate type variables.
Tip: Your type annotation uses `b` and `a` as separate type variables.
Your code seems to be saying they are the same though. Maybe they
should be the same in your type annotation? Maybe your code uses them
in a weird way?
@ -7980,8 +7980,8 @@ All branches in an `if` must have the same type!
ability_first_demand_not_indented_enough,
indoc!(
r#"
Eq has
eq : a, a -> U64 | a has Eq
MEq has
eq : a, a -> U64 | a has MEq
1
"#
@ -7992,8 +7992,8 @@ All branches in an `if` must have the same type!
I was partway through parsing an ability definition, but I got stuck
here:
4 Eq has
5 eq : a, a -> U64 | a has Eq
4 MEq has
5 eq : a, a -> U64 | a has MEq
^
I suspect this line is not indented enough (by 1 spaces)
@ -8004,9 +8004,9 @@ All branches in an `if` must have the same type!
ability_demands_not_indented_with_first,
indoc!(
r#"
Eq has
eq : a, a -> U64 | a has Eq
neq : a, a -> U64 | a has Eq
MEq has
eq : a, a -> U64 | a has MEq
neq : a, a -> U64 | a has MEq
1
"#
@ -8017,8 +8017,8 @@ All branches in an `if` must have the same type!
I was partway through parsing an ability definition, but I got stuck
here:
5 eq : a, a -> U64 | a has Eq
6 neq : a, a -> U64 | a has Eq
5 eq : a, a -> U64 | a has MEq
6 neq : a, a -> U64 | a has MEq
^
I suspect this line is indented too much (by 4 spaces)"#
@ -8028,8 +8028,8 @@ All branches in an `if` must have the same type!
ability_demand_value_has_args,
indoc!(
r#"
Eq has
eq b c : a, a -> U64 | a has Eq
MEq has
eq b c : a, a -> U64 | a has MEq
1
"#
@ -8040,7 +8040,7 @@ All branches in an `if` must have the same type!
I was partway through parsing an ability definition, but I got stuck
here:
5 eq b c : a, a -> U64 | a has Eq
5 eq b c : a, a -> U64 | a has MEq
^
I was expecting to see a : annotating the signature of this value
@ -8051,7 +8051,7 @@ All branches in an `if` must have the same type!
ability_non_signature_expression,
indoc!(
r#"
Eq has
MEq has
123
1
@ -8063,7 +8063,7 @@ All branches in an `if` must have the same type!
I was partway through parsing an ability definition, but I got stuck
here:
4 Eq has
4 MEq has
5 123
^
@ -8334,23 +8334,23 @@ All branches in an `if` must have the same type!
r#"
app "test" provides [] to "./platform"
Eq has eq : a, b -> Bool.Bool | a has Eq, b has Eq
MEq has eq : a, b -> Bool.Bool | a has MEq, b has MEq
"#
),
@r#"
ABILITY MEMBER BINDS MULTIPLE VARIABLES /code/proj/Main.roc
The definition of the ability member `eq` includes multiple variables
bound to the `Eq`` ability:`
bound to the `MEq`` ability:`
3 Eq has eq : a, b -> Bool.Bool | a has Eq, b has Eq
^^^^^^^^^^^^^^^^^^
3 MEq has eq : a, b -> Bool.Bool | a has MEq, b has MEq
^^^^^^^^^^^^^^^^^^^^
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`?
Hint: Did you mean to only bind `a` to `MEq`?
"#
);
@ -8429,11 +8429,11 @@ All branches in an `if` must have the same type!
r#"
app "test" provides [eq, le] to "./platform"
Eq has
eq : a, a -> Bool | a has Eq
le : a, a -> Bool | a has Eq
MEq has
eq : a, a -> Bool | a has MEq
le : a, a -> Bool | a has MEq
Id := U64 has [Eq {eq}]
Id := U64 has [MEq {eq}]
eq = \@Id m, @Id n -> m == n
"#
@ -8441,10 +8441,10 @@ All branches in an `if` must have the same type!
@r###"
INCOMPLETE ABILITY IMPLEMENTATION /code/proj/Main.roc
This type does not fully implement the `Eq` ability:
This type does not fully implement the `MEq` ability:
7 Id := U64 has [Eq {eq}]
^^^^^^^
7 Id := U64 has [MEq {eq}]
^^^^^^^^
The following necessary members are missing implementations:
@ -8568,10 +8568,10 @@ All branches in an `if` must have the same type!
r#"
app "test" provides [eq] to "./platform"
Eq has
eq : a, a -> Bool | a has Eq
MEq has
eq : a, a -> Bool | a has MEq
You := {} has [Eq {eq}]
You := {} has [MEq {eq}]
AndI := {}
eq = \@You {}, @AndI {} -> False
@ -8676,7 +8676,7 @@ All branches in an `if` must have the same type!
15 notYet: hash (A 1),
^^^
Roc can't generate an implementation of the `#UserApp.MHash` ability for
I can't generate an implementation of the `MHash` ability for
[A (Num a)]b
@ -9098,21 +9098,20 @@ All branches in an `if` must have the same type!
main = Encode.toEncoder \x -> x
"#
),
@r#"
TYPE MISMATCH /code/proj/Main.roc
@r###"
TYPE MISMATCH /code/proj/Main.roc
This expression has a type that does not implement the abilities it's expected to:
This expression has a type that does not implement the abilities it's expected to:
3 main = Encode.toEncoder \x -> x
^^^^^^^
3 main = Encode.toEncoder \x -> x
^^^^^^^
Roc can't generate an implementation of the `Encode.Encoding` ability
for
I can't generate an implementation of the `Encoding` ability for
a -> a
a -> a
Note: `Encoding` cannot be generated for functions.
"#
Note: `Encoding` cannot be generated for functions.
"###
);
test_report!(
@ -9135,8 +9134,7 @@ All branches in an `if` must have the same type!
4 main = Encode.toEncoder { x: @A {} }
^^^^^^^^^^^^
Roc can't generate an implementation of the `Encode.Encoding` ability
for
I can't generate an implementation of the `Encoding` ability for
{ x : A }
@ -9190,9 +9188,9 @@ All branches in an `if` must have the same type!
r#"
app "test" provides [A] to "./platform"
Eq has eq : a, a -> U64 | a has Eq
MEq has eq : a, a -> U64 | a has MEq
A := U8 has [Eq {eq}]
A := U8 has [MEq {eq}]
"#
),
@r###"
@ -9200,8 +9198,8 @@ All branches in an `if` must have the same type!
An implementation of `eq` could not be found in this scope:
5 A := U8 has [Eq {eq}]
^^
5 A := U8 has [MEq {eq}]
^^
Tip: consider adding a value of name `eq` in this scope, or using
another variable that implements this ability member, like
@ -9209,10 +9207,10 @@ All branches in an `if` must have the same type!
INCOMPLETE ABILITY IMPLEMENTATION /code/proj/Main.roc
This type does not fully implement the `Eq` ability:
This type does not fully implement the `MEq` ability:
5 A := U8 has [Eq {eq}]
^^^^^^^
5 A := U8 has [MEq {eq}]
^^^^^^^^
The following necessary members are missing implementations:
@ -9224,36 +9222,36 @@ All branches in an `if` must have the same type!
opaque_ability_impl_not_found,
indoc!(
r#"
app "test" provides [A, myEq] to "./platform"
app "test" provides [A, myMEq] to "./platform"
Eq has eq : a, a -> Bool | a has Eq
MEq has eq : a, a -> Bool | a has MEq
A := U8 has [ Eq {eq: aEq} ]
A := U8 has [ MEq {eq: aMEq} ]
myEq = \m, n -> m == n
myMEq = \m, n -> m == n
"#
),
@r###"
UNRECOGNIZED NAME /code/proj/Main.roc
Nothing is named `aEq` in this scope.
Nothing is named `aMEq` in this scope.
5 A := U8 has [ Eq {eq: aEq} ]
^^^
5 A := U8 has [ MEq {eq: aMEq} ]
^^^^
Did you mean one of these?
MEq
Eq
myEq
myMEq
eq
U8
INCOMPLETE ABILITY IMPLEMENTATION /code/proj/Main.roc
This type does not fully implement the `Eq` ability:
This type does not fully implement the `MEq` ability:
5 A := U8 has [ Eq {eq: aEq} ]
^^^^^^^^^^^^
5 A := U8 has [ MEq {eq: aMEq} ]
^^^^^^^^^^^^^^
The following necessary members are missing implementations:
@ -9265,13 +9263,13 @@ All branches in an `if` must have the same type!
opaque_ability_impl_optional,
indoc!(
r#"
app "test" provides [A, myEq] to "./platform"
app "test" provides [A, myMEq] to "./platform"
Eq has eq : a, a -> Bool | a has Eq
MEq has eq : a, a -> Bool | a has MEq
A := U8 has [ Eq {eq ? aEq} ]
A := U8 has [ MEq {eq ? aMEq} ]
myEq = \m, n -> m == n
myMEq = \m, n -> m == n
"#
),
@r###"
@ -9279,8 +9277,8 @@ All branches in an `if` must have the same type!
Ability implementations cannot be optional:
5 A := U8 has [ Eq {eq ? aEq} ]
^^^^^^^^
5 A := U8 has [ MEq {eq ? aMEq} ]
^^^^^^^^^
Custom implementations must be supplied fully.
@ -9288,10 +9286,10 @@ All branches in an `if` must have the same type!
INCOMPLETE ABILITY IMPLEMENTATION /code/proj/Main.roc
This type does not fully implement the `Eq` ability:
This type does not fully implement the `MEq` ability:
5 A := U8 has [ Eq {eq ? aEq} ]
^^^^^^^^^^^^^
5 A := U8 has [ MEq {eq ? aMEq} ]
^^^^^^^^^^^^^^^
The following necessary members are missing implementations:
@ -9345,9 +9343,9 @@ All branches in an `if` must have the same type!
r#"
app "test" provides [A] to "./platform"
Eq has eq : a, a -> Bool | a has Eq
MEq has eq : a, a -> Bool | a has MEq
A := U8 has [ Eq {eq : Bool.eq} ]
A := U8 has [ MEq {eq : Bool.eq} ]
"#
),
@r###"
@ -9355,18 +9353,18 @@ All branches in an `if` must have the same type!
This ability implementation is qualified:
5 A := U8 has [ Eq {eq : Bool.eq} ]
^^^^^^^
5 A := U8 has [ MEq {eq : Bool.eq} ]
^^^^^^^
Custom implementations must be defined in the local scope, and
unqualified.
INCOMPLETE ABILITY IMPLEMENTATION /code/proj/Main.roc
This type does not fully implement the `Eq` ability:
This type does not fully implement the `MEq` ability:
5 A := U8 has [ Eq {eq : Bool.eq} ]
^^^^^^^^^^^^^^^^^
5 A := U8 has [ MEq {eq : Bool.eq} ]
^^^^^^^^^^^^^^^^^^
The following necessary members are missing implementations:
@ -9380,9 +9378,9 @@ All branches in an `if` must have the same type!
r#"
app "test" provides [A] to "./platform"
Eq has eq : a, a -> Bool | a has Eq
MEq has eq : a, a -> Bool | a has MEq
A := U8 has [ Eq {eq : \m, n -> m == n} ]
A := U8 has [ MEq {eq : \m, n -> m == n} ]
"#
),
@r###"
@ -9390,8 +9388,8 @@ All branches in an `if` must have the same type!
This ability implementation is not an identifier:
5 A := U8 has [ Eq {eq : \m, n -> m == n} ]
^^^^^^^^^^^^^^^
5 A := U8 has [ MEq {eq : \m, n -> m == n} ]
^^^^^^^^^^^^^^^
Custom ability implementations defined in this position can only be
unqualified identifiers, not arbitrary expressions.
@ -9400,10 +9398,10 @@ All branches in an `if` must have the same type!
INCOMPLETE ABILITY IMPLEMENTATION /code/proj/Main.roc
This type does not fully implement the `Eq` ability:
This type does not fully implement the `MEq` ability:
5 A := U8 has [ Eq {eq : \m, n -> m == n} ]
^^^^^^^^^^^^^^^^^^^^^^^^^
5 A := U8 has [ MEq {eq : \m, n -> m == n} ]
^^^^^^^^^^^^^^^^^^^^^^^^^^
The following necessary members are missing implementations:
@ -9417,9 +9415,9 @@ All branches in an `if` must have the same type!
r#"
app "test" provides [A] to "./platform"
Eq has eq : a, a -> Bool | a has Eq
MEq has eq : a, a -> Bool | a has MEq
A := U8 has [ Eq {eq: eqA, eq: eqA} ]
A := U8 has [ MEq {eq: eqA, eq: eqA} ]
eqA = \@A m, @A n -> m == n
"#
@ -9429,13 +9427,13 @@ All branches in an `if` must have the same type!
This ability member implementation is duplicate:
5 A := U8 has [ Eq {eq: eqA, eq: eqA} ]
^^^^^^^
5 A := U8 has [ MEq {eq: eqA, eq: eqA} ]
^^^^^^^
The first implementation was defined here:
5 A := U8 has [ Eq {eq: eqA, eq: eqA} ]
^^^^^^^
5 A := U8 has [ MEq {eq: eqA, eq: eqA} ]
^^^^^^^
Only one custom implementation can be defined for an ability member.
"###
@ -9475,18 +9473,18 @@ All branches in an `if` must have the same type!
A := {} has [Ab]
"#
),
@r#"
ILLEGAL DERIVE /code/proj/Main.roc
@r###"
ILLEGAL DERIVE /code/proj/Main.roc
This ability cannot be derived:
This ability cannot be derived:
5 A := {} has [Ab]
^^
5 A := {} has [Ab]
^^
Only builtin abilities can be derived.
Only builtin abilities can be derived.
Note: The builtin abilities are `Encode.Encoding`
"#
Note: The builtin abilities are `Encoding`, `Decoding`, `Hash`, `Eq`
"###
);
test_report!(
@ -9498,18 +9496,18 @@ All branches in an `if` must have the same type!
A a := a -> a has [Encode.Encoding]
"#
),
@r#"
INCOMPLETE ABILITY IMPLEMENTATION /code/proj/Main.roc
@r###"
INCOMPLETE ABILITY IMPLEMENTATION /code/proj/Main.roc
Roc can't derive an implementation of the `Encode.Encoding` for `A`:
I can't derive an implementation of the `Encoding` ability for `A`:
3 A a := a -> a has [Encode.Encoding]
^^^^^^^^^^^^^^^
3 A a := a -> a has [Encode.Encoding]
^^^^^^^^^^^^^^^
Note: `Encoding` cannot be generated for functions.
Note: `Encoding` cannot be generated for functions.
Tip: You can define a custom implementation of `Encode.Encoding` for `A`.
"#
Tip: You can define a custom implementation of `Encoding` for `A`.
"###
);
test_report!(
@ -9523,19 +9521,19 @@ All branches in an `if` must have the same type!
B := {}
"#
),
@r#"
INCOMPLETE ABILITY IMPLEMENTATION /code/proj/Main.roc
@r###"
INCOMPLETE ABILITY IMPLEMENTATION /code/proj/Main.roc
Roc can't derive an implementation of the `Encode.Encoding` for `A`:
I can't derive an implementation of the `Encoding` ability for `A`:
3 A := B has [Encode.Encoding]
^^^^^^^^^^^^^^^
3 A := B has [Encode.Encoding]
^^^^^^^^^^^^^^^
Tip: `B` does not implement `Encoding`. Consider adding a custom
implementation or `has Encode.Encoding` to the definition of `B`.
Tip: `B` does not implement `Encoding`. Consider adding a custom
implementation or `has Encode.Encoding` to the definition of `B`.
Tip: You can define a custom implementation of `Encode.Encoding` for `A`.
"#
Tip: You can define a custom implementation of `Encoding` for `A`.
"###
);
test_report!(
@ -10205,14 +10203,14 @@ All branches in an `if` must have the same type!
@r###"
INCOMPLETE ABILITY IMPLEMENTATION /code/proj/Main.roc
Roc can't derive an implementation of the `Decode.Decoding` for `A`:
I can't derive an implementation of the `Decoding` ability for `A`:
3 A a := a -> a has [Decode.Decoding]
^^^^^^^^^^^^^^^
Note: `Decoding` cannot be generated for functions.
Tip: You can define a custom implementation of `Decode.Decoding` for `A`.
Tip: You can define a custom implementation of `Decoding` for `A`.
"###
);
@ -10230,7 +10228,7 @@ All branches in an `if` must have the same type!
@r###"
INCOMPLETE ABILITY IMPLEMENTATION /code/proj/Main.roc
Roc can't derive an implementation of the `Decode.Decoding` for `A`:
I can't derive an implementation of the `Decoding` ability for `A`:
3 A := B has [Decode.Decoding]
^^^^^^^^^^^^^^^
@ -10238,7 +10236,7 @@ All branches in an `if` must have the same type!
Tip: `B` does not implement `Decoding`. Consider adding a custom
implementation or `has Decode.Decoding` to the definition of `B`.
Tip: You can define a custom implementation of `Decode.Decoding` for `A`.
Tip: You can define a custom implementation of `Decoding` for `A`.
"###
);
@ -10289,8 +10287,7 @@ All branches in an `if` must have the same type!
5 myDecoder = decoder
^^^^^^^
Roc can't generate an implementation of the `Decode.Decoding` ability
for
I can't generate an implementation of the `Decoding` ability for
a -> a
@ -10321,8 +10318,7 @@ All branches in an `if` must have the same type!
7 myDecoder = decoder
^^^^^^^
Roc can't generate an implementation of the `Decode.Decoding` ability
for
I can't generate an implementation of the `Decoding` ability for
{ x : A }
@ -10503,8 +10499,7 @@ All branches in an `if` must have the same type!
6 Ok rcd -> rcd.first rcd.second
^^^^^^^^^
Roc can't generate an implementation of the `Decode.Decoding` ability
for
I can't generate an implementation of the `Decoding` ability for
a -> b
@ -10526,24 +10521,23 @@ All branches in an `if` must have the same type!
"#
),
@r###"
TYPE MISMATCH /code/proj/Main.roc
TYPE MISMATCH /code/proj/Main.roc
This expression has a type that does not implement the abilities it's expected to:
This expression has a type that does not implement the abilities it's expected to:
5 myDecoder = decoder
^^^^^^^
5 myDecoder = decoder
^^^^^^^
Roc can't generate an implementation of the `Decode.Decoding` ability
for
I can't generate an implementation of the `Decoding` ability for
{ x : Str, y ? Str }
{ x : Str, y ? Str }
Note: I can't derive decoding for a record with an optional field,
which in this case is `.y`. Optional record fields are polymorphic over
records that may or may not contain them at compile time, but are not
a concept that extends to runtime!
Maybe you wanted to use a `Result`?
"###
Note: I can't derive decoding for a record with an optional field,
which in this case is `.y`. Optional record fields are polymorphic over
records that may or may not contain them at compile time, but are not
a concept that extends to runtime!
Maybe you wanted to use a `Result`?
"###
);
test_report!(
@ -10861,14 +10855,14 @@ All branches in an `if` must have the same type!
@r###"
INCOMPLETE ABILITY IMPLEMENTATION /code/proj/Main.roc
Roc can't derive an implementation of the `Hash.Hash` for `A`:
I can't derive an implementation of the `Hash` ability 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`.
Tip: You can define a custom implementation of `Hash` for `A`.
"###
);
@ -10886,7 +10880,7 @@ All branches in an `if` must have the same type!
@r###"
INCOMPLETE ABILITY IMPLEMENTATION /code/proj/Main.roc
Roc can't derive an implementation of the `Hash.Hash` for `A`:
I can't derive an implementation of the `Hash` ability for `A`:
3 A := B has [Hash]
^^^^
@ -10894,7 +10888,7 @@ All branches in an `if` must have the same type!
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`.
Tip: You can define a custom implementation of `Hash` for `A`.
"###
);
@ -10973,7 +10967,7 @@ All branches in an `if` must have the same type!
5 main = foo (\x -> x)
^^^^^^^
Roc can't generate an implementation of the `Hash.Hash` ability for
I can't generate an implementation of the `Hash` ability for
a -> a
@ -11000,7 +10994,7 @@ All branches in an `if` must have the same type!
5 main = foo (A (\x -> x) B)
^^^^^^^^^^^^^
Roc can't generate an implementation of the `Hash.Hash` ability for
I can't generate an implementation of the `Hash` ability for
[A (a -> a) [B]a]b
@ -11097,7 +11091,30 @@ All branches in an `if` must have the same type!
But `contains` needs its 2nd argument to be:
U8
Int Unsigned8
"###
);
test_report!(
derive_eq_for_function,
indoc!(
r#"
app "test" provides [A] to "./platform"
A a := a -> a has [Eq]
"#
),
@r###"
INCOMPLETE ABILITY IMPLEMENTATION /code/proj/Main.roc
I can't derive an implementation of the `Eq` ability for `A`:
3 A a := a -> a has [Eq]
^^
Note: `Eq` cannot be generated for functions.
Tip: You can define a custom implementation of `Eq` for `A`.
"###
);
@ -11132,4 +11149,285 @@ All branches in an `if` must have the same type!
The branches must be cases of the `when` condition's type!
"###
);
test_report!(
derive_eq_for_f32,
indoc!(
r#"
app "test" provides [A] to "./platform"
A := F32 has [Eq]
"#
),
@r###"
INCOMPLETE ABILITY IMPLEMENTATION /code/proj/Main.roc
I can't derive an implementation of the `Eq` ability for `A`:
3 A := F32 has [Eq]
^^
Note: I can't derive `Bool.isEq` for floating-point types. That's
because Roc's floating-point numbers cannot be compared for total
equality - in Roc, `NaN` is never comparable to `NaN`. If a type
doesn't support total equality, it cannot support the `Eq` ability!
Tip: You can define a custom implementation of `Eq` for `A`.
"###
);
test_report!(
derive_eq_for_f64,
indoc!(
r#"
app "test" provides [A] to "./platform"
A := F64 has [Eq]
"#
),
@r###"
INCOMPLETE ABILITY IMPLEMENTATION /code/proj/Main.roc
I can't derive an implementation of the `Eq` ability for `A`:
3 A := F64 has [Eq]
^^
Note: I can't derive `Bool.isEq` for floating-point types. That's
because Roc's floating-point numbers cannot be compared for total
equality - in Roc, `NaN` is never comparable to `NaN`. If a type
doesn't support total equality, it cannot support the `Eq` ability!
Tip: You can define a custom implementation of `Eq` for `A`.
"###
);
test_report!(
derive_eq_for_non_eq_opaque,
indoc!(
r#"
app "test" provides [A] to "./platform"
A := B has [Eq]
B := {}
"#
),
@r###"
INCOMPLETE ABILITY IMPLEMENTATION /code/proj/Main.roc
I can't derive an implementation of the `Eq` ability for `A`:
3 A := B has [Eq]
^^
Tip: `B` does not implement `Eq`. Consider adding a custom implementation
or `has Bool.Eq` to the definition of `B`.
Tip: You can define a custom implementation of `Eq` for `A`.
"###
);
test_report!(
derive_eq_for_other_has_eq,
indoc!(
r#"
app "test" provides [A] to "./platform"
A := B has [Eq]
B := {} has [Eq]
"#
),
@"" // no error
);
test_report!(
derive_eq_for_recursive_deriving,
indoc!(
r#"
app "test" provides [MyNat] to "./platform"
MyNat := [S MyNat, Z] has [Eq]
"#
),
@"" // no error
);
test_report!(
derive_eq_for_record,
indoc!(
r#"
app "test" provides [main] to "./platform"
foo : a -> {} | a has Eq
main = foo {a: "", b: 1}
"#
),
@"" // no error
);
test_report!(
derive_eq_for_tag,
indoc!(
r#"
app "test" provides [main] to "./platform"
foo : a -> {} | a has Eq
t : [A {}, B U8 U64, C Str]
main = foo t
"#
),
@"" // no error
);
test_report!(
cannot_derive_eq_for_function,
indoc!(
r#"
app "test" provides [main] to "./platform"
foo : a -> {} | a has Eq
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)
^^^^^^^
I can't generate an implementation of the `Eq` ability for
a -> a
Note: `Eq` cannot be generated for functions.
"###
);
test_report!(
cannot_derive_eq_for_structure_containing_function,
indoc!(
r#"
app "test" provides [main] to "./platform"
foo : a -> {} | a has Eq
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)
^^^^^^^^^^^^^
I can't generate an implementation of the `Eq` ability for
[A (a -> a) [B]a]b
In particular, an implementation for
a -> a
cannot be generated.
Note: `Eq` cannot be generated for functions.
"###
);
test_report!(
cannot_eq_functions,
indoc!(
r#"
(\x -> x) == (\x -> x)
"#
),
@r###"
TYPE MISMATCH /code/proj/Main.roc
This expression has a type that does not implement the abilities it's expected to:
4 (\x -> x) == (\x -> x)
^^^^^^^
I can't generate an implementation of the `Eq` ability for
a -> a
Note: `Eq` cannot be generated for functions.
"###
);
test_report!(
cannot_not_eq_functions,
indoc!(
r#"
(\x -> x) == (\x -> x)
"#
),
@r###"
TYPE MISMATCH /code/proj/Main.roc
This expression has a type that does not implement the abilities it's expected to:
4 (\x -> x) == (\x -> x)
^^^^^^^
I can't generate an implementation of the `Eq` ability for
a -> a
Note: `Eq` cannot be generated for functions.
"###
);
test_report!(
cannot_import_structural_eq_not_eq,
indoc!(
r#"
{
a: Bool.structuralEq,
b: Bool.structuralNotEq,
}
"#
),
@r###"
NOT EXPOSED /code/proj/Main.roc
The Bool module does not expose `structuralEq`:
5 a: Bool.structuralEq,
^^^^^^^^^^^^^^^^^
Did you mean one of these?
Bool.true
Bool.isNotEq
Bool.false
Bool.isEq
NOT EXPOSED /code/proj/Main.roc
The Bool module does not expose `structuralNotEq`:
6 b: Bool.structuralNotEq,
^^^^^^^^^^^^^^^^^^^^
Did you mean one of these?
Bool.isNotEq
Bool.true
Bool.boolIsEq
Bool.false
"###
);
}