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