What you're seeing here is a result of "Optional promotion" in Swift: if a function takes a value of type T? and you try to pass a value of type T to it, Swift will implicitly wrap your value in .some in order to convert it to the right type. This includes static func ==(...).
Although Any is allowed to contain a nil value despite not being an Optional, Swift's Optional promotion rules still apply to it; this means that when you write any == nil, Swift still promotes any: Any to .some(any): Any? in order to compare against nil. In this case, .some(any) is notnil, hence the false results.
If you try to compile this code outside of a playground, you'll get warnings letting you know that this is about to happen:
/main.swift:2:16: warning: expression implicitly coerced from 'String?' to 'Any'
var any: Any = optional // nil
^~~~~~~~
/main.swift:2:16: note: provide a default value to avoid this warning
var any: Any = optional // nil
^~~~~~~~
?? <#default value#>
/main.swift:2:16: note: force-unwrap the value to avoid this warning
var any: Any = optional // nil
^~~~~~~~
!
/main.swift:2:16: note: explicitly cast to 'Any' with 'as Any' to silence this warning
var any: Any = optional // nil
^~~~~~~~
as Any
/main.swift:5:17: warning: comparing non-optional value of type 'Any' to 'nil' always returns false
debugPrint( any == nil ? "true" : "false" ) // print "false"
~~~ ^ ~~~
One way to get the Optionality back is to force-cast any to an Optional:
var any: Any = optional // nil
// 🔶 Warning: Expression implicitly coerced from 'String?' to 'Any'
This could do as a test:
if let v = any as? Optional<Int> {
if let v {
print("false") // can't happen if there's a type mismatch
} else {
print("true")
}
} else {
print("false")
}
@itaiferber I agree with your explaination, Can u explain me the behaviour in 2nd case?
var optional: String?
var any: Any = optional
print(any) // nil
if case Optional<Any>.none = nil { //by "Optional Promotion" compare is translated into ` case Optional<Any>.none = Optional<Any>.none `
print("true") // print "true"
} else {
print("false")
}
if case Optional<Any>.none = any { // by "Optional Promotion" compare is translated into ` case Optional<Any>.none = Optional<Any>.some(any) `
print("true") // print "true" -----> Why?
} else {
print("false")
}
if Optional<Any>.some(any) == nil { // by "Optional Promotion" compare is translated into ` case Optional<Any>.some(any) = Optional<Any>.none `
print("true")
} else {
print("false") // print "false"
}
if case Optional<Any>.none = Optional<Any>.some(any) { // We already explicitated both as Optional so there no promotion
print("true")
} else {
print("false") // print "false"
}
This is a great question! When you write if case _ = _, you are actually pattern matching the two sides of the = together to "destructure" the right-hand-side into the left-hand-side. This happens dynamically, and here, optional promotion actually does not happen unless it's absolutely necessary, in order to keep the pattern matching consistent.
For example, you can write code like
let x = 5
if case Optional<Any>.none = x {
print("nil??")
} else {
print("Not nil")
}
but you'll get a warning:
/main.swift:2:4: warning: non-optional expression of type 'Any' used in a check for optionals
if case Optional<Any>.none = x {
^ ~
The compiler can promote x from Int to Int?, but it knows the condition will always fail. However, if you explicitly type x: Any = 5, the warning goes away (as expected), because the value of x will be checked dynamically at runtime.
So, to annotate your cases:
case Optional<Any>.none = nil: not actually optional promotion; nilis just Optional<_>.none, so this is equivalent to writing case Optional<Any>.none = Optional<Any>.none, which matches
case Optional<Any>.none = any: not optional promotion, but dynamically matching the value inside any to Optional<Any>.none
Optional<Any>.some(any) == nil: like (1), actually not optional promotion; nil is just Optional<_>.none, so this is equivalent to Optional<Any>.some(any) == Optional<Any>.none
case Optional<Any>.none = Optional<Any>.some(any): like you say, no promotion here; this is just a direct mismatch
If you really need to store an optional value inside of Any, this would actually likely be the preferred approach rather than the as! cast I suggested above.