`any` type is not fulfill the protocol requirement inside KeyPath

In Swift 5.7, raw any type can be passing to some type without any problem, while if the protocol requirement is nested inside a KeyPath, any type will fail to fulfill the requirement. I wonder if it's an intended behavior or a bug.

protocol StarField: Codable, Equatable {
    var name: String { get set }
}


// KeyPtah

class FieldContainer {
    var starField: any StarField
    
    init(starField: any StarField) {
        self.starField = starField
    }
}

class StarFieldRunner {
    func runSome(_ v: some StarField) {
        
    }
    
    func runSome2<Value: Equatable>(value: WritableKeyPath<FieldContainer, Value>) { 
    }
    
    func runAny(_ v: any StarField) {
        // 1. Works fine
        runSome(v)
        
        // 2. Type 'any StarField' cannot conform to 'Equatable'
        let d = FieldContainer(starField: v)
        runSome2(value: \.starField)
    }
}

because of the typo:
replace runSome2(value: \.starFeld)
with:runSome2(value: \.starField)

It's the intended behavior.

Type any StarField is an existential type, which means it's a kind of "box" that can hold any concrete type that conforms to StarField and Codable and Equatable.

The box itself, though, does not conform to StarField or Codable or Equatable. That is for technical reasons, not an arbitrary decision.**

When you have a function parameter of some StarField, the compiler generates code on your behalf to extract the value of concrete type from the box, and pass that value into runSome. The some keyword here means the concrete type is opaque — we can't "see" what concrete type the box contains — but that doesn't matter, because all possible concrete types are conforming.

OK, that's why runSome works.

For runSome2, something completely different is going wrong. You want to pass in a keypath, and the WritableKeyPath type requires a base type (FieldContainer in your example) which must be Equatable.

But your FieldContainer isn't Equatable!

You would need to do at least this:

class FieldContainer: Equatable {

but that's not enough on its own. FieldContainer has a property of type any StarField which isn't Equatable (see above), so the compiler can't synthesize Equatable conformance for FieldContainer for you.

To make this work, you'd have to provide your own implementation of Equatable for your FieldContainer class.


** Here's the reason why existential box types cannot in general conform to their underlying protocol:

Think about any Equatable as a simplified example. If any Equatable conformed to Equatable, you could have one any Equatable box containing an Int, and another containing a String, because both Int and String conform to Equatable.

But you couldn't check whether those 2 boxes were == to each other, since there's no equality function to test types Int and String.

1 Like

Thanks, typo has been fixed.

Thanks for your explanation.

Inside:

func runSome2<Value: Equatable>(value: WritableKeyPath<FieldContainer, Value>)

The keypath require the Value (StarField in my example) conform to Equtable as implemented in the example.
I think the Swift compiler can unbox concrete values, but not the abstract type.

 // 1. Concrete value, it works
runSome(v)
        
// 2. Only type information, compile error
runSome2(value: \.starField)