At the moment, the following code triggers an error diagnostic:
let a: Any? = nil
let b = a as? CFDictionary // Conditional downcast to CoreFoundation type 'CFDictionary' will always succeed
I think the runtime should compare the type IDs using CFGetTypeID
and then perform a cast, which will allow us to get rid of this diagnostic.
Would it be okay to add this check to the runtime? cc @Joe_Groff @John_McCall
Sure, I think we always had it in mind that we could support dynamic casting to CF types via the type-id mechanism. There are three big caveats, though:
- The implementation has to handle the fact that foreign class metadata candidates for CF classes can't be relied on to include whatever additional information enables CF casting. That probably means that this has to be a side-table of information that we can emit when we emit a candidate rather than something we can assume is embedded in the metadata.
- We know for a fact that CF type IDs don't always match the imported CF class hierarchy; in fact CF type IDs can't express a hierarchy at all. And we don't really know what all the potential compatibility landmines are here; very little code actually uses CF type IDs for anything.
- Casting to toll-free-bridged types like
CFDictionary
would almost certainly be better handled by using ObjC casting to the NS equivalent type instead of using CF type IDs.
But if you want to look into it as a general matter, you're welcome to.
2 Likes
Something like this?
import Foundation
protocol CFType: AnyObject {
static var typeID: CFTypeID { get }
}
protocol CFTollFreeBridgedType: CFType {
associatedtype BridgedNSType
}
func cfCast<T: CFType>(_ v: Any, to type: T.Type = T.self) -> T? {
let ref = v as CFTypeRef
if CFGetTypeID(ref) == type.typeID {
return (ref as! T)
} else {
return nil
}
}
func cfCast<T: CFTollFreeBridgedType>(_ v: Any, to type: T.Type = T.self) -> T? {
if let nsValue = v as? T.BridgedNSType {
return (nsValue as! T)
} else {
return nil
}
}
// =================================
extension CFString: CFTollFreeBridgedType {
typealias BridgedNSType = NSString
static var typeID = CFStringGetTypeID()
}
extension CFAllocator: CFType {
static var typeID = CFAllocatorGetTypeID()
}
let cfString: Any = "foo" as CFString
let nsString: Any = "foo" as NSString
let swiftString: Any = "foo" as String
let cfNumber: Any = 1 as CFNumber
let nsNumber: Any = 1 as NSNumber
let swiftNumber: Any = 1 as Int
let cfAllocator: Any = kCFAllocatorSystemDefault as Any
cfCast(cfString, to: CFString.self) // "foo"
cfCast(nsString, to: CFString.self) // "foo"
cfCast(swiftString, to: CFString.self) // "foo"
cfCast(cfNumber, to: CFString.self) // nil
cfCast(nsNumber, to: CFString.self) // nil
cfCast(swiftNumber, to: CFString.self) // nil
cfCast(cfAllocator, to: CFString.self) // nil
cfCast(cfString, to: CFAllocator.self) // nil
cfCast(nsString, to: CFAllocator.self) // nil
cfCast(swiftString, to: CFAllocator.self) // nil
cfCast(cfNumber, to: CFAllocator.self) // nil
cfCast(nsNumber, to: CFAllocator.self) // nil
cfCast(swiftNumber, to: CFAllocator.self) // nil
cfCast(cfAllocator, to: CFAllocator.self) // CFAllocator
// usage
CFStringGetLength(cfCast(cfString)) // 3
1 Like
I’m making a package that should make interacting with the Keychain easier, lots of CF-prefixes and some weird casting issues. I was hoping to create a protocol and use it to cast values similarly to CFType in the example.
As someone who’s stumbled into this thread looking for a solution that would look very much like this, is it viable solution?
Or, to put it another way, do your caveats also apply to the example? Caveat three seems to be taken care of, but the first two stump me a bit.