Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tomlokhorst/287c5ecc87f34454f9e0ea8b0e38a6a3 to your computer and use it in GitHub Desktop.
Save tomlokhorst/287c5ecc87f34454f9e0ea8b0e38a6a3 to your computer and use it in GitHub Desktop.
/// - 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