[Pitch] Reducing the bridging magic in dynamic casts


(Joe Groff) #1

When we introduced Swift, we wanted to provide value types for common containers, with the safety and state isolation benefits they provide, while still working well with the reference-oriented world of Cocoa. To that end, we invested a lot of work into bridging between Swift’s value semantics containers and their analogous Cocoa container classes. This bridging consisted of several pieces in the language, the compiler, and the runtime:

Importer bridging, importing Objective-C APIs that take and return NSString, NSArray, NSDictionary and NSSet so that they take and return Swift’s analogous value types instead.

Originally, the language allowed implicit conversions in both directions between Swift value types and their analogous classes. We’ve been working on phasing the implicit conversions out—we removed the object-to-value implicit conversion in Swift 1.2, and propose to remove the other direction in SE–0072—but the conversions can still be performed by an explicit coercion string as NSString. These required-explicit as coercions don’t otherwise exist in the language, since as generally is used to force coercions that can also happen implicitly, and value-preserving conversions are more idiomatically performed by constructors in the standard library.

The runtime supports dynamic bridging casts. If you have a value that’s dynamically of a Swift value type, and try to as?, as!, or is-cast it to its bridged Cocoa class type, the cast will succeed, and the runtime will apply the bridging conversion:

// An Any that dynamically contains a value "foo": String
let x: Any = "foo"
// Cast succeeds and produces the bridged "foo": NSString
let y = x as! NSString
Since Swift first came out, Cocoa has done a great job of “Swiftification”, aided by new Objective-C features like nullability and lightweight generics that have greatly improved the up-front quality of importer-bridged APIs. This has let us deemphasize and gradually remove the special case implicit conversions from the language. I think it’s time to consider extricating them from the dynamic type system as well, making it so that as?, as!, and is casts only concern themselves with typechecks, and transitioning to using standard initializers and methods for performing bridging conversions. I’d like to propose the following changes:

Dynamic casts as?, as! and is should no longer perform bridging conversions between value types and Cocoa classes.
Coercion syntax as should no longer be used to explicitly force certain bridging conversions.
To replace this functionality, we should add initializers to bridged value types and classes that perform the value-preserving bridging operations.
The Rules of as[?]

Our original goal implementing this behavior into the dynamic casting machinery was to preserve some transitivity identities between implicit conversions and casts that users could reason about, including:

x as! T as! U === x as! U, if x as! T succeeds. Casting to a type U should succeed and give the same result for any derived cast result.
x as! T as U === x as! U. If T is coercible to U, then you should get the same result by casting to Tand coercing to U as by casting to U directly.
x as T as! U === x as! U. Likewise, coercing shouldn’t affect the result of any ensuing dynamic casts.
x as T as U === x as U.
The interaction of these identities with the bridging conversions, as well as with other type system features like implicit nonoptional-to-Optional conversion, occasionally requires surprising behavior, for instance the behavior of nil Optional values in https://github.com/apple/swift/pull/1949. These rules also inform the otherwise-inconsistent use of as to perform explicit bridging conversions, when as normally only forces implicit conversions. By simplifying the scope of dynamic casts, it becomes easier to preserve these rules without bugs and unfortunate edge cases.

The Abilities of as? Today

In discussing how to change the behavior of dynamic casts, it’s worth enumerating all the things dynamic casts are currently able to do:

Check that an object is an instance of a specific class.

class Base {}; class Derived: Base {}

func isKindOfDerived(object: Base) -> Bool {
  return object is Derived
}

isKindOfDerived(object: Derived()) // true
isKindOfDerived(object: Base()) // false
Check that an existential contains an instance of a type.

protocol P {}
extension Int: P {}
extension Double: P {}

func isKindOfInt(value: P) -> Bool {
  return value is Int
}
isKindOfInt(value: 0) // true
isKindOfInt(value: 0.0) // false
Check that a generic value is also an instance of a different type.

