[Proposal] [Discussion] Explicit Toll-Free Conversions


(Robert Widmann) #1

Good evening all,

Attached is the proposal text that came out of a short discussion on Twitter <https://twitter.com/dgregor79/status/844760063663259651> about squashing one more case of implicit conversions in the language. This proposal seeks to remove the implicit nature of toll-free bridging in favor of an explicit as-cast. The proposal can also be read as a gist <https://gist.github.com/CodaFi/fc6fa3aac64c4b1deffd1434a37e295f>, and the most up-to-date copy may be found on swift-evolution itself <https://github.com/apple/swift-evolution/pull/655>.

Cheers,

~Robert Widmann

Explicit Toll-Free Conversions

Proposal: SE-NNNN <https://gist.github.com/CodaFi/NNNN-filename.md>
Authors: Robert Widmann <https://github.com/codafi>
Review Manager: TBD
Status: Awaiting review
<https://gist.github.com/CodaFi/fc6fa3aac64c4b1deffd1434a37e295f#introduction>Introduction

To continue removing further implicit conversions from the language, implicit toll-free bridging should be deprecated in Swift 3 and removed in Swift 4.0.

<https://gist.github.com/CodaFi/fc6fa3aac64c4b1deffd1434a37e295f#motivation>Motivation

Swift currently allows implicit conversions between CF types and their toll-free-bridged Objective-C counterparts. This was meant to maintain parity with the feature in Objective-C. However, the presence of implicit conversions is no longer in-line with the expectations of Swift's type system. In addition, advancements in the Clang importer have made interaction with imported CF and Foundation types nearly seamless - obviating the need for this feature in general. For the sake of safety, and in the spirit of continuing the work of SE-0072 <https://github.com/apple/swift-evolution/blob/master/proposals/0072-eliminate-implicit-bridging-conversions.md> and facilitating SE-0083 <https://github.com/apple/swift-evolution/blob/master/proposals/0083-remove-bridging-from-dynamic-casts.md>, we propose the deprecation and removal of this implicit conversion in favor of an explicit as cast.

<https://gist.github.com/CodaFi/fc6fa3aac64c4b1deffd1434a37e295f#proposed-solution>Proposed solution

The implicit conversion between toll-free-bridged types shall be removed. In its place, the user should use explicit ascasts to convert bewteen bridged CF and Objective-C types.

<https://gist.github.com/CodaFi/fc6fa3aac64c4b1deffd1434a37e295f#detailed-design>Detailed Design

When in Swift 3 mode, the compiler shall warn when it encounters the need to perform an implicit conversion. At the warning site, it can offer the explicit as cast to the user as a fix-it.

func nsToCF(_ ns: NSString) -> CFString {
  return ns // warning: 'NSString' is not implicitly convertible to 'CFString'; did you mean to use 'as' to explicitly convert?
}

func cfToNS(_ cf: CFString) -> NSString {
  return cf // warning: 'CFString' is not implicitly convertible to 'NSString'; did you mean to use 'as' to explicitly convert?
}
When in Swift 4 mode, the compiler shall no longer perform the implicit conversion and relying on this behavior will be a hard error.

func nsToCF(_ ns: NSString) -> CFString {
  return ns // error: cannot convert return expression of type 'NSString' to return type 'CFString'
}

func cfToNS(_ cf: CFString) -> NSString {
  return cf // error: cannot convert return expression of type 'CFString' to return type 'NSString'
}
<https://gist.github.com/CodaFi/fc6fa3aac64c4b1deffd1434a37e295f#source-compatibility>Source compatibility

This introduces a source-breaking change that affects consumers of APIs that are explicitly taking or vending instances of bridged CF or Objective-C types. However, recent importer advances have made APIs like this far less numerous. In practice, we expect the source breakage to eludicate places where implicit conversions were accidentally being performed behind the user's back.

<https://gist.github.com/CodaFi/fc6fa3aac64c4b1deffd1434a37e295f#effect-on-abi-stability>Effect on ABI stability

