From 3fcdcb4a2e02378213b4d3d34c5dc435bc0f57c7 Mon Sep 17 00:00:00 2001 From: David Hart Date: Fri, 4 Mar 2016 18:03:21 +0100 Subject: [PATCH 1/6] new proposal for objectve-c keypahts --- proposals/XXXX-objc-keypaths.md | 63 +++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 proposals/XXXX-objc-keypaths.md diff --git a/proposals/XXXX-objc-keypaths.md b/proposals/XXXX-objc-keypaths.md new file mode 100644 index 0000000000..a59f7176ce --- /dev/null +++ b/proposals/XXXX-objc-keypaths.md @@ -0,0 +1,63 @@ +# Referencing Objective-C key-paths + +* Proposal: [SE-XXXX](https://github.com/apple/swift-evolution/blob/master/proposals/XXXX-objc-keypaths.md) +* Author(s): [David Hart](https://github.com/hartbit) +* Status: TBD +* Review manager: TBD + +## Introduction + +In Objective-C and Swift, key-paths used by KVC and KVO are represented as string literals (e.g., `"friend.address.streetName"`). This proposal seeks to improve the safety and resilience to modification of code using key-paths by introducing a compiler-checked expression. + +## Motivation + +The use of string literals for key paths is extremely error-prone: there is no checking that the string corresponds to a valid key-path. In a similar manner to the proposal for the Objective-C selector expression [SE-0022](https://github.com/apple/swift-evolution/blob/master/proposals/0022-objc-selectors.md), this proposal introduces a syntax for referencing compiler-checked key-paths. When the referenced properties and methods are renamed or deleted, the programmer will be notified by a compiler error. + +## Proposed solution + +Introduce a new expression `#keypath` that allows one to build a `String` from a key-path: + +```swift +class Person: NSObject { + dynamic var firstName: String = "" + dynamic var lastName: String = "" + dynamic var friends: [Person] = [] + dynamic var bestFriend: Person? + + init(firstName: String, lastName: String) { + self.firstName = firstName + self.lastName = lastName + } +} + +let chris = Person(firstName: "Chris", lastName: "Lattner") +let joe = Person(firstName: "Joe", lastName: "Groff") +let douglas = Person(firstName: "Douglas", lastName: "Gregor") +chris.friends = [joe, douglas] +chris.bestFriend = joe + + +#keypath(Person.firstName) // => "firstName" +chris.valueForKey(#keypath(Person.firstName)) // => Chris +#keypath(Person.bestFriend.lastName) // => "bestFriend.lastName" +chris.valueForKeyPath(#keypath(Person.bestFriend.lastName)) // => Groff +#keypath(Person.friends.firstName) // => "friends.firstName" +chris.valueForKeyPath(#keypath(Person.friends.firstName)) // => ["Joe", "Douglas"] + +``` + +By having the `#keypath` expression do the work to form the Objective-C key-path string, we free the developer from having to do the manual typing and get static checking that the key-path exists and is exposed to Objective-C. + +## Impact on existing code + +The introduction of the `#keypath` expression has no impact on existing code as it returns a `String`. It is simply a modification-safe alternative to using literal strings for referencing key-paths. + +## Alternatives considered + +One aspect of the design which seems potentially complicated is the reference to key-paths which include an collection in the middle of the path. + +```swift +chris.valueForKeyPath(#keypath(Person.friends.firstName)) +``` + + The above example is potentially harder to implement because the argument of `#keypath` is not a valid Swift expression, compared to the other two examples. An alternative would be to remove the ability to reference those key-paths, making the proposal less useful, but easier to implement. From a9efebb16775006ec87357b62079e7e031bc6226 Mon Sep 17 00:00:00 2001 From: David Hart Date: Thu, 10 Mar 2016 08:50:26 +0100 Subject: [PATCH 2/6] added ability to use value expressions --- proposals/XXXX-objc-keypaths.md | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/proposals/XXXX-objc-keypaths.md b/proposals/XXXX-objc-keypaths.md index a59f7176ce..088a9ba785 100644 --- a/proposals/XXXX-objc-keypaths.md +++ b/proposals/XXXX-objc-keypaths.md @@ -15,7 +15,7 @@ The use of string literals for key paths is extremely error-prone: there is no c ## Proposed solution -Introduce a new expression `#keypath` that allows one to build a `String` from a key-path: +Introduce a new expression `#keypath` that allows one to build a compile-time string literal from a key-path (to allow it be used as `StaticString` and `StringLiteralConvertible`): ```swift class Person: NSObject { @@ -48,9 +48,19 @@ chris.valueForKeyPath(#keypath(Person.friends.firstName)) // => ["Joe", "Douglas By having the `#keypath` expression do the work to form the Objective-C key-path string, we free the developer from having to do the manual typing and get static checking that the key-path exists and is exposed to Objective-C. +It would also be very convenient for the `#keypath` to accept value (instead of static) expressions: + +``` +extension Person { + class func find(name: String) -> [Person] { + return DB.find("SELECT * FROM Person WHERE \(#keypath(firstName)) LIKE '%\(name)%' OR \(#keypath(lastName)) LIKE '%\(name)%'") + } +} +``` + ## Impact on existing code -The introduction of the `#keypath` expression has no impact on existing code as it returns a `String`. It is simply a modification-safe alternative to using literal strings for referencing key-paths. +The introduction of the `#keypath` expression has no impact on existing code as it returns a literal string. It is simply a modification-safe alternative to using literal strings directly for referencing key-paths. ## Alternatives considered From f2bfdd499809a173e9dbf26036d13df91f8dadb9 Mon Sep 17 00:00:00 2001 From: David Hart Date: Sat, 12 Mar 2016 22:31:45 +0100 Subject: [PATCH 3/6] more detail on collection operators --- proposals/XXXX-objc-keypaths.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/proposals/XXXX-objc-keypaths.md b/proposals/XXXX-objc-keypaths.md index 088a9ba785..a9383a6ad9 100644 --- a/proposals/XXXX-objc-keypaths.md +++ b/proposals/XXXX-objc-keypaths.md @@ -58,6 +58,10 @@ extension Person { } ``` +## Collection Operators + +This proposal purposely does not attempt to implement Collection Operators as the current functionality stands on its own and is useful even without the Objective-C runtime (as can be seen in the previous example). On the contrary, collection operators will require more design, and are only useable with `valueForKeyPath:` which is not available on Linux. + ## Impact on existing code The introduction of the `#keypath` expression has no impact on existing code as it returns a literal string. It is simply a modification-safe alternative to using literal strings directly for referencing key-paths. From 441ba7cfd72b08bdd97552251be4c8b36711d09f Mon Sep 17 00:00:00 2001 From: David Hart Date: Thu, 17 Mar 2016 08:13:26 +0100 Subject: [PATCH 4/6] Clarified collection keypaths and naming disucssion --- proposals/XXXX-objc-keypaths.md | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/proposals/XXXX-objc-keypaths.md b/proposals/XXXX-objc-keypaths.md index a9383a6ad9..c8d7144894 100644 --- a/proposals/XXXX-objc-keypaths.md +++ b/proposals/XXXX-objc-keypaths.md @@ -58,6 +58,22 @@ extension Person { } ``` +## Collection Keypaths + +One aspect of the design which seems potentially problematic is the reference to key-paths into collections. As Foundation types are not strongly-typed, paths that reference: + +* a type conforming to `SequenceType` are allowed to add a key to reference properties on that type and properties on the `Element` type +* a type conforming to `NSArray`, `NSDictionary`, `NSSet` are allowed to add a key to reference properties on that type but not on the contained objects + +```swift +let swiftArray = ["Chris", "Joe", "Douglas"] +let nsArray = NSArray(array: swiftArray) +swiftArray.valueForKeyPath(#keypath(swiftArray.count)) // => 3 +swiftArray.valueForKeyPath(#keypath(swiftArray.uppercaseString)) // => ["CHRIS", "JOE", "DOUGLAS"] +swiftArray.valueForKeyPath(#keypath(nsArray.count)) // => 3 +swiftArray.valueForKeyPath(#keypath(nsArray.uppercaseString)) // compiler error +``` + ## Collection Operators This proposal purposely does not attempt to implement Collection Operators as the current functionality stands on its own and is useful even without the Objective-C runtime (as can be seen in the previous example). On the contrary, collection operators will require more design, and are only useable with `valueForKeyPath:` which is not available on Linux. @@ -68,10 +84,4 @@ The introduction of the `#keypath` expression has no impact on existing code as ## Alternatives considered -One aspect of the design which seems potentially complicated is the reference to key-paths which include an collection in the middle of the path. - -```swift -chris.valueForKeyPath(#keypath(Person.friends.firstName)) -``` - - The above example is potentially harder to implement because the argument of `#keypath` is not a valid Swift expression, compared to the other two examples. An alternative would be to remove the ability to reference those key-paths, making the proposal less useful, but easier to implement. +There does not seem to be any obvious alternatives. The only point of discussion was on the name of the expression. `#key` was proposed: it is shorted but does not seem to express that the expression accepts paths. \ No newline at end of file From 4361d3209b84685f7f552f13e84f8e6949b144cd Mon Sep 17 00:00:00 2001 From: David Hart Date: Sat, 19 Mar 2016 18:57:22 +0100 Subject: [PATCH 5/6] Minor modifications to make the text and examples read better --- proposals/XXXX-objc-keypaths.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/proposals/XXXX-objc-keypaths.md b/proposals/XXXX-objc-keypaths.md index c8d7144894..f4031bdeaa 100644 --- a/proposals/XXXX-objc-keypaths.md +++ b/proposals/XXXX-objc-keypaths.md @@ -11,11 +11,11 @@ In Objective-C and Swift, key-paths used by KVC and KVO are represented as strin ## Motivation -The use of string literals for key paths is extremely error-prone: there is no checking that the string corresponds to a valid key-path. In a similar manner to the proposal for the Objective-C selector expression [SE-0022](https://github.com/apple/swift-evolution/blob/master/proposals/0022-objc-selectors.md), this proposal introduces a syntax for referencing compiler-checked key-paths. When the referenced properties and methods are renamed or deleted, the programmer will be notified by a compiler error. +The use of string literals for key paths is extremely error-prone: there is no compile-time assurance that the string corresponds to a valid key-path. In a similar manner to the proposal for the Objective-C selector expression [SE-0022](https://github.com/apple/swift-evolution/blob/master/proposals/0022-objc-selectors.md), this proposal introduces syntax for referencing compiler-checked key-paths. When the referenced properties and methods are renamed or deleted, the programmer will be notified by a compiler error. ## Proposed solution -Introduce a new expression `#keypath` that allows one to build a compile-time string literal from a key-path (to allow it be used as `StaticString` and `StringLiteralConvertible`): +Introduce a new expression `#keypath()` that allows one to build a compile-time valid key-path string literal (to allow it be used as `StaticString` and `StringLiteralConvertible`): ```swift class Person: NSObject { @@ -43,7 +43,6 @@ chris.valueForKey(#keypath(Person.firstName)) // => Chris chris.valueForKeyPath(#keypath(Person.bestFriend.lastName)) // => Groff #keypath(Person.friends.firstName) // => "friends.firstName" chris.valueForKeyPath(#keypath(Person.friends.firstName)) // => ["Joe", "Douglas"] - ``` By having the `#keypath` expression do the work to form the Objective-C key-path string, we free the developer from having to do the manual typing and get static checking that the key-path exists and is exposed to Objective-C. @@ -53,14 +52,16 @@ It would also be very convenient for the `#keypath` to accept value (instead of ``` extension Person { class func find(name: String) -> [Person] { - return DB.find("SELECT * FROM Person WHERE \(#keypath(firstName)) LIKE '%\(name)%' OR \(#keypath(lastName)) LIKE '%\(name)%'") + return DB.execute("SELECT * FROM Person WHERE \(#keypath(firstName)) LIKE '%\(name)%'") } } ``` +In this case, `#keypath(firstName)` is understood to represent `#keypath(Person.firstName)`. + ## Collection Keypaths -One aspect of the design which seems potentially problematic is the reference to key-paths into collections. As Foundation types are not strongly-typed, paths that reference: +One aspect of the design which seems potentially problematic is the reference to key-paths into collections. As Foundation types are not strongly-typed, keys-paths that reference: * a type conforming to `SequenceType` are allowed to add a key to reference properties on that type and properties on the `Element` type * a type conforming to `NSArray`, `NSDictionary`, `NSSet` are allowed to add a key to reference properties on that type but not on the contained objects @@ -80,7 +81,7 @@ This proposal purposely does not attempt to implement Collection Operators as th ## Impact on existing code -The introduction of the `#keypath` expression has no impact on existing code as it returns a literal string. It is simply a modification-safe alternative to using literal strings directly for referencing key-paths. +The introduction of the `#keypath` expression has no impact on existing code, and is simply a modification-safe alternative to using strings literal for referencing key-paths. ## Alternatives considered From 9cb3f3bba19790dc14615e6cc1876284ba3f8c42 Mon Sep 17 00:00:00 2001 From: David Hart Date: Thu, 24 Mar 2016 08:21:55 +0100 Subject: [PATCH 6/6] added remarks from Douglas Gregor --- proposals/XXXX-objc-keypaths.md | 40 ++++++++++++++++----------------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/proposals/XXXX-objc-keypaths.md b/proposals/XXXX-objc-keypaths.md index f4031bdeaa..34006fb033 100644 --- a/proposals/XXXX-objc-keypaths.md +++ b/proposals/XXXX-objc-keypaths.md @@ -15,7 +15,7 @@ The use of string literals for key paths is extremely error-prone: there is no c ## Proposed solution -Introduce a new expression `#keypath()` that allows one to build a compile-time valid key-path string literal (to allow it be used as `StaticString` and `StringLiteralConvertible`): +Introduce a new expression `#keyPath()` that allows one to build a compile-time valid key-path string literal (to allow it be used as `StaticString` and `StringLiteralConvertible`): ```swift class Person: NSObject { @@ -37,43 +37,41 @@ chris.friends = [joe, douglas] chris.bestFriend = joe -#keypath(Person.firstName) // => "firstName" -chris.valueForKey(#keypath(Person.firstName)) // => Chris -#keypath(Person.bestFriend.lastName) // => "bestFriend.lastName" -chris.valueForKeyPath(#keypath(Person.bestFriend.lastName)) // => Groff -#keypath(Person.friends.firstName) // => "friends.firstName" -chris.valueForKeyPath(#keypath(Person.friends.firstName)) // => ["Joe", "Douglas"] +#keyPath(Person.firstName) // => "firstName" +chris.valueForKey(#keyPath(Person.firstName)) // => Chris +#keyPath(Person.bestFriend.lastName) // => "bestFriend.lastName" +chris.valueForKeyPath(#keyPath(Person.bestFriend.lastName)) // => Groff +#keyPath(Person.friends.firstName) // => "friends.firstName" +chris.valueForKeyPath(#keyPath(Person.friends.firstName)) // => ["Joe", "Douglas"] ``` -By having the `#keypath` expression do the work to form the Objective-C key-path string, we free the developer from having to do the manual typing and get static checking that the key-path exists and is exposed to Objective-C. +By having the `#keyPath` expression do the work to form the Objective-C key-path string, we free the developer from having to do the manual typing and get static checking that the key-path exists and is exposed to Objective-C. -It would also be very convenient for the `#keypath` to accept value (instead of static) expressions: +It would also be very convenient for the `#keyPath` to accept value (instead of static) expressions: ``` extension Person { class func find(name: String) -> [Person] { - return DB.execute("SELECT * FROM Person WHERE \(#keypath(firstName)) LIKE '%\(name)%'") + return DB.execute("SELECT * FROM Person WHERE \(#keyPath(firstName)) LIKE '%\(name)%'") } } ``` -In this case, `#keypath(firstName)` is understood to represent `#keypath(Person.firstName)`. +In this case, `#keyPath(firstName)` is understood to represent `#keyPath(Person.firstName)`. ## Collection Keypaths -One aspect of the design which seems potentially problematic is the reference to key-paths into collections. As Foundation types are not strongly-typed, keys-paths that reference: - -* a type conforming to `SequenceType` are allowed to add a key to reference properties on that type and properties on the `Element` type -* a type conforming to `NSArray`, `NSDictionary`, `NSSet` are allowed to add a key to reference properties on that type but not on the contained objects +As Foundation types are not strongly-typed, the key-path expression should only accept traversing `SequenceType` conforming types: ```swift let swiftArray = ["Chris", "Joe", "Douglas"] let nsArray = NSArray(array: swiftArray) -swiftArray.valueForKeyPath(#keypath(swiftArray.count)) // => 3 -swiftArray.valueForKeyPath(#keypath(swiftArray.uppercaseString)) // => ["CHRIS", "JOE", "DOUGLAS"] -swiftArray.valueForKeyPath(#keypath(nsArray.count)) // => 3 -swiftArray.valueForKeyPath(#keypath(nsArray.uppercaseString)) // compiler error +swiftArray.valueForKeyPath(#keyPath(swiftArray.count)) // => 3 +swiftArray.valueForKeyPath(#keyPath(swiftArray.uppercased)) // => ["CHRIS", "JOE", "DOUGLAS"] +swiftArray.valueForKeyPath(#keyPath(nsArray.count)) // => 3 +swiftArray.valueForKeyPath(#keyPath(nsArray.uppercaseString)) // compiler error ``` +There is some implicit bridging going on here that could use some detailed design. If I refer to `Person.lastName.uppercased`, that's a method on the value type `String`. At runtime, we're depending on getting the `uppercaseString` method on `NSString`. This may be as simple as saying that we follow the `_ObjectiveCBridgeable` conformance for any value type encountered along the way. ## Collection Operators @@ -81,8 +79,8 @@ This proposal purposely does not attempt to implement Collection Operators as th ## Impact on existing code -The introduction of the `#keypath` expression has no impact on existing code, and is simply a modification-safe alternative to using strings literal for referencing key-paths. +The introduction of the `#keyPath` expression has no impact on existing code, and is simply a modification-safe alternative to using strings literal for referencing key-paths. ## Alternatives considered -There does not seem to be any obvious alternatives. The only point of discussion was on the name of the expression. `#key` was proposed: it is shorted but does not seem to express that the expression accepts paths. \ No newline at end of file +There does not seem to be any obvious alternatives. The only point of discussion was on the name of the expression. `#key` was proposed: it is shorter but does not seem to express that the expression accepts paths. \ No newline at end of file