More crosscasting confusion

I create a dictionary like this:


["someKey": []]


to pass into a method that's expecting [String, Any], but when I try to use the array value corresponding to "someKey", I get a value that doesn't match 'is [Any]'. In fact, the debugger can't even display what the value is. So I explored some variations:


(lldb) po [].dynamicType
__NSArrayI

(lldb) po [1].dynamicType
Swift.Array<Swift.Int>

(lldb) po ["abc"].dynamicType
Swift.Array<Swift.String>

(lldb) po Array<Any>()
0 elements

(lldb) po Array<Any>().dynamicType
Swift.Array<protocol<>>

(lldb) po Array<AnyObject>().dynamicType
Swift.Array<Swift.AnyObject>


I find the first of these extremely surprising. Indeed if I create the dictionary like this:


["someKey": Array<Any>()]


I no longer get the crash, but I don't understand why the other form should crap out. Is it me or the compiler that's doing something wrong?

Answered by Jens in 20394022

Small observation of something you may already know:

// import Foundation // Compile time errors below happens only when not importing Foundation (ie no objc bridging)

let a = ["someKey": []] // Compile error 1: '_' is not convertible to 'StringLiteralConvertible'
                        // Compile error 2: Type of expression is ambiguous without more context


So Swift will complain and not let you be that unspecific about the element type of the Array, unless Array is bridged to NSArray (as it is when importing eg Foundation)?

Accepted Answer

Small observation of something you may already know:

// import Foundation // Compile time errors below happens only when not importing Foundation (ie no objc bridging)

let a = ["someKey": []] // Compile error 1: '_' is not convertible to 'StringLiteralConvertible'
                        // Compile error 2: Type of expression is ambiguous without more context


So Swift will complain and not let you be that unspecific about the element type of the Array, unless Array is bridged to NSArray (as it is when importing eg Foundation)?

Both? 🙂


You haven't given enough information about the type of the value, and the compiler should probably throw an error saying that.


An NSArray can't be bridged to Array<Any> because it can't hold non-class instances.

Well, that's certainly half of the answer. 🙂


But that doesn't really explain why '[] as? [Any]' would crash.


It also seems like a lousy joke to play on us developers, if 'import Foundation' changes the meaning of things that apparently have nothing do with it.

Oh, yeah. I keep thinking that bridging is a conversion from an Obj-C instance to a Swift instance, and conversion of NSArray to Array<Any> seems reasonable, but if it's a type re-interpretation the bridging isn't reasonable.

Um, wait a minute.


NSArray can be bridged to Array<AnyObject> because … it can. But isn't Array<AnyObject> a kind of Array<Any>? When I try, I just get more confused:


let c: AnyObject = "A"
if c is Any {
     let d: Any = c
}
let a: [String: AnyObject] = [:]
if a is [String: Any] {
     let b: [String: Any] = a as [String: Any]
}


Line 2 works (with a warning that it always works), but line 6 gives an error ("Type Any does not conform to protocol AnyObject"). Line 7 doesn't give any error, but how can 'as' be acceptable to the compiler when 'is' isn't? This just keeps getting nastier.

I think that the reason is that when you are using 'as' in the assignments you are making a copy of the array/dictionary, so the conversion won't have side effects because you will always be dealing with a new instance.


When you just use 'is' to test an object, you would still be interacting with the original object later (trying to set a property, etc), so that original object would have to work properly when accessed like the Type you are asking about.

A plain AnyObject can be downcast to Any because you would always still be able to interact with it without problems if you treat it as Any, but the type of a dictionary [String: AnyObject] can't be downcast to [String: Any] because interacting with it as [String: Any] might not work correctly (setting new values, etc).


So, my understanding of it is:

Line 2 and line 3 work because downcasting or converting a plain AnyObject instance to Any always works.

Line 6 doesn't work, because an existing [String: AnyObject] can't be interacted with as if it were [String: Any]

Line 7 works because converting [String: AnyObject] to a new [String: Any] works.

Sadly, it changes a lot of things that apparently have nothing to do with it ... Most of them having to do with things like Swift Array <-> NSArray.

More crosscasting confusion
 
 
Q