func is<T, U>(value: T, kindOf: U.Type) -> Bool {
  return value is U
}

is(value: Derived(), kindOf: Derived.self) // true
is(value: Derived(), kindOf: Base.self) // true
is(value: Base(), kindOf: Derived.self) // false
is(value: 0, kindOf: Int.self) // true
Check whether the type of a value conforms to a protocol, and wrap it in an existential if so:

protocol Fooable { func foo() }

func fooIfYouCanFoo<T>(value: T) {
  if let fooable = value as? Fooable {
    return fooable.foo()
  }
}

extension Int: Fooable { func foo() { print("foo!") } }

fooIfYouCanFoo(value: 1) // Prints "foo!"
fooIfYouCanFoo(value: "bar") // No-op
Check whether a value is _ObjectiveCBridgeable to a class, or conversely, that an object is _ObjectiveCBridgeable to a value type, and perform the bridging conversion if so:

func getAsString<T>(value: T) -> String? {
  return value as? String
}
func getAsNSString<T>(value: T) -> NSString {
  return value as? NSString
}

getAsString(value: "string") // produces "string": String
getAsNSString(value: "string") // produces "string": NSString

let ns = NSString("nsstring")
getAsString(value: ns) // produces "nsstring": String
getAsNSString(value: ns) // produces "nsstring": NSString
Check whether a value conforms to ErrorProtocol, and bridge it to NSError if so:

enum CommandmentError { case Killed, Stole, GravenImage, CovetedOx }

func getAsNSError<T>(value: T) -> NSError? {
  return value as? NSError
}

getAsNSError(CommandmentError.GravenImage) // produces bridged NSError
This is what enables the use of catch let x as NSError pattern matching to catch Swift errors as NSErrorobjects today.

Check whether an NSError object has a domain and code matching a type conforming to _ObjectiveCBridgeableErrorProtocol, and extracting the Swift error if so:

func getAsNSCocoaError(error: NSError) -> NSCocoaError? {
  return error as? NSCocoaError
}

// Returns NSCocoaError.fileNoSuchFileError
getAsNSCocoaError(error: NSError(domain: NSCocoaErrorDomain,
                                 code: NSFileNoSuchFileError,
                                 userInfo: []))
Drill through Optionals. If an Optional contains some value, it is extracted, and the cast is attempted on the contained value; the cast fails if the source value is none and the result type is not optional:

var x: String? = "optional string"
getAsNSString(value: x) // produces "optional string": NSString
x = nil
getAsNSString(value: x) // fails
If the result type is also Optional, a successful cast is wrapped as some value of the result Optional type. nil source values succeed and become nil values of the result Optional type:

func getAsOptionalNSString<T>(value: T) -> NSString?? {
  return value as? NSString?
}

var x: String? = "optional string"
getAsOptionalNSString(value: x) // produces "optional string": NSString?
x = nil
getAsOptionalNSString(value: x) // produces nil: NSString?
Perform covariant container element checks and conversions for Array, Dictionary, and Set.

There are roughly three categories of functionality intertwined here. (1) through (4) are straightforward dynamic type checks. ((4) is arguably a bit different from (1) through (3) in that protocol conformances are extrinsic to a type, whereas (1) through (3) check the intrinsic type only of the participating value.) (5) through (7) involve Cocoa bridging conversions. (8) and (9) reflect additional implicit conversions supported by the language at compile time into the runtime type system. Optional and covariant container conversions have also been criticized as occasionally surprising and inconsistent with the rest of the language. If we curtail these conversions in the compiler, we would also want to consider removing their special dynamic cast behavior too. For the purposes of this discussion, I’d like to focus on removing the bridging behavior, cases (5) through (7).

Replacements for Dynamic Cast Behavior

If we remove bridging behavior from dynamic casts, we still need to provide API for performing those conversions. I’d recommend introducing unlabeled initializers for these conversions, matching the conventions for other value-preserving conversions in the standard library:

