Prior to the xcode 10.2 update (that includes swift 5 support) in following code both "a" and "b" would be "true", as intended. Now In xcode 10.2 with swift 4.2 in a previously created project "a" has started evaluating to "false".
If I change the project to swift 5, use an older version of xcode, or use the playground in xcode 10.2, "a" evaluates to "true" as intended.
As far as I can tell both "a" and "b" should never be different since T and Any? are the same thing in this case.
Why would this logic change and what could I do to achieve the same result?
class Class<T> {
var val: String?
func test(val: Any?) {
let a = val is T
let b = val is Any?
}
}
let thing = Class<Any?>()
thing.test(val: nil)
Sorry, this one's my bad. To recap the history:
In Swift 4.1, when casting from an optional value to a generic placeholder T, the compiler assumed that T was a non-optional type and so unwrapped the optional value before performing the cast. This means that val is T in your example would evaluate to false, as it would be evaluated as val.map { $0 is T } ?? false (not an actual AST transformation).
Last year I opened a pull request (#13910) to fix this behaviour such that the compiler is now more conservative with the unwrapping, allowing val is T to evaluate to true.
This fix made it into 4.2, however, due to not being guarded by -swift-version 5, caused a compatibility regression (SR-8704).
To resolve the compatibility regression, for Swift 5 the fix was limited to Swift 5 mode only (#19217) in order for the original behaviour to be preserved under Swift 4 and 4.2 compatibility modes. I was hoping we could also get this into 4.2.1 (#19562) in order to minimise the flip-flopping in behaviour between 4.2 and 5.0, but unfortunately that didn't happen.
So long story short, in -swift-version 4.2, you're getting the original casting behaviour. To get the new behaviour, you can either use -swift-version 5, or erase the optionality of the source from the compiler, for example:
class Class<T> {
var val: String?
func test(val: Any?) {
let a = (val as Any) is T
let b = val is Any?
print(a, b)
}
}
or, with a dedicated function:
func valueIs<T, U>(_ x: T, ofType _: U.Type) -> Bool {
return x is U
}
class Class<T> {
var val: String?
func test(val: Any?) {
let a = valueIs(val, ofType: T.self)
let b = val is Any?
print(a, b)
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With