Last active
April 5, 2022 08:13
-
-
Save anandabits/97a72336ce3080f9fc856fe99fb28d50 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
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