Swift 4.2: Issue from optional T? to Any?

So I have a simple function getObject(_: String) -> T?.
Testing it with XCTAssertNil(_: Any?) used to work before but is now broken in Swift 4.2.

XCTAssertNil(getObject(forKey: .userCacheKey)) // true in Swift 4.1, false in Swift 4.2

My understanding is that T is inferred as Any?. I'm aware there's been changes on casting with optional and generics. Yet I would have expected it to be inferred as Any, just like it did in Swift 4.1

Is it really intended behaviour of the changes made in Swift 4.2?

Only way I found to fix the issue is using casting which does not satisfy me:

XCTAssertNil(getObject(forKey: .userCacheKey) as String?)  // returns true

Are you sure the value is actually nil? What does it print if you change it to the following?

if let nonNil = getObject(forKey: .userCacheKey) {
    print(type(of: nonNil)) // Actual dynamic type of the instance.
    print(nonNil) // The instance’s value.
} else {
    print("nil")
}

cc @hamishknight, @rudkx

As far as I'm aware, T should be inferred to be Any in both 4.1 and 4.2, here's a minimal example:

  enum Key { case userCacheKey }
  func getObject<T>(forKey key: Key) -> T? {
    print(T.self)
    return nil
  }

  func test() {
    XCTAssertNil(getObject(forKey: .userCacheKey))
    // passes and prints: Any
  }

Is it possible to show us the implementation of your getObject(forKey:) function?

I would hazard a guess that the issue you're running into is a result of the change in casting behaviour from optional values to generic placeholders that you mention in your post – see swift - iOS 12 SDK Generic function returns Optional.some(nil) - Stack Overflow & SR-8704 for more info.

1 Like

@hamishknight code is pretty simple and similar to what is logged into SR-8704:

func getObject<T>(forKey key: Key) -> T? {
        return UserDefaults.standard.object(forKey: String(describing: key)) as? T
}

Just to understand the fix you did: what will be the inferred T in Swift 5 ? Any or Any? ?

T should still be inferred to be Any – to be clear, my patch didn't alter the way in which generic placeholders get satisfied, it only changed the behaviour of a cast from an optional value to a generic placeholder type.

And given your implementation of getObject(forKey:) involves such a cast, that's likely the issue you're running into.

Perfect. I'll test with the patch when available.

Thanks