This proposal has no effect on the Swift ABI.

<https://gist.github.com/CodaFi/fc6fa3aac64c4b1deffd1434a37e295f#effect-on-api-resilience>Effect on API resilience

This proposal has no effect on API resilience.

<https://gist.github.com/CodaFi/fc6fa3aac64c4b1deffd1434a37e295f#alternatives-considered>Alternatives considered

Do nothing and continue to accept this implicit conversion.


(Slava Pestov) #2

I thought CF APIs were still imported as taking and returning CFArray, etc, without automatic bridging to Swift collections. Is this no longer the case?

Slava

···

On Mar 22, 2017, at 10:35 PM, Robert Widmann via swift-evolution <swift-evolution@swift.org> wrote:

This introduces a source-breaking change that affects consumers of APIs that are explicitly taking or vending instances of bridged CF or Objective-C types. However, recent importer advances have made APIs like this far less numerous. In practice, we expect the source breakage to eludicate places where implicit conversions were accidentally being performed behind the user's back.


(Slava Pestov) #3

One alternative would be to import CFArray as a typealias for NSArray, etc, erasing the distinction between the two types completely. I did suggest this to Jordan at one point and he pointed out some problems, but I don’t remember them now. Hopefully Jordan can chime in.

Slava

···

On Mar 22, 2017, at 10:35 PM, Robert Widmann via swift-evolution <swift-evolution@swift.org> wrote:

Alternatives considered

Do nothing and continue to accept this implicit conversion.


(Robert Widmann) #4

Correct. This section is referring more to the NS-side of things.

···

On Mar 23, 2017, at 1:38 AM, Slava Pestov <spestov@apple.com> wrote:

On Mar 22, 2017, at 10:35 PM, Robert Widmann via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

This introduces a source-breaking change that affects consumers of APIs that are explicitly taking or vending instances of bridged CF or Objective-C types. However, recent importer advances have made APIs like this far less numerous. In practice, we expect the source breakage to eludicate places where implicit conversions were accidentally being performed behind the user's back.

I thought CF APIs were still imported as taking and returning CFArray, etc, without automatic bridging to Swift collections. Is this no longer the case?

Slava


(Charlie Monroe) #5

Alternatives considered

Do nothing and continue to accept this implicit conversion.

One alternative would be to import CFArray as a typealias for NSArray, etc, erasing the distinction between the two types completely. I did suggest this to Jordan at one point and he pointed out some problems, but I don’t remember them now. Hopefully Jordan can chime in.

With CFArray you can create a non-retaining array without creating a weak-wrapper for each object. While it's a less likely scenario, I've used in the past (until I created a weak array to be used in Swift).

···

On Mar 23, 2017, at 6:41 AM, Slava Pestov via swift-evolution <swift-evolution@swift.org> wrote:

On Mar 22, 2017, at 10:35 PM, Robert Widmann via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

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


(Joe Groff) #6

I'd prefer this solution as well, especially since toll-free bridged CF types are nearly indistinguishable from their NS counterparts at runtime, so trying to maintain the distinction for dynamic casts or reflection has historically been problematic. Part of the problem is that, as Charlie noted, CFArray/Dictionary/Set can take an arbitrary set of retain/release callbacks, in which case the resulting container isn't fully NSArray-compatible. I'm not sure that happens often enough with CF containers in the SDKs that accounting for that case is worth the burden of separating the types.

-Joe

···

On Mar 22, 2017, at 10:41 PM, Slava Pestov via swift-evolution <swift-evolution@swift.org> wrote:

On Mar 22, 2017, at 10:35 PM, Robert Widmann via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Alternatives considered

Do nothing and continue to accept this implicit conversion.

One alternative would be to import CFArray as a typealias for NSArray, etc, erasing the distinction between the two types completely. I did suggest this to Jordan at one point and he pointed out some problems, but I don’t remember them now. Hopefully Jordan can chime in.


(Charles Srstka) #7

I didn’t even know the compiler still supported these implicit conversions. So, +1, I guess.

