> 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
>
> <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
> <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
>
> <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
> <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
>
> <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
>
> <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
>
> <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