Search code examples
swiftdictionaryswift5

False positive on optional in Swift5.4


I have a Dictionary extension which has worked perfectly up until the release of Xcode 12.5 with Swift 5.4. The problem is that in Swift5.4 there seems to have been a change which makes an optional optional return as a false positive. In ways it does make sense however now my extension fails.

Consider the following example code:

let dictionary: [String: Any] = ["foo": "bar"]

if let foo = dictionary["foo"] as? String? {
    debugPrint("Foo exists")
}

if let bar = dictionary["bar"] as? String? {
    debugPrint("bar exists")
}

In pre Swift 5.4 this would result in the following:

"Foo exists"

However now in Swift 5.4 I get the following result:

"Foo exists"
"bar exists"

This is due to that even though the dictionary is defined as [String: Any], an String? conforms to type Any and thus nil conforms to any and so the if case would trigger since nil is any.

For me this brings an issue with a require function I'm using, which triggers if the generic type is set to optional. My extension:

enum DictionaryExtractionError: Error {
    case missingProperty(String), casting(String)
}

extension Dictionary where Key == String, Value == Any {
    func require<T>(_ name: String) throws -> T {
        if let prop = self[name] as? T, (prop as Any?) != nil {
            return prop
        }
        throw self[name] == nil ? DictionaryExtractionError.missingProperty(name) : DictionaryExtractionError.casting(name)
    }

    func require<T>(_ name: String, fallback: T) -> T {
        do {
            return try require(name) ?? fallback
        } catch {
            return fallback
        }
    }
}

let dictionary: [String: Any] = ["fooBar": "barFoo"]
let valid: String = dictionary.require("fooBar", fallback: "no Foobar")
let validMissing: String = dictionary.require("doesntExist", fallback: "no Foobar")
let inValidMissing: String? = dictionary.require("doesntExist", fallback: "no Foobar")

debugPrint(valid) // Should return: "barFoo"
debugPrint(validMissing) // Should return: "no Foobar"
debugPrint(inValidMissing) // Should return: Optional("no Foobar") <<<<<< This one is wrong

My question: Is there some way to check if T is of optional type? If so I can adapt my code to check where generic type is optional, make sure prop is not nil.


Solution

  • Your example code prints "bar exists" in Xcode 12.4 also.

    Anyway, try this implementation of require instead:

    func require<T>(_ name: String) throws -> T {
        guard let index = self.index(forKey: name) else {
            throw DictionaryExtractionError.missingProperty(name)
        }
        guard let t = self[index].value as? T else {
            throw DictionaryExtractionError.casting(name)
        }
        return t
    }