> On Dec 5, 2017, at 3:56 PM, David Hart <[email protected]> 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 <[email protected]
> <mailto:[email protected]>> wrote:
>
>>> On Dec 5, 2017, at 5:28 PM, Douglas Gregor via swift-dev
>>> <[email protected] <mailto:[email protected]>> 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
>> [email protected] <mailto:[email protected]>
>> https://lists.swift.org/mailman/listinfo/swift-dev
>> <https://lists.swift.org/mailman/listinfo/swift-dev>
_______________________________________________
swift-dev mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-dev