[swift-evolution] [Proposal draft] Bridge Optional As Its Payload Or NSNull

Jaden Geller jaden.geller at gmail.com
Wed Aug 24 18:52:57 CDT 2016


I'd imagine an implementation something like this (but, in the bridging logic, not as a top level function…):

func dynamicCast<T>(_ array: [Any]) -> [T?] {
    var _type: CollectionCastType?
    func assertType(_ checkType: CollectionCastType) {
        guard let type = _type else {
            _type = checkType
            return
        }
        assert(type == checkType)
    }
    return array.map { element in
        switch element {
        case let element as T?:
            assertType(.normal)
            return element
        case let element as T:
            assertType(.lifting)
            return element
        case is NSNull:
            assertType(.lifting)
            return nil
        default:
            fatalError("Incorrect types")
        }
    }
}

Essentially, it either identifies an array as being entirely of `T?` (normal cast type) or being entirely of both `T` and `NSNull` (lifted cast type) and returns the proper value. In the bridging logic, this would be done lazily (like how `NSArray`s bridged to `[T]` lazily check element type on access).

What are your thoughts?

> On Aug 24, 2016, at 4:08 PM, Jaden Geller <jaden.geller at gmail.com> wrote:
> 
> First of all, I'm really happy with this proposal so far. I really appreciate the work that's been done to improve Swift and Objective-C interoperability.
> 
> Now, question: Does this proposal also improve bridging from Objective-C to Swift or only the other direction? For example, let's say an `[Any]` contains either `Foo` or `NSNull`. Could this bridge to Swift as `[Foo?]`? I'd like to be able to write
> 
> let x = [Foo(), Foo(), nil] as [Foo?] as [Any]
> let x2 = x as! [Foo?] // Already works
> 
> let y = [Foo(), Foo(), NSNull()] as [Any]
> let y2 = y as! [Foo?] // Should work
> 
> and have this succeed. That is, an `[Any]` can be cast to `[T?]` either if it only contains `T` and `NSNull` or if it only contains `T?`, not some combination of both.
> 
>> On Aug 24, 2016, at 11:20 AM, Douglas Gregor via swift-evolution <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> wrote:
>> 
>> 
>>> On Aug 24, 2016, at 4:16 AM, David Rönnqvist <david.ronnqvist at gmail.com <mailto:david.ronnqvist at gmail.com>> wrote:
>>> 
>>> I have some problems understanding the scope of this proposal. More specifically if it’s limited to arrays and dictionaries or if it’s broader than that, and if it’s limited to objects that originate in Swift or if the same applies for objects that originate in Objective-C code.
>> 
>> It’s broader than that. It affects any Optional value that is put into an ‘Any’ and passed to Objective-C. Note, however, that if you have a nullable parameter in Objective-C, e.g.,
>> 
>> 	-(void)methodWithObject:(nullable id)object;
>> 
>> Which comes into Swift as
>> 
>> 	func method(with object: Any?)
>> 
>> Then ‘nil’ will be passed through as ‘nil’. This only affects the case where you’re passing a Swift optional to a non-optional parameter:
>> 
>> 	-(void)methodWithNonNullObject:(nonnull id)object;
>> 
>> 	func method(withNonNullObject object: Any)
>> 
>>> 
>>> For me, it makes sense that Swift arrays of type [C?] and [Any] would bridge to Objective-C as NSArrays bridge nils to NSNull. That feels like the most natural way of representing those missing values in Objective-C. 
>> 
>> Right. The alternative is that nil values bridge to an opaque box type known only to the Swift runtime. NSNull seems strictly better here, because Objective-C code can reason about it.
>> 
>>> For dictionaries of type [K:C?] and [K:Any] I feel that bridging Swift nils to NSNull is pretty straight forward and allows for the distinction of a key with no value and a key with an explicit nil value. However, I feel that the same doesn’t work in the other direction. If a NSNull value in an Objective-C NSDictionary would bridge to a nil value it wouldn’t be possible to distinguish between a key without a value and key with a nil value (something one might have to do when checking the KVO change dictionary).  
>> 
>> NSNulls are handled dynamically. If you wanted to check whether Objective-C put an ‘NSNull’ in there explicitly, you can do so with “as? NSNull”.  If instead you do “as? SomeType?”, the NSNull will become the ‘nil’ value in the SomeType.
>> 
>>> 
>>> There are also some APIs that make a distinction between NSNull and nil, for example action(for:forKey:) on CALayerDelegate. Does this proposal have any impact on those APIs?
>> 
>> That method returns “CAAction?”, so ‘nil’ will come through as ‘nil’ and NSNull can be stored in the .some(x).
>> 
>> 	- Doug
>> 
>>> 
>>> - David
>>> 
>>>> On 24 Aug 2016, at 00:36, Douglas Gregor via swift-evolution <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> wrote:
>>>> 
>>>> Introduction
>>>> 
>>>> Optionals can be used as values of the type Any, but only bridge as opaque objects in Objective-C. We should bridge Optionals with some value by bridging the wrapped value, and bridge nils to the NSNull singleton.
>>>> 
>>>> Swift-evolution thread: TBD <https://lists.swift.org/pipermail/swift-evolution/>
>>>>  <https://github.com/jckarter/swift-evolution/blob/be49e08f56450ffea394306198bcd25f58915e30/proposals/XXXX-bridge-optional-to-nsnull.md#motivation>Motivation
>>>> 
>>>> SE-0116 <https://github.com/apple/swift-evolution/blob/master/proposals/0116-id-as-any.md> changed how Objective-C's id and untyped collections import into Swift to use the Any type. This makes it much more natural to pass in Swift value types such as String and Array, but introduces the opportunity for optionality mismatches, since an Any can contain a wrapped Optional value just like anything else. Our current behavior, where Optional is given only the default opaque bridging behavior, leads to brittle transitivity problems with collection subtyping. For example, an array of Optional objects bridges to an NSArray of opaque objects, unusable from ObjC:
>>>> 
>>>> class C {}
>>>> let objects: [C?] = [C(), nil, C()]
>>>> The more idiomatic mapping would be to use NSNull or some other sentinel to represent the missing values (since NSArray cannot directly store nil). Counterintuitively, this is in fact what you get if you bridge an array of Any with nil elements:
>>>> 
>>>> class C {}
>>>> let objects: [Any] = [C(), nil as C?, C()]
>>>> though with an opaque box taking the place of the standard NSNull sentinel. Since there's a subtype relationship between T and Optional<T>, it's also intuitive to expect that the bridging operation be consistent between T and occupied values of Optional<T>.
>>>> 
>>>>  <https://github.com/jckarter/swift-evolution/blob/be49e08f56450ffea394306198bcd25f58915e30/proposals/XXXX-bridge-optional-to-nsnull.md#proposed-solution>Proposed solution
>>>> 
>>>> When an Optional<T> value is bridged to an Objective-C object, if it contains some value, that value should be bridged; otherwise, NSNull or another sentinel object should be used.
>>>> 
>>>>  <https://github.com/jckarter/swift-evolution/blob/be49e08f56450ffea394306198bcd25f58915e30/proposals/XXXX-bridge-optional-to-nsnull.md#detailed-design>Detailed design
>>>> 
>>>> some maps to the bridged value
>>>> none maps to NSNull
>>>> if we don't want to lose information about nested optionals, we'd need a unique SwiftNull object for every optional type, so that .some(.none) maps to NSNull and .none maps to SwiftNull(T?)
>>>>  <https://github.com/jckarter/swift-evolution/blob/be49e08f56450ffea394306198bcd25f58915e30/proposals/XXXX-bridge-optional-to-nsnull.md#impact-on-existing-code>Impact on existing code
>>>> 
>>>> This change has no static source impact, but changes the dynamic behavior of the Objective-C bridge. From Objective-C's perspective, Optionals that used to bridge as opaque objects will now come in as semantically meaningful Objective-C objects. This should be a safe change, since existing code should not be relying on the behavior of opaque bridged objects. From Swift's perspective, values should still be able to round-trip from Optional to Any to id to Anyand back by dynamic casting.
>>>> 
>>>>  <https://github.com/jckarter/swift-evolution/blob/be49e08f56450ffea394306198bcd25f58915e30/proposals/XXXX-bridge-optional-to-nsnull.md#alternatives-considered>Alternatives considered
>>>> 
>>>> Do nothing
>>>> Attempt to trap or error when Optionals are used as Anys -- would be good QoI to warn, but it can't be prevented, and is occasionally desired
>>>> 
>>>> _______________________________________________
>>>> swift-evolution mailing list
>>>> swift-evolution at swift.org <mailto:swift-evolution at swift.org>
>>>> https://lists.swift.org/mailman/listinfo/swift-evolution <https://lists.swift.org/mailman/listinfo/swift-evolution>
>>> 
>> 
>> _______________________________________________
>> swift-evolution mailing list
>> swift-evolution at swift.org <mailto:swift-evolution at swift.org>
>> https://lists.swift.org/mailman/listinfo/swift-evolution
> 

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160824/c0fe2739/attachment.html>


More information about the swift-evolution mailing list