New conformances and source compatibility


(Jordan Rose) #1

Hi, everyone. I wanted to get some advice on an issue I've been worrying about: the effect of added conformances on source compatibility. Let's take my own (stale) PR as an example: #4568 <https://github.com/apple/swift/pull/4568> makes all CF types Equatable and Hashable, using the functions CFHash and CFEqual. What happens if somebody's already done that in their own extension of, say, CFBag <https://developer.apple.com/reference/corefoundation/cfbag-s1l#//apple_ref/swift/cl/c:@T@CFBagRef>?

(Let's not debate in this thread whether this is a good idea. It's consistent with NSObject, at least.)

Since conformances are globally unique today, I think the only valid short-term answer is to say the user's conformance is invalid, and indeed that's the behavior you would get today if you update to a newer version of a package and discover that they've added a conformance.

The trouble is that this breaks Swift 3 source compatibility. Unlike many other changes, conformances cannot be made conditional on the language version. This isn't just because of syntax; someone may have a generic type that relies on the conformance being present, and it would be an error to construct that type without the conformance.

The solution I thought of was to downgrade the error to a warning, saying "this conformance is invalid and will be ignored". This isn't ideal, but it will probably do the right thing—how often is there a protocol you can add to a type in multiple, significantly different ways?

Okay, great. Except this only solves half the problem: in order to implement Hashable, I need to provide a 'hashValue' property. We normally don't treat API additions as breaking changes because that would keep us from making any changes, but protocol requirements are a little different—in those cases we are clearly going to have naming conflicts. These, of course, are reported as errors today, and it feels much stranger to downgrade those errors to warnings. "Sorry, this code you have written is going to be ignored, but we'll keep building anyway?" Seems very fishy.

Options (as I see them):
- Downgrade redeclaration errors to warnings for both conformances and members
- Downgrade conformance redeclaration errors to warnings, but leave member redeclaration as an error
- Just leave both of these as errors, and accept that this facet of source compatibility is imperfect, and anyone who needs to continue compiling with Swift 3.1 can add #if.

Thoughts, answers? Thanks,
Jordan

P.S. All of this will come back in spades with library evolution <http://jrose-apple.github.io/swift-library-evolution>, and in particular there are open issues <http://jrose-apple.github.io/swift-library-evolution/#open-issues> in the document around this.

P.P.S. Thanks to Dave Abrahams and Huon Wilson for initial discussion on this.


(Joe Groff) #2

In the short term, making them errors seems like the least bad solution to me. For the longer term library evolution issues, we need to accept that conformances can't be globally unique in the face of separate compilation and library evolution, nor can member name collisions be avoided, and design for that. We may still want to discourage people from introducing conflicting conformances or redeclaring names among modules when they know about each other for general tidiness, but we should also have ways to handle these name and conformance conflicts when they do occur.

-Joe

···

On Mar 23, 2017, at 11:42 AM, Jordan Rose via swift-dev <swift-dev@swift.org> wrote:

Hi, everyone. I wanted to get some advice on an issue I've been worrying about: the effect of added conformances on source compatibility. Let's take my own (stale) PR as an example: #4568 makes all CF types Equatable and Hashable, using the functions CFHash and CFEqual. What happens if somebody's already done that in their own extension of, say, CFBag?

(Let's not debate in this thread whether this is a good idea. It's consistent with NSObject, at least.)

Since conformances are globally unique today, I think the only valid short-term answer is to say the user's conformance is invalid, and indeed that's the behavior you would get today if you update to a newer version of a package and discover that they've added a conformance.

The trouble is that this breaks Swift 3 source compatibility. Unlike many other changes, conformances cannot be made conditional on the language version. This isn't just because of syntax; someone may have a generic type that relies on the conformance being present, and it would be an error to construct that type without the conformance.

The solution I thought of was to downgrade the error to a warning, saying "this conformance is invalid and will be ignored". This isn't ideal, but it will probably do the right thing—how often is there a protocol you can add to a type in multiple, significantly different ways?