extension String {
  init(_ ns: NSString)
}
extension NSString {
  init(_ value: String)
}
extension Array where Element: _ObjectiveCBridgeable {
  init(_ ns: NSArray<Element._ObjectiveCType>)
}
extension NSArray {
  init<BridgedElement: _ObjectiveCBridgeable
       where BridgedElement._ObjectiveCType == Element>(
    _ value: Array<BridgedElement>)
}
/* etc. */

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

extension NSError {
  init(_ swiftError: ErrorType)
}

It’s also useful to be able to conditionally bridge to a value type from AnyObject, especially when working with heterogeneous property lists from Cocoa. This could be handled using failable initializers:

extension String {
  init?(bridging: AnyObject)
}
extension Array {
  init?(bridging: AnyObject)
}
extension Dictionary {
  init?(bridging: AnyObject)
}
/* etc. */

(This can probably be factored into a protocol extension on _ObjectiveCBridgeable.) Similarly, one could add a failable initializer to ErrorProtocol for bridging NSErrors back to Swift error values:

extension ErrorType {
  init?(bridging: NSError)
}

If you want to get really reductionist, you can ask whether as? and related operations really need special syntax at all; they could in theory be fully expressed as global functions, or as extension methods on Any/AnyObject if we allowed such things. Regardless, I think we want type-checking dynamic casts to be clearly a different operation from these bridging conversions. This will lead to a cleaner, easier-to-understand model with less special-case magic behavior.

-Joe


(Chris Lattner) #2

I’d like to propose the following changes:

Dynamic casts as?, as! and is should no longer perform bridging conversions between value types and Cocoa classes.
Coercion syntax as should no longer be used to explicitly force certain bridging conversions.
To replace this functionality, we should add initializers to bridged value types and classes that perform the value-preserving bridging operations.

+1. I think that this will lead to a much cleaner and more predictable set of rules. It will probably also define away a ton of bugs in the compiler and runtime.

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.

-Chris

···

On Apr 29, 2016, at 3:00 PM, Joe Groff via swift-evolution <swift-evolution@swift.org> wrote:


(Charles Srstka) #3

Here’s another argument in favor of the pitch:

Take an array:

let array = ["Foo", "Bar”]

We can convert this to an NSArray via the bridge:

let nsArray = array as NSArray

We can also convert this to a CFArray:

let cfArray = array as CFArray

Now, let’s convert them back.

let nsUntypedArray = nsArray as Array
let cfUntypedArray = cfArray as Array

This works, but both arrays are now Array<AnyObject>, which probably isn’t what we want. Since Swift arrays care about type, and NS/CFArrays generally don’t, we’ll want to do a check when converting them back:

let nsToNativeArray = nsArray as? Array<String>
let cfToNativeArray = cfArray as? Array<String>

Checking the value of the first one there, we get a nice Optional(["Foo", "Bar”]), as expected. However, checking the second one reveals that it now contains *nil!* Worse, the bug won’t be discovered until runtime, and may be hard to track down, since the code above *looks* fine.

Adding an intermediate cast to NSArray, of course, makes it work fine:

let cfToNativeArray = cfArray as NSArray? as? Array<String> // Optional(["Foo", "Bar"])

This may be a bug, maybe even a known one. However, if this had been done via initializers on Array rather than via bridging magic, the compiler would have thrown a type error when we tried to pass a CFArray to Array’s initializer if Array didn’t have an initializer that took a CFArray. The bridge, however, just cheerfully returns nil at runtime, leaving you with no idea something’s wrong until it all blows up mysteriously at runtime.

So basically, I guess I’m +1 on the pitch.

Charles

···

On Apr 29, 2016, at 5:00 PM, Joe Groff via swift-evolution <swift-evolution@swift.org> wrote:

When we introduced Swift, we wanted to provide value types for common containers, with the safety and state isolation benefits they provide, while still working well with the reference-oriented world of Cocoa. To that end, we invested a lot of work into bridging between Swift’s value semantics containers and their analogous Cocoa container classes. This bridging consisted of several pieces in the language, the compiler, and the runtime:

Importer bridging, importing Objective-C APIs that take and return NSString, NSArray, NSDictionary and NSSet so that they take and return Swift’s analogous value types instead.

Originally, the language allowed implicit conversions in both directions between Swift value types and their analogous classes. We’ve been working on phasing the implicit conversions out—we removed the object-to-value implicit conversion in Swift 1.2, and propose to remove the other direction in SE–0072—but the conversions can still be performed by an explicit coercion string as NSString. These required-explicit as coercions don’t otherwise exist in the language, since as generally is used to force coercions that can also happen implicitly, and value-preserving conversions are more idiomatically performed by constructors in the standard library.

The runtime supports dynamic bridging casts. If you have a value that’s dynamically of a Swift value type, and try to as?, as!, or is-cast it to its bridged Cocoa class type, the cast will succeed, and the runtime will apply the bridging conversion:

// An Any that dynamically contains a value "foo": String
let x: Any = "foo"
// Cast succeeds and produces the bridged "foo": NSString
let y = x as! NSString
Since Swift first came out, Cocoa has done a great job of “Swiftification”, aided by new Objective-C features like nullability and lightweight generics that have greatly improved the up-front quality of importer-bridged APIs. This has let us deemphasize and gradually remove the special case implicit conversions from the language. I think it’s time to consider extricating them from the dynamic type system as well, making it so that as?, as!, and is casts only concern themselves with typechecks, and transitioning to using standard initializers and methods for performing bridging conversions. I’d like to propose the following changes:

Dynamic casts as?, as! and is should no longer perform bridging conversions between value types and Cocoa classes.
Coercion syntax as should no longer be used to explicitly force certain bridging conversions.
To replace this functionality, we should add initializers to bridged value types and classes that perform the value-preserving bridging operations.
The Rules of as[?]

Our original goal implementing this behavior into the dynamic casting machinery was to preserve some transitivity identities between implicit conversions and casts that users could reason about, including:

x as! T as! U === x as! U, if x as! T succeeds. Casting to a type U should succeed and give the same result for any derived cast result.
x as! T as U === x as! U. If T is coercible to U, then you should get the same result by casting to Tand coercing to U as by casting to U directly.
x as T as! U === x as! U. Likewise, coercing shouldn’t affect the result of any ensuing dynamic casts.
x as T as U === x as U.
The interaction of these identities with the bridging conversions, as well as with other type system features like implicit nonoptional-to-Optional conversion, occasionally requires surprising behavior, for instance the behavior of nil Optional values in https://github.com/apple/swift/pull/1949. These rules also inform the otherwise-inconsistent use of as to perform explicit bridging conversions, when as normally only forces implicit conversions. By simplifying the scope of dynamic casts, it becomes easier to preserve these rules without bugs and unfortunate edge cases.

The Abilities of as? Today

In discussing how to change the behavior of dynamic casts, it’s worth enumerating all the things dynamic casts are currently able to do:

Check that an object is an instance of a specific class.

class Base {}; class Derived: Base {}

func isKindOfDerived(object: Base) -> Bool {
  return object is Derived
}

isKindOfDerived(object: Derived()) // true
isKindOfDerived(object: Base()) // false
Check that an existential contains an instance of a type.

protocol P {}
extension Int: P {}
extension Double: P {}

func isKindOfInt(value: P) -> Bool {
  return value is Int
}
isKindOfInt(value: 0) // true
isKindOfInt(value: 0.0) // false
Check that a generic value is also an instance of a different type.

func is<T, U>(value: T, kindOf: U.Type) -> Bool {
  return value is U
}

is(value: Derived(), kindOf: Derived.self) // true
is(value: Derived(), kindOf: Base.self) // true
is(value: Base(), kindOf: Derived.self) // false
is(value: 0, kindOf: Int.self) // true
Check whether the type of a value conforms to a protocol, and wrap it in an existential if so:

protocol Fooable { func foo() }

func fooIfYouCanFoo<T>(value: T) {
  if let fooable = value as? Fooable {
    return fooable.foo()
  }
}

extension Int: Fooable { func foo() { print("foo!") } }

fooIfYouCanFoo(value: 1) // Prints "foo!"
fooIfYouCanFoo(value: "bar") // No-op
Check whether a value is _ObjectiveCBridgeable to a class, or conversely, that an object is _ObjectiveCBridgeable to a value type, and perform the bridging conversion if so:

func getAsString<T>(value: T) -> String? {
  return value as? String
}
func getAsNSString<T>(value: T) -> NSString {
  return value as? NSString
}

getAsString(value: "string") // produces "string": String
getAsNSString(value: "string") // produces "string": NSString

let ns = NSString("nsstring")
getAsString(value: ns) // produces "nsstring": String
getAsNSString(value: ns) // produces "nsstring": NSString
Check whether a value conforms to ErrorProtocol, and bridge it to NSError if so:

enum CommandmentError { case Killed, Stole, GravenImage, CovetedOx }

func getAsNSError<T>(value: T) -> NSError? {
  return value as? NSError
}

getAsNSError(CommandmentError.GravenImage) // produces bridged NSError
This is what enables the use of catch let x as NSError pattern matching to catch Swift errors as NSErrorobjects today.

Check whether an NSError object has a domain and code matching a type conforming to _ObjectiveCBridgeableErrorProtocol, and extracting the Swift error if so:

func getAsNSCocoaError(error: NSError) -> NSCocoaError? {
  return error as? NSCocoaError
}

// Returns NSCocoaError.fileNoSuchFileError
getAsNSCocoaError(error: NSError(domain: NSCocoaErrorDomain,
                                 code: NSFileNoSuchFileError,
                                 userInfo: []))
Drill through Optionals. If an Optional contains some value, it is extracted, and the cast is attempted on the contained value; the cast fails if the source value is none and the result type is not optional:

var x: String? = "optional string"
getAsNSString(value: x) // produces "optional string": NSString
x = nil
getAsNSString(value: x) // fails
If the result type is also Optional, a successful cast is wrapped as some value of the result Optional type. nil source values succeed and become nil values of the result Optional type:

func getAsOptionalNSString<T>(value: T) -> NSString?? {
  return value as? NSString?
}

var x: String? = "optional string"
getAsOptionalNSString(value: x) // produces "optional string": NSString?
x = nil
getAsOptionalNSString(value: x) // produces nil: NSString?
Perform covariant container element checks and conversions for Array, Dictionary, and Set.

There are roughly three categories of functionality intertwined here. (1) through (4) are straightforward dynamic type checks. ((4) is arguably a bit different from (1) through (3) in that protocol conformances are extrinsic to a type, whereas (1) through (3) check the intrinsic type only of the participating value.) (5) through (7) involve Cocoa bridging conversions. (8) and (9) reflect additional implicit conversions supported by the language at compile time into the runtime type system. Optional and covariant container conversions have also been criticized as occasionally surprising and inconsistent with the rest of the language. If we curtail these conversions in the compiler, we would also want to consider removing their special dynamic cast behavior too. For the purposes of this discussion, I’d like to focus on removing the bridging behavior, cases (5) through (7).

Replacements for Dynamic Cast Behavior

If we remove bridging behavior from dynamic casts, we still need to provide API for performing those conversions. I’d recommend introducing unlabeled initializers for these conversions, matching the conventions for other value-preserving conversions in the standard library:

extension String {
  init(_ ns: NSString)
}
extension NSString {
  init(_ value: String)
}
extension Array where Element: _ObjectiveCBridgeable {
  init(_ ns: NSArray<Element._ObjectiveCType>)
}
extension NSArray {
  init<BridgedElement: _ObjectiveCBridgeable
       where BridgedElement._ObjectiveCType == Element>(
    _ value: Array<BridgedElement>)
}
/* etc. */

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

extension NSError {
  init(_ swiftError: ErrorType)
}

It’s also useful to be able to conditionally bridge to a value type from AnyObject, especially when working with heterogeneous property lists from Cocoa. This could be handled using failable initializers:

extension String {
  init?(bridging: AnyObject)
}
extension Array {
  init?(bridging: AnyObject)
}
extension Dictionary {
  init?(bridging: AnyObject)
}
/* etc. */

(This can probably be factored into a protocol extension on _ObjectiveCBridgeable.) Similarly, one could add a failable initializer to ErrorProtocol for bridging NSErrors back to Swift error values:

extension ErrorType {
  init?(bridging: NSError)
}

If you want to get really reductionist, you can ask whether as? and related operations really need special syntax at all; they could in theory be fully expressed as global functions, or as extension methods on Any/AnyObject if we allowed such things. Regardless, I think we want type-checking dynamic casts to be clearly a different operation from these bridging conversions. This will lead to a cleaner, easier-to-understand model with less special-case magic behavior.

-Joe
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Joe Groff) #4

Thanks everyone for the initial round of feedback. I've submitted a draft proposal:

https://github.com/apple/swift-evolution/pull/289
https://github.com/jckarter/swift-evolution/blob/remove-bridging-conversion-dynamic-casts/proposals/XXXX-remove-bridging-from-dynamic-casts.md

-Joe


(Joe Groff) #5

If we remove the bridging magic and do nothing else, then the best you can do to catch any error and handle it as an NSError becomes a two-liner:

  do {
    try something()
  } catch let error {
    let nsError = NSError(error)
    handle(nsError)
  }

That's definitely uglier, but just to play devil's advocate, this does have the benefit of making it much clearer that the 'catch' is exhaustive. 'as' patterns are usually refutable, and it's a weird exception that 'error as NSError' is an exhaustive match (and we do have bugs where we get this wrong, especially inside closures when we haven't fully propagated contextual types yet).

-Joe

···

On May 2, 2016, at 2:45 PM, Chris Lattner <clattner@apple.com> wrote:

On Apr 29, 2016, at 3:00 PM, Joe Groff via swift-evolution <swift-evolution@swift.org> wrote:

I’d like to propose the following changes:

  • Dynamic casts as?, as! and is should no longer perform bridging conversions between value types and Cocoa classes.
  • Coercion syntax as should no longer be used to explicitly force certain bridging conversions.
  • To replace this functionality, we should add initializers to bridged value types and classes that perform the value-preserving bridging operations.

+1. I think that this will lead to a much cleaner and more predictable set of rules. It will probably also define away a ton of bugs in the compiler and runtime.

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.


(Erica Sadun) #6

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

···

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.


(Adrian Zubarev) #7

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. 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.)

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?

Wouldn’t this move the bridging mechanism to its own area?

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:

https://github.com/apple/swift-evolution/pull/289
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


(Chéyo Jiménez) #8

Hi Joe,

Would I still be able to cast an AnyObject to a String or Array etc?

I am thinking about working with JSON files and using the Apple JSON Parser.

Thanks

···

On May 3, 2016, at 4:50 PM, Joe Groff via swift-evolution <swift-evolution@swift.org> wrote:

Thanks everyone for the initial round of feedback. I've submitted a draft proposal:

https://github.com/apple/swift-evolution/pull/289
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


(Chris Lattner) #9

I’m not sure what you’re saying.

-Chris

···

On May 2, 2016, at 2:48 PM, Erica Sadun <erica@ericasadun.com> wrote:

On May 2, 2016, at 3:45 PM, Chris Lattner via swift-evolution <swift-evolution@swift.org <mailto: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.


(Chris Lattner) #10

Right, that’s what I expected. The problem here is that this is a super common pattern. The solution to this is pretty straight-forward though: we should just make the most commonly used members of NSError be a protocol extension on ErrorProtocol (née ErrorType). This would eliminate the most common reasons that people need this pattern. Do you know of any issues with this, or are we merely a proposal away from making this happen?

-Chris

···

On May 2, 2016, at 3:43 PM, Joe Groff <jgroff@apple.com> wrote:

On May 2, 2016, at 2:45 PM, Chris Lattner <clattner@apple.com> wrote:

On Apr 29, 2016, at 3:00 PM, Joe Groff via swift-evolution <swift-evolution@swift.org> wrote:

I’d like to propose the following changes:

  • Dynamic casts as?, as! and is should no longer perform bridging conversions between value types and Cocoa classes.
  • Coercion syntax as should no longer be used to explicitly force certain bridging conversions.
  • To replace this functionality, we should add initializers to bridged value types and classes that perform the value-preserving bridging operations.

+1. I think that this will lead to a much cleaner and more predictable set of rules. It will probably also define away a ton of bugs in the compiler and runtime.

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.

If we remove the bridging magic and do nothing else, then the best you can do to catch any error and handle it as an NSError becomes a two-liner:

  do {
    try something()
  } catch let error {
    let nsError = NSError(error)
    handle(nsError)
  }

That's definitely uglier, but just to play devil's advocate, this does have the benefit of making it much clearer that the 'catch' is exhaustive. 'as' patterns are usually refutable, and it's a weird exception that 'error as NSError' is an exhaustive match (and we do have bugs where we get this wrong, especially inside closures when we haven't fully propagated contextual types yet).


(Erica Sadun) #11

It's a message of support, riffing on the famous Serenity Prayer (https://en.wikipedia.org/wiki/Serenity_Prayer), agreeing with you and saying that partial implementation of a good idea (limiting bridging conversions between value types and a subset of Cocoa classes) is to be preferred to a full implementation of an idea that requires extraordinary effort for one special case (NSError).

-- E

···

On May 2, 2016, at 4:15 PM, Chris Lattner <clattner@apple.com> wrote:

On May 2, 2016, at 2:48 PM, Erica Sadun <erica@ericasadun.com <mailto:erica@ericasadun.com>> wrote:

On May 2, 2016, at 3:45 PM, Chris Lattner via swift-evolution <swift-evolution@swift.org <mailto: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.

I’m not sure what you’re saying.

-Chris


(Charles Srstka) #12

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)”)

···

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 <mailto: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

--

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.

Charles


(Joe Groff) #13

Hi Joe,

Would I still be able to cast an AnyObject to a String or Array etc?

You would do so via constructors rather than using `as?`, something like String(bridging: object).

-Joe

···

On May 6, 2016, at 7:24 PM, Jose Cheyo Jimenez <cheyo@masters3d.com> wrote:

I am thinking about working with JSON files and using the Apple JSON Parser.

Thanks

On May 3, 2016, at 4:50 PM, Joe Groff via swift-evolution <swift-evolution@swift.org> wrote:

Thanks everyone for the initial round of feedback. I've submitted a draft proposal:

https://github.com/apple/swift-evolution/pull/289
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


(Joe Groff) #14

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 https://github.com/apple/swift-evolution/pull/299, `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

···

On May 6, 2016, at 12:04 AM, Adrian Zubarev via swift-evolution <swift-evolution@swift.org> wrote:

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:

https://github.com/apple/swift-evolution/pull/289
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


(Charles Srstka) #15

I’ve made a pitch, "Consistent bridging for NSErrors at the language boundary”, which I believe would not only eliminate the need for “as” to contain bridging magic, but is also much less ugly than either the current pattern or the example above.

https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160502/016618.html

Charles

···

On May 6, 2016, at 2:04 AM, Adrian Zubarev via swift-evolution <swift-evolution@swift.org> wrote:

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?


(Chris Lattner) #16

Hah, ok, thanks. I admit that I clicked the link and was disappointed that it wasn’t a firefly reference ;-)

-Chris

···

On May 2, 2016, at 3:21 PM, Erica Sadun <erica@ericasadun.com> wrote:

On May 2, 2016, at 4:15 PM, Chris Lattner <clattner@apple.com <mailto:clattner@apple.com>> wrote:

On May 2, 2016, at 2:48 PM, Erica Sadun <erica@ericasadun.com <mailto:erica@ericasadun.com>> wrote:

On May 2, 2016, at 3:45 PM, Chris Lattner via swift-evolution <swift-evolution@swift.org <mailto: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.

I’m not sure what you’re saying.

-Chris

It's a message of support, riffing on the famous Serenity Prayer (https://en.wikipedia.org/wiki/Serenity_Prayer), agreeing with you and saying that partial implementation of a good idea (limiting bridging conversions between value types and a subset of Cocoa classes) is to be preferred to a full implementation of an idea that requires extraordinary effort for one special case (NSError).


(Joe Groff) #17

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

···

On May 2, 2016, at 3:50 PM, Chris Lattner <clattner@apple.com> wrote:

On May 2, 2016, at 3:43 PM, Joe Groff <jgroff@apple.com> wrote:

On May 2, 2016, at 2:45 PM, Chris Lattner <clattner@apple.com> wrote:

On Apr 29, 2016, at 3:00 PM, Joe Groff via swift-evolution <swift-evolution@swift.org> wrote:

I’d like to propose the following changes:

  • Dynamic casts as?, as! and is should no longer perform bridging conversions between value types and Cocoa classes.
  • Coercion syntax as should no longer be used to explicitly force certain bridging conversions.
  • To replace this functionality, we should add initializers to bridged value types and classes that perform the value-preserving bridging operations.

+1. I think that this will lead to a much cleaner and more predictable set of rules. It will probably also define away a ton of bugs in the compiler and runtime.

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.

If we remove the bridging magic and do nothing else, then the best you can do to catch any error and handle it as an NSError becomes a two-liner:

  do {
    try something()
  } catch let error {
    let nsError = NSError(error)
    handle(nsError)
  }

That's definitely uglier, but just to play devil's advocate, this does have the benefit of making it much clearer that the 'catch' is exhaustive. 'as' patterns are usually refutable, and it's a weird exception that 'error as NSError' is an exhaustive match (and we do have bugs where we get this wrong, especially inside closures when we haven't fully propagated contextual types yet).

Right, that’s what I expected. The problem here is that this is a super common pattern. The solution to this is pretty straight-forward though: we should just make the most commonly used members of NSError be a protocol extension on ErrorProtocol (née ErrorType). This would eliminate the most common reasons that people need this pattern. Do you know of any issues with this, or are we merely a proposal away from making this happen?


(TJ Usiyan) #18

+1 to this. `as` is a hairy beast.

···

On Mon, May 2, 2016 at 8: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.

Charles

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(David Hart) #19

I got the reference, made me laugh :slight_smile:

···

On 03 May 2016, at 00:21, Erica Sadun via swift-evolution <swift-evolution@swift.org> wrote:

On May 2, 2016, at 4:15 PM, Chris Lattner <clattner@apple.com <mailto:clattner@apple.com>> wrote:

On May 2, 2016, at 2:48 PM, Erica Sadun <erica@ericasadun.com <mailto:erica@ericasadun.com>> wrote:

On May 2, 2016, at 3:45 PM, Chris Lattner via swift-evolution <swift-evolution@swift.org <mailto: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.

I’m not sure what you’re saying.

-Chris

It's a message of support, riffing on the famous Serenity Prayer (https://en.wikipedia.org/wiki/Serenity_Prayer), agreeing with you and saying that partial implementation of a good idea (limiting bridging conversions between value types and a subset of Cocoa classes) is to be preferred to a full implementation of an idea that requires extraordinary effort for one special case (NSError).

-- E

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Joe Groff) #20

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

-Joe

···

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.