Created
April 14, 2020 18:52
-
-
Save anandabits/d9494d14fef221983ff4f1cafa318d47 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// - returns: `true` when dynamic type is `Equatable` and `==` returns `true`, otherwise `false`. | |
func areEquatablyEqual(_ lhs: Any, _ rhs: Any) -> Bool { | |
func receiveLHS<LHS>(_ typedLHS: LHS) -> Bool { | |
guard | |
let rhsAsLHS = rhs as? LHS | |
else { return false } | |
return areEquatablyEqual(typedLHS, rhsAsLHS) | |
} | |
return _openExistential(lhs, do: receiveLHS) | |
} | |
/// - returns: `true` when `T: Equatable` and `==` returns `true`, otherwise `false`. | |
func areEquatablyEqual<T>(_ lhs: T, _ rhs: T) -> Bool { | |
AreEquatablyEqual(lhs: lhs, rhs: rhs).open() == true | |
} | |
// The proxy indirection is necessary to avoid a compiler error on `receive`: | |
// "Same-type requirement makes generic parameters 'T' and 'PossiblyEquatable' equivalent" | |
private protocol OpenerProxyProtocol { | |
associatedtype Proxied | |
} | |
private enum OpenerProxy<Proxied>: OpenerProxyProtocol {} | |
/// Opens `Equatable` for the `PossiblyEquatable` proxied type. | |
/// If successful, attempts to cast rhs to the same type and use `==`. | |
private struct AreEquatablyEqual<Proxy: OpenerProxyProtocol>: EquatableOpener { | |
typealias PossiblyEquatable = Proxy.Proxied | |
let lhs: PossiblyEquatable | |
let rhs: PossiblyEquatable | |
init<T>(lhs: T, rhs: T) where Proxy == OpenerProxy<T> { | |
self.lhs = lhs | |
self.rhs = rhs | |
} | |
func receive<T: Equatable>(_ equatable: T.Type) -> Bool where T == PossiblyEquatable { | |
lhs == rhs | |
} | |
} | |
/// Supports recovering the Equatable constraint on an unconstrained generic type that is known to be | |
/// (or known to _possibly_ be) `Equatable` despite that information not being present in the type system. | |
/// | |
/// Usage: create an instance of a conforming type and call `open` | |
/// | |
/// - note: This is a general pattern that can be used for any constraints and is ammenable to codegen | |
private protocol EquatableOpener { | |
/// The type that might be `Equatable` | |
associatedtype PossiblyEquatable | |
/// The type of results produced by this opener when it receives recovered type information | |
associatedtype Result | |
/// Receives the recovered type information and uses it to produce a `Result` | |
/// Unfortunately the `Result` type cannot depend on `PossiblyEquatable` actually being `Equatable` | |
/// so the type information is only available for the duration of a call to this method, | |
/// (although it can escape if it is captured in a new type-erasing context). | |
func receive<T: Equatable>(_ equatable: T.Type) -> Result where T == PossiblyEquatable | |
} | |
extension EquatableOpener { | |
/// This is the main entry point called by users in order to produce a result after using the `Equatable` constraint. | |
/// Opens the value if possible, forwards it to `receive` and then returns the result of that call. | |
func open() -> Result? { | |
// when this cast succeeds we have recovered the `Equatable` conformance by using the `EquatableOpenerTrampoline`'s | |
// conditional conformance to `EquatableOpenerProtocol` | |
let opener = EquatableOpenerTrampoline<Self>.self as? EquatableOpenerTrampolineProtocol.Type | |
// calls down to the trampoline which calls back to `self.receive` and forwards the return value back here | |
return opener?.open(self) | |
} | |
} | |
private protocol EquatableOpenerTrampolineProtocol { | |
// This has to be generic to avoid an associated type | |
// because we need to dynamic cast in `EquatableOpener.open` above. | |
static func open<Opener: EquatableOpener>(_ opener: Opener) -> Opener.Result? | |
} | |
private enum EquatableOpenerTrampoline<Opener: EquatableOpener> {} | |
extension EquatableOpenerTrampoline: EquatableOpenerTrampolineProtocol where Opener.PossiblyEquatable: Equatable { | |
/// - precondition: `Opener == DynamicOpener` (the method is only generic because we need to use `EquatableOpenerProtocol` as a type) | |
static func open<DynamicOpener: EquatableOpener>(_ opener: DynamicOpener) -> DynamicOpener.Result? { | |
// forwards the recovered type information to the user and returns the result of using that information | |
return (opener as? Opener)?.receive(Opener.PossiblyEquatable.self) as? DynamicOpener.Result | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment