Skip to content

Commit 56f1a55

Browse files
authored
Merge pull request #33797 from tbkka/tbkka/dynamicCastRework-Specification-d
2 parents 7c81f49 + 115b53f commit 56f1a55

File tree

1 file changed

+41
-31
lines changed

1 file changed

+41
-31
lines changed

docs/DynamicCasting.md

Lines changed: 41 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,26 @@ Note that it is _not_ sufficient for argument and return types to be castable; t
308308
Conceptually, an "existential type" is an opaque wrapper that carries a type and an instance of that type.
309309
The various existential types differ in what kinds of types they can hold (for example, `AnyObject` can only hold reference types) and in the capabilities exposed by the container (`AnyHashable` exposes equality testing and hashing).
310310

311+
The key invariant for existential types `E` is the following:
312+
* Strong existential invariant: If `t` is any instance, `U` is any type, and `t is E` then `(t as! E) as? U` produces the same result as `t as? U`
313+
314+
Intuitively, this simply says that if you can put an instance `t` into an existential `E`, then you can take it back out again via casting.
315+
For Equatable types, this implies that the results of the two operations here are equal to each other when they succeed.
316+
It also implies that if either of these `as?` casts fails, so will the other.
317+
318+
`AnyObject` and `AnyHashable` do not fully satisfy the strong invariant described above. Instead, they satisfy the following weaker version:
319+
* Weak existential invariant: If `t` is any instance, `U` is any type, and both `t is U` and `t is E`, then `(t as! E) as? U` produces the same result as `t as? U`
320+
321+
### Objective-C Interactions
322+
323+
The difference between the strong and weak existential invariants comes about because casting to `AnyObject` or `AnyHashable` can trigger Objective-C bridging conversions.
324+
The result of these conversions may support casts that the original type did not.
325+
As a result, `(t as! E)` may be castable to `U` even if `t` alone is not directly castable.
326+
327+
One example of this is Foundation's `NSNumber` type which conditionally bridges to several Swift numeric types.
328+
As a result, when Foundation is in scope, `Int(7) is Double == false` but `(Int(7) as! AnyObject) is Double == true`.
329+
In general, the ability to add new bridging behaviors from a single type to several distinct types implies that Swift casting cannot be transitive.
330+
311331
### Any
312332

313333
Any Swift instance can be cast to the type `Any`.
@@ -318,14 +338,7 @@ Invariants
318338
* If `t` is any instance, then `t is Any == true`
319339
* If `t` is any instance, `t as! Any` always succeeds
320340
* For every type `T` (including protocol types), `T.self is Any.Type`
321-
* If `t` is any instance and `U` is any `Equatable` type, then `t as? U == (t as! Any) as? U`.
322-
323-
This last invariant deserves some explanation, as a similar pattern appears repeatedly throughout this document.
324-
In essence, this invariant just says that putting something into an "Any box" (`t as! Any`) and taking it out again (`as? U`) does not change the result.
325-
The requirement that `U` be `Equatable` is a technical necessity for using `==` in this statement.
326-
327-
Note that in many cases, we've shortened such invariants to the form `t is U == (t as! Any) is U`.
328-
Using `is` here simply avoids the technical necessity that `U` be `Equatable` but except where explicitly called out, the intention in every case is that such casting does not change the value.
341+
* Strong existential invariant: If `t` is any instance and `U` is any type, then `(t as! Any) as? U` produces the same result as `t as? U`.
329342

330343
### AnyObject
331344

@@ -334,49 +347,44 @@ Any class, enum, struct, tuple, function, metatype, or existential metatype inst
334347
XXX TODO The runtime logic has code to cast protocol types to `AnyObject` only if they are compatible with `__SwiftValue`. What is the practical effect of this logic? Does it mean that casting a protocol type to `AnyObject` will sometimes unwrap (if the protocol is incompatible) and sometimes not? What protocols are affected by this?
335348

336349
The contents of an `AnyObject` container can be accessed by casting to another type:
337-
* If `t` is any instance, `U` is any type, `t is AnyObject` and `t is U`, then `(t as! AnyObject) is U`.
350+
* Weak existential invariant: If `t` is any instance, `U` is any type, `t is AnyObject`, and `t is U`, then `(t as! AnyObject) as? U` will produce the same result as `t as? U`
338351

339352
Implementation Note: `AnyObject` is represented in memory as a pointer to a refcounted object. The dynamic type of the object can be recovered from the "isa" field of the object. The optional form `AnyObject?` is the same except that it allows null. Reference types (class, metatype, or existential metatype instances) can be directly assigned to an `AnyObject` without any conversion. For non-reference types -- including struct, enum, and tuple types -- the casting logic will first look for an `_ObjectiveCBridgeable` conformance that it can use to convert the source into a tailored reference type. If that fails, the value will be copied into an opaque `_SwiftValue` container.
340353

341354
(See "The _ObjectiveCBridgeable Protocol" below for more details.)
342355

343-
### Objective-C Interactions
344-
345-
Note the invariant above cannot be an equality because Objective-C bridging allows libraries to introduce new relationships that can alter the behavior of seemingly-unrelated casts.
346-
One example of this is Foundation's `NSNumber` type which conditionally bridges to several Swift numeric types.
347-
As a result, when Foundation is in scope, `Int(7) is Double == false` but `(Int(7) as! AnyObject) is Double == true`.
348-
In general, the ability to add new bridging behaviors from a single type to several distinct types implies that Swift casting cannot be transitive.
349-
350356
### Error (SE-0112)
351357

352-
Although the Error protocol is specially handled by the Swift compiler and runtime (as detailed in [SE-0112](https://github.com/apple/swift-evolution/blob/master/proposals/0112-nserror-bridging.md)), it behaves like an ordinary protocol type for casting purposes.
358+
The `Error` type behaves like an ordinary existential type for casting purposes.
353359

354-
(See "Note: 'Self-conforming' protocols" below for additional details relevant to the Error protocol.)
360+
(See "Note: 'Self-conforming' protocols" below for additional details relevant to the `Error` protocol.)
355361

356362
### AnyHashable (SE-0131)
357363

358364
For casting purposes, `AnyHashable` behaves like an existential type.
365+
It satisfies the weak existential invariant above.
359366

360367
However, note that `AnyHashable` does not act like an existential for other purposes.
361368
For example, it's metatype is named `AnyHashable.Type` and it does not have an existential metatype.
362369

363370
### Protocol Witness types
364371

365-
Caveat:
366-
Protocols that have `associatedtype` properties or which make use of the `Self` typealias cannot be used as independent types.
367-
As such, the discussion below does not apply to them.
372+
Any protocol definition (except those that include an `associatedtype` property or which makes use of the `Self` typealias) has an associated existential type named after the protocol.
368373

369-
Any Swift instance of a concrete type `T` can be cast to `P` iff `T` conforms to `P`.
370-
The result is a "protocol witness" instance that provides access only to those methods and properties defined on `P`.
374+
Specifically, assume you have a protocol definition
375+
```
376+
protocol P {}
377+
```
378+
379+
As a result of this definition, there is an existential type (also called `P`).
380+
This existential type is also known as a "protocol witness type" since it exposes exactly the capabilities that are defined by the protocol.
371381
Other capabilities of the type `T` are not accessible from a `P` instance.
382+
Any Swift instance of a concrete type `T` can be cast to the type `P` iff `T` conforms to the protocol `P`.
372383

373384
The contents of a protocol witness can be accessed by casting to some other appropriate type:
374-
* For any protocol `P`, instance `t`, and type `U`, if `t is P`, then `t as? U == (t as! P) as? U`
375-
376-
XXX TODO: The invariant above does not apply to AnyObject, AnyHashable.
377-
Does it suffice to explicitly exclude those two, or do other protocols share that behavior? The alternative would seem to be to change the equality here into an implication.
385+
* Strong existential Invariant: For any protocol `P`, instance `t`, and type `U`, if `t is P`, then `t as? U` produces the same result as `(t as! P) as? U`
378386

379-
In addition to the protocol witness type, every Swift protocol `P` implicitly defines two other types:
387+
In addition to the protocol witness type `P`, every Swift protocol `P` implicitly defines two other types:
380388
`P.Protocol` is the "protocol metatype", the type of `P.self`.
381389
`P.Type` is the "protocol existential metatype".
382390
These are described in more detail below.
@@ -422,9 +430,11 @@ S.svar // Shorthand for S.self.svar
422430
```
423431

424432
For most Swift types, the metatype of `T` is named `T.Type`.
425-
However, in two cases the metatype has a different name:
433+
However, in the following cases the metatype has a different name:
426434
* For a nominal protocol type `P`, the metatype is named `P.Protocol`
427-
* For a type bound to a generic variable `G`, the metatype is named `G.Type` _even if `G` is bound to a protocol type_. Specifically, if `G` is bound to the nominal protocol type `P`, then `G.Type` is another name for the metatype `P.Protocol`, and hence `G.Type.self == P.Protocol.self`.
435+
* For non-protocol existential types `E`, the metatype is also named `E.Protocol`. For example, `Any.Protocol`, `AnyObject.Protocol`, and `Error.Protocol`.
436+
* For a type bound to a generic variable `G`, the metatype is named `G.Type` _even if `G` is bound to a protocol or existential type_. Specifically, if `G` is bound to the nominal protocol type `P`, then `G.Type` is another name for the metatype `P.Protocol`, and hence `G.Type.self == P.Protocol.self`.
437+
* As explained above, although `AnyHashable` behaves like an existential type in some respects, its metatype is called `AnyHashable.Type`.
428438

429439
Example:
430440
```
@@ -456,7 +466,7 @@ Invariants
456466
* For a nominal non-protocol type `T`, `T.self is T.Type`
457467
* For a nominal protocol type `P`, `P.self is P.Protocol`
458468
* `P.Protocol` is a singleton: `T.self is P.Protocol` iff `T` is exactly `P`
459-
* A non-protocol type `T` conforms to a protocol `P` iff `T.self is P.Type`
469+
* A non-protocol type `T` conforms to a protocol `P` iff `T.self is P.Type` (If `T` is a protocol type, see "Self conforming existential types" below for details.)
460470
* `T` is a subtype of a non-protocol type `U` iff `T.self is U.Type`
461471
* Subtypes define metatype subtypes: if `T` and `U` are non-protocol types, `T.self is U.Type == T.Type.self is U.Type.Type`
462472
* Subtypes define metatype subtypes: if `T` is a non-protocol type and `P` is a protocol type, `T.self is P.Protocol == T.Type.self is P.Protocol.Type`

0 commit comments

Comments
 (0)