Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Comparing to Generic type in xcode 10.2

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)
like image 795
allanjwalter Avatar asked Aug 12 '26 10:08

allanjwalter


1 Answers

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)
  }
}
like image 123
Hamish Avatar answered Aug 15 '26 10:08

Hamish



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!