-
Notifications
You must be signed in to change notification settings - Fork 10.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement Enum Case KeyPaths #58940
base: main
Are you sure you want to change the base?
Implement Enum Case KeyPaths #58940
Conversation
@swift-ci please test |
@swift-ci please test |
What does this do in the presence of an enum case overloaded by base name? |
It kind of just works enum Color {
case generic(Int)
case generic(thing: String)
case generic(Int, String)
}
let x = \Color.generic // ambiguous, obviously
let y = \Color.generic(_:) // refers to generic(Int)
let z = \Color.generic(_:_:) // refers to generic(Int, String)
let a = \Color.generic(thing:) // refers to generic(thing: String) I'll add lots more tests exercising this and other oddities. |
@swift-ci please test macOS |
@swift-ci please test Windows |
This is fantastic! This is only read-only, right? var genericColor = Color.generic("#FFF")
var blueColor = Color.blue
genericColor[\.generic] = "#000" -> genericColor == .generic("#000")
blueColor[\.generic] = "#000" -> crash, like force-unwrapping Optional.none or accessing some collection out of bounds Would this be reaching too far for this PR, or would this need to go through Swift Evolution regarding the behavior for the last case? |
Right, these are read only right now so trying to use the subscript to mutate will be a compilation error (there wouldn't be a crash at runtime) |
@Azoy Thanks for your response. I'm understanding that it won't compile with the current state of this PR, but I was imagining the shape of a read-write API. After thinking more about it, I think crashes at runtime when setting the value of the "wrong" enum Color {
case blue
case generic(String)
case white(Float, Float)
}
var myBlue = Color.blue
var myGeneric = Color.generic("#FFF")
var myGray = Color.white(0.5, 1.0)
myBlue[\.blue] = () => myBlue = .blue (Symmetric to the "get" part)
myBlue[\.generic] = "#000" => NOOP
myBlue[\.white?.0] = 0.6 => NOOP
myBlue[\.white] = (0.6, 0.0) => NOOP
myGeneric[\.blue] = () => NOOP
myGeneric[\.generic] = "#000" => myGeneric == .generic("#000") (Symmetric to the "get" part)
myGeneric[\.white?.0] = 0.6 => NOOP
myGeneric[\.white] = (0.6, 0.0) => NOOP
myGray[\.blue] = () => NOOP
myGray[\.generic] = "#000" => NOOP
myGray[\.white?.0] = 0.6 => myGray == .white(0.6, 1.0) (Symmetric to the "get" part)
myGray[\.white] = (0.6, 0.0) => myGray == .white(0.6, 0.0) (Symmetric to the "get" part) In this case, it would work as one would most likely expect, and it would crash only when we force unwrap the chained cases myGeneric[\.white] = (0.6, 0.0) => | myGeneric == .white(0.6, 0.0) (Flip)
or | myGeneric == .generic("#FFF") (NOOP) That's the part that is still open. I understand it goes beyond the scope of this PR. This would however be very useful to derive bindings involving an enum in SwiftUI (as they're often relying on But this is already a very welcome feature! Thank you very much! |
This is great, thanks @Azoy! A few things:
@tgrapperon We will most likely need to introduce new subclasses of |
@Azoy What about cases without payloads? The case value could be represented as a Bool, or |
Hmm,
|
I'm not sure if there's much of an arguable difference, though, or precedent for needing to distinguish between the two. (I imagine Case names can also no longer be overloaded, so no need to worry about overlapping case paths to a
Definitely seems like |
Should we start a pitch thread on the forums to start discussing design points like these? |
@jckarter While our library use of case paths has given us plenty of time and experience with the problem space, we’ve held off on writing any kind of pitch because we haven’t had any engineers familiar enough with the compiler available to help with the implementation. If @Azoy (or someone else with the knowhow) would like to work on a pitch with us, please let us know! |
I think a pitch thread is a great idea. I'd be happy to work with you and others on a pitch @stephencelis. |
@stephencelis @Azoy I don't think the technical aspects need to be 100% nailed down to start a pitch. It'd be good to share your experience and figure out how we can best integrate the functionality into the core key path implementation. |
@jckarter @Azoy Sounds good! @mbrandonw and I will try to get a draft together soon. |
@swift-ci please build toolchain |
@swift-ci please build toolchain |
@swift-ci please build toolchain |
auto varDecl = cast<VarDecl>(decl); | ||
// Key paths don't work with mutating-get properties. | ||
assert(!varDecl->isGetterMutating()); | ||
// Key paths don't currently support static members. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Key paths don't currently support static members.
Can't we lift this limitation too?
enum Enum {
case case1
static let case2 = case1
}
let kp1 = \Enum.case1 // now supported
let kp2 = \Enum.case2 // Key path cannot refer to static member 'case2'
KeyPath for a case is very similar to a static member, like enum cases can stand as witness to static protocol requirements?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I haven't looked into static members at all yet, but my initial reaction is that they are a little different from enum cases from an implementation point of view.
@swift-ci please build toolchain |
update the projector
Aka case paths, implement payload case key paths that will optionally return the tuple of a case's associated value. If the value of type enum is not the same case as the key path, nil will be returned. For example:
Because tuple element key paths are already implemented, accessing different members of the associated value is really easy.