Why do `is` checks between existential and Core Foundation types always return true?

I stumbled across a confusing behavior of Swift that seems to be intentional:

let x: some Equatable = "this is clearly a string"
x is CGColor // true 🤔

I wrote that this seems to be intentional because the compiler shows this warning:

'is' test is always true because 'CGColor' is a Core Foundation type

Why is that? Where does this behavior come?

This even caused a crash in my code where a switch over a generic type that might or might not represent a CGColor, NSColor, SwiftUI.Color, or something else entirely matched CGColor and accessing its alpha property caused a crash when the value was either an NSColor or SwiftUI.Color. Interestingly, the same code does not crash in a playground, but still matches CGColor when passing in an NSColor. A SwiftUI.Color works fine in the playground, though:

struct Box<Value: Equatable> {

    var value: Value

    func printAlpha() {
        switch self.value {
        case let color as CGColor:
            print("CGColor alpha: \(color.alpha)")
        case let color as NSColor:
            print("NSColor alpha: \(color.alphaComponent)")
        case let color as SwiftUI.Color:
            print("SwiftUI.Color alpha: \(color.cgColor!.alpha)")
            print("not a color")

let nsColorBox = Box(value: NSColor.black)
nsColorBox.printAlpha() // CGColor alpha: 1.0 ❌

let swiftUIColorBox = Box(value: SwiftUI.Color.black)
swiftUIColorBox.printAlpha() // SwiftUI.Color alpha: 1.0 ✅

let cgColorBox = Box(value: CGColor.black)
cgColorBox.printAlpha() // CGColor alpha: 1.0 ✅

Here’s a screenshot of the crash in my app (not a playground). value here is an NSColor or SwiftUI.Color, but you can clearly see that the CGColor case is matched and that CGColorGetAlpha is being called, which unsurprisingly doesn’t work:

Note that this crash is easily fixed by moving the case let color as NSColor above the corresponding CGColor case :man_shrugging:

1 Like

I think you’re supposed to check type ID of CF types, but the compiler doesn’t know how to do that for arbitrary CF types or even for “known” CF types. So instead, it just emits a warning.

1 Like

For reference, see a detailed discussion [SR-1612] Type comparison of certain CG types with AnyObject object is always true - Swift

Thanks for the input. What you write definitely makes sense when comparing CF types against each other, but what we have here is either just some Equatable or (in the case of my Box example) a generic type that is constrained to Equatable – which I would guess/hope is pretty much the same in terms of behavior. It feels unfeasible to me that all generic Swift code needs to give special treatment to CF types.

But at least it seems to be something that can be done:

let x: some Equatable = "this is clearly a string"
CFGetTypeID(x as CFTypeRef) == CGColor.typeID // false ✅

Thanks for the link! This at least explains where the motivation for the diagnostic comes from. It also sounds like I shouldn’t expect much progress in this are since this hasn’t changed in the last ~6 years.

1 Like