let x: Int? = 1
let y: Int? = 2
let a: [Int]? = [x, y] as? [Int] // ⚠️ Conditional downcast from 'Int?' to 'Int' does nothing
print(a ?? "nil") // [1, 2]
The warning says I'm downcasting from Int? to Int although what happens is that a [Int?] is casted into a [Int]?.
It also says that this does nothing and provides the fixit "Remove as ? [Int]", but applying the fixit will result in a compiler error (so clearly it did something rather than nothing):
let x: Int? = 1
let y: Int? = 2
let a: [Int]? = [x, y] // 🛑 Cannot convert value of type 'Int?' to expected element type 'Array<Int>.ArrayLiteralElement' (aka 'Int')
print(a ?? "nil")
Also, the following program is equivalent to the first AFAICT, yet there's no warning:
let x: Int? = 1
let y: Int? = 2
let xy = [x, y]
let a: [Int]? = xy as? [Int]
print(a ?? "nil") // [1, 2]
So what happens when any of the elements is nil?
let x: Int? = 1
let y: Int? = nil
let xy = [x, y]
let a: [Int]? = xy as? [Int]
print(a ?? "nil") // nil
Ie, the array of optionals is compact mapped, but only if all the elements are not nil, otherwise the resulting optional array will be nil.
What is intended behavior and what is compiler bugs in this?
let x: Int? = 1
let y: Int? = 2
let z = y as? Int // ⚠️ Conditional downcast from 'Int?' to 'Int' does nothing
In the version of your example that produces no warning:
let a: [Int]? = xy as? [Int]
there are two "layers" of casting: one layer of per-element casts (similar to a compactMap as you've noted) to try to convert the values from Int? to Int, and a second cast to try to convert the result to [Int], depending on the result of the first layer.
In the original version:
let a: [Int]? = [x, y] as? [Int]
I think there are three layers of casting: an initial attempt to cast the expression result type of [x, y] to [Int] by type inference on the array elements — which doesn't succeed in getting Ints so the result is [Int?], but produces the warning — then the other two layers. The warning occurs in that extra "layer", so it only appears in this version.
Ok. But the warning still seems invalid (applying its suggested fix won’t fix it, but produce an error), right?
And the quite complex functionality that the as? has here, ie compact mapping iff all elements are not nil, otherwise returning nil, is that intentional and if so documented somewhere?
Yes, I think the warning is irrelevant, so it's a kind of bug.
This is intentional AFAIK. It's always been the way to cast an array bridged from NSArray where the array elements are of a known type — just not known to the compiler.
You can also use it, for example, to check whether an array in a JSON deserialization consists only of String elements.
Ah, that must be (at least part of) the explanation. It's unfortunate that it results in this kind of confusing behavior. I guess the issue with the nonsensical warning might somehow be caused by this as well.
[Int?] and [Int] are quite different types.. (I believe internally there'll be 9 (or 10?) bytes per element for the former and only 8 for the latter (on 64 bit platforms).
The cast above is not redundant, it converts from [Int?] to [Int], converting every element of array from Int? to Int (if it can), doing this in O(n) time, and if it can't (i.e. when one of the elements is nil) it returns nil. That's what as? is doing. I believe you'll get the same result using:
([x, y] as [Int?]) as! [Int]?
in other way as? [Int] and as! [Int]? should be equivalent.
(the difference is that if for array of, say, strings, as? [Int] would produce nil and as! [Int]? would crash.)