Okay, great. Except this only solves half the problem: in order to implement Hashable, I need to provide a 'hashValue' property. We normally don't treat API additions as breaking changes because that would keep us from making any changes, but protocol requirements are a little different—in those cases we are clearly going to have naming conflicts. These, of course, are reported as errors today, and it feels much stranger to downgrade those errors to warnings. "Sorry, this code you have written is going to be ignored, but we'll keep building anyway?" Seems very fishy.

Options (as I see them):
- Downgrade redeclaration errors to warnings for both conformances and members
- Downgrade conformance redeclaration errors to warnings, but leave member redeclaration as an error
- Just leave both of these as errors, and accept that this facet of source compatibility is imperfect, and anyone who needs to continue compiling with Swift 3.1 can add #if.

Thoughts, answers? Thanks,
Jordan

P.S. All of this will come back in spades with library evolution, and in particular there are open issues in the document around this.

P.P.S. Thanks to Dave Abrahams and Huon Wilson for initial discussion on this.


(Jon Hull) #3

I think a stop-gap solution is ok in the short term.

Longer term, it would be nice to have:
1) A way to mark a conformance as weak. That is, it is only used if a non-weak conformance is unavailable.
2) A way to explicitly declare that a method/property satisfies a protocol’s method/property even if the names don’t match (as long as the shapes still do)

Xiaodi and I had a fairly length discussion about #2 on evolution back in phase 1, and there was discussion of #1 back in the Swift 3 timeframe. Both discussions fizzled out without consensus, as there were more exciting discussions happening at the same time.

It is probably out of scope for Swift 4, but I believe we will need to tackle some of these nuances around protocol conformances before we can stabilize.

···

On Mar 23, 2017, at 11:42 AM, Jordan Rose via swift-dev <swift-dev@swift.org> wrote:

Hi, everyone. I wanted to get some advice on an issue I've been worrying about: the effect of added conformances on source compatibility. Let's take my own (stale) PR as an example: #4568 <https://github.com/apple/swift/pull/4568> makes all CF types Equatable and Hashable, using the functions CFHash and CFEqual. What happens if somebody's already done that in their own extension of, say, CFBag <https://developer.apple.com/reference/corefoundation/cfbag-s1l#//apple_ref/swift/cl/c:@T@CFBagRef>?

(Let's not debate in this thread whether this is a good idea. It's consistent with NSObject, at least.)

Since conformances are globally unique today, I think the only valid short-term answer is to say the user's conformance is invalid, and indeed that's the behavior you would get today if you update to a newer version of a package and discover that they've added a conformance.

The trouble is that this breaks Swift 3 source compatibility. Unlike many other changes, conformances cannot be made conditional on the language version. This isn't just because of syntax; someone may have a generic type that relies on the conformance being present, and it would be an error to construct that type without the conformance.

The solution I thought of was to downgrade the error to a warning, saying "this conformance is invalid and will be ignored". This isn't ideal, but it will probably do the right thing—how often is there a protocol you can add to a type in multiple, significantly different ways?

Okay, great. Except this only solves half the problem: in order to implement Hashable, I need to provide a 'hashValue' property. We normally don't treat API additions as breaking changes because that would keep us from making any changes, but protocol requirements are a little different—in those cases we are clearly going to have naming conflicts. These, of course, are reported as errors today, and it feels much stranger to downgrade those errors to warnings. "Sorry, this code you have written is going to be ignored, but we'll keep building anyway?" Seems very fishy.

Options (as I see them):
- Downgrade redeclaration errors to warnings for both conformances and members
- Downgrade conformance redeclaration errors to warnings, but leave member redeclaration as an error
- Just leave both of these as errors, and accept that this facet of source compatibility is imperfect, and anyone who needs to continue compiling with Swift 3.1 can add #if.

Thoughts, answers? Thanks,
Jordan

P.S. All of this will come back in spades with library evolution <http://jrose-apple.github.io/swift-library-evolution>, and in particular there are open issues <http://jrose-apple.github.io/swift-library-evolution/#open-issues> in the document around this.

P.P.S. Thanks to Dave Abrahams and Huon Wilson for initial discussion on this.
_______________________________________________
swift-dev mailing list
swift-dev@swift.org
https://lists.swift.org/mailman/listinfo/swift-dev