Hi everyone!
Wanted to write up a proper pitch for this feature proposal. Still not sure if this requires a full round of evolution or not (since it is debatably covered by the accepted language of SE-0249), but it's a small enough addition that I felt it couldn't hurt to be a bit more precise. The latest version of this text can be found at the swift-evolution PR.
- Implementation: apple/swift#39612
Introduction
Allow key path literals to partake in the full generality of function-function conversions, so that the following code compiles without error:
let _: (String) -> Int? = \.count
Motivation
SE-0249 introduced a conversion between key path literals and function types, which allowed users to write code like the following:
let strings = ["Hello", "world", "!"]
let counts = strings.map(\.count) // [5, 5, 1]
However, SE-0249 does not quite live up to its promise of allowing the equivalent key path construction "wherever it allows (Root) -> Value functions." Function types permit conversions that are covariant in the result type and contravariant in the parameter types, but key path literals require exact type matches. This can lead to some potentially confusing behavior from the compiler:
struct S {
var x: Int
}
// All of the following are okay...
let f1: (S) -> Int = \.x
let f2: (S) -> Int? = f1
let f3: (S) -> Int? = { $0.x }
let f4: (S) -> Int? = { kp in { root in root[keyPath: kp] } }(\S.x)
let f5: (S) -> Int? = \.x as (S) -> Int
// But the direct conversion fails!
let f6: (S) -> Int? = \.x // <------------------- Error!
Proposed solution
Allow key path literals to be converted freely in the same manner as functions are converted today. This would allow the definition f6
above to compile without error, in addition to allowing constructions like:
class Base {
var derived: Derived { Derived() }
}
class Derived: Base {}
let g1: (Derived) -> Base = \Base.derived
Detailed design
Rather than permitting a key path literal with root type Root
and value type Value
to only be converted to a function type (Root) -> Value
, key path literals will be permitted to be converted to any function type which (Root) -> Value
may be converted to.
The actual key-path-to-function conversion transformation proceeds exactly as before, generating code with the following semantics (adapting an example from SE-0249):
// You write this:
let f: (User) -> String? = \User.email
// The compiler generates something like this:
let f: (User) -> String? = { kp in { root in root[keyPath: kp] } }(\User.email)
Source compatibility
This proposal only makes previously invalid code valid and does not have any source compatibility implications.
Effect on ABI stability
N/A
Effect on API resilience
N/A
Acknowledgements
Thanks to @ChrisOffner for kicking off this discussion on the forums to point out the inconsistency here.