> On Dec 5, 2017, at 3:56 PM, David Hart <da...@hartbit.com> wrote: > > 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)?
As I noted to John, we still need to handle the general case for the right-hand side of a same-type constraint, which can be an arbitrary type. That requires all of the functionality of type(named:). That said, from my perspective, adding type(named:) is entirely “gravy”. We get it for free from this implementation plan, but IMO it could be added to some later version of Swift and that would be fine. - Doug > > On 6 Dec 2017, at 00:42, John McCall via swift-dev <swift-dev@swift.org > <mailto:swift-dev@swift.org>> wrote: > >>> On Dec 5, 2017, at 5:28 PM, Douglas Gregor via swift-dev >>> <swift-dev@swift.org <mailto: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 <mailto:swift-dev@swift.org> >> https://lists.swift.org/mailman/listinfo/swift-dev >> <https://lists.swift.org/mailman/listinfo/swift-dev>
_______________________________________________ swift-dev mailing list swift-dev@swift.org https://lists.swift.org/mailman/listinfo/swift-dev