Charles

···

On Mar 23, 2017, at 12:35 AM, Robert Widmann via swift-evolution <swift-evolution@swift.org> wrote:

Good evening all,

Attached is the proposal text that came out of a short discussion on Twitter <https://twitter.com/dgregor79/status/844760063663259651> about squashing one more case of implicit conversions in the language. This proposal seeks to remove the implicit nature of toll-free bridging in favor of an explicit as-cast. The proposal can also be read as a gist <https://gist.github.com/CodaFi/fc6fa3aac64c4b1deffd1434a37e295f>, and the most up-to-date copy may be found on swift-evolution itself <https://github.com/apple/swift-evolution/pull/655>.

Cheers,

~Robert Widmann

Explicit Toll-Free Conversions

Proposal: SE-NNNN <https://gist.github.com/CodaFi/NNNN-filename.md>
Authors: Robert Widmann <https://github.com/codafi>
Review Manager: TBD
Status: Awaiting review
<https://gist.github.com/CodaFi/fc6fa3aac64c4b1deffd1434a37e295f#introduction>Introduction

To continue removing further implicit conversions from the language, implicit toll-free bridging should be deprecated in Swift 3 and removed in Swift 4.0.

<https://gist.github.com/CodaFi/fc6fa3aac64c4b1deffd1434a37e295f#motivation>Motivation

Swift currently allows implicit conversions between CF types and their toll-free-bridged Objective-C counterparts. This was meant to maintain parity with the feature in Objective-C. However, the presence of implicit conversions is no longer in-line with the expectations of Swift's type system. In addition, advancements in the Clang importer have made interaction with imported CF and Foundation types nearly seamless - obviating the need for this feature in general. For the sake of safety, and in the spirit of continuing the work of SE-0072 <https://github.com/apple/swift-evolution/blob/master/proposals/0072-eliminate-implicit-bridging-conversions.md> and facilitating SE-0083 <https://github.com/apple/swift-evolution/blob/master/proposals/0083-remove-bridging-from-dynamic-casts.md>, we propose the deprecation and removal of this implicit conversion in favor of an explicit as cast.

<https://gist.github.com/CodaFi/fc6fa3aac64c4b1deffd1434a37e295f#proposed-solution>Proposed solution

The implicit conversion between toll-free-bridged types shall be removed. In its place, the user should use explicit ascasts to convert bewteen bridged CF and Objective-C types.

<https://gist.github.com/CodaFi/fc6fa3aac64c4b1deffd1434a37e295f#detailed-design>Detailed Design

When in Swift 3 mode, the compiler shall warn when it encounters the need to perform an implicit conversion. At the warning site, it can offer the explicit as cast to the user as a fix-it.

func nsToCF(_ ns: NSString) -> CFString {
  return ns // warning: 'NSString' is not implicitly convertible to 'CFString'; did you mean to use 'as' to explicitly convert?
}

func cfToNS(_ cf: CFString) -> NSString {
  return cf // warning: 'CFString' is not implicitly convertible to 'NSString'; did you mean to use 'as' to explicitly convert?
}
When in Swift 4 mode, the compiler shall no longer perform the implicit conversion and relying on this behavior will be a hard error.

func nsToCF(_ ns: NSString) -> CFString {
  return ns // error: cannot convert return expression of type 'NSString' to return type 'CFString'
}

func cfToNS(_ cf: CFString) -> NSString {
  return cf // error: cannot convert return expression of type 'CFString' to return type 'NSString'
}
<https://gist.github.com/CodaFi/fc6fa3aac64c4b1deffd1434a37e295f#source-compatibility>Source compatibility

This introduces a source-breaking change that affects consumers of APIs that are explicitly taking or vending instances of bridged CF or Objective-C types. However, recent importer advances have made APIs like this far less numerous. In practice, we expect the source breakage to eludicate places where implicit conversions were accidentally being performed behind the user's back.

