//! To avoid duplicating derived implementations for the same type, derived implementations are //! addressed by a key of their type content. However, different derived implementations can be //! reused based on different properties of the type. For example: //! //! - `Eq` does not care about surface type representations; its derived implementations can be //! uniquely addressed by the [`Layout`][crate::layout::Layout] of a type. //! - `Encoding` must care about surface type representations; for example, `{ a: "" }` and //! `{ b: "" }` have different derived implementations. However, it does not need to distinguish //! between e.g. required and optional record fields. //! - `Decoding` is like encoding, but has some differences. For one, it *does* need to distinguish //! between required and optional record fields. //! //! For these reasons the content keying is based on a strategy as well, which are the variants of //! [`DeriveKey`]. pub mod decoding; pub mod encoding; pub mod hash; mod util; use decoding::{FlatDecodable, FlatDecodableKey}; use encoding::{FlatEncodable, FlatEncodableKey}; use hash::{FlatHash, FlatHashKey}; use roc_module::symbol::Symbol; use roc_types::subs::{Subs, Variable}; #[derive(Debug, PartialEq, Eq)] pub enum DeriveError { /// Unbound variable present in the type-to-derive. It may be possible to derive for this type /// once the unbound variable is resolved. UnboundVar, /// The type is underivable for the given ability member. Underivable, } #[derive(Hash, PartialEq, Eq, Debug, Clone)] #[repr(u8)] pub enum DeriveKey { ToEncoder(FlatEncodableKey), Decoder(FlatDecodableKey), Hash(FlatHashKey), } impl DeriveKey { pub fn debug_name(&self) -> String { match self { DeriveKey::ToEncoder(key) => format!("toEncoder_{}", key.debug_name()), DeriveKey::Decoder(key) => format!("decoder_{}", key.debug_name()), DeriveKey::Hash(key) => format!("hash_{}", key.debug_name()), } } } #[derive(Hash, Clone, PartialEq, Eq, Debug)] pub enum Derived { /// If a derived implementation name is well-known ahead-of-time, we can inline the symbol /// directly rather than associating a key for an implementation to be made later on. /// /// Immediates refer to ability members that are "inlined" at the derivation call site. Immediate(Symbol), /// Like an [Derived::Immediate], but with the additional constraint that the immediate /// symbol is statically known to have exactly one lamdba set. /// This unlocks some optimization opportunities, as regioned lambda sets do not need to be /// chased. SingleLambdaSetImmediate(Symbol), /// Key of the derived implementation to use. This allows association of derived implementation /// names to a key, when the key is known ahead-of-time but the implementation (and it's name) /// is yet-to-be-made. Key(DeriveKey), } /// The builtin ability member to derive. #[derive(Clone, Copy, Debug)] pub enum DeriveBuiltin { ToEncoder, Decoder, Hash, IsEq, } impl TryFrom for DeriveBuiltin { type Error = Symbol; fn try_from(value: Symbol) -> Result { match value { Symbol::ENCODE_TO_ENCODER => Ok(DeriveBuiltin::ToEncoder), Symbol::DECODE_DECODER => Ok(DeriveBuiltin::Decoder), Symbol::HASH_HASH => Ok(DeriveBuiltin::Hash), Symbol::BOOL_IS_EQ => Ok(DeriveBuiltin::IsEq), _ => Err(value), } } } impl Derived { pub fn builtin( builtin: DeriveBuiltin, subs: &Subs, var: Variable, ) -> Result { match builtin { DeriveBuiltin::ToEncoder => match encoding::FlatEncodable::from_var(subs, var)? { FlatEncodable::Immediate(imm) => Ok(Derived::Immediate(imm)), FlatEncodable::Key(repr) => Ok(Derived::Key(DeriveKey::ToEncoder(repr))), }, DeriveBuiltin::Decoder => match decoding::FlatDecodable::from_var(subs, var)? { FlatDecodable::Immediate(imm) => Ok(Derived::Immediate(imm)), FlatDecodable::Key(repr) => Ok(Derived::Key(DeriveKey::Decoder(repr))), }, DeriveBuiltin::Hash => match hash::FlatHash::from_var(subs, var)? { FlatHash::SingleLambdaSetImmediate(imm) => { Ok(Derived::SingleLambdaSetImmediate(imm)) } FlatHash::Key(repr) => Ok(Derived::Key(DeriveKey::Hash(repr))), }, DeriveBuiltin::IsEq => { // If obligation checking passes, we always lower derived implementations of `isEq` // to the `Eq` low-level, to be fulfilled by the backends. Ok(Derived::SingleLambdaSetImmediate( Symbol::BOOL_STRUCTURAL_EQ, )) } } } pub fn builtin_with_builtin_symbol( builtin: DeriveBuiltin, symbol: Symbol, ) -> Result { match builtin { DeriveBuiltin::ToEncoder => match encoding::FlatEncodable::from_builtin_symbol(symbol)? { FlatEncodable::Immediate(imm) => Ok(Derived::Immediate(imm)), FlatEncodable::Key(repr) => Ok(Derived::Key(DeriveKey::ToEncoder(repr))), }, DeriveBuiltin::Decoder => match decoding::FlatDecodable::from_builtin_symbol(symbol)? { FlatDecodable::Immediate(imm) => Ok(Derived::Immediate(imm)), FlatDecodable::Key(repr) => Ok(Derived::Key(DeriveKey::Decoder(repr))), }, DeriveBuiltin::Hash => match hash::FlatHash::from_builtin_symbol(symbol)? { FlatHash::SingleLambdaSetImmediate(imm) => { Ok(Derived::SingleLambdaSetImmediate(imm)) } FlatHash::Key(repr) => Ok(Derived::Key(DeriveKey::Hash(repr))), }, DeriveBuiltin::IsEq => { // If obligation checking passes, we always lower derived implementations of `isEq` // to the `Eq` low-level, to be fulfilled by the backends. Ok(Derived::SingleLambdaSetImmediate( Symbol::BOOL_STRUCTURAL_EQ, )) } } } }