I'm unable to solve this problem. I need a way to loop over a collection of key-path values but the compiler does not let me when the Value is an existentential or a protocol:
protocol A {}
protocol B {}
extension Int : A, B {}
extension String : A, B {}
struct Foo {
let x: Int = 42
let y: String = "swift"
}
let test: [KeyPath<Foo, A & B>] = [
\.x, // Key path value type 'Int' cannot be converted to contextual type 'A & B'
\.y
]
I also tried:
let test: [AnyKeyPath] = ....
let test2 = test.compactMap { $0 as? KeyPath<Foo, A & B> }
But test2 won't contain any values.
Joe_Groff
(Joe Groff)
2
Key paths are not covariant like this. You can have a collection of [PartialKeyPath<Foo>], though.
Is there no real way to workaround this issue? I really need it to be KeyPath<Foo, A & B>.
Joe_Groff
(Joe Groff)
4
You can apply a PartialKeyPath to a base value and get a value of Any, and cast that to A & B.
jrose
(Jordan Rose)
5
If you really need this, you can define computed properties on the type itself:
extension Foo {
fileprivate var x_AB: A & B { return self.x }
fileprivate var y_AB: A & B { return self.y }
}
let test: [KeyPath<Foo, A & B>] = [\.x_AB, \.y_AB]
Or you can use closures instead of key paths:
let test: [(Foo) -> A & B] = [
{ $0.x },
{ $0.y }
]
(note: I didn't test either of these code samples, so they may have small bugs)
4 Likes
Joe_Groff
(Joe Groff)
6
That, or provide an extension property on the protocol composition:
extension A where Self: B {
var asAB: A & B { return self }
}
which would let you use \.x.asAB and \.y.asAB. Note that these would no longer be equal to \.x and \.y, if that's important.
5 Likes
I'd like to try that but I'm already hitting the next wall. One of the path that I need has a subscript which uses an enum as a key.
\BluetoothCharacteristic.cameraSetting[.iso] // Type of expression is ambiguous without more context
What I can do is this, but it's really verbose and tedious:
let a = \BluetoothCharacteristic.cameraSetting
let b: PartialKeyPath<BluetoothCharacteristic> = a.appending(path: \.[.iso])
Is there a better way of expressing it one line?
Joe_Groff
(Joe Groff)
8
That looks like a type-checker bug. Does it work if you explicitly name EnumName.iso?
No, but it seems I can silent the error by adding as PartialKeyPath<BluetoothCharacteristic> at the end. 
Joe_Groff
(Joe Groff)
10
Weird. Does cameraSetting by chance come from a superclass of BluetoothCharacteristic? @rudkx fixed a bug related to that recently. If not, this is definitely worth a bug of its own.
No classes are involved here, but the types are a little complex:
extension CameraSettingCharacteristic {
///
public struct Path {
///
public subscript (_ kind: Kind) -> ORWCharacteristicContainer<
CameraSettingCharacteristic, UInt8, Data
> { ... }
}
}
public struct BluetoothCharacteristic {
public let cameraSetting = CameraSettingCharacteristic.Path()
}
If I can shrink it down to something small that reproduces the issue, I'll file an issue tomorrow.
@Joe_Groff other then that, wouldn't it make sense to make Key Paths also covariant like collections?
This worked for me as a workaround. That way the type information I need won't be lost, thank youl