<https://gist.github.com/CodaFi/fc6fa3aac64c4b1deffd1434a37e295f#effect-on-abi-stability>Effect on ABI stability

This proposal has no effect on the Swift ABI.

<https://gist.github.com/CodaFi/fc6fa3aac64c4b1deffd1434a37e295f#effect-on-api-resilience>Effect on API resilience

This proposal has no effect on API resilience.

<https://gist.github.com/CodaFi/fc6fa3aac64c4b1deffd1434a37e295f#alternatives-considered>Alternatives considered

Do nothing and continue to accept this implicit conversion.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Jordan Rose) #8

[adding Philippe, who has also thought about this problem]

The abstract problems I know of are:

(1) custom callbacks
(2) it's legal to import CF without importing Foundation
(3) CF types will not get generics

In practice, (1) is both exceedingly rare (only one public API that I know of) and something that can be worked around by using the CF APIs on the array—that is, we can keep CFArrayGetValueAtIndex around and just have it take an NSArray. (2) is something we can deprecate and/or fix. (3) just means we have to import the CFArray as NSArray rather than Array.

We would have to be very sure that all toll-free bridged CF types are marked as such; adding toll-free bridging would not be a backwards-compatible change since it would conflate two types into one. This leads us to a fourth problem: this is a source-breaking change if anyone has overloads for both CFArray and NSArray, or adds a protocol to CFArray that NSArray already has. We probably already need to solve the latter in some way, and could try to cross our fingers about the former.

Jordan

···

On Mar 23, 2017, at 09:05, Joe Groff via swift-evolution <swift-evolution@swift.org> wrote:

On Mar 22, 2017, at 10:41 PM, Slava Pestov via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Mar 22, 2017, at 10:35 PM, Robert Widmann via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Alternatives considered

Do nothing and continue to accept this implicit conversion.

One alternative would be to import CFArray as a typealias for NSArray, etc, erasing the distinction between the two types completely. I did suggest this to Jordan at one point and he pointed out some problems, but I don’t remember them now. Hopefully Jordan can chime in.

I'd prefer this solution as well, especially since toll-free bridged CF types are nearly indistinguishable from their NS counterparts at runtime, so trying to maintain the distinction for dynamic casts or reflection has historically been problematic. Part of the problem is that, as Charlie noted, CFArray/Dictionary/Set can take an arbitrary set of retain/release callbacks, in which case the resulting container isn't fully NSArray-compatible. I'm not sure that happens often enough with CF containers in the SDKs that accounting for that case is worth the burden of separating the types.


(Philippe Hausler) #9

I think there are a bit more ramifications that are under the hood here; we should consider the full extent of what changing the behavior between CF types and NS types a bit more deeply than just a cursory proposal. Additionally as with any change to Foundation and CoreFoundation this should be passed around internally to the appropriate stakeholders that will be in charge of providing long term support for any sort of change this would apply to.

As an initial gut reaction; I would say that this is well past our stage for changes for Foundation and I would hate to see something be pushed out last minute that takes away development time from other important tasks on the table.

···

On Mar 23, 2017, at 10:14 AM, Jordan Rose <jordan_rose@apple.com> wrote:

On Mar 23, 2017, at 09:05, Joe Groff via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Mar 22, 2017, at 10:41 PM, Slava Pestov via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Mar 22, 2017, at 10:35 PM, Robert Widmann via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Alternatives considered

Do nothing and continue to accept this implicit conversion.

One alternative would be to import CFArray as a typealias for NSArray, etc, erasing the distinction between the two types completely. I did suggest this to Jordan at one point and he pointed out some problems, but I don’t remember them now. Hopefully Jordan can chime in.

I'd prefer this solution as well, especially since toll-free bridged CF types are nearly indistinguishable from their NS counterparts at runtime, so trying to maintain the distinction for dynamic casts or reflection has historically been problematic. Part of the problem is that, as Charlie noted, CFArray/Dictionary/Set can take an arbitrary set of retain/release callbacks, in which case the resulting container isn't fully NSArray-compatible. I'm not sure that happens often enough with CF containers in the SDKs that accounting for that case is worth the burden of separating the types.

