But that wouldn’t allow surfacing a func type(named: String) Standard Library function that worked on any type, wouldn’t it (like the Dictionary with non-Hashable key)?
> On 6 Dec 2017, at 00:42, John McCall via swift-dev <swift-dev@swift.org> > wrote: > >> On Dec 5, 2017, at 5:28 PM, Douglas Gregor via swift-dev >> <swift-dev@swift.org> wrote: >> Hi all, >> >> The main missing piece for conditional conformances >> (https://github.com/apple/swift-evolution/blob/master/proposals/0143-conditional-conformances.md) >> is support for dynamic casting. Right now, dynamic casting that queries a >> conditional conformance will always fail. Here’s an example: >> >> protocol P { >> func foo() >> } >> >> struct X : P { >> func foo() { print("X.P") } >> } >> >> struct Y<T> { >> var wrapped: T >> } >> >> extension Y: P where T: P { >> func foo() { wrapped.foo() } >> } >> >> func tryAsP(_ value: Any) { >> if let p = value as? P { >> p.foo() >> } >> } >> >> let yx = Y(wrapped: X()) >> tryAsP(yx) >> >> This won’t print anything, but should print “X.P”. We’d like to fix that :) >> >> Joe Groff, Slava, and I discussed an approach to implement dynamic casting >> for conditional conformances, which I’d like to outline here. >> >> Background >> The Swift runtime expresses the conformance of a specific type (say >> Array<Int>) to a particular protocol (say, Equatable) using a witness table. >> Witness tables contain the actual associated types as well as function >> pointers for each of the requirements of a protocol. Slava and John’s talk >> on Implementing Swift Generics (https://llvm.org/devmtg/2017-10/#talk15) >> gives a bunch of background here. >> >> When a conformance is conditional, the witness table also stores the >> conditional requirements. So, the witness table for Array<Int>: Equatable >> has to store the witness table Int: Equatable. In the example above, the >> witness table for Y<X>: P needs to store a pointer to the witness table X: >> P. The compiler knows how to pass along the witness tables needed for a >> conditional conformance, but for runtime casting to work, we need to (1) >> evaluate all of the conditional requirements to determine whether they apply >> (e.g., does T: P for a specific T?) and then (2) pass those witness tables >> along when building the witness table. The cast should fail if any of the >> conditional requirements goes unsatisfied, and produce a valid witness table >> otherwise. >> >> Protocol Conformance Records >> Whenever code declares conformance of a particular type to a given protocol, >> the compiler emits a “protocol conformance record” (documented at >> https://github.com/apple/swift/blob/master/docs/ABI/TypeMetadata.rst#protocol-conformance-records). >> These protocol conformance records state the type declaring conformance >> (e.g., Y) and the protocol (e.g., P), and then provide a witness table >> accessor to form the witness table given type metadata for a specific type >> instance (e.g., Y<X>). In the case of a conditional conformance, this >> accessor also needs to be passed the witness tables for any conditional >> requirements, e.g., the witness table for X: P. >> >> Conditional Requirements in Protocol Conformance Records >> Somehow we need to dynamically evaluate the conditional requirements. For >> example, we need to know that for the X<T>: P conformance to be valid, we >> need T: P to hold for the given T. So, we’ll extend the protocol conformance >> record with an encoding of those requirements. Fortunately, we already have >> a way to encode arbitrary generic requirements: the mangling of a >> generic-signature (see >> https://github.com/apple/swift/blob/master/docs/ABI/Mangling.rst#generics) >> already encodes arbitrary generic requirements, so we can put a string >> comprised of the mangled conditional requirements into the protocol >> conformance record. >> >> When evaluating a conditional conformance, we demangle the conditional >> requirements to get something like: “T must conform to P”. We then need to >> substitute the type arguments (e.g., X) for the corresponding type >> parameters (T) to form type metadata for the requirements. In this case, >> we’d get the type metadata pointer for Y and the protocol descriptor for P, >> and then call swift_conformsToProtocol() to determine whether the >> requirement holds. If it does, swift_conformsToProtocol() produces the Y: P >> witness table we need to pass along to the witness table accessor. >> >> Note that the conditional requirements can be arbitrarily complicated. For >> example, given: >> >> extension Dictionary: P where Value == (Key) -> Bool { … } >> >> Even though the result of evaluating this same-type requirement doesn’t need >> to be passed along to the witness table accessor, we still need to evaluate >> the requirement to determine whether the conditional conformance to P >> applies. To do so, we will need to substitute type arguments for both Value >> and Key, and need to form the type metadata representing the (substituted) >> function type (Key) -> Bool to determine if it is equivalent to the >> (substituted) type of Value (which is then determined by type metadata >> pointer equality because the runtime uniques type metadata). >> >> Looking up Type Metadata For a Mangled Name >> To perform that mapping from a mangled type in a conditional requirement to >> type metadata, we effectively need an operation to take a mangled type name >> and turn it into a type metadata pointer. This is something we could surface >> in the Swift standard library/runtime as, e.g., >> >> func type(named: String) -> Any.Type? >> >> to take a mangled type name and try to get the type metadata for it. From >> there, one can query protocol conformances that (say) allow one to construct >> instances of the arbitrarily-named type. Think of it as NSClassFromString >> for any type in the Swift language, including specializations of generic >> types. >> >> To get here, we’ll also need to extend the nominal type descriptor >> (https://github.com/apple/swift/blob/master/docs/ABI/TypeMetadata.rst#nominal-type-descriptor) >> to contain the generic signature of a nominal type. That let’s us safely >> create specializations of generic types. For example, if one tries to form >> Dictionary<A, B> where A does not conform to Hashable, type(named:) should >> return “nil” rather than an invalid type. The same logic that will be used >> to form type metadata when checking conditional requirements will apply >> here. Indeed, it’s probably worth proposing type(named:) as a separate >> language feature on the path to conditional conformances. >> >> Looking Forward: Generalized Existentials >> Generalized existentials >> (https://github.com/apple/swift/blob/master/docs/GenericsManifesto.md#generalized-existentials) >> allows us to express more kinds of “existential” type in the language, >> e.g., “A Collection of some type of Element that we know is Equatable”, e.g., >> >> var a: Any<Collection where .Element: Equatable> >> a = [1, 2, 3, 4, 5] >> a = Set([“a”, “b”, “c”]) >> >> To get there, we can change (or extend) the protocol metadata >> (https://github.com/apple/swift/blob/master/docs/ABI/TypeMetadata.rst#protocol-metadata) >> to contain a complete generic signature, which can be evaluated dynamically >> using the same mechanisms described above. For example: >> >> func f(any: Any) { >> if let c = any as? Any<Collection where .Element: Equatable> { … } >> } >> >> We should make the metadata change to allow this form of generalized >> existentials before locking down the ABI, even if we don’t settle on the >> feature in the surface language. >> >> Thoughts? > > Couldn't we just encode a metadata path for the associated type and then do > the conformance lookup? > > John. > _______________________________________________ > swift-dev mailing list > swift-dev@swift.org > https://lists.swift.org/mailman/listinfo/swift-dev
_______________________________________________ swift-dev mailing list swift-dev@swift.org https://lists.swift.org/mailman/listinfo/swift-dev