# Odd behavior related to [T?] as? [T]

Note the warning in this program:

``````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?

3 Likes

I think the warning is the same one you get here:

``````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 `Int`s 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.

The result is `[Int]?`, not `[Int?]`.

I meant the result of the `[x, y]` expression in isolation. That's `[Int?]`, just like the type of your `xy` declaration.

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?

2 Likes

Is `[Int?]` a subtype of `[Int]` btw?

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.

For other types than `Array`, the behavior is different for this use of `as?`:

``````struct Brray<T> : ExpressibleByArrayLiteral {
let a, b: T
init(arrayLiteral elements: T...) {
precondition(elements.count == 2, "Only two elements are supported so far :)")
self.a = elements
self.b = elements
}
}

func af<T>(_ x: Array<T?>) -> Array<T>? { x as? Array<T> }
func bf<T>(_ x: Brray<T?>) -> Brray<T>? { x as? Brray<T> }

let array: Array<Int?> = [1, 2]
let brray: Brray<Int?> = [1, 2]

let ar: Array<Int>? = af(array)
let br: Brray<Int>? = bf(brray)

print("ar:", ar ?? "nil") // [1, 2]
print("br:", br ?? "nil") // nil
``````

Is this because of a compiler bug or by design?
If it's by design, how to explain it?

3 Likes

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.

So the conclusion might be that everything except the warning is working as intended?

Here's a way to get rid of the warning for now:

``````func f(_ x: Int?, _ y: Int?) -> [Int]? {
return [x, y] as? [Int] // ⚠️ Conditional downcast from 'Int?' to 'Int' does nothing
}

func g(_ x: Int?, _ y: Int?) -> [Int]? {
return ([x, y] as [Int?]) as? [Int] // ✅ No warning when adding this seemingly redundant cast.
}
``````
3 Likes

No warning in a Xcode Version 13.3 beta (13E5086k) Playground. What's your Xcode version? If not the same as mine, maybe Swift 5.6 is why?

[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.)

1 Like

I meant the first cast, `as [Int?]`, is redundant, it’s the only difference between f and g.

(Both f and g have the `as? [Int]`.)

I’m using the default toolchain in Xcode 13.2.1, haven’t tried it in a playground.

I’m using 13.3 beta. Try that and see?

I’m away from computer. But if anyone else knows that this warning has been removed after Swift 5.5.2, that would be interesting to know.

Could you try it out in a fresh command line project on 13.3 beta as well? (I don’t trust playground🙂)

``````% swiftc --version
swift-driver version: 1.44.2 Apple Swift version 5.6 (swiftlang-5.6.0.320.8 clang-1316.0.18.8)
Target: x86_64-apple-macosx12.0
% swiftc jen.swift
% cat jen.swift
import Foundation

func f(_ x: Int?, _ y: Int?) -> [Int]? {
[x, y] as? [Int] // ⚠️ Conditional downcast from 'Int?' to 'Int' does nothing
}
``````

so with Swift 5.5, warning. with Swift 5.6, no warning

1 Like