[adding Philippe, who has also thought about this problem]

The abstract problems I know of are:

(1) custom callbacks
(2) it's legal to import CF without importing Foundation
(3) CF types will not get generics

In practice, (1) is both exceedingly rare (only one public API that I know of) and something that can be worked around by using the CF APIs on the array—that is, we can keep CFArrayGetValueAtIndex around and just have it take an NSArray. (2) is something we can deprecate and/or fix. (3) just means we have to import the CFArray as NSArray rather than Array.

We would have to be very sure that all toll-free bridged CF types are marked as such; adding toll-free bridging would not be a backwards-compatible change since it would conflate two types into one. This leads us to a fourth problem: this is a source-breaking change if anyone has overloads for both CFArray and NSArray, or adds a protocol to CFArray that NSArray already has. We probably already need to solve the latter in some way, and could try to cross our fingers about the former.

Jordan


(Slava Pestov) #10

What is the fallout you expect from this change in Foundation? On Linux codelibs-foundation, there already is no toll-free bridging AFAIK and the CF* types are not exposed at all. In the Foundation overlay on Darwin, the migration can be mostly automatic, inserting ‘as’ casts in the appropriate places where the implicit conversion is taking place now.

I guess there will be some impact on documentation and sample code?

Slava

···

On Mar 23, 2017, at 10:47 AM, Philippe Hausler <phausler@apple.com> wrote:

On Mar 23, 2017, at 10:14 AM, Jordan Rose <jordan_rose@apple.com <mailto:jordan_rose@apple.com>> wrote:

On Mar 23, 2017, at 09:05, Joe Groff via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Mar 22, 2017, at 10:41 PM, Slava Pestov via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Mar 22, 2017, at 10:35 PM, Robert Widmann via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Alternatives considered

Do nothing and continue to accept this implicit conversion.

One alternative would be to import CFArray as a typealias for NSArray, etc, erasing the distinction between the two types completely. I did suggest this to Jordan at one point and he pointed out some problems, but I don’t remember them now. Hopefully Jordan can chime in.

I'd prefer this solution as well, especially since toll-free bridged CF types are nearly indistinguishable from their NS counterparts at runtime, so trying to maintain the distinction for dynamic casts or reflection has historically been problematic. Part of the problem is that, as Charlie noted, CFArray/Dictionary/Set can take an arbitrary set of retain/release callbacks, in which case the resulting container isn't fully NSArray-compatible. I'm not sure that happens often enough with CF containers in the SDKs that accounting for that case is worth the burden of separating the types.

[adding Philippe, who has also thought about this problem]

The abstract problems I know of are:

(1) custom callbacks
(2) it's legal to import CF without importing Foundation
(3) CF types will not get generics

In practice, (1) is both exceedingly rare (only one public API that I know of) and something that can be worked around by using the CF APIs on the array—that is, we can keep CFArrayGetValueAtIndex around and just have it take an NSArray. (2) is something we can deprecate and/or fix. (3) just means we have to import the CFArray as NSArray rather than Array.

We would have to be very sure that all toll-free bridged CF types are marked as such; adding toll-free bridging would not be a backwards-compatible change since it would conflate two types into one. This leads us to a fourth problem: this is a source-breaking change if anyone has overloads for both CFArray and NSArray, or adds a protocol to CFArray that NSArray already has. We probably already need to solve the latter in some way, and could try to cross our fingers about the former.

Jordan

I think there are a bit more ramifications that are under the hood here; we should consider the full extent of what changing the behavior between CF types and NS types a bit more deeply than just a cursory proposal. Additionally as with any change to Foundation and CoreFoundation this should be passed around internally to the appropriate stakeholders that will be in charge of providing long term support for any sort of change this would apply to.

As an initial gut reaction; I would say that this is well past our stage for changes for Foundation and I would hate to see something be pushed out last minute that takes away development time from other important tasks on the table.