[Pitch] Reducing the bridging magic in dynamic casts

Right, you'd need to do NSString(string) as AnyObject to explicitly bridge.

Okay great I'm fine with that. :)

The @objc-ness of AnyObject is more or less an implementation detail. On Darwin platforms at least, AnyObject still has the magic ability to dispatch to all @objc methods, similar to `id` in Objective-C, which vaguely defends its @objc-ness. (If we're going to rename it, my own preference would be to drop the Any and just call it `Object`, since we don't put Any in any other protocol names.)

Did I miss something again? I checked SR-0006 and it still has protocols like `Any`, `AnyIterator` or `AnyCollectionProtocol`.

···

--
Adrian Zubarev

Am 9. Mai 2016 um 21:38:22, Joe Groff (jgroff@apple.com(mailto:jgroff@apple.com)) schrieb:

> On May 6, 2016, at 12:04 AM, Adrian Zubarev via swift-evolution <swift-evolution@swift.org> wrote:
>
> Definitely a welcome change from me (+1). But this proposal makes me curious about the impact on the `AnyObject` protocol?
>
> let string = "foo"
> let nsString = string as AnyObject
> nsString.dynamicType // _NSCFConstantString.Type
> NSString().dynamicType // __NSCFConstantString.Type // there are two different types?
>
> This sample won’t bridge anymore if SE-0083 will be accepted.

Right, you'd need to do NSString(string) as AnyObject to explicitly bridge.

> Can we also drop the @objc from `AnyObject` protocol and leave it as an implicit protocol for classes? (Personally I’d rename `AnyObject` to `AnyReference` if Swift will introduce other reference types.)

The @objc-ness of AnyObject is more or less an implementation detail. On Darwin platforms at least, AnyObject still has the magic ability to dispatch to all @objc methods, similar to `id` in Objective-C, which vaguely defends its @objc-ness. (If we're going to rename it, my own preference would be to drop the Any and just call it `Object`, since we don't put Any in any other protocol names.)

> This change might allow the replacement of the `class` keyword from protocols with the implicit `AnyObject` protocol, which can be discussed in this thread: Should we rename "class" when referring to protocol conformance?
>
> One more thing I’d like to ask: is there any possibility of adding a new `bridge` keyword, which would allow explicit bridging to a different language type (ObjC, etc. if there are any more languages we can bridge to [C or maybe one day C++])?
>
> T `bridge` U
> T? `bridge` U?

One could write `bridge` as a method in most cases; it doesn't need to be a keyword with special syntax, since you could write `T.bridge(U.self)` (or, if we accept Remove `.self` and freely allow type references in expressions by jckarter · Pull Request #299 · apple/swift-evolution · GitHub, `T.bridge(U)`). Idiomatically, though, we generally use initializers for value-preserving conversions, so U(T) would be more consistent with the rest of the standard library.

-Joe

> The ugly NSError pattern could be rewritten and migrated to:
>
> do {
> try something()
> } catch let error {
> handle(error `bridge` NSError)
> }
>
> Is such a change complicated, what do you think?
>
> --
> Adrian Zubarev
> Sent with Airmail
>
> Am 4. Mai 2016 bei 01:50:54, Joe Groff via swift-evolution (swift-evolution@swift.org) schrieb:
>
> > Thanks everyone for the initial round of feedback. I've submitted a draft proposal:
> >
> > Remove bridging conversion behavior from dynamic casts by jckarter · Pull Request #289 · apple/swift-evolution · GitHub
> > https://github.com/jckarter/swift-evolution/blob/remove-bridging-conversion-dynamic-casts/proposals/XXXX-remove-bridging-from-dynamic-casts.md
> >
> > -Joe
> > _______________________________________________
> > swift-evolution mailing list
> > swift-evolution@swift.org
> > https://lists.swift.org/mailman/listinfo/swift-evolution
> _______________________________________________
> swift-evolution mailing list
> swift-evolution@swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution

This could be fantastic if it accompanied the addition to ErrorProtocol of an “userInfo” property (or even _userInfo, although I’d really prefer that all three of these be de-underscored) that NSError(self) would pick up on when doing the conversion. Otherwise, we’re just going to have to keep on using custom .toNSError() methods instead of the official way, so it won’t matter if the latter is “as NSError” or “NSError(foo)” or something else.

Charles

···

On May 2, 2016, at 5:53 PM, Joe Groff via swift-evolution <swift-evolution@swift.org> wrote:

I can't think of any problems that would block us from doing that today. It'd be pretty easy to write an ErrorProtocol extension that just forwards NSError's interface via bridging, and I bet that'd cover most use cases for 'as NSError':

extension ErrorProtocol {
var domain: String { return NSError(self).domain }
var code: Int { return NSError(self).code }
var userInfo: [String: AnyObject] { return NSError(self).userInfo }
/* etc. */
}

-Joe

Yes, but it might present an argument in favor of the pitch, since simplifying what “as” means would likely reduce the possibility for bugs like this to crop up.

The difficulty in working around it is also kind of telling. You’d expect to be able to just do this:

if let err = self as? NSError {
  return err
}

but of course the “as?” is going to invoke the bridging mechanism rather than what you wanted, which is just telling you what the thing is. Trying “if self is NSError” doesn’t work either, since the “is” check comes out to be always true due to the bridging mechanism. When I was playing around with this, the only way I could find to just do a plain old type-check without bridging something was to kludge around it using a mirror and unsafeBitCast:

extension ErrorType {
    public func toNSError() -> NSError {
        // NOPE!!! -- for var mirror: Mirror? = Mirror(reflecting: self); mirror != nil; mirror = mirror?.superclassMirror() {
        
        var argh: Mirror? = Mirror(reflecting: self)
        
        while let mirror = argh {
            if mirror.subjectType == NSError.self {
                return unsafeBitCast(self, NSError.self)
            }
            
            argh = mirror.superclassMirror()
        }
        
        return self as NSError
    }
}

Now, isn’t that code beautiful? Truly a work of art.

Anyway, the point is that with a simpler type-checking system, we could avoid complications like this.

As an aside, I wonder if NSError could be bridged by doing something similar to how other core Foundation types like NSString and NSArray are bridged. How would it be if:

1. ErrorProtocol has public, non-underscored methods for domain, code, and userInfo, the last of these having a default implementation that just returns an empty dictionary.

2. NSError no longer conforms to ErrorProtocol.

3. A new private error value type is introduced that does conform to ErrorProtocol, probably called something like _ObjCErrorType. This type wraps an NSError, and forwards its domain, code, and userInfo properties to it.

4. There is a subclass of NSError that wraps a native Swift error and forwards the domain, code, and userInfo properties. This already exists in _SwiftNativeNSError, except this would now forward userInfo as well.

5. Objective-C APIs that return NSError object present it as ErrorProtocol in the signature. If the NSError is a _SwiftNativeNSError, the original Swift error is unwrapped and returned. Otherwise, the NSError is wrapped in an instance of _ObjCErrorType and returned as an ErrorProtocol, and the user can access its domain, code, and userInfo through the three methods in the protocol.

6. Objective-C APIs that take NSError objects now show ErrorProtocol in their signatures as well. If an _ObjCErrorType is passed to one of these APIs, its wrapped NSError is unwrapped and passed to the API; otherwise, the error is wrapped in a _SwiftNativeNSError and passed through to the API.

7. For manually bridging from one side to the other, NSError would get an initializer in an extension that would take a Swift error and result in a bridged error. It would also get a swiftNativeError() method that would return an ErrorProtocol.

On the downside, special casing is still needed in steps 5 and 6 to distinguish between wrapped and unwrapped errors. On the upside:

a) This is very similar to the bridging that already exists for String<->NSString, Array<->NSArray, when crossing the language boundary.

b) The special casing would be mostly restricted to the special magic that the compiler inserts when crossing the boundary.

c) As NSError would no longer be an ErrorProtocol, shenanigans related to not knowing whether an ErrorProtocol was native or not, as in the above example, would never happen.

d) Since the is, as, as?, and as! operators would no longer be need to bridge NSErrors to native errors and back, the pitch here becomes viable, and these operators can be made to no longer act in often surprising and confusing ways.

e) The programmer would never have to deal with NSError objects again.

What think you?

Charles

···

On May 3, 2016, at 11:03 AM, Joe Groff <jgroff@apple.com> wrote:

On May 2, 2016, at 5:45 PM, Charles Srstka via swift-evolution <swift-evolution@swift.org> wrote:

On May 2, 2016, at 4:48 PM, Erica Sadun via swift-evolution <swift-evolution@swift.org> wrote:

On May 2, 2016, at 3:45 PM, Chris Lattner via swift-evolution <swift-evolution@swift.org> wrote:

NSError bridging can also be extracted from the runtime, and the same functionality exposed as a factory initializer on NSError:

I think that this proposal is overall really great, but what does it do to the “catch let x as NSError” pattern? What is the replacement? If the result is ugly, we may have to subset out NSError out of this pass, and handle it specifically with improvements to the error bridging story.

Grant me the serenity to accept the `NSError` I cannot change and the courage to change the bridging conversions I should. Grant me the wisdom to know the difference between a partial solution that offers a cleaner more predictable interface set now and a full solution that cannot be achieved in a reasonable timeframe.

-- E

Among the things that Billy Pilgrim could not change were the past, the present, and the future. Hopefully we have better luck, because the ErrorType to NSError bridging is currently a bit buggy.

Have a look at this code, and take a guess at what the results should be:

import Foundation

extension ErrorType {
  public func toNSError() -> NSError {
    return self as NSError
  }
}

let error = NSError(domain: "Foo", code: -1, userInfo: [NSLocalizedFailureReasonErrorKey : "Something went wrong"])

let ns = error.toNSError()

print("Type of error was \(error.dynamicType), type of ns is \(ns.dynamicType)")

print("error's user info: \(error.userInfo)")
print("ns user info: \(ns.userInfo)”)

--

The results are a bit surprising:

Type of error was NSError, type of ns is _SwiftNativeNSError
error's user info: [NSLocalizedFailureReason: Something went wrong]
ns user info: [:]

What happened was:

1. The toNSError() method showed up for our NSError, since NSError is presented to Swift as conforming to ErrorType.

2. However, due to a lack of dynamism, the code in the extension assumes that we have a Swift native error and *not* an NSError, so it goes ahead and wraps the NSError inside another NSError.

3. And since Swift doesn’t currently do anything to address the userInfo field, the userInfo that our error had gets chopped off.

Whoops.

This is a known bug, not intended behavior. The runtime rewraps NSErrors in a bridging _SwiftNativeNSError when it should just pass them through.

-Joe

"Any" is a typealias for the protocol type without any protocol requirements, which is natively spelled "protocol<>". "AnyIterator" and "AnyCollection" are both wrapper types for holding a value that conforms to the Iterator or Collection protocol; they aren't protocols themselves.

-Joe

···

On May 9, 2016, at 2:28 PM, Adrian Zubarev via swift-evolution <swift-evolution@swift.org> wrote:

Right, you'd need to do NSString(string) as AnyObject to explicitly bridge.

Okay great I'm fine with that. :)

The @objc-ness of AnyObject is more or less an implementation detail. On Darwin platforms at least, AnyObject still has the magic ability to dispatch to all @objc methods, similar to `id` in Objective-C, which vaguely defends its @objc-ness. (If we're going to rename it, my own preference would be to drop the Any and just call it `Object`, since we don't put Any in any other protocol names.)

Did I miss something again? I checked SR-0006 and it still has protocols like `Any`, `AnyIterator` or `AnyCollectionProtocol`.