Any Plans to Enhance KeyPath Ergonomics?

Consider this playground:

The Pain Point

Once a KeyPath is type-erased to AnyKeyPath, it becomes really difficult to use it in any sort of dynamic way. (i.e. in a way where you don't have to explicitly type out the specializations for the generics of the more specialized KeyPath types.) This makes walking a collection of type-erased AnyKeyPath objects much more cumbersome than it ought to be.

Swift already creates the "most appropriate" type of KeyPath (WritableKeyPath, KeyPath, ReferenceWritableKeyPath, etc) based on the declaration of the object the KeyPath is targeting (let vs var, value vs reference type, etc.)

Why can't Swift go the opposite way automatically? Why does it have to be so painful to get back to a ReferenceWritableKeyPath from an AnyKeyPath when the AnyKeyPath already has its rootType and valueType specified? All the stuff it needs is right there!

I'd settle for something like this:

foo[keyPath: (kps.first! as! ReferenceWritableKeyPath)] = nil

What stops Swift from realizing that it already has the information it needs to specialize ReferenceWritableKeyPath and does not need the developer to manually type out the generics?

The ergonomics of getting to AnyKeyPath are great. But the ergonomics of going the other way to actually use that KeyPath are...terrible. Are there any plans to change that?

Not really. Those properties are static, so I don't think they do anything useful in modern Swift. Subclasses don't even bother with it.

WritableKeyPath<Int, Bool>.rootType // any Any.Type

It used to be that getting a metatype from something type-erased required a property, but now you can just open existentials.

protocol PartialKeyPath<Root> {
  associatedtype Root
}

extension PartialKeyPath {
  static var kindaPointless: Root.Type { Root.self }
}

func f(
  concrete: some PartialKeyPath<Bool>,
  some: some PartialKeyPath, // This can take an `any PartialKeyPath` now.
  any: any PartialKeyPath
) {
  type(of: concrete).kindaPointless // Bool.Type
  type(of: concrete).Root.self // Bool.Type

  type(of: some).kindaPointless // (some PartialKeyPath).Root.Type
  type(of: some).Root.self // (some PartialKeyPath).Root.Type

  type(of: any).kindaPointless // any Any.Type
  type(of: any).Root.self // Does not compile.
}

Key paths are, of course, not special when it comes to type erasure ergonomics.

You can get to this, at least:

foo[keyPath: (kps.first! as! ReferenceWritableKeyPath<_, String?>)] = nil

You can write that line better than that, but you cannot get rid of the String?, because AnyKeyPath and PartialKeyPath don't provide their Value to the type system. nil is strongly-typed, and so I'm having trouble imagining where @bdkjones thinks it could be acquiring that.

foo[keyPath: kps.first as! ReferenceWritableKeyPath] = nil as String?
foo[keyPath: kps.first as! ReferenceWritableKeyPath] = String?.none

typedNil(of: type(of: foo.name))

could be rewritten as

type(of: \Foo.name).Value.none
extension KeyPath { typealias Value = Value }

But that's the same as

foo.name = nil