From 16d12a51c27b9ad3151fb6f3d1db7735dfa89e7c Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Wed, 5 Oct 2022 14:33:37 -0500 Subject: [PATCH 01/42] Add `Eq` to the standard library --- crates/compiler/builtins/roc/Eq.roc | 39 +++++++++++++++++++++++ crates/compiler/builtins/roc/Num.roc | 2 +- crates/compiler/builtins/src/roc.rs | 2 ++ crates/compiler/load_internal/src/file.rs | 4 +++ crates/compiler/module/src/ident.rs | 1 + crates/compiler/module/src/symbol.rs | 10 ++++-- crates/compiler/solve/tests/solve_expr.rs | 18 +++++++++++ 7 files changed, 73 insertions(+), 3 deletions(-) create mode 100644 crates/compiler/builtins/roc/Eq.roc diff --git a/crates/compiler/builtins/roc/Eq.roc b/crates/compiler/builtins/roc/Eq.roc new file mode 100644 index 0000000000..ba95a823a2 --- /dev/null +++ b/crates/compiler/builtins/roc/Eq.roc @@ -0,0 +1,39 @@ +interface Eq + exposes [ + Eq, + isEq, + isNotEq, + ] + imports [ + Bool, + ] + +## 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 `Eq.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 + +## Calls [isEq] on the given values, then calls [not] on the result. +## +## `a != b` is shorthand for `Eq.isNotEq a b`. +isNotEq : a, a -> Bool | a has Eq +isNotEq = \a, b -> Bool.not (isEq a b) diff --git a/crates/compiler/builtins/roc/Num.roc b/crates/compiler/builtins/roc/Num.roc index 0b95884eb6..a5f1d087a5 100644 --- a/crates/compiler/builtins/roc/Num.roc +++ b/crates/compiler/builtins/roc/Num.roc @@ -1291,7 +1291,7 @@ toF64Checked : Num * -> Result F64 [OutOfBounds]* ## >>> Num.isNaN (Num.pow -1 0.5) ## ## *NaN* is unusual from other numberic values in that: -## * *NaN* is not equal to any other number, even itself. [Bool.isEq] always returns `Bool.false` if either argument is *NaN*. +## * *NaN* is not equal to any other number, even itself. [Eq.isEq] always returns `Bool.false` if either argument is *NaN*. ## * *NaN* has no ordering, so [isLt], [isLte], [isGt], and [isGte] always return `Bool.false` if either argument is *NaN*. ## ## These rules come from the [IEEE-754](https://en.wikipedia.org/wiki/IEEE_754) diff --git a/crates/compiler/builtins/src/roc.rs b/crates/compiler/builtins/src/roc.rs index 68b44a9714..661abf91c0 100644 --- a/crates/compiler/builtins/src/roc.rs +++ b/crates/compiler/builtins/src/roc.rs @@ -14,6 +14,7 @@ pub fn module_source(module_id: ModuleId) -> &'static str { ModuleId::ENCODE => ENCODE, ModuleId::DECODE => DECODE, ModuleId::HASH => HASH, + ModuleId::EQ => EQ, ModuleId::JSON => JSON, _ => panic!( "ModuleId {:?} is not part of the standard library", @@ -33,4 +34,5 @@ const BOOL: &str = include_str!("../roc/Bool.roc"); const ENCODE: &str = include_str!("../roc/Encode.roc"); const DECODE: &str = include_str!("../roc/Decode.roc"); const HASH: &str = include_str!("../roc/Hash.roc"); +const EQ: &str = include_str!("../roc/Eq.roc"); const JSON: &str = include_str!("../roc/Json.roc"); diff --git a/crates/compiler/load_internal/src/file.rs b/crates/compiler/load_internal/src/file.rs index bd8b931b62..0665902229 100644 --- a/crates/compiler/load_internal/src/file.rs +++ b/crates/compiler/load_internal/src/file.rs @@ -178,6 +178,7 @@ impl Default for ModuleCache<'_> { ENCODE, DECODE, HASH, + EQ, JSON, } @@ -2244,6 +2245,7 @@ fn update<'a>( extend_header_with_builtin(&mut header, ModuleId::ENCODE); extend_header_with_builtin(&mut header, ModuleId::DECODE); extend_header_with_builtin(&mut header, ModuleId::HASH); + extend_header_with_builtin(&mut header, ModuleId::EQ); } state @@ -3278,6 +3280,7 @@ fn load_module<'a>( "Encode", ModuleId::ENCODE "Decode", ModuleId::DECODE "Hash", ModuleId::HASH + "Eq", ModuleId::EQ "Json", ModuleId::JSON } @@ -4768,6 +4771,7 @@ fn canonicalize_and_constrain<'a>( | ModuleId::DICT | ModuleId::SET | ModuleId::HASH + | ModuleId::EQ ); if !name.is_builtin() || should_include_builtin { diff --git a/crates/compiler/module/src/ident.rs b/crates/compiler/module/src/ident.rs index f0cb88b5e5..005c2d7fff 100644 --- a/crates/compiler/module/src/ident.rs +++ b/crates/compiler/module/src/ident.rs @@ -86,6 +86,7 @@ impl ModuleName { pub const ENCODE: &'static str = "Encode"; pub const DECODE: &'static str = "Decode"; pub const HASH: &'static str = "Hash"; + pub const EQ: &'static str = "Eq"; pub const JSON: &'static str = "Json"; pub fn as_str(&self) -> &str { diff --git a/crates/compiler/module/src/symbol.rs b/crates/compiler/module/src/symbol.rs index cd01b2b4c6..20f0620b1d 100644 --- a/crates/compiler/module/src/symbol.rs +++ b/crates/compiler/module/src/symbol.rs @@ -1513,9 +1513,15 @@ define_builtins! { 15 HASH_HASH_STR_BYTES: "hashStrBytes" 16 HASH_HASH_LIST: "hashList" } - 14 JSON: "Json" => { + 14 EQ: "Eq" => { + 0 EQ_EQ: "Eq" exposed_type=true + 1 EQ_IS_EQ: "isEq" + 2 EQ_IS_NOT_EQ: "isNotEq" + 3 EQ_STRUCTURAL_EQ: "structuralEq" + } + 15 JSON: "Json" => { 0 JSON_JSON: "Json" } - num_modules: 15 // Keep this count up to date by hand! (TODO: see the mut_map! macro for how we could determine this count correctly in the macro) + num_modules: 16 // Keep this count up to date by hand! (TODO: see the mut_map! macro for how we could determine this count correctly in the macro) } diff --git a/crates/compiler/solve/tests/solve_expr.rs b/crates/compiler/solve/tests/solve_expr.rs index 95050db875..f23b0d688e 100644 --- a/crates/compiler/solve/tests/solve_expr.rs +++ b/crates/compiler/solve/tests/solve_expr.rs @@ -7972,4 +7972,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 = Eq.isEq (@Trivial {}) (@Trivial {}) + "# + ), + "Bool", + ); + } } From 4c30e4c4bb81628bf7a463f743a242d203484716 Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Wed, 5 Oct 2022 14:44:30 -0500 Subject: [PATCH 02/42] Add derive-key implementation for IsEq --- crates/compiler/derive_key/src/lib.rs | 7 +++++++ crates/compiler/test_derive/src/tests.rs | 1 + crates/compiler/test_derive/src/util.rs | 5 +++++ 3 files changed, 13 insertions(+) diff --git a/crates/compiler/derive_key/src/lib.rs b/crates/compiler/derive_key/src/lib.rs index ac5f4b66da..fc16c2df32 100644 --- a/crates/compiler/derive_key/src/lib.rs +++ b/crates/compiler/derive_key/src/lib.rs @@ -76,6 +76,7 @@ pub enum DeriveBuiltin { ToEncoder, Decoder, Hash, + IsEq, } impl TryFrom for DeriveBuiltin { @@ -86,6 +87,7 @@ impl TryFrom for DeriveBuiltin { Symbol::ENCODE_TO_ENCODER => Ok(DeriveBuiltin::ToEncoder), Symbol::DECODE_DECODER => Ok(DeriveBuiltin::Decoder), Symbol::HASH_HASH => Ok(DeriveBuiltin::Hash), + Symbol::EQ_IS_EQ => Ok(DeriveBuiltin::IsEq), _ => Err(value), } } @@ -112,6 +114,11 @@ 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_EQ)) + } } } } diff --git a/crates/compiler/test_derive/src/tests.rs b/crates/compiler/test_derive/src/tests.rs index 080dbc4dc7..12cae58582 100644 --- a/crates/compiler/test_derive/src/tests.rs +++ b/crates/compiler/test_derive/src/tests.rs @@ -2,6 +2,7 @@ mod decoding; mod encoding; +mod eq; mod hash; mod pretty_print; diff --git a/crates/compiler/test_derive/src/util.rs b/crates/compiler/test_derive/src/util.rs index 84e0de860d..4239034b66 100644 --- a/crates/compiler/test_derive/src/util.rs +++ b/crates/compiler/test_derive/src/util.rs @@ -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::EQ, + module_source(ModuleId::EQ), + builtins_path.join("Eq.roc"), + ), } } From 5931dd5fc2c145786f4980d00113b749cd812605 Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Wed, 5 Oct 2022 15:07:18 -0500 Subject: [PATCH 03/42] Check in test derive for eq --- crates/compiler/test_derive/src/eq.rs | 38 +++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 crates/compiler/test_derive/src/eq.rs diff --git a/crates/compiler/test_derive/src/eq.rs b/crates/compiler/test_derive/src/eq.rs new file mode 100644 index 0000000000..75d12e409e --- /dev/null +++ b/crates/compiler/test_derive/src/eq.rs @@ -0,0 +1,38 @@ +#![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_EQ); + check_single_lset_immediate(IsEq, v!(U16), Symbol::BOOL_EQ); + check_single_lset_immediate(IsEq, v!(U32), Symbol::BOOL_EQ); + check_single_lset_immediate(IsEq, v!(U64), Symbol::BOOL_EQ); + check_single_lset_immediate(IsEq, v!(U128), Symbol::BOOL_EQ); + check_single_lset_immediate(IsEq, v!(I8), Symbol::BOOL_EQ); + check_single_lset_immediate(IsEq, v!(I16), Symbol::BOOL_EQ); + check_single_lset_immediate(IsEq, v!(I32), Symbol::BOOL_EQ); + check_single_lset_immediate(IsEq, v!(I64), Symbol::BOOL_EQ); + check_single_lset_immediate(IsEq, v!(I128), Symbol::BOOL_EQ); + check_single_lset_immediate(IsEq, v!(STR), Symbol::BOOL_EQ); + check_single_lset_immediate(IsEq, v!(Symbol::LIST_LIST v!(U8)), Symbol::BOOL_EQ); + check_single_lset_immediate(IsEq, v!(Symbol::LIST_LIST v!(STR)), Symbol::BOOL_EQ); + check_single_lset_immediate(IsEq, v!({ a: v!(U8), }), Symbol::BOOL_EQ); + check_single_lset_immediate(IsEq, v!(EMPTY_RECORD), Symbol::BOOL_EQ); + check_single_lset_immediate(IsEq, v!([ A v!(U8) v!(STR), B v!(STR) ]), Symbol::BOOL_EQ); + check_single_lset_immediate(IsEq, v!([ A v!(U8) v!(STR), B v!(STR) ]), Symbol::BOOL_EQ); + check_single_lset_immediate(IsEq, v!([ Nil, Cons v!(^lst)] as lst), Symbol::BOOL_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_EQ); +} From b587bcf0c2d4e8b20cf30657c65e7520105c0065 Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Wed, 5 Oct 2022 15:07:27 -0500 Subject: [PATCH 04/42] Implement obligation checking for the `Eq` ability Every type can have `Eq.isEq` derived for it, as long as - it does not transitively contain a function - it does not transitively contain a floating point value - it does not transitively contain an opaque type that does not support `Eq` --- crates/compiler/module/src/symbol.rs | 1 + crates/compiler/solve/src/ability.rs | 123 +++++++- crates/compiler/solve_problem/src/lib.rs | 6 + crates/reporting/src/error/type.rs | 15 +- crates/reporting/tests/test_reporting.rs | 373 ++++++++++++++++++----- 5 files changed, 437 insertions(+), 81 deletions(-) diff --git a/crates/compiler/module/src/symbol.rs b/crates/compiler/module/src/symbol.rs index 20f0620b1d..d5a48f1938 100644 --- a/crates/compiler/module/src/symbol.rs +++ b/crates/compiler/module/src/symbol.rs @@ -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::EQ_EQ, &[Symbol::EQ_IS_EQ]), ]; /// 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 77b6ae7f2e..98322819a1 100644 --- a/crates/compiler/solve/src/ability.rs +++ b/crates/compiler/solve/src/ability.rs @@ -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::EQ_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,102 @@ impl DerivableVisitor for DeriveHash { } } +struct DeriveEq; +impl DerivableVisitor for DeriveEq { + const ABILITY: Symbol = Symbol::EQ_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 { + 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_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( diff --git a/crates/compiler/solve_problem/src/lib.rs b/crates/compiler/solve_problem/src/lib.rs index fafd42b1b4..e1de64944d 100644 --- a/crates/compiler/solve_problem/src/lib.rs +++ b/crates/compiler/solve_problem/src/lib.rs @@ -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, +} diff --git a/crates/reporting/src/error/type.rs b/crates/reporting/src/error/type.rs index 7208addabc..3f66d73f5c 100644 --- a/crates/reporting/src/error/type.rs +++ b/crates/reporting/src/error/type.rs @@ -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}; @@ -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::EQ_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::EQ_EQ), + alloc.reflow(" ability!"), + ]))) + } + }, } } diff --git a/crates/reporting/tests/test_reporting.rs b/crates/reporting/tests/test_reporting.rs index 27f86c654b..f9116657c7 100644 --- a/crates/reporting/tests/test_reporting.rs +++ b/crates/reporting/tests/test_reporting.rs @@ -2098,7 +2098,7 @@ mod test_reporting { Ok U8 Box - f + Eq "### ); @@ -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 @@ -9190,9 +9190,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 +9200,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 +9209,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 +9224,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 +9265,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 +9279,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 +9288,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 +9345,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 +9355,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 +9380,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 +9390,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 +9400,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 +9417,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 +9429,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. "### @@ -11101,6 +11101,29 @@ All branches in an `if` must have the same type! "### ); + 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 ─ + + Roc can't derive an implementation of the `Eq.Eq` 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.Eq` for `A`. + "### + ); + test_report!( big_char_does_not_fit_in_u8_pattern, indoc!( @@ -11132,4 +11155,198 @@ 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 ─ + + Roc can't derive an implementation of the `Eq.Eq` for `A`: + + 3│ A := F32 has [Eq] + ^^ + + Note: I can't derive `Eq.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.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 ─ + + Roc can't derive an implementation of the `Eq.Eq` for `A`: + + 3│ A := F64 has [Eq] + ^^ + + Note: I can't derive `Eq.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.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 ─ + + Roc can't derive an implementation of the `Eq.Eq` for `A`: + + 3│ A := B has [Eq] + ^^ + + Tip: `B` does not implement `Eq`. Consider adding a custom implementation + or `has Eq.Eq` to the definition of `B`. + + Tip: You can define a custom implementation of `Eq.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) + ^^^^^^^ + + Roc can't generate an implementation of the `Eq.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) + ^^^^^^^^^^^^^ + + Roc can't generate an implementation of the `Eq.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. + "### + ); } From 0045c16b3ecf532a6143d6d76659d7476926579a Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Wed, 5 Oct 2022 15:10:46 -0500 Subject: [PATCH 05/42] Improve notes on underivable abilities --- crates/reporting/src/error/canonicalize.rs | 4 +- crates/reporting/src/error/type.rs | 2 +- crates/reporting/tests/test_reporting.rs | 66 +++++++++++----------- 3 files changed, 37 insertions(+), 35 deletions(-) diff --git a/crates/reporting/src/error/canonicalize.rs b/crates/reporting/src/error/canonicalize.rs index c3c76c5bd4..22a1864749 100644 --- a/crates/reporting/src/error/canonicalize.rs +++ b/crates/reporting/src/error/canonicalize.rs @@ -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(", "), ) } diff --git a/crates/reporting/src/error/type.rs b/crates/reporting/src/error/type.rs index 3f66d73f5c..aaeae475f3 100644 --- a/crates/reporting/src/error/type.rs +++ b/crates/reporting/src/error/type.rs @@ -317,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("."), diff --git a/crates/reporting/tests/test_reporting.rs b/crates/reporting/tests/test_reporting.rs index f9116657c7..4cc253dd83 100644 --- a/crates/reporting/tests/test_reporting.rs +++ b/crates/reporting/tests/test_reporting.rs @@ -9475,18 +9475,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 +9498,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`: + Roc can't derive an implementation of the `Encode.Encoding` 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 +9523,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`: + Roc can't derive an implementation of the `Encode.Encoding` 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!( @@ -10212,7 +10212,7 @@ All branches in an `if` must have the same type! 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`. "### ); @@ -10238,7 +10238,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`. "### ); @@ -10868,7 +10868,7 @@ All branches in an `if` must have the same type! 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`. "### ); @@ -10894,7 +10894,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`. "### ); @@ -11120,7 +11120,7 @@ All branches in an `if` must have the same type! Note: `Eq` cannot be generated for functions. - Tip: You can define a custom implementation of `Eq.Eq` for `A`. + Tip: You can define a custom implementation of `Eq` for `A`. "### ); @@ -11178,7 +11178,7 @@ All branches in an `if` must have the same type! 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.Eq` for `A`. + Tip: You can define a custom implementation of `Eq` for `A`. "### ); @@ -11204,7 +11204,7 @@ All branches in an `if` must have the same type! 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.Eq` for `A`. + Tip: You can define a custom implementation of `Eq` for `A`. "### ); @@ -11230,7 +11230,7 @@ All branches in an `if` must have the same type! Tip: `B` does not implement `Eq`. Consider adding a custom implementation or `has Eq.Eq` to the definition of `B`. - Tip: You can define a custom implementation of `Eq.Eq` for `A`. + Tip: You can define a custom implementation of `Eq` for `A`. "### ); From 8310230f788cc1d42f1001f8c54c18e1b694615e Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Wed, 5 Oct 2022 15:22:02 -0500 Subject: [PATCH 06/42] Add gen test for custom Eq --- crates/compiler/test_gen/src/gen_abilities.rs | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/crates/compiler/test_gen/src/gen_abilities.rs b/crates/compiler/test_gen/src/gen_abilities.rs index 17974b64b2..ab1d3c5f54 100644 --- a/crates/compiler/test_gen/src/gen_abilities.rs +++ b/crates/compiler/test_gen/src/gen_abilities.rs @@ -1602,3 +1602,41 @@ 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 Eq.isEq a b && !(Eq.isEq b c) then + "okay" + else + "fail" + "# + ), + RocStr::from("okay"), + RocStr + ) + } +} From 54e977be2dff304fefd0852a871ed7959770d8f4 Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Wed, 5 Oct 2022 17:16:27 -0500 Subject: [PATCH 07/42] Constrain function type on closure type, not signature --- crates/compiler/constrain/src/expr.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/crates/compiler/constrain/src/expr.rs b/crates/compiler/constrain/src/expr.rs index 869b063901..12990f3d88 100644 --- a/crates/compiler/constrain/src/expr.rs +++ b/crates/compiler/constrain/src/expr.rs @@ -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), }, ); From 3674f6861e1dc6f921005a325517a73bdeb675d8 Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Wed, 5 Oct 2022 17:17:35 -0500 Subject: [PATCH 08/42] Compile derived structural equality --- crates/compiler/builtins/roc/Eq.roc | 5 +++++ crates/compiler/can/src/builtins.rs | 2 ++ crates/compiler/can/src/module.rs | 1 + crates/compiler/load_internal/src/file.rs | 6 +++++- crates/compiler/module/src/low_level.rs | 2 ++ crates/compiler/module/src/symbol.rs | 11 +++++++++++ crates/compiler/test_gen/src/gen_abilities.rs | 15 +++++++++++++++ 7 files changed, 41 insertions(+), 1 deletion(-) diff --git a/crates/compiler/builtins/roc/Eq.roc b/crates/compiler/builtins/roc/Eq.roc index ba95a823a2..15e5011d03 100644 --- a/crates/compiler/builtins/roc/Eq.roc +++ b/crates/compiler/builtins/roc/Eq.roc @@ -3,6 +3,7 @@ interface Eq Eq, isEq, isNotEq, + structuralEq, ] imports [ Bool, @@ -37,3 +38,7 @@ Eq has ## `a != b` is shorthand for `Eq.isNotEq a b`. isNotEq : a, a -> Bool | a has Eq isNotEq = \a, b -> Bool.not (isEq 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 diff --git a/crates/compiler/can/src/builtins.rs b/crates/compiler/can/src/builtins.rs index 461eaeada9..5f9a113e1a 100644 --- a/crates/compiler/can/src/builtins.rs +++ b/crates/compiler/can/src/builtins.rs @@ -70,6 +70,7 @@ macro_rules! map_symbol_to_lowlevel_and_arity { // Below, we explicitly handle some exceptions to the pattern where a lowlevel maps // directly to a symbol. If you are unsure if your lowlevel is an exception, assume // that it isn't and just see if that works. + #[allow(unreachable_patterns)] // TODO: remove after we replace `BOOL_EQ` with `EQ_EQ` wholly match lowlevel { $( LowLevel::$lowlevel => Symbol::$symbol, @@ -192,6 +193,7 @@ map_symbol_to_lowlevel_and_arity! { NumToStr; NUM_TO_STR; 1, Eq; BOOL_EQ; 2, + Eq; EQ_STRUCTURAL_EQ; 2, NotEq; BOOL_NEQ; 2, And; BOOL_AND; 2, Or; BOOL_OR; 2, diff --git a/crates/compiler/can/src/module.rs b/crates/compiler/can/src/module.rs index 005071ef1d..6435fc65f2 100644 --- a/crates/compiler/can/src/module.rs +++ b/crates/compiler/can/src/module.rs @@ -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)); } diff --git a/crates/compiler/load_internal/src/file.rs b/crates/compiler/load_internal/src/file.rs index 0665902229..ba05c85125 100644 --- a/crates/compiler/load_internal/src/file.rs +++ b/crates/compiler/load_internal/src/file.rs @@ -4469,7 +4469,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(); ( diff --git a/crates/compiler/module/src/low_level.rs b/crates/compiler/module/src/low_level.rs index 0d6ba4846d..8b70664d88 100644 --- a/crates/compiler/module/src/low_level.rs +++ b/crates/compiler/module/src/low_level.rs @@ -184,6 +184,7 @@ macro_rules! map_symbol_to_lowlevel { // Below, we explicitly handle some exceptions to the pattern where a lowlevel maps // directly to a symbol. If you are unsure if your lowlevel is an exception, assume // that it isn't and just see if that works. + #[allow(unreachable_patterns)] // TODO: remove after we replace `BOOL_EQ` with `EQ_EQ` wholly match lowlevel { $( LowLevel::$lowlevel => Symbol::$symbol, @@ -310,6 +311,7 @@ map_symbol_to_lowlevel! { NumShiftRightZfBy <= NUM_SHIFT_RIGHT_ZERO_FILL, NumToStr <= NUM_TO_STR, Eq <= BOOL_EQ, + Eq <= EQ_STRUCTURAL_EQ, NotEq <= BOOL_NEQ, And <= BOOL_AND, Or <= BOOL_OR, diff --git a/crates/compiler/module/src/symbol.rs b/crates/compiler/module/src/symbol.rs index d5a48f1938..7efe44fc11 100644 --- a/crates/compiler/module/src/symbol.rs +++ b/crates/compiler/module/src/symbol.rs @@ -99,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::EQ_STRUCTURAL_EQ + ) && false + } + pub fn module_string<'a>(&self, interns: &'a Interns) -> &'a ModuleName { interns .module_ids diff --git a/crates/compiler/test_gen/src/gen_abilities.rs b/crates/compiler/test_gen/src/gen_abilities.rs index ab1d3c5f54..6e89ed0a90 100644 --- a/crates/compiler/test_gen/src/gen_abilities.rs +++ b/crates/compiler/test_gen/src/gen_abilities.rs @@ -1639,4 +1639,19 @@ mod eq { RocStr ) } + + #[test] + fn derive_structural_eq() { + assert_evals_to!( + indoc!( + r#" + app "test" provides [main] to "./platform" + + main = Eq.isEq 10u8 10u8 + "# + ), + true, + bool + ) + } } From 4adae7651fd4e029d7245c0e8f54eaed8850f7cf Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Wed, 5 Oct 2022 17:48:31 -0500 Subject: [PATCH 09/42] Use Eq.structuralEq for deriving --- crates/compiler/derive_key/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/compiler/derive_key/src/lib.rs b/crates/compiler/derive_key/src/lib.rs index fc16c2df32..da33c13ec8 100644 --- a/crates/compiler/derive_key/src/lib.rs +++ b/crates/compiler/derive_key/src/lib.rs @@ -117,7 +117,7 @@ impl Derived { 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_EQ)) + Ok(Derived::SingleLambdaSetImmediate(Symbol::EQ_STRUCTURAL_EQ)) } } } From c618ced74338b00fef9bb8b19ca3d8cfa9a05ceb Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Sat, 8 Oct 2022 11:50:04 -0500 Subject: [PATCH 10/42] Replace `Bool.isEq` with `Eq.isEq` --- crates/compiler/builtins/roc/Bool.roc | 17 +---------------- crates/compiler/builtins/roc/Dict.roc | 23 +++++++++++++---------- crates/compiler/builtins/roc/Eq.roc | 2 +- crates/compiler/builtins/roc/Json.roc | 1 + crates/compiler/builtins/roc/List.roc | 11 ++++++----- crates/compiler/builtins/roc/Num.roc | 9 +++++---- crates/compiler/builtins/roc/Set.roc | 20 +++++++++++--------- crates/compiler/builtins/roc/Str.roc | 1 + crates/compiler/can/src/operator.rs | 2 +- 9 files changed, 40 insertions(+), 46 deletions(-) diff --git a/crates/compiler/builtins/roc/Bool.roc b/crates/compiler/builtins/roc/Bool.roc index e7dfc2ff71..bb90ca245f 100644 --- a/crates/compiler/builtins/roc/Bool.roc +++ b/crates/compiler/builtins/roc/Bool.roc @@ -1,5 +1,5 @@ interface Bool - exposes [Bool, true, false, and, or, not, isEq, isNotEq] + exposes [Bool, true, false, and, or, not, isNotEq] imports [] Bool := [True, False] @@ -67,21 +67,6 @@ 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` diff --git a/crates/compiler/builtins/roc/Dict.roc b/crates/compiler/builtins/roc/Dict.roc index 2400cc3121..2f496b6f8d 100644 --- a/crates/compiler/builtins/roc/Dict.roc +++ b/crates/compiler/builtins/roc/Dict.roc @@ -19,6 +19,7 @@ interface Dict ] imports [ Bool.{ Bool }, + Eq.{ Eq }, Result.{ Result }, List, Num.{ Nat }, @@ -72,7 +73,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 +84,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 +97,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 +112,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 -> @@ -128,7 +131,7 @@ update : Dict k v, k, ([Present v, Missing] -> [Present v, Missing]) - -> Dict k v + -> Dict k v | k has Eq update = \dict, key, alter -> possibleValue = get dict key @@ -151,7 +154,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 +181,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 +205,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 diff --git a/crates/compiler/builtins/roc/Eq.roc b/crates/compiler/builtins/roc/Eq.roc index 15e5011d03..ceba050852 100644 --- a/crates/compiler/builtins/roc/Eq.roc +++ b/crates/compiler/builtins/roc/Eq.roc @@ -6,7 +6,7 @@ interface Eq structuralEq, ] imports [ - Bool, + Bool.{ Bool }, ] ## A type that can be compared for total equality. diff --git a/crates/compiler/builtins/roc/Json.roc b/crates/compiler/builtins/roc/Json.roc index ed39e33819..e4bbbb4e9d 100644 --- a/crates/compiler/builtins/roc/Json.roc +++ b/crates/compiler/builtins/roc/Json.roc @@ -34,6 +34,7 @@ interface Json Dec, }, Bool.{ Bool }, + Eq.{ Eq }, Result, ] diff --git a/crates/compiler/builtins/roc/List.roc b/crates/compiler/builtins/roc/List.roc index 7d5b90d40e..f4afdca982 100644 --- a/crates/compiler/builtins/roc/List.roc +++ b/crates/compiler/builtins/roc/List.roc @@ -66,6 +66,7 @@ interface List ] imports [ Bool.{ Bool }, + Eq.{ Eq }, Result.{ Result }, Num.{ Nat, Num, Int }, ] @@ -354,7 +355,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 +904,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 +916,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 +945,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 +960,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 -> diff --git a/crates/compiler/builtins/roc/Num.roc b/crates/compiler/builtins/roc/Num.roc index a5f1d087a5..e8d284c66b 100644 --- a/crates/compiler/builtins/roc/Num.roc +++ b/crates/compiler/builtins/roc/Num.roc @@ -146,6 +146,7 @@ interface Num imports [ Bool.{ Bool }, Result.{ Result }, + Eq, ] ## Represents a number that could be either an [Int] or a [Frac]. @@ -793,7 +794,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 +803,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 +828,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 +848,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) diff --git a/crates/compiler/builtins/roc/Set.roc b/crates/compiler/builtins/roc/Set.roc index c1fe50a5ad..d9b9305813 100644 --- a/crates/compiler/builtins/roc/Set.roc +++ b/crates/compiler/builtins/roc/Set.roc @@ -14,9 +14,11 @@ interface Set intersection, difference, ] - imports [List, Bool.{ Bool }, Dict.{ Dict }, Num.{ Nat }] + imports [List, Bool.{ Bool }, Eq.{ 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) diff --git a/crates/compiler/builtins/roc/Str.roc b/crates/compiler/builtins/roc/Str.roc index 3e10116b05..0fe718a830 100644 --- a/crates/compiler/builtins/roc/Str.roc +++ b/crates/compiler/builtins/roc/Str.roc @@ -48,6 +48,7 @@ interface Str ] imports [ Bool.{ Bool }, + Eq.{ Eq }, Result.{ Result }, List, Num.{ Nat, Num, U8, U16, U32, U64, U128, I8, I16, I32, I64, I128, F32, F64, Dec }, diff --git a/crates/compiler/can/src/operator.rs b/crates/compiler/can/src/operator.rs index 9a46592f0a..efb0e4ae3c 100644 --- a/crates/compiler/can/src/operator.rs +++ b/crates/compiler/can/src/operator.rs @@ -409,7 +409,7 @@ fn binop_to_function(binop: BinOp) -> (&'static str, &'static str) { Percent => (ModuleName::NUM, "rem"), Plus => (ModuleName::NUM, "add"), Minus => (ModuleName::NUM, "sub"), - Equals => (ModuleName::BOOL, "isEq"), + Equals => (ModuleName::EQ, "isEq"), NotEquals => (ModuleName::BOOL, "isNotEq"), LessThan => (ModuleName::NUM, "isLt"), GreaterThan => (ModuleName::NUM, "isGt"), From 1c753ae031c4ca412bf2a32fd70ca2a6db11afd2 Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Sat, 8 Oct 2022 11:54:15 -0500 Subject: [PATCH 11/42] Update solve tests --- crates/compiler/solve/tests/solve_expr.rs | 24 +++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/crates/compiler/solve/tests/solve_expr.rs b/crates/compiler/solve/tests/solve_expr.rs index f23b0d688e..c7167ec089 100644 --- a/crates/compiler/solve/tests/solve_expr.rs +++ b/crates/compiler/solve/tests/solve_expr.rs @@ -3469,7 +3469,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 +3730,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 +3742,7 @@ mod solve_expr { reconstructPath "# ), - "Dict position position, position -> List position", + "Dict position position, position -> List position | position has Eq", ); } @@ -3777,7 +3777,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 +3792,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 +4441,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 +4555,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 +4585,7 @@ mod solve_expr { main : RBTree I64 I64 main = - removeHelp 1 Empty + removeHelp 1i64 Empty "# ), "RBTree I64 I64", @@ -4643,7 +4643,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 +4678,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 +4701,7 @@ mod solve_expr { main : RBTree I64 I64 main = - removeHelp 1 Empty + removeHelp 1i64 Empty "# ), "RBTree I64 I64", From b6ffd09adf44362715c686da087d47ce42cdf67d Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Sat, 8 Oct 2022 11:56:16 -0500 Subject: [PATCH 12/42] Update reporting tests --- crates/reporting/tests/test_reporting.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/reporting/tests/test_reporting.rs b/crates/reporting/tests/test_reporting.rs index 4cc253dd83..a9200b7420 100644 --- a/crates/reporting/tests/test_reporting.rs +++ b/crates/reporting/tests/test_reporting.rs @@ -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? @@ -11097,7 +11097,7 @@ All branches in an `if` must have the same type! But `contains` needs its 2nd argument to be: - U8 + Int Unsigned8 "### ); From f3a6b45452849ccf754dbf4b087b90a3f529782a Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Mon, 10 Oct 2022 13:19:54 -0500 Subject: [PATCH 13/42] Add must_use to all unification results Making sure that no unspecialized lambda sets slip through the cracks! --- crates/compiler/unify/src/unify.rs | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/crates/compiler/unify/src/unify.rs b/crates/compiler/unify/src/unify.rs index ed20698976..e9ef0be264 100644 --- a/crates/compiler/unify/src/unify.rs +++ b/crates/compiler/unify/src/unify.rs @@ -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( env: &mut Env, var1: Variable, @@ -360,6 +362,7 @@ pub fn unify_with_collector( } #[inline(always)] +#[must_use] fn unify_help( env: &mut Env, var1: Variable, @@ -416,6 +419,7 @@ fn unify_help( } #[inline(always)] +#[must_use] pub fn unify_pool( env: &mut Env, pool: &mut Pool, @@ -496,6 +500,7 @@ fn debug_print_unified_types( }) } +#[must_use] fn unify_context(env: &mut Env, pool: &mut Pool, ctx: Context) -> Outcome { #[cfg(debug_assertions)] debug_print_unified_types::(env, &ctx, None); @@ -555,6 +560,7 @@ fn not_in_range_mismatch() -> Outcome { } #[inline(always)] +#[must_use] fn unify_ranged_number( 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( env: &mut Env, pool: &mut Pool, @@ -812,6 +819,7 @@ fn unify_two_aliases( // Unifies a structural alias #[inline(always)] +#[must_use] fn unify_alias( 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( env: &mut Env, pool: &mut Pool, @@ -934,6 +943,7 @@ fn unify_opaque( } #[inline(always)] +#[must_use] fn unify_structure( env: &mut Env, pool: &mut Pool, @@ -1032,6 +1042,7 @@ fn unify_structure( } #[inline(always)] +#[must_use] fn unify_lambda_set( 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] fn unify_unspecialized_lambdas( env: &mut Env, pool: &mut Pool, @@ -1602,6 +1614,7 @@ fn unify_unspecialized_lambdas( )) } +#[must_use] fn unify_lambda_set_help( env: &mut Env, pool: &mut Pool, @@ -1695,6 +1708,7 @@ fn unify_lambda_set_help( whole_outcome } +#[must_use] fn unify_record( env: &mut Env, pool: &mut Pool, @@ -1801,6 +1815,7 @@ enum OtherFields { type SharedFields = Vec<(Lowercase, (RecordField, RecordField))>; +#[must_use] fn unify_shared_fields( 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( 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( env: &mut Env, pool: &mut Pool, @@ -2539,6 +2556,7 @@ fn unify_shared_tags_new( } } +#[must_use] fn unify_shared_tags_merge_new( env: &mut Env, ctx: &Context, @@ -2558,6 +2576,7 @@ fn unify_shared_tags_merge_new( } #[inline(always)] +#[must_use] fn unify_flat_type( env: &mut Env, pool: &mut Pool, @@ -2757,6 +2776,7 @@ fn unify_flat_type( } } +#[must_use] fn unify_zip_slices( env: &mut Env, pool: &mut Pool, @@ -2778,6 +2798,7 @@ fn unify_zip_slices( } #[inline(always)] +#[must_use] fn unify_rigid( env: &mut Env, ctx: &Context, @@ -2821,6 +2842,7 @@ fn unify_rigid( } #[inline(always)] +#[must_use] fn unify_rigid_able( env: &mut Env, ctx: &Context, @@ -2870,6 +2892,7 @@ fn unify_rigid_able( } #[inline(always)] +#[must_use] fn unify_flex( env: &mut Env, ctx: &Context, @@ -2906,6 +2929,7 @@ fn unify_flex( } #[inline(always)] +#[must_use] fn unify_flex_able( env: &mut Env, ctx: &Context, @@ -2981,6 +3005,7 @@ fn unify_flex_able( } } +#[must_use] fn merge_flex_able_with_concrete( env: &mut Env, ctx: &Context, @@ -3014,6 +3039,7 @@ fn merge_flex_able_with_concrete( } #[inline(always)] +#[must_use] fn unify_recursion( env: &mut Env, pool: &mut Pool, @@ -3100,6 +3126,7 @@ fn unify_recursion( } } +#[must_use] pub fn merge(env: &mut Env, ctx: &Context, content: Content) -> Outcome { let mut outcome: Outcome = 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( env: &mut Env, pool: &mut Pool, From bc2f4569aa21a9557bbefe7b2af74b42296eea5f Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Mon, 10 Oct 2022 13:33:28 -0500 Subject: [PATCH 14/42] Able variables that are never used can become void --- crates/compiler/mono/src/layout.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/crates/compiler/mono/src/layout.rs b/crates/compiler/mono/src/layout.rs index 1996e3c618..c6494e6d8a 100644 --- a/crates/compiler/mono/src/layout.rs +++ b/crates/compiler/mono/src/layout.rs @@ -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, { + return 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) From 71c4731256e38a1a7c126276a08bb15d8858d14a Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Mon, 10 Oct 2022 13:34:11 -0500 Subject: [PATCH 15/42] Remove test that is no longer correct --- crates/compiler/test_gen/src/gen_compare.rs | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/crates/compiler/test_gen/src/gen_compare.rs b/crates/compiler/test_gen/src/gen_compare.rs index da9e2a2437..1a44485307 100644 --- a/crates/compiler/test_gen/src/gen_compare.rs +++ b/crates/compiler/test_gen/src/gen_compare.rs @@ -77,23 +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() { From 0b02ef2803e5acf7714e14df8bfb1d62dec45203 Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Mon, 10 Oct 2022 13:34:59 -0500 Subject: [PATCH 16/42] Fix gen-tests except those that rely on Bool eq --- crates/compiler/test_gen/src/gen_compare.rs | 6 ++++-- crates/compiler/test_gen/src/gen_list.rs | 2 +- crates/compiler/test_gen/src/gen_primitives.rs | 8 ++++---- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/crates/compiler/test_gen/src/gen_compare.rs b/crates/compiler/test_gen/src/gen_compare.rs index 1a44485307..7cf09fabf1 100644 --- a/crates/compiler/test_gen/src/gen_compare.rs +++ b/crates/compiler/test_gen/src/gen_compare.rs @@ -96,6 +96,7 @@ fn neq_f64() { #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[ignore = "must support Eq on Bool directly"] fn eq_bool_tag() { assert_evals_to!( indoc!( @@ -113,6 +114,7 @@ fn eq_bool_tag() { #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[ignore = "must support Eq on Bool directly"] fn neq_bool_tag() { assert_evals_to!( indoc!( @@ -656,8 +658,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] diff --git a/crates/compiler/test_gen/src/gen_list.rs b/crates/compiler/test_gen/src/gen_list.rs index 9d5985caa3..188fcd7db4 100644 --- a/crates/compiler/test_gen/src/gen_list.rs +++ b/crates/compiler/test_gen/src/gen_list.rs @@ -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 diff --git a/crates/compiler/test_gen/src/gen_primitives.rs b/crates/compiler/test_gen/src/gen_primitives.rs index 5a65af1f17..8f8340dcf3 100644 --- a/crates/compiler/test_gen/src/gen_primitives.rs +++ b/crates/compiler/test_gen/src/gen_primitives.rs @@ -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 From 12778762b205b7bf88b74811058bbf1658f69466 Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Mon, 10 Oct 2022 13:35:39 -0500 Subject: [PATCH 17/42] Make sure `Box` is handled as an `Apply` type --- crates/compiler/solve/src/ability.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/crates/compiler/solve/src/ability.rs b/crates/compiler/solve/src/ability.rs index 98322819a1..3430060870 100644 --- a/crates/compiler/solve/src/ability.rs +++ b/crates/compiler/solve/src/ability.rs @@ -1027,7 +1027,11 @@ impl DerivableVisitor for DeriveEq { fn visit_apply(var: Variable, symbol: Symbol) -> Result { if matches!( symbol, - Symbol::LIST_LIST | Symbol::SET_SET | Symbol::DICT_DICT | Symbol::STR_STR, + Symbol::LIST_LIST + | Symbol::SET_SET + | Symbol::DICT_DICT + | Symbol::STR_STR + | Symbol::BOX_BOX_TYPE, ) { Ok(Descend(true)) } else { From 729f0a8e2778e6a9ea86571fc8bda9891b743860 Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Mon, 10 Oct 2022 13:36:39 -0500 Subject: [PATCH 18/42] Remove `return` on unreachable result --- crates/compiler/mono/src/layout.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/compiler/mono/src/layout.rs b/crates/compiler/mono/src/layout.rs index c6494e6d8a..a184594e01 100644 --- a/crates/compiler/mono/src/layout.rs +++ b/crates/compiler/mono/src/layout.rs @@ -2216,7 +2216,7 @@ impl<'a> Layout<'a> { } FlexAbleVar(_, _) | RigidAbleVar(_, _) => { roc_debug_flags::dbg_do!(roc_debug_flags::ROC_NO_UNBOUND_LAYOUT, { - return todo_abilities!("Able var is unbound!"); + todo_abilities!("Able var is unbound!"); }); // If we encounter an unbound type var (e.g. `*` or `a`) From 7298705db75d290a15d520015b43b5016ab9f8e3 Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Mon, 10 Oct 2022 14:41:29 -0500 Subject: [PATCH 19/42] Fix parse error --- crates/compiler/builtins/roc/Dict.roc | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/crates/compiler/builtins/roc/Dict.roc b/crates/compiler/builtins/roc/Dict.roc index 2f496b6f8d..7bdcf5dd51 100644 --- a/crates/compiler/builtins/roc/Dict.roc +++ b/crates/compiler/builtins/roc/Dict.roc @@ -127,11 +127,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 | k has Eq +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 From bd5f5ed735328ba46bc8bb8d885de8a66e9a83fe Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Mon, 10 Oct 2022 15:08:51 -0500 Subject: [PATCH 20/42] Old implementations can be overwritten before solving --- crates/compiler/can/src/abilities.rs | 8 ++++++-- crates/compiler/load_internal/src/file.rs | 6 ++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/crates/compiler/can/src/abilities.rs b/crates/compiler/can/src/abilities.rs index 20c05dbf8b..649d449e7f 100644 --- a/crates/compiler/can/src/abilities.rs +++ b/crates/compiler/can/src/abilities.rs @@ -504,8 +504,12 @@ impl IAbilitiesStore { 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) ); } diff --git a/crates/compiler/load_internal/src/file.rs b/crates/compiler/load_internal/src/file.rs index ba05c85125..68a6368d39 100644 --- a/crates/compiler/load_internal/src/file.rs +++ b/crates/compiler/load_internal/src/file.rs @@ -345,6 +345,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)); } @@ -4335,6 +4338,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. From a256947a9f9d918e320655eee9550f655d7e44e5 Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Mon, 10 Oct 2022 15:22:57 -0500 Subject: [PATCH 21/42] Move Eq to Bool --- crates/compiler/builtins/roc/Bool.roc | 40 +++++++++++++- crates/compiler/builtins/roc/Dict.roc | 3 +- crates/compiler/builtins/roc/Eq.roc | 44 --------------- crates/compiler/builtins/roc/Json.roc | 3 +- crates/compiler/builtins/roc/List.roc | 3 +- crates/compiler/builtins/roc/Num.roc | 1 - crates/compiler/builtins/roc/Set.roc | 2 +- crates/compiler/builtins/roc/Str.roc | 3 +- crates/compiler/builtins/src/roc.rs | 2 - crates/compiler/can/src/builtins.rs | 1 - crates/compiler/can/src/operator.rs | 2 +- crates/compiler/load_internal/src/file.rs | 4 -- crates/compiler/module/src/ident.rs | 1 - crates/compiler/module/src/low_level.rs | 2 - crates/compiler/module/src/symbol.rs | 17 +++--- crates/compiler/solve/tests/solve_expr.rs | 3 +- crates/compiler/test_derive/src/eq.rs | 54 ++++++++++++------- crates/compiler/test_derive/src/util.rs | 4 +- crates/compiler/test_gen/src/gen_abilities.rs | 4 +- 19 files changed, 92 insertions(+), 101 deletions(-) delete mode 100644 crates/compiler/builtins/roc/Eq.roc diff --git a/crates/compiler/builtins/roc/Bool.roc b/crates/compiler/builtins/roc/Bool.roc index bb90ca245f..189dc15d18 100644 --- a/crates/compiler/builtins/roc/Bool.roc +++ b/crates/compiler/builtins/roc/Bool.roc @@ -1,8 +1,34 @@ interface Bool - exposes [Bool, true, false, and, or, not, isNotEq] + exposes [Bool, Eq, true, false, and, or, not, isEq, isNotEq, structuralEq] 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 `Eq.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 @@ -74,3 +100,13 @@ not : Bool -> Bool ## Note that `isNotEq` takes `'val` instead of `val`, which means `isNotEq` does not ## accept arguments whose types contain functions. isNotEq : a, a -> Bool + +# ## Calls [isEq] on the given values, then calls [not] on the result. +# ## +# ## `a != b` is shorthand for `Eq.isNotEq a b`. +# isNotEq : a, a -> Bool | a has Eq +# isNotEq = \a, b -> Bool.not (isEq 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 diff --git a/crates/compiler/builtins/roc/Dict.roc b/crates/compiler/builtins/roc/Dict.roc index 7bdcf5dd51..fc5c6e2113 100644 --- a/crates/compiler/builtins/roc/Dict.roc +++ b/crates/compiler/builtins/roc/Dict.roc @@ -18,8 +18,7 @@ interface Dict removeAll, ] imports [ - Bool.{ Bool }, - Eq.{ Eq }, + Bool.{ Bool, Eq }, Result.{ Result }, List, Num.{ Nat }, diff --git a/crates/compiler/builtins/roc/Eq.roc b/crates/compiler/builtins/roc/Eq.roc deleted file mode 100644 index ceba050852..0000000000 --- a/crates/compiler/builtins/roc/Eq.roc +++ /dev/null @@ -1,44 +0,0 @@ -interface Eq - exposes [ - Eq, - isEq, - isNotEq, - structuralEq, - ] - imports [ - Bool.{ Bool }, - ] - -## 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 `Eq.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 - -## Calls [isEq] on the given values, then calls [not] on the result. -## -## `a != b` is shorthand for `Eq.isNotEq a b`. -isNotEq : a, a -> Bool | a has Eq -isNotEq = \a, b -> Bool.not (isEq 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 diff --git a/crates/compiler/builtins/roc/Json.roc b/crates/compiler/builtins/roc/Json.roc index e4bbbb4e9d..40981b98c2 100644 --- a/crates/compiler/builtins/roc/Json.roc +++ b/crates/compiler/builtins/roc/Json.roc @@ -33,8 +33,7 @@ interface Json F64, Dec, }, - Bool.{ Bool }, - Eq.{ Eq }, + Bool.{ Bool, Eq }, Result, ] diff --git a/crates/compiler/builtins/roc/List.roc b/crates/compiler/builtins/roc/List.roc index f4afdca982..19d6fc3ed5 100644 --- a/crates/compiler/builtins/roc/List.roc +++ b/crates/compiler/builtins/roc/List.roc @@ -65,8 +65,7 @@ interface List countIf, ] imports [ - Bool.{ Bool }, - Eq.{ Eq }, + Bool.{ Bool, Eq }, Result.{ Result }, Num.{ Nat, Num, Int }, ] diff --git a/crates/compiler/builtins/roc/Num.roc b/crates/compiler/builtins/roc/Num.roc index e8d284c66b..0cbc677cef 100644 --- a/crates/compiler/builtins/roc/Num.roc +++ b/crates/compiler/builtins/roc/Num.roc @@ -146,7 +146,6 @@ interface Num imports [ Bool.{ Bool }, Result.{ Result }, - Eq, ] ## Represents a number that could be either an [Int] or a [Frac]. diff --git a/crates/compiler/builtins/roc/Set.roc b/crates/compiler/builtins/roc/Set.roc index d9b9305813..e55209400f 100644 --- a/crates/compiler/builtins/roc/Set.roc +++ b/crates/compiler/builtins/roc/Set.roc @@ -14,7 +14,7 @@ interface Set intersection, difference, ] - imports [List, Bool.{ Bool }, Eq.{ Eq }, Dict.{ Dict }, Num.{ Nat }] + imports [List, Bool.{ Bool, Eq }, Dict.{ Dict }, Num.{ Nat }] Set k := Dict.Dict k {} has [Eq { isEq: setEq }] diff --git a/crates/compiler/builtins/roc/Str.roc b/crates/compiler/builtins/roc/Str.roc index 0fe718a830..ed22935ac7 100644 --- a/crates/compiler/builtins/roc/Str.roc +++ b/crates/compiler/builtins/roc/Str.roc @@ -47,8 +47,7 @@ interface Str withPrefix, ] imports [ - Bool.{ Bool }, - Eq.{ Eq }, + Bool.{ Bool, Eq }, Result.{ Result }, List, Num.{ Nat, Num, U8, U16, U32, U64, U128, I8, I16, I32, I64, I128, F32, F64, Dec }, diff --git a/crates/compiler/builtins/src/roc.rs b/crates/compiler/builtins/src/roc.rs index 661abf91c0..68b44a9714 100644 --- a/crates/compiler/builtins/src/roc.rs +++ b/crates/compiler/builtins/src/roc.rs @@ -14,7 +14,6 @@ pub fn module_source(module_id: ModuleId) -> &'static str { ModuleId::ENCODE => ENCODE, ModuleId::DECODE => DECODE, ModuleId::HASH => HASH, - ModuleId::EQ => EQ, ModuleId::JSON => JSON, _ => panic!( "ModuleId {:?} is not part of the standard library", @@ -34,5 +33,4 @@ const BOOL: &str = include_str!("../roc/Bool.roc"); const ENCODE: &str = include_str!("../roc/Encode.roc"); const DECODE: &str = include_str!("../roc/Decode.roc"); const HASH: &str = include_str!("../roc/Hash.roc"); -const EQ: &str = include_str!("../roc/Eq.roc"); const JSON: &str = include_str!("../roc/Json.roc"); diff --git a/crates/compiler/can/src/builtins.rs b/crates/compiler/can/src/builtins.rs index 5f9a113e1a..5b4dec3760 100644 --- a/crates/compiler/can/src/builtins.rs +++ b/crates/compiler/can/src/builtins.rs @@ -192,7 +192,6 @@ map_symbol_to_lowlevel_and_arity! { NumShiftRightZfBy; NUM_SHIFT_RIGHT_ZERO_FILL; 2, NumToStr; NUM_TO_STR; 1, - Eq; BOOL_EQ; 2, Eq; EQ_STRUCTURAL_EQ; 2, NotEq; BOOL_NEQ; 2, And; BOOL_AND; 2, diff --git a/crates/compiler/can/src/operator.rs b/crates/compiler/can/src/operator.rs index efb0e4ae3c..9a46592f0a 100644 --- a/crates/compiler/can/src/operator.rs +++ b/crates/compiler/can/src/operator.rs @@ -409,7 +409,7 @@ fn binop_to_function(binop: BinOp) -> (&'static str, &'static str) { Percent => (ModuleName::NUM, "rem"), Plus => (ModuleName::NUM, "add"), Minus => (ModuleName::NUM, "sub"), - Equals => (ModuleName::EQ, "isEq"), + Equals => (ModuleName::BOOL, "isEq"), NotEquals => (ModuleName::BOOL, "isNotEq"), LessThan => (ModuleName::NUM, "isLt"), GreaterThan => (ModuleName::NUM, "isGt"), diff --git a/crates/compiler/load_internal/src/file.rs b/crates/compiler/load_internal/src/file.rs index 68a6368d39..d35f496f70 100644 --- a/crates/compiler/load_internal/src/file.rs +++ b/crates/compiler/load_internal/src/file.rs @@ -178,7 +178,6 @@ impl Default for ModuleCache<'_> { ENCODE, DECODE, HASH, - EQ, JSON, } @@ -2248,7 +2247,6 @@ fn update<'a>( extend_header_with_builtin(&mut header, ModuleId::ENCODE); extend_header_with_builtin(&mut header, ModuleId::DECODE); extend_header_with_builtin(&mut header, ModuleId::HASH); - extend_header_with_builtin(&mut header, ModuleId::EQ); } state @@ -3283,7 +3281,6 @@ fn load_module<'a>( "Encode", ModuleId::ENCODE "Decode", ModuleId::DECODE "Hash", ModuleId::HASH - "Eq", ModuleId::EQ "Json", ModuleId::JSON } @@ -4781,7 +4778,6 @@ fn canonicalize_and_constrain<'a>( | ModuleId::DICT | ModuleId::SET | ModuleId::HASH - | ModuleId::EQ ); if !name.is_builtin() || should_include_builtin { diff --git a/crates/compiler/module/src/ident.rs b/crates/compiler/module/src/ident.rs index 005c2d7fff..f0cb88b5e5 100644 --- a/crates/compiler/module/src/ident.rs +++ b/crates/compiler/module/src/ident.rs @@ -86,7 +86,6 @@ impl ModuleName { pub const ENCODE: &'static str = "Encode"; pub const DECODE: &'static str = "Decode"; pub const HASH: &'static str = "Hash"; - pub const EQ: &'static str = "Eq"; pub const JSON: &'static str = "Json"; pub fn as_str(&self) -> &str { diff --git a/crates/compiler/module/src/low_level.rs b/crates/compiler/module/src/low_level.rs index 8b70664d88..f0d6921bec 100644 --- a/crates/compiler/module/src/low_level.rs +++ b/crates/compiler/module/src/low_level.rs @@ -184,7 +184,6 @@ macro_rules! map_symbol_to_lowlevel { // Below, we explicitly handle some exceptions to the pattern where a lowlevel maps // directly to a symbol. If you are unsure if your lowlevel is an exception, assume // that it isn't and just see if that works. - #[allow(unreachable_patterns)] // TODO: remove after we replace `BOOL_EQ` with `EQ_EQ` wholly match lowlevel { $( LowLevel::$lowlevel => Symbol::$symbol, @@ -310,7 +309,6 @@ map_symbol_to_lowlevel! { NumShiftRightBy <= NUM_SHIFT_RIGHT, NumShiftRightZfBy <= NUM_SHIFT_RIGHT_ZERO_FILL, NumToStr <= NUM_TO_STR, - Eq <= BOOL_EQ, Eq <= EQ_STRUCTURAL_EQ, NotEq <= BOOL_NEQ, And <= BOOL_AND, diff --git a/crates/compiler/module/src/symbol.rs b/crates/compiler/module/src/symbol.rs index 7efe44fc11..18ae4e58f1 100644 --- a/crates/compiler/module/src/symbol.rs +++ b/crates/compiler/module/src/symbol.rs @@ -1251,8 +1251,11 @@ 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 EQ_EQ: "Eq" exposed_type=true + 9 EQ_IS_EQ: "isEq" + 10 EQ_STRUCTURAL_EQ: "structuralEq" + 11 BOOL_IS_EQ_IMPL: "boolIsEq" } 5 STR: "Str" => { 0 STR_STR: "Str" exposed_apply_type=true // the Str.Str type alias @@ -1525,15 +1528,9 @@ define_builtins! { 15 HASH_HASH_STR_BYTES: "hashStrBytes" 16 HASH_HASH_LIST: "hashList" } - 14 EQ: "Eq" => { - 0 EQ_EQ: "Eq" exposed_type=true - 1 EQ_IS_EQ: "isEq" - 2 EQ_IS_NOT_EQ: "isNotEq" - 3 EQ_STRUCTURAL_EQ: "structuralEq" - } - 15 JSON: "Json" => { + 14 JSON: "Json" => { 0 JSON_JSON: "Json" } - num_modules: 16 // Keep this count up to date by hand! (TODO: see the mut_map! macro for how we could determine this count correctly in the macro) + num_modules: 15 // Keep this count up to date by hand! (TODO: see the mut_map! macro for how we could determine this count correctly in the macro) } diff --git a/crates/compiler/solve/tests/solve_expr.rs b/crates/compiler/solve/tests/solve_expr.rs index c7167ec089..0113ee380d 100644 --- a/crates/compiler/solve/tests/solve_expr.rs +++ b/crates/compiler/solve/tests/solve_expr.rs @@ -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", ); @@ -7984,7 +7985,7 @@ mod solve_expr { isEq = \@Trivial {}, @Trivial {} -> Bool.true - main = Eq.isEq (@Trivial {}) (@Trivial {}) + main = Bool.isEq (@Trivial {}) (@Trivial {}) "# ), "Bool", diff --git a/crates/compiler/test_derive/src/eq.rs b/crates/compiler/test_derive/src/eq.rs index 75d12e409e..17429b0f22 100644 --- a/crates/compiler/test_derive/src/eq.rs +++ b/crates/compiler/test_derive/src/eq.rs @@ -13,26 +13,42 @@ use roc_derive_key::DeriveBuiltin::IsEq; #[test] fn immediates() { // Everything is an immediate for `Eq`. - check_single_lset_immediate(IsEq, v!(U8), Symbol::BOOL_EQ); - check_single_lset_immediate(IsEq, v!(U16), Symbol::BOOL_EQ); - check_single_lset_immediate(IsEq, v!(U32), Symbol::BOOL_EQ); - check_single_lset_immediate(IsEq, v!(U64), Symbol::BOOL_EQ); - check_single_lset_immediate(IsEq, v!(U128), Symbol::BOOL_EQ); - check_single_lset_immediate(IsEq, v!(I8), Symbol::BOOL_EQ); - check_single_lset_immediate(IsEq, v!(I16), Symbol::BOOL_EQ); - check_single_lset_immediate(IsEq, v!(I32), Symbol::BOOL_EQ); - check_single_lset_immediate(IsEq, v!(I64), Symbol::BOOL_EQ); - check_single_lset_immediate(IsEq, v!(I128), Symbol::BOOL_EQ); - check_single_lset_immediate(IsEq, v!(STR), Symbol::BOOL_EQ); - check_single_lset_immediate(IsEq, v!(Symbol::LIST_LIST v!(U8)), Symbol::BOOL_EQ); - check_single_lset_immediate(IsEq, v!(Symbol::LIST_LIST v!(STR)), Symbol::BOOL_EQ); - check_single_lset_immediate(IsEq, v!({ a: v!(U8), }), Symbol::BOOL_EQ); - check_single_lset_immediate(IsEq, v!(EMPTY_RECORD), Symbol::BOOL_EQ); - check_single_lset_immediate(IsEq, v!([ A v!(U8) v!(STR), B v!(STR) ]), Symbol::BOOL_EQ); - check_single_lset_immediate(IsEq, v!([ A v!(U8) v!(STR), B v!(STR) ]), Symbol::BOOL_EQ); - check_single_lset_immediate(IsEq, v!([ Nil, Cons v!(^lst)] as lst), Symbol::BOOL_EQ); + check_single_lset_immediate(IsEq, v!(U8), Symbol::EQ_STRUCTURAL_EQ); + check_single_lset_immediate(IsEq, v!(U16), Symbol::EQ_STRUCTURAL_EQ); + check_single_lset_immediate(IsEq, v!(U32), Symbol::EQ_STRUCTURAL_EQ); + check_single_lset_immediate(IsEq, v!(U64), Symbol::EQ_STRUCTURAL_EQ); + check_single_lset_immediate(IsEq, v!(U128), Symbol::EQ_STRUCTURAL_EQ); + check_single_lset_immediate(IsEq, v!(I8), Symbol::EQ_STRUCTURAL_EQ); + check_single_lset_immediate(IsEq, v!(I16), Symbol::EQ_STRUCTURAL_EQ); + check_single_lset_immediate(IsEq, v!(I32), Symbol::EQ_STRUCTURAL_EQ); + check_single_lset_immediate(IsEq, v!(I64), Symbol::EQ_STRUCTURAL_EQ); + check_single_lset_immediate(IsEq, v!(I128), Symbol::EQ_STRUCTURAL_EQ); + check_single_lset_immediate(IsEq, v!(STR), Symbol::EQ_STRUCTURAL_EQ); + check_single_lset_immediate(IsEq, v!(Symbol::LIST_LIST v!(U8)), Symbol::EQ_STRUCTURAL_EQ); + check_single_lset_immediate( + IsEq, + v!(Symbol::LIST_LIST v!(STR)), + Symbol::EQ_STRUCTURAL_EQ, + ); + check_single_lset_immediate(IsEq, v!({ a: v!(U8), }), Symbol::EQ_STRUCTURAL_EQ); + check_single_lset_immediate(IsEq, v!(EMPTY_RECORD), Symbol::EQ_STRUCTURAL_EQ); + check_single_lset_immediate( + IsEq, + v!([ A v!(U8) v!(STR), B v!(STR) ]), + Symbol::EQ_STRUCTURAL_EQ, + ); + check_single_lset_immediate( + IsEq, + v!([ A v!(U8) v!(STR), B v!(STR) ]), + Symbol::EQ_STRUCTURAL_EQ, + ); + check_single_lset_immediate( + IsEq, + v!([ Nil, Cons v!(^lst)] as lst), + Symbol::EQ_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_EQ); + check_single_lset_immediate(IsEq, v!(F64), Symbol::EQ_STRUCTURAL_EQ); } diff --git a/crates/compiler/test_derive/src/util.rs b/crates/compiler/test_derive/src/util.rs index 4239034b66..ed8011638e 100644 --- a/crates/compiler/test_derive/src/util.rs +++ b/crates/compiler/test_derive/src/util.rs @@ -56,8 +56,8 @@ fn module_source_and_path(builtin: DeriveBuiltin) -> (ModuleId, &'static str, Pa builtins_path.join("Hash.roc"), ), DeriveBuiltin::IsEq => ( - ModuleId::EQ, - module_source(ModuleId::EQ), + ModuleId::BOOL, + module_source(ModuleId::BOOL), builtins_path.join("Eq.roc"), ), } diff --git a/crates/compiler/test_gen/src/gen_abilities.rs b/crates/compiler/test_gen/src/gen_abilities.rs index 6e89ed0a90..509a232825 100644 --- a/crates/compiler/test_gen/src/gen_abilities.rs +++ b/crates/compiler/test_gen/src/gen_abilities.rs @@ -1629,7 +1629,7 @@ mod eq { a = @LyingEq 10 b = @LyingEq 5 c = @LyingEq 5 - if Eq.isEq a b && !(Eq.isEq b c) then + if Bool.isEq a b && !(Bool.isEq b c) then "okay" else "fail" @@ -1647,7 +1647,7 @@ mod eq { r#" app "test" provides [main] to "./platform" - main = Eq.isEq 10u8 10u8 + main = Bool.isEq 10u8 10u8 "# ), true, From 4fa5cc0ac3dd15073b1ca134b83b5e1b128c0761 Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Mon, 10 Oct 2022 15:37:11 -0500 Subject: [PATCH 22/42] Disable Bool, Dict, Set caching until abilities caching --- crates/compiler/load/build.rs | 4 ++-- crates/compiler/load/src/lib.rs | 14 ++++++++------ 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/crates/compiler/load/build.rs b/crates/compiler/load/build.rs index 08583d00d6..fd943c2644 100644 --- a/crates/compiler/load/build.rs +++ b/crates/compiler/load/build.rs @@ -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"), diff --git a/crates/compiler/load/src/lib.rs b/crates/compiler/load/src/lib.rs index 1a5925fa0a..fdebab3759 100644 --- a/crates/compiler/load/src/lib.rs +++ b/crates/compiler/load/src/lib.rs @@ -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 &[_]; @@ -182,15 +182,17 @@ fn read_cached_types() -> MutMap { // Wasm seems to re-order definitions between build time and runtime, but only in release mode. // 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)); + // TODO: temporarily disable subs caching for the following modules until we have ability-store + // caching. + // output.insert(ModuleId::BOOL, deserialize_help(BOOL)); + // output.insert(ModuleId::DICT, deserialize_help(DICT)); + // output.insert(ModuleId::SET, deserialize_help(SET)); + 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::ENCODE, deserialize_help(ENCODE)); From 99f097ddb5f34f79f31561d80550e49b036b4dd1 Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Mon, 10 Oct 2022 15:39:50 -0500 Subject: [PATCH 23/42] Improve error messages for abilities --- crates/reporting/src/error/type.rs | 12 +-- crates/reporting/tests/test_reporting.rs | 104 +++++++++++------------ 2 files changed, 55 insertions(+), 61 deletions(-) diff --git a/crates/reporting/src/error/type.rs b/crates/reporting/src/error/type.rs index aaeae475f3..89f7eff44b 100644 --- a/crates/reporting/src/error/type.rs +++ b/crates/reporting/src/error/type.rs @@ -284,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)), @@ -305,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)), diff --git a/crates/reporting/tests/test_reporting.rs b/crates/reporting/tests/test_reporting.rs index a9200b7420..257c113147 100644 --- a/crates/reporting/tests/test_reporting.rs +++ b/crates/reporting/tests/test_reporting.rs @@ -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 } @@ -9501,7 +9499,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 `Encode.Encoding` for `A`: + I can't derive an implementation of the `Encoding` ability for `A`: 3│ A a := a -> a has [Encode.Encoding] ^^^^^^^^^^^^^^^ @@ -9526,7 +9524,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 `Encode.Encoding` for `A`: + I can't derive an implementation of the `Encoding` ability for `A`: 3│ A := B has [Encode.Encoding] ^^^^^^^^^^^^^^^ @@ -10205,7 +10203,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 a := a -> a has [Decode.Decoding] ^^^^^^^^^^^^^^^ @@ -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] ^^^^^^^^^^^^^^^ @@ -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,7 +10855,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 a := a -> a has [Hash] ^^^^ @@ -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] ^^^^ @@ -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 @@ -11113,7 +11107,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 `Eq.Eq` for `A`: + I can't derive an implementation of the `Eq` ability for `A`: 3│ A a := a -> a has [Eq] ^^ @@ -11168,15 +11162,15 @@ 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 `Eq.Eq` for `A`: + I can't derive an implementation of the `Eq` ability for `A`: 3│ A := F32 has [Eq] ^^ - Note: I can't derive `Eq.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! + 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`. "### @@ -11194,15 +11188,15 @@ 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 `Eq.Eq` for `A`: + I can't derive an implementation of the `Eq` ability for `A`: 3│ A := F64 has [Eq] ^^ - Note: I can't derive `Eq.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! + 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`. "### @@ -11222,13 +11216,13 @@ 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 `Eq.Eq` for `A`: + 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 Eq.Eq` to the definition of `B`. + or `has Bool.Eq` to the definition of `B`. Tip: You can define a custom implementation of `Eq` for `A`. "### @@ -11309,7 +11303,7 @@ All branches in an `if` must have the same type! 5│ main = foo (\x -> x) ^^^^^^^ - Roc can't generate an implementation of the `Eq.Eq` ability for + I can't generate an implementation of the `Eq` ability for a -> a @@ -11336,7 +11330,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 `Eq.Eq` ability for + I can't generate an implementation of the `Eq` ability for [A (a -> a) [B]a]b From 43086a7e25220836e5a2ec34633dc40892c3840a Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Mon, 10 Oct 2022 15:46:07 -0500 Subject: [PATCH 24/42] Enable remaining gen tests --- crates/compiler/test_gen/src/gen_compare.rs | 2 -- crates/compiler/test_gen/src/gen_list.rs | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/compiler/test_gen/src/gen_compare.rs b/crates/compiler/test_gen/src/gen_compare.rs index 7cf09fabf1..29c27d51ff 100644 --- a/crates/compiler/test_gen/src/gen_compare.rs +++ b/crates/compiler/test_gen/src/gen_compare.rs @@ -96,7 +96,6 @@ fn neq_f64() { #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] -#[ignore = "must support Eq on Bool directly"] fn eq_bool_tag() { assert_evals_to!( indoc!( @@ -114,7 +113,6 @@ fn eq_bool_tag() { #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] -#[ignore = "must support Eq on Bool directly"] fn neq_bool_tag() { assert_evals_to!( indoc!( diff --git a/crates/compiler/test_gen/src/gen_list.rs b/crates/compiler/test_gen/src/gen_list.rs index 188fcd7db4..e6deae9f97 100644 --- a/crates/compiler/test_gen/src/gen_list.rs +++ b/crates/compiler/test_gen/src/gen_list.rs @@ -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 From 6f2115a7398cb620b8c364c0871362f812c7ff43 Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Mon, 10 Oct 2022 15:46:21 -0500 Subject: [PATCH 25/42] Update ability usage in AStar.roc --- crates/cli_testing_examples/benchmarks/AStar.roc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/cli_testing_examples/benchmarks/AStar.roc b/crates/cli_testing_examples/benchmarks/AStar.roc index c038a7fda2..42c3968846 100644 --- a/crates/cli_testing_examples/benchmarks/AStar.roc +++ b/crates/cli_testing_examples/benchmarks/AStar.roc @@ -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 {} From 0f96a1231ea1ff0310dfd0fd8dbf747b88abf87f Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Mon, 10 Oct 2022 15:47:18 -0500 Subject: [PATCH 26/42] Fromat Bool.roc --- crates/compiler/builtins/roc/Bool.roc | 29 +++++++++++++-------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/crates/compiler/builtins/roc/Bool.roc b/crates/compiler/builtins/roc/Bool.roc index 189dc15d18..ebe30488d8 100644 --- a/crates/compiler/builtins/roc/Bool.roc +++ b/crates/compiler/builtins/roc/Bool.roc @@ -12,21 +12,21 @@ interface Bool ## 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 `Eq.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 + ## Returns `Bool.true` if the two values are equal, and `Bool.false` otherwise. + ## + ## `a == b` is shorthand for `Eq.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}] +Bool := [True, False] has [Eq { isEq: boolIsEq }] boolIsEq = \@Bool b1, @Bool b2 -> structuralEq b1 b2 @@ -106,7 +106,6 @@ isNotEq : a, a -> Bool # ## `a != b` is shorthand for `Eq.isNotEq a b`. # isNotEq : a, a -> Bool | a has Eq # isNotEq = \a, b -> Bool.not (isEq 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 From b04d7a9471bf8ce425358ae41346e42bc1c929e8 Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Mon, 10 Oct 2022 15:54:28 -0500 Subject: [PATCH 27/42] Bind types passed to `isNotEq` to `Eq` --- crates/compiler/builtins/roc/Bool.roc | 12 ++++++------ crates/compiler/can/src/builtins.rs | 2 +- crates/compiler/module/src/symbol.rs | 3 ++- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/crates/compiler/builtins/roc/Bool.roc b/crates/compiler/builtins/roc/Bool.roc index ebe30488d8..be956110f7 100644 --- a/crates/compiler/builtins/roc/Bool.roc +++ b/crates/compiler/builtins/roc/Bool.roc @@ -99,13 +99,13 @@ not : Bool -> Bool ## ## 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 -# ## Calls [isEq] on the given values, then calls [not] on the result. -# ## -# ## `a != b` is shorthand for `Eq.isNotEq a b`. -# isNotEq : a, a -> Bool | a has Eq -# isNotEq = \a, b -> Bool.not (isEq 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 diff --git a/crates/compiler/can/src/builtins.rs b/crates/compiler/can/src/builtins.rs index 5b4dec3760..d82a5a7e8c 100644 --- a/crates/compiler/can/src/builtins.rs +++ b/crates/compiler/can/src/builtins.rs @@ -193,7 +193,7 @@ map_symbol_to_lowlevel_and_arity! { NumToStr; NUM_TO_STR; 1, Eq; EQ_STRUCTURAL_EQ; 2, - NotEq; BOOL_NEQ; 2, + NotEq; EQ_STRUCTURAL_NOT_EQ; 2, And; BOOL_AND; 2, Or; BOOL_OR; 2, Not; BOOL_NOT; 1, diff --git a/crates/compiler/module/src/symbol.rs b/crates/compiler/module/src/symbol.rs index 18ae4e58f1..aea2cab1eb 100644 --- a/crates/compiler/module/src/symbol.rs +++ b/crates/compiler/module/src/symbol.rs @@ -1255,7 +1255,8 @@ define_builtins! { 8 EQ_EQ: "Eq" exposed_type=true 9 EQ_IS_EQ: "isEq" 10 EQ_STRUCTURAL_EQ: "structuralEq" - 11 BOOL_IS_EQ_IMPL: "boolIsEq" + 11 EQ_STRUCTURAL_NOT_EQ: "structuralNotEq" + 12 BOOL_IS_EQ_IMPL: "boolIsEq" } 5 STR: "Str" => { 0 STR_STR: "Str" exposed_apply_type=true // the Str.Str type alias From fe9b84868680d3a35e8b11b650d15302c1587fc3 Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Mon, 10 Oct 2022 15:55:48 -0500 Subject: [PATCH 28/42] Mark `structuralEq` as not exposed on the surface --- crates/compiler/builtins/roc/Bool.roc | 2 +- crates/compiler/module/src/symbol.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/compiler/builtins/roc/Bool.roc b/crates/compiler/builtins/roc/Bool.roc index be956110f7..001112884f 100644 --- a/crates/compiler/builtins/roc/Bool.roc +++ b/crates/compiler/builtins/roc/Bool.roc @@ -1,5 +1,5 @@ interface Bool - exposes [Bool, Eq, true, false, and, or, not, isEq, isNotEq, structuralEq] + exposes [Bool, Eq, true, false, and, or, not, isEq, isNotEq] imports [] ## A type that can be compared for total equality. diff --git a/crates/compiler/module/src/symbol.rs b/crates/compiler/module/src/symbol.rs index aea2cab1eb..16f3df9315 100644 --- a/crates/compiler/module/src/symbol.rs +++ b/crates/compiler/module/src/symbol.rs @@ -107,7 +107,7 @@ impl Symbol { // The `structuralEq` call used deriving structural equality, which will wrap the `Eq` // low-level implementation. &Self::EQ_STRUCTURAL_EQ - ) && false + ) } pub fn module_string<'a>(&self, interns: &'a Interns) -> &'a ModuleName { From 3192bed4177456f3672738e72810c04aface5546 Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Mon, 10 Oct 2022 16:25:12 -0500 Subject: [PATCH 29/42] Make sure structuralEq, structuralNotEq is not importable --- crates/compiler/module/src/symbol.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/crates/compiler/module/src/symbol.rs b/crates/compiler/module/src/symbol.rs index 16f3df9315..19257c4622 100644 --- a/crates/compiler/module/src/symbol.rs +++ b/crates/compiler/module/src/symbol.rs @@ -808,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 @@ -955,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, @@ -1254,9 +1258,9 @@ define_builtins! { 7 BOOL_NEQ: "isNotEq" 8 EQ_EQ: "Eq" exposed_type=true 9 EQ_IS_EQ: "isEq" - 10 EQ_STRUCTURAL_EQ: "structuralEq" - 11 EQ_STRUCTURAL_NOT_EQ: "structuralNotEq" - 12 BOOL_IS_EQ_IMPL: "boolIsEq" + 10 BOOL_IS_EQ_IMPL: "boolIsEq" + unexposed 11 EQ_STRUCTURAL_EQ: "structuralEq" + unexposed 12 EQ_STRUCTURAL_NOT_EQ: "structuralNotEq" } 5 STR: "Str" => { 0 STR_STR: "Str" exposed_apply_type=true // the Str.Str type alias From ce2760d96d1df5a7a475c15a588c6efc03f8c6cb Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Mon, 10 Oct 2022 16:25:29 -0500 Subject: [PATCH 30/42] Some more reporting tests for Eq --- crates/reporting/tests/test_reporting.rs | 87 ++++++++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/crates/reporting/tests/test_reporting.rs b/crates/reporting/tests/test_reporting.rs index 257c113147..d0e3d19feb 100644 --- a/crates/reporting/tests/test_reporting.rs +++ b/crates/reporting/tests/test_reporting.rs @@ -11343,4 +11343,91 @@ All branches in an `if` must have the same type! 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 + "### + ); } From 8d913708366185f210720c9d657ed87dbf4ec805 Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Mon, 10 Oct 2022 16:30:07 -0500 Subject: [PATCH 31/42] TODO is resolved --- crates/compiler/can/src/builtins.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/compiler/can/src/builtins.rs b/crates/compiler/can/src/builtins.rs index d82a5a7e8c..9b06d551f1 100644 --- a/crates/compiler/can/src/builtins.rs +++ b/crates/compiler/can/src/builtins.rs @@ -70,7 +70,6 @@ macro_rules! map_symbol_to_lowlevel_and_arity { // Below, we explicitly handle some exceptions to the pattern where a lowlevel maps // directly to a symbol. If you are unsure if your lowlevel is an exception, assume // that it isn't and just see if that works. - #[allow(unreachable_patterns)] // TODO: remove after we replace `BOOL_EQ` with `EQ_EQ` wholly match lowlevel { $( LowLevel::$lowlevel => Symbol::$symbol, From 81fa0407b63079521513043086d8f980a82340e5 Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Mon, 10 Oct 2022 16:31:33 -0500 Subject: [PATCH 32/42] Satiate must_use reason --- crates/compiler/unify/src/unify.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/compiler/unify/src/unify.rs b/crates/compiler/unify/src/unify.rs index e9ef0be264..08bcf62e39 100644 --- a/crates/compiler/unify/src/unify.rs +++ b/crates/compiler/unify/src/unify.rs @@ -1369,7 +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 = "must use outcomes!"] fn unify_unspecialized_lambdas( env: &mut Env, pool: &mut Pool, From 6149c289fc8dde240a2eada97d4f4a571872fd7b Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Mon, 10 Oct 2022 16:54:12 -0500 Subject: [PATCH 33/42] Update Eq symbols --- crates/compiler/can/src/builtins.rs | 4 +-- crates/compiler/derive_key/src/lib.rs | 6 ++-- crates/compiler/module/src/low_level.rs | 2 +- crates/compiler/module/src/symbol.rs | 12 +++---- crates/compiler/solve/src/ability.rs | 4 +-- crates/compiler/test_derive/src/eq.rs | 42 ++++++++++++++----------- crates/reporting/src/error/type.rs | 4 +-- 7 files changed, 40 insertions(+), 34 deletions(-) diff --git a/crates/compiler/can/src/builtins.rs b/crates/compiler/can/src/builtins.rs index 9b06d551f1..a1202e92ee 100644 --- a/crates/compiler/can/src/builtins.rs +++ b/crates/compiler/can/src/builtins.rs @@ -191,8 +191,8 @@ map_symbol_to_lowlevel_and_arity! { NumShiftRightZfBy; NUM_SHIFT_RIGHT_ZERO_FILL; 2, NumToStr; NUM_TO_STR; 1, - Eq; EQ_STRUCTURAL_EQ; 2, - NotEq; EQ_STRUCTURAL_NOT_EQ; 2, + Eq; BOOL_STRUCTURAL_EQ; 2, + NotEq; BOOL_STRUCTURAL_NOT_EQ; 2, And; BOOL_AND; 2, Or; BOOL_OR; 2, Not; BOOL_NOT; 1, diff --git a/crates/compiler/derive_key/src/lib.rs b/crates/compiler/derive_key/src/lib.rs index da33c13ec8..65a0062a7d 100644 --- a/crates/compiler/derive_key/src/lib.rs +++ b/crates/compiler/derive_key/src/lib.rs @@ -87,7 +87,7 @@ impl TryFrom for DeriveBuiltin { Symbol::ENCODE_TO_ENCODER => Ok(DeriveBuiltin::ToEncoder), Symbol::DECODE_DECODER => Ok(DeriveBuiltin::Decoder), Symbol::HASH_HASH => Ok(DeriveBuiltin::Hash), - Symbol::EQ_IS_EQ => Ok(DeriveBuiltin::IsEq), + Symbol::BOOL_IS_EQ => Ok(DeriveBuiltin::IsEq), _ => Err(value), } } @@ -117,7 +117,9 @@ impl Derived { 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::EQ_STRUCTURAL_EQ)) + Ok(Derived::SingleLambdaSetImmediate( + Symbol::BOOL_STRUCTURAL_EQ, + )) } } } diff --git a/crates/compiler/module/src/low_level.rs b/crates/compiler/module/src/low_level.rs index f0d6921bec..3af824ad86 100644 --- a/crates/compiler/module/src/low_level.rs +++ b/crates/compiler/module/src/low_level.rs @@ -309,7 +309,7 @@ map_symbol_to_lowlevel! { NumShiftRightBy <= NUM_SHIFT_RIGHT, NumShiftRightZfBy <= NUM_SHIFT_RIGHT_ZERO_FILL, NumToStr <= NUM_TO_STR, - Eq <= EQ_STRUCTURAL_EQ, + Eq <= BOOL_STRUCTURAL_EQ, NotEq <= BOOL_NEQ, And <= BOOL_AND, Or <= BOOL_OR, diff --git a/crates/compiler/module/src/symbol.rs b/crates/compiler/module/src/symbol.rs index 19257c4622..96435a7319 100644 --- a/crates/compiler/module/src/symbol.rs +++ b/crates/compiler/module/src/symbol.rs @@ -51,7 +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::EQ_EQ, &[Symbol::EQ_IS_EQ]), + (Symbol::BOOL_EQ, &[Symbol::BOOL_IS_EQ]), ]; /// In Debug builds only, Symbol has a name() method that lets @@ -106,7 +106,7 @@ impl Symbol { self, // The `structuralEq` call used deriving structural equality, which will wrap the `Eq` // low-level implementation. - &Self::EQ_STRUCTURAL_EQ + &Self::BOOL_STRUCTURAL_EQ ) } @@ -1256,11 +1256,11 @@ define_builtins! { 5 BOOL_NOT: "not" 6 BOOL_XOR: "xor" 7 BOOL_NEQ: "isNotEq" - 8 EQ_EQ: "Eq" exposed_type=true - 9 EQ_IS_EQ: "isEq" + 8 BOOL_EQ: "Eq" exposed_type=true + 9 BOOL_IS_EQ: "isEq" 10 BOOL_IS_EQ_IMPL: "boolIsEq" - unexposed 11 EQ_STRUCTURAL_EQ: "structuralEq" - unexposed 12 EQ_STRUCTURAL_NOT_EQ: "structuralNotEq" + 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 diff --git a/crates/compiler/solve/src/ability.rs b/crates/compiler/solve/src/ability.rs index 3430060870..3533672f1b 100644 --- a/crates/compiler/solve/src/ability.rs +++ b/crates/compiler/solve/src/ability.rs @@ -277,7 +277,7 @@ impl ObligationCache { Some(DeriveHash::is_derivable(self, abilities_store, subs, var)) } - Symbol::EQ_EQ => Some(DeriveEq::is_derivable(self, abilities_store, subs, var)), + Symbol::BOOL_EQ => Some(DeriveEq::is_derivable(self, abilities_store, subs, var)), _ => None, }; @@ -1011,7 +1011,7 @@ impl DerivableVisitor for DeriveHash { struct DeriveEq; impl DerivableVisitor for DeriveEq { - const ABILITY: Symbol = Symbol::EQ_EQ; + const ABILITY: Symbol = Symbol::BOOL_EQ; #[inline(always)] fn is_derivable_builtin_opaque(symbol: Symbol) -> bool { diff --git a/crates/compiler/test_derive/src/eq.rs b/crates/compiler/test_derive/src/eq.rs index 17429b0f22..32ba64a1c0 100644 --- a/crates/compiler/test_derive/src/eq.rs +++ b/crates/compiler/test_derive/src/eq.rs @@ -13,42 +13,46 @@ use roc_derive_key::DeriveBuiltin::IsEq; #[test] fn immediates() { // Everything is an immediate for `Eq`. - check_single_lset_immediate(IsEq, v!(U8), Symbol::EQ_STRUCTURAL_EQ); - check_single_lset_immediate(IsEq, v!(U16), Symbol::EQ_STRUCTURAL_EQ); - check_single_lset_immediate(IsEq, v!(U32), Symbol::EQ_STRUCTURAL_EQ); - check_single_lset_immediate(IsEq, v!(U64), Symbol::EQ_STRUCTURAL_EQ); - check_single_lset_immediate(IsEq, v!(U128), Symbol::EQ_STRUCTURAL_EQ); - check_single_lset_immediate(IsEq, v!(I8), Symbol::EQ_STRUCTURAL_EQ); - check_single_lset_immediate(IsEq, v!(I16), Symbol::EQ_STRUCTURAL_EQ); - check_single_lset_immediate(IsEq, v!(I32), Symbol::EQ_STRUCTURAL_EQ); - check_single_lset_immediate(IsEq, v!(I64), Symbol::EQ_STRUCTURAL_EQ); - check_single_lset_immediate(IsEq, v!(I128), Symbol::EQ_STRUCTURAL_EQ); - check_single_lset_immediate(IsEq, v!(STR), Symbol::EQ_STRUCTURAL_EQ); - check_single_lset_immediate(IsEq, v!(Symbol::LIST_LIST v!(U8)), Symbol::EQ_STRUCTURAL_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::EQ_STRUCTURAL_EQ, + Symbol::BOOL_STRUCTURAL_EQ, ); - check_single_lset_immediate(IsEq, v!({ a: v!(U8), }), Symbol::EQ_STRUCTURAL_EQ); - check_single_lset_immediate(IsEq, v!(EMPTY_RECORD), Symbol::EQ_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::EQ_STRUCTURAL_EQ, + Symbol::BOOL_STRUCTURAL_EQ, ); check_single_lset_immediate( IsEq, v!([ A v!(U8) v!(STR), B v!(STR) ]), - Symbol::EQ_STRUCTURAL_EQ, + Symbol::BOOL_STRUCTURAL_EQ, ); check_single_lset_immediate( IsEq, v!([ Nil, Cons v!(^lst)] as lst), - Symbol::EQ_STRUCTURAL_EQ, + 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::EQ_STRUCTURAL_EQ); + check_single_lset_immediate(IsEq, v!(F64), Symbol::BOOL_STRUCTURAL_EQ); } diff --git a/crates/reporting/src/error/type.rs b/crates/reporting/src/error/type.rs index 89f7eff44b..3ed8c75082 100644 --- a/crates/reporting/src/error/type.rs +++ b/crates/reporting/src/error/type.rs @@ -438,10 +438,10 @@ fn underivable_hint<'b>( NotDerivableEq::FloatingPoint => { Some(alloc.note("").append(alloc.concat([ alloc.reflow("I can't derive "), - alloc.symbol_qualified(Symbol::EQ_IS_EQ), + 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::EQ_EQ), + alloc.symbol_unqualified(Symbol::BOOL_EQ), alloc.reflow(" ability!"), ]))) } From c5a025f8539d544b5a821325a25581df17cdc654 Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Mon, 10 Oct 2022 16:57:25 -0500 Subject: [PATCH 34/42] s/Eq/Bool --- crates/compiler/builtins/roc/Bool.roc | 2 +- crates/compiler/builtins/roc/Num.roc | 2 +- crates/compiler/test_derive/src/util.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/compiler/builtins/roc/Bool.roc b/crates/compiler/builtins/roc/Bool.roc index 001112884f..f161e06887 100644 --- a/crates/compiler/builtins/roc/Bool.roc +++ b/crates/compiler/builtins/roc/Bool.roc @@ -14,7 +14,7 @@ interface Bool Eq has ## Returns `Bool.true` if the two values are equal, and `Bool.false` otherwise. ## - ## `a == b` is shorthand for `Eq.isEq a b`. + ## `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: diff --git a/crates/compiler/builtins/roc/Num.roc b/crates/compiler/builtins/roc/Num.roc index 0cbc677cef..b8709bbd00 100644 --- a/crates/compiler/builtins/roc/Num.roc +++ b/crates/compiler/builtins/roc/Num.roc @@ -1291,7 +1291,7 @@ toF64Checked : Num * -> Result F64 [OutOfBounds]* ## >>> Num.isNaN (Num.pow -1 0.5) ## ## *NaN* is unusual from other numberic values in that: -## * *NaN* is not equal to any other number, even itself. [Eq.isEq] always returns `Bool.false` if either argument is *NaN*. +## * *NaN* is not equal to any other number, even itself. [Bool.isEq] always returns `Bool.false` if either argument is *NaN*. ## * *NaN* has no ordering, so [isLt], [isLte], [isGt], and [isGte] always return `Bool.false` if either argument is *NaN*. ## ## These rules come from the [IEEE-754](https://en.wikipedia.org/wiki/IEEE_754) diff --git a/crates/compiler/test_derive/src/util.rs b/crates/compiler/test_derive/src/util.rs index ed8011638e..5851a50556 100644 --- a/crates/compiler/test_derive/src/util.rs +++ b/crates/compiler/test_derive/src/util.rs @@ -58,7 +58,7 @@ fn module_source_and_path(builtin: DeriveBuiltin) -> (ModuleId, &'static str, Pa DeriveBuiltin::IsEq => ( ModuleId::BOOL, module_source(ModuleId::BOOL), - builtins_path.join("Eq.roc"), + builtins_path.join("Bool.roc"), ), } } From 9f099830db9a3766d4f6c82b6fc9b9ed06916b12 Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Mon, 10 Oct 2022 19:36:25 -0500 Subject: [PATCH 35/42] Disable peg grammar test using abilities --- crates/highlight/tests/peg_grammar.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/highlight/tests/peg_grammar.rs b/crates/highlight/tests/peg_grammar.rs index 294c992b84..f46054ca41 100644 --- a/crates/highlight/tests/peg_grammar.rs +++ b/crates/highlight/tests/peg_grammar.rs @@ -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")); From 595433b8c820c805b39e79231e2cbb0ce698c9fa Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Mon, 10 Oct 2022 19:36:45 -0500 Subject: [PATCH 36/42] Make sure to map structuralNotEq to NotEq --- crates/compiler/module/src/low_level.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/compiler/module/src/low_level.rs b/crates/compiler/module/src/low_level.rs index 3af824ad86..e92d455e05 100644 --- a/crates/compiler/module/src/low_level.rs +++ b/crates/compiler/module/src/low_level.rs @@ -310,7 +310,7 @@ map_symbol_to_lowlevel! { NumShiftRightZfBy <= NUM_SHIFT_RIGHT_ZERO_FILL, NumToStr <= NUM_TO_STR, Eq <= BOOL_STRUCTURAL_EQ, - NotEq <= BOOL_NEQ, + NotEq <= BOOL_STRUCTURAL_NOT_EQ, And <= BOOL_AND, Or <= BOOL_OR, Not <= BOOL_NOT, From 861800ceeaa0af757367fc84c53f902a3c5e6b52 Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Mon, 10 Oct 2022 19:38:44 -0500 Subject: [PATCH 37/42] Fix gen tests in the presence of Eq --- crates/compiler/test_gen/src/gen_compare.rs | 17 ----------------- crates/compiler/test_gen/src/gen_num.rs | 2 +- 2 files changed, 1 insertion(+), 18 deletions(-) diff --git a/crates/compiler/test_gen/src/gen_compare.rs b/crates/compiler/test_gen/src/gen_compare.rs index 29c27d51ff..1e826bb1bf 100644 --- a/crates/compiler/test_gen/src/gen_compare.rs +++ b/crates/compiler/test_gen/src/gen_compare.rs @@ -77,23 +77,6 @@ fn neq_u64() { ); } -#[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() { diff --git a/crates/compiler/test_gen/src/gen_num.rs b/crates/compiler/test_gen/src/gen_num.rs index 3c2bf1ad36..71eb0aea6b 100644 --- a/crates/compiler/test_gen/src/gen_num.rs +++ b/crates/compiler/test_gen/src/gen_num.rs @@ -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 From ccfb85325df89b8cc06144a5b4457044f28c83ec Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Mon, 10 Oct 2022 20:21:48 -0500 Subject: [PATCH 38/42] Update load tests --- .../tests/fixtures/build/interface_with_deps/AStar.roc | 10 +++++----- crates/compiler/load_internal/tests/test_load.rs | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/compiler/load_internal/tests/fixtures/build/interface_with_deps/AStar.roc b/crates/compiler/load_internal/tests/fixtures/build/interface_with_deps/AStar.roc index dd81f5f633..4b8dfb98fa 100644 --- a/crates/compiler/load_internal/tests/fixtures/build/interface_with_deps/AStar.roc +++ b/crates/compiler/load_internal/tests/fixtures/build/interface_with_deps/AStar.roc @@ -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 _ -> diff --git a/crates/compiler/load_internal/tests/test_load.rs b/crates/compiler/load_internal/tests/test_load.rs index f3df71041e..c463414cb3 100644 --- a/crates/compiler/load_internal/tests/test_load.rs +++ b/crates/compiler/load_internal/tests/test_load.rs @@ -486,7 +486,7 @@ fn load_astar() { "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)]*", + "astar" => "(position, position -> F64), (position -> Set position), position, Model position -> [Err [KeyNotFound]*, Ok (List position)]* | position has Eq", }, ); } From 7fa2778ecf9f53faa92c62786feb6a327b996d2b Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Mon, 10 Oct 2022 20:24:00 -0500 Subject: [PATCH 39/42] Fix even more load tests --- crates/compiler/load_internal/tests/test_load.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/compiler/load_internal/tests/test_load.rs b/crates/compiler/load_internal/tests/test_load.rs index c463414cb3..934aa212fb 100644 --- a/crates/compiler/load_internal/tests/test_load.rs +++ b/crates/compiler/load_internal/tests/test_load.rs @@ -481,11 +481,11 @@ 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]*", + "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", }, ); From 6e5de0d0a9670d1e8c68ec447c7ace12af41323a Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Mon, 10 Oct 2022 20:24:08 -0500 Subject: [PATCH 40/42] Disable test on gen-dev, for now --- crates/compiler/test_gen/src/gen_num.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/compiler/test_gen/src/gen_num.rs b/crates/compiler/test_gen/src/gen_num.rs index 71eb0aea6b..c9b92b11ec 100644 --- a/crates/compiler/test_gen/src/gen_num.rs +++ b/crates/compiler/test_gen/src/gen_num.rs @@ -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!( From b8cbaf69466bcb98aed76bf412d15b7fdeaaabe7 Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Wed, 12 Oct 2022 16:40:20 -0500 Subject: [PATCH 41/42] Resume caching Bool, Dict, and Set --- crates/compiler/load/src/lib.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/crates/compiler/load/src/lib.rs b/crates/compiler/load/src/lib.rs index fdebab3759..56804d34da 100644 --- a/crates/compiler/load/src/lib.rs +++ b/crates/compiler/load/src/lib.rs @@ -182,11 +182,7 @@ fn read_cached_types() -> MutMap { // Wasm seems to re-order definitions between build time and runtime, but only in release mode. // That is very strange, but we can solve it separately if !cfg!(target_family = "wasm") && !cfg!(windows) && !SKIP_SUBS_CACHE { - // TODO: temporarily disable subs caching for the following modules until we have ability-store - // caching. - // output.insert(ModuleId::BOOL, deserialize_help(BOOL)); - // output.insert(ModuleId::DICT, deserialize_help(DICT)); - // output.insert(ModuleId::SET, deserialize_help(SET)); + output.insert(ModuleId::BOOL, deserialize_help(BOOL)); output.insert(ModuleId::RESULT, deserialize_help(RESULT)); output.insert(ModuleId::NUM, deserialize_help(NUM)); @@ -195,6 +191,9 @@ fn read_cached_types() -> MutMap { output.insert(ModuleId::STR, deserialize_help(STR)); 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)); From ee1d93ba06bd9d7b70819421b7edd2c758b8336d Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Wed, 12 Oct 2022 16:42:11 -0500 Subject: [PATCH 42/42] Rollup review comment suggestions from #4302 --- crates/compiler/serialize/src/bytes.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/crates/compiler/serialize/src/bytes.rs b/crates/compiler/serialize/src/bytes.rs index 58d4b3d5b5..cf0c9060c2 100644 --- a/crates/compiler/serialize/src/bytes.rs +++ b/crates/compiler/serialize/src/bytes.rs @@ -11,7 +11,7 @@ pub fn serialize_slice( written: usize, ) -> io::Result { let alignment = std::mem::align_of::(); - 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(bytes: &[u8], length: usize, mut offset: usize let alignment = std::mem::align_of::(); let size = std::mem::size_of::(); - 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(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)]