A colleague approached me with (a much more complicated version of) the following code today. I'm somewhat stumped but wondering if this is a compiler bug.
let item: Any? = String?.none as Any
struct X<T> {
static func checkType() {
print("Generic type: \(T.self)")
if let value = item as? T {
print("1: Item can be cast as \(T.self)")
} else {
print("1: Item cannot be cast as \(T.self)")
}
if let value = item as? Optional<String> {
print("2: Item can be cast as Optional<String>")
} else {
print("2: Item cannot be cast as Optional<String>")
}
}
}
X<String?>.checkType()
The output is as follows:
Generic type: Optional<String>
1: Item can be cast as Optional<String>
2: Item cannot be cast as Optional<String>
I can come up with a good story for either both being castable or both not being castable. But I can't explain why they would differ. Any enlightenment would be appreciated. And yes, it all works if I make item be Any or I cast String?.none to Any?, the question is why would the cast yield differing results.
The first test is a result of Swift generics not being template instantiations. There is only one version of X.checkType, regardless of T. So the as? cast must produce T?. In this case, it produces (T?).some(.none), which for T == String? becomes (String??).some(.none).
The if let sees a .some case and successfully unwraps a value of type T. That value is (String?).none.
What happens is that the cast to (String?) is successful, and it would produce (String??).some(.none) but that is flattened to (String?).none. Then the if let sees .none so optional binding does not succeed and execution proceeds to the else block.
let item: Any? = String?.none as Any
struct X<T> {
static func checkType() {
print("Generic type: \(T.self)")
if let _ = item as? T {
print("1: Item can be cast as \(T.self)")
} else {
print("1: Item cannot be cast as \(T.self)")
}
switch item {
case _ as String?:
print("2: Item can be cast as \(String?.self)")
default:
print("2: Item cannot be cast as \(String?.self)")
}
}
}
X<String?>.checkType()
// Generic type: Optional<String>
// 1: Item can be cast as Optional<String>
// 2: Item can be cast as Optional<String>
I think these two explanations combined are going to be what I go with. switch in particular should spell it out, but i need the non-template piece to say why it is that way.
Note also that the logic in your algorithm is flawed.
if let value = item as? Optional<String> doesn't just mean "Item can be cast as Optional".
It means "it can be cast" AND "the value is not nil"
To be more accurate your test should have been :
let item: Any? = String?.none as Any
struct X<T> {
static func checkType() {
print("Generic type: \(T.self)")
if item is T {
print("1: Item can be cast as \(T.self)")
} else {
print("1: Item cannot be cast as \(T.self)")
}
if item is Optional<String> {
print("2: Item can be cast as Optional<String>")
} else {
print("2: Item cannot be cast as Optional<String>")
}
}
}
X<String?>.checkType()
however there is still some shade on the type casting because 'Optional.none is Optional' returns true.