[swift-evolution] [Pitch] Making some RawRepresentable things bridge to ObjC as their raw value
Joe Groff
jgroff at apple.com
Tue Dec 6 15:35:02 CST 2016
> On Dec 5, 2016, at 11:46 PM, Zach Waldowski via swift-evolution <swift-evolution at swift.org> wrote:
>
> I hate to be a thread necromancer, but I ran into the limits of the current behavior several times today alone. I was perhaps overexcited by the additional ObjectiveCBridgeable compliance of numeric types, and took off several `.rawValue`s in a way that were hard to track down.
>
> A few other examples, similar to what came up in the original thread:
>
> 1. An options dictionary.
>
> ```
> let detectedEncoding = NSString.stringEncoding(for: content, encodingOptions: [
> .suggestedEncodingsKey: [ String.Encoding.utf8 ]
> ], convertedString: nil, usedLossyConversion: nil)
> ```
>
> This syntax looks surprisingly Swifty, but fails at runtime. id-as-Any means a new developer can go a long way without knowing many types even /are/ RawRepresentable.
>
> 2. KVC. Given a newtype wrapper, I can have:
>
> ```
> class StrawMan: NSObject {
> dynamic var lookMa: Notification.Name?
> }
>
> var demo: StrawMan = …
> demo.lookMa = .noStrings
> ````
>
> But I can’t do:
>
> ```
> demo.setValue(Notification.Name.noStrings, forKey: #keyPath(StrawMan.lookMa))
> ```
>
> Worse, again, this only fails at runtime, rather than at compile time.
>
> 3. Generics, similar in scope to #2.
>
> ```
> class ValueTransformer<In, Out>: Foundation.ValueTransformer {
> // use your imagination, id-as-Any is involved
> }
>
> @objc enum TestEnum: Int { case one, two, three }
>
> let t = ValueTransformer<Int, TestEnum>(…) // mysteriously fails because a TestEnum crosses Any as _SwiftValue
> ```
>
> Like I say above, armed with the vague recollection of my proselytizing about id-as-Any, especially the extent to how well stuff like NSNull bridging works, a team member could easily make mistakes that aren’t caught until runtime. Between example #2 and #3 I consider the behavior today to be approaching bug territory because of its big breakage of the principle of least surprise.
>
> Though I’m sympathetic with the fears from the original thread(s) about making the bridge too fuzzy, I think being @objc, conforming to RawRepresentable, and the RawValue conforming to ObjectiveCBridgeable is more than enough information to go on (without treading into territory of purposefully misusing RawRepresentable). RawRepresentable requires a failable initializer, and I expect a well-behaved initializer to tie in with casting of the RawValue to make an overall “as?” cast work as expected, fuzziness be damned.
>
> In basically every case I can think of involving the Cocoa bridge, bridging using the RawValue is the right behavior. In most cases not considering the Cocoa bridge, a predictable set of rules combined with an explicit “as?” Cast is more than explicit enough to justify whatever behavior the compiler comes up with.
>
> Overall, It makes me more than uneasy to use a compiler feature about which the most confidence I can get is reading the stdlib/overlay sources to find out what secret conformances are declared.
Given that we already bridge imported types with the "swift_newtype" attribute, it seems reasonable to me to do so for imported NS_ENUM/NS_OPTIONS types too, and perhaps as magic for @objc enums too. In all these cases, the bridging is introduced at the point of declaration of the type, so there isn't the concern about types becoming post-hoc bridgable by extensions that you'd have by making RawRepresentable imply bridging.
-Joe
More information about the swift-evolution
mailing list