I’d like to investigate separating Objective-C bridging from the behavior of 
the as/as?/is operator family again for Swift 4. Last year, I proposed SE–0083 
<https://github.com/apple/swift-evolution/blob/master/proposals/0083-remove-bridging-from-dynamic-casts.md>,
 but we deferred the proposal for lack of time to evaluate its impact. As 
complicating factors, we now have source compatibility with Swift 3 as a 
requirement, and the id-as-Any work from SE–0116 
<https://github.com/apple/swift-evolution/blob/master/proposals/0116-id-as-any.md>
 more or less requires bridging dynamic casts to work. I think we can 
nonetheless make important improvements in this area in order to simplify the 
core language and provide more portable behavior across platforms with and 
without ObjC interop. In retrospect, submitting SE–0083 as an omnibus “fix 
casting” proposal was a mistake. We can separate out a few smaller subproblems 
from the overall concept:

Replacing as for bridging coercion

Swift 0 shipped with implicit conversions between standard library value types 
and their bridged Cocoa classes in both directions, and as we’ve eased off of 
the implicit conversions, we still left the as operator with the ability to 
force the conversions. This complicates the meaning of as: normally, it just 
provides type context, but it also has the power to force bridging conversions. 
These meanings are often at odds:

// `NSNumber` is `ExpressibleByIntegerLiteral`, so this gives type context to 
the literal 0
// and is equivalent to `NSNumber(integerLiteral: 0)`
0 as NSNumber

// `x` already has type `Int`, so this forces the bridging conversion and is 
equivalent to
// `_bridgeToObjectiveC(x)` (and thereby gives you a different kind of 
`NSNumber`!)
let x: Int = 0
x as NSNumber
Aside from the complexity and non-portability of this behavior, this is also 
inconsistent with the Swift naming conventions, which recommend that 
conversions between related types be presented as initializers. Additionally, 
the bridging conversions often have specialized behavior for performance or 
semantic reasons that aren’t intended to be exposed in the normal API of either 
type (for example, bridging a Swift number type to NSNumber produces a 
“type-preserving” instance of NSNumber so that the bridge doesn’t lose type 
information, even though NSNumber’s own API presents a type-agnostic numeric 
object). Therefore, I propose that we remove the bridging behavior from as, and 
provide APIs for conversion where they don’t yet exist. as is purely a 
compile-time construct with no runtime interaction, so the Swift 3 
compatibility and ABI issues are much simpler than they are when runtime 
casting behavior becomes involved.

Warning on is/as?/as! casts that statically induce bridging

Without changing the runtime behavior of casting, we could still discourage 
users from using dynamic casting to perform bridging conversions when it’s 
statically evident that a bridging conversion is the only way a cast succeeds. 
For example:

func abuseBridgingCasts(on object: AnyObject) {
    // warning: dynamic cast requires a bridging conversion; use 
`Int(bridgedFrom:)` instead
    let _ = object as? Int
}
This wouldn’t be perfect, since we wouldn’t be able to warn about fully dynamic 
casts, but it could help encourage users to write portable code that doesn’t 
rely on the Objective-C bridge in common situations.

Limiting when the runtime allows bridging in dynamic casts

Ideally, we would be able to change runtime dynamic casting itself to not 
involve bridging. However, as I mentioned above, there are at least two 
situations where bridging dynamic casts are necessary to meet design 
requirements:

To maintain compatibility with Swift 3 code
For dynamic-casting Anys that were bridged from an Objective-C id, since we 
don’t know statically whether a bridge to any particular Swift type is needed
However, neither of these is an insurmountable barrier. For Swift 3 
compatibility, if nothing else, we could ship a parallel set of runtime entry 
points for dynamic casting with the Swift 3 behavior that would be used when 
compiling code in Swift 3 mode (and those entry points could possibly be 
banished to a separate compatibility dylib to avoid weighing down the 
ABI-stable Swift runtime forever). For id-as-Any bridging, existentials could 
potentially carry a bit to indicate whether casting out of that particular 
value should admit bridging casts. Doing that has complications of its 
own—having otherwise equivalent Any values have different behavior is 
undeniably weird—but, if we can keep the behavior isolated to code that 
directly interfaces with ObjC, I think it’s worth investigating in the interest 
of making the overall language more predictable.

-Joe
_______________________________________________
swift-evolution mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to