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.
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
}