Skip to content

Instantly share code, notes, and snippets.

@anandabits
Last active April 5, 2022 08:13
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save anandabits/97a72336ce3080f9fc856fe99fb28d50 to your computer and use it in GitHub Desktop.
Save anandabits/97a72336ce3080f9fc856fe99fb28d50 to your computer and use it in GitHub Desktop.
func testIsBidirectional() {
func assert<C: Collection>(_ collection: C, isBidirectional: Bool) {
XCTAssertEqual(collection.isBidirectional, isBidirectional)
}
assert([1, 2, 3], isBidirectional: true)
assert(Set([1, 2, 3]), isBidirectional: false)
}
extension Collection {
var isBidirectional: Bool {
IsBidirectional(Self.self).open() == true
}
}
// The proxy indirection is necessary to avoid a compiler error on `receive`:
// "Same-type requirement makes generic parameters 'T' and 'PossiblyBidirectionalCollection' equivalent"
private protocol OpenerProxyProtocol {
associatedtype Proxied
}
private enum OpenerProxy<Proxied>: OpenerProxyProtocol {}
/// Opens `BidrectionalCollection` for the `PossiblyBidirectionalCollection` proxied type.
/// If successful, attempts to cast rhs to the same type and use `==`.
private struct IsBidirectional<Proxy: OpenerProxyProtocol>: BidirectionalCollectionOpener {
typealias PossiblyBidirectionalCollection = Proxy.Proxied
init<T>(_: T.Type) where Proxy == OpenerProxy<T> {}
func receive<T: BidirectionalCollection>(_: T.Type) -> Bool where T == PossiblyBidirectionalCollection {
// This is the trivial case, we can also store value(s) of type `PossiblyBidirectionalCollection`
// in the instance and use the `BidirectionalCollection` conformance on those value(s) here.
true
}
}
/// Supports recovering the `BidirectionalCollection` constraint on an unconstrained generic type that is known to be
/// (or known to _possibly_ be) `BidirectionalCollection` 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 BidirectionalCollectionOpener {
/// The type that might be `BidirectionalCollection`
associatedtype PossiblyBidirectionalCollection
/// 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 `PossiblyBidirectionalCollection` actually being `BidirectionalCollection`
/// 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: BidirectionalCollection>(_ bidirectional: T.Type) -> Result where T == PossiblyBidirectionalCollection
}
extension BidirectionalCollectionOpener {
/// This is the main entry point called by users in order to produce a result after using the `BidirectionalCollection` 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 `BidirectionalCollection` conformance by using the `BidirectionalCollectionOpenerTrampoline`'s
// conditional conformance to `BidirectionalCollectionOpenerTrampolineProtocol`
let opener = BidirectionalCollectionOpenerTrampoline<Self>.self as? BidirectionalCollectionOpenerTrampolineProtocol.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 BidirectionalCollectionOpenerTrampolineProtocol {
// This has to be generic to avoid an associated type
// because we need to dynamic cast in `BidirectionalCollectionOpener.open` above.
static func open<Opener: BidirectionalCollectionOpener>(_ opener: Opener) -> Opener.Result?
}
private enum BidirectionalCollectionOpenerTrampoline<Opener: BidirectionalCollectionOpener> {}
extension BidirectionalCollectionOpenerTrampoline: BidirectionalCollectionOpenerTrampolineProtocol where Opener.PossiblyBidirectionalCollection: BidirectionalCollection {
/// - precondition: `Opener == DynamicOpener` (the method is only generic because we need to use `BidirectionalCollectionOpenerTrampolineProtocol` as a type)
static func open<DynamicOpener: BidirectionalCollectionOpener>(_ 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.PossiblyBidirectionalCollection.self) as? DynamicOpener.Result
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment