> On Sep 6, 2017, at 5:44 AM, Rod Brown via swift-evolution 
> <swift-evolution@swift.org> wrote:
> 
>> 
>> On 6 Sep 2017, at 12:05 pm, Jarod Long via swift-evolution 
>> <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
>> 
>> From the perspective of primarily an app developer rather than library 
>> author, I'm not a big fan of this change. I find myself in the "unhappy with 
>> the loss of compiler warnings" camp -- if I'm switching over every case of 
>> an enum, then I almost certainly want to be notified that a new case has 
>> been added by way of a compiler error than via the arbitrary runtime 
>> behavior I added in the previously-unreachable default case.
> 
> I think what you’re really asking for here is the “future” case mentioned in 
> the Alternatives Considered section. I think that Jordan makes a good point 
> that this would result in untestable code, which is bad practice. While the 
> lack of clear highlighting of non-exhaustive cases is undesirable, I think 
> untestable code is a much larger problem here.

This is generally the switch! that I've suggested listed in alternatives as 
well - that generally brings current behavior. For a project that is regularly 
maintained, I believe that this makes sense given that the enums are only 
likely to change once a year at most (with new OS releases)...

> 
> Either way we need a way to handle forward compatibility for our code when 
> cases get added to external frameworks, that much is clear. Swift is broken 
> in regards to this, and we need to handle it somehow. I’m hoping you’re not 
> suggesting that we just don’t make this change at all. We need this for 
> forward compatibility for framework development with Swift.
> 
>> 
>> This seems like a clear situation where source compatibility is not desired 
>> to me. For those who want to maximize compatibility, it is possible to opt 
>> into it by adding a default case to an exhaustive switch over a library 
>> enum, but the reverse is not true if this change is made as-is. You can't 
>> opt into an exhaustive switch for nonexhaustive enums if handling every case 
>> is valued over source compatibility.
>> 
>> A secondary concern I have is that this introduces extra complexity that 
>> could be confusing for new Swift developers. The current enum exhaustivity 
>> rules are consistent and easy to explain, but they become more cumbersome 
>> with this added exception that only applies to some enums that specifically 
>> only come from outside the current module. If this change is made, I would 
>> encourage some effort towards a specific error message when switching over 
>> all cases of a nonexhaustive enum without a default case. Rather than the 
>> existing "Switch must be exhaustive", I think it would go a long way towards 
>> avoiding confusion to say something like "Switch over a nonexhaustive enum 
>> must have a default case".
>> 
>> In any case, I don't think these are terrible issues -- I agree with the 
>> proposal's statement that switches over nonexhaustive enums are generally 
>> uncommon. But if that's true, it feels like the source compatibility 
>> motivation is weak, since not much code is affected anyways. Perhaps the 
>> benefits from a library author's perspective make this change worth it, but 
>> at least for me and my coworkers, it would be an unwelcome change overall.
>> 
>> Jarod
>> 
>> On Sep 5, 2017, 17:19 -0700, Jordan Rose via swift-evolution 
>> <swift-evolution@swift.org <mailto:swift-evolution@swift.org>>, wrote:
>>> I've taken everyone's feedback into consideration and written this up as a 
>>> proposal: 
>>> https://github.com/jrose-apple/swift-evolution/blob/non-exhaustive-enums/proposals/nnnn-non-exhaustive-enums.md
>>>  
>>> <https://github.com/jrose-apple/swift-evolution/blob/non-exhaustive-enums/proposals/nnnn-non-exhaustive-enums.md>.
>>>  The next step is working on an implementation, but if people have further 
>>> pre-review comments I'd be happy to hear them.
>>> 
>>> Jordan
>>> 
>>> 
>>>> On Aug 8, 2017, at 15:27, Jordan Rose <jordan_r...@apple.com 
>>>> <mailto:jordan_r...@apple.com>> wrote:
>>>> 
>>>> Hi, everyone. Now that Swift 5 is starting up, I'd like to circle back to 
>>>> an issue that's been around for a while: the source compatibility of 
>>>> enums. Today, it's an error to switch over an enum without handling all 
>>>> the cases, but this breaks down in a number of ways:
>>>> 
>>>> - A C enum may have "private cases" that aren't defined inside the 
>>>> original enum declaration, and there's no way to detect these in a switch 
>>>> without dropping down to the rawValue.
>>>> - For the same reason, the compiler-synthesized 'init(rawValue:)' on an 
>>>> imported enum never produces 'nil', because who knows how anyone's using C 
>>>> enums anyway?
>>>> - Adding a new case to a Swift enum in a library breaks any client code 
>>>> that was trying to switch over it.
>>>> 
>>>> (This list might sound familiar, and that's because it's from a message of 
>>>> mine on a thread started by Matthew Johnson back in February called 
>>>> "[Pitch] consistent public access modifiers". Most of the rest of this 
>>>> email is going to go the same way, because we still need to make progress 
>>>> here.)
>>>> 
>>>> At the same time, we really like our exhaustive switches, especially over 
>>>> enums we define ourselves. And there's a performance side to this whole 
>>>> thing too; if all cases of an enum are known, it can be passed around much 
>>>> more efficiently than if it might suddenly grow a new case containing a 
>>>> struct with 5000 Strings in it.
>>>> 
>>>> 
>>>> Behavior
>>>> 
>>>> I think there's certain behavior that is probably not terribly 
>>>> controversial:
>>>> 
>>>> - When enums are imported from Apple frameworks, they should always 
>>>> require a default case, except for a few exceptions like NSRectEdge. (It's 
>>>> Apple's job to handle this and get it right, but if we get it wrong with 
>>>> an imported enum there's still the workaround of dropping down to the raw 
>>>> value.)
>>>> - When I define Swift enums in the current framework, there's obviously no 
>>>> compatibility issues; we should allow exhaustive switches.
>>>> 
>>>> Everything else falls somewhere in the middle, both for enums defined in 
>>>> Objective-C:
>>>> 
>>>> - If I define an Objective-C enum in the current framework, should it 
>>>> allow exhaustive switching, because there are no compatibility issues, or 
>>>> not, because there could still be private cases defined in a .m file?
>>>> - If there's an Objective-C enum in another framework (that I built 
>>>> locally with Xcode, Carthage, CocoaPods, SwiftPM, etc.), should it allow 
>>>> exhaustive switching, because there are no binary compatibility issues, or 
>>>> not, because there may be source compatibility issues? We'd really like 
>>>> adding a new enum case to not be a breaking change even at the source 
>>>> level.
>>>> - If there's an Objective-C enum coming in through a bridging header, 
>>>> should it allow exhaustive switching, because I might have defined it 
>>>> myself, or not, because it might be non-modular content I've used the 
>>>> bridging header to import?
>>>> 
>>>> And in Swift:
>>>> 
>>>> - If there's a Swift enum in another framework I built locally, should it 
>>>> allow exhaustive switching, because there are no binary compatibility 
>>>> issues, or not, because there may be source compatibility issues? Again, 
>>>> we'd really like adding a new enum case to notbe a breaking change even at 
>>>> the source level.
>>>> 
>>>> Let's now flip this to the other side of the equation. I've been talking 
>>>> about us disallowing exhaustive switching, i.e. "if the enum might grow 
>>>> new cases you must have a 'default' in a switch". In previous (in-person) 
>>>> discussions about this feature, it's been pointed out that the code in an 
>>>> otherwise-fully-covered switch is, by definition, unreachable, and 
>>>> therefore untestable. This also isn't a desirable situation to be in, but 
>>>> it's mitigated somewhat by the fact that there probably aren't many 
>>>> framework enums you should exhaustively switch over anyway. (Think about 
>>>> Apple's frameworks again.) I don't have a great answer, though.
>>>> 
>>>> For people who like exhaustive switches, we thought about adding a new 
>>>> kind of 'default'—let's call it 'unknownCase' just to be able to talk 
>>>> about it. This lets you get warnings when you update to a new SDK, but is 
>>>> even more likely to be untested code. We didn't think this was worth the 
>>>> complexity.
>>>> 
>>>> 
>>>> Terminology
>>>> 
>>>> The "Library Evolution 
>>>> <http://jrose-apple.github.io/swift-library-evolution/>" doc (mostly 
>>>> written by me) originally called these "open" and "closed" enums 
>>>> ("requires a default" and "allows exhaustive switching", respectively), 
>>>> but this predated the use of 'open' to describe classes and class members. 
>>>> Matthew's original thread did suggest using 'open' for enums as well, but 
>>>> I argued against that, for a few reasons:
>>>> 
>>>> - For classes, "open" and "non-open" restrict what the client can do. For 
>>>> enums, it's more about providing the client with additional guarantees—and 
>>>> "non-open" is the one with more guarantees.
>>>> - The "safe" default is backwards: a merely-public class can be made 
>>>> 'open', while an 'open' class cannot be made non-open. Conversely, an 
>>>> "open" enum can be made "closed" (making default cases unnecessary), but a 
>>>> "closed" enum cannot be made "open".
>>>> 
>>>> That said, Clang now has an 'enum_extensibility' attribute that does take 
>>>> 'open' or 'closed' as an argument.
>>>> 
>>>> On Matthew's thread, a few other possible names came up, though mostly 
>>>> only for the "closed" case:
>>>> 
>>>> - 'final': has the right meaning abstractly, but again it behaves 
>>>> differently than 'final' on a class, which is a restriction on code 
>>>> elsewhere in the same module.
>>>> - 'locked': reasonable, but not a standard term, and could get confused 
>>>> with the concurrency concept
>>>> - 'exhaustive': matches how we've been explaining it (with an "exhaustive 
>>>> switch"), but it's not exactly the enum that's exhaustive, and it's a long 
>>>> keyword to actually write in source.
>>>> 
>>>> - 'extensible': matches the Clang attribute, but also long
>>>> 
>>>> 
>>>> I don't have better names than "open" and "closed", so I'll continue using 
>>>> them below even though I avoided them above. But I would really like to 
>>>> find some.
>>>> 
>>>> 
>>>> Proposal
>>>> 
>>>> Just to have something to work off of, I propose the following:
>>>> 
>>>> 1. All enums (NS_ENUMs) imported from Objective-C are "open" unless they 
>>>> are declared "non-open" in some way (likely using the enum_extensibility 
>>>> attribute mentioned above).
>>>> 2. All public Swift enums in modules compiled "with resilience" (still to 
>>>> be designed) have the option to be either "open" or "closed". This only 
>>>> applies to libraries not distributed with an app, where binary 
>>>> compatibility is a concern.
>>>> 3. All public Swift enums in modules compiled from source have the option 
>>>> to be either "open" or "closed".
>>>> 4. In Swift 5 mode, a public enum should be required to declare if it is 
>>>> "open" or "closed", so that it's a conscious decision on the part of the 
>>>> library author. (I'm assuming we'll have a "Swift 4 compatibility mode" 
>>>> next year that would leave unannotated enums as "closed".)
>>>> 5. None of this affects non-public enums.
>>>> 
>>>> (4) is the controversial one, I expect. "Open" enums are by far the common 
>>>> case in Apple's frameworks, but that may be less true in Swift.
>>>> 
>>>> 
>>>> Why now?
>>>> 
>>>> Source compatibility was a big issue in Swift 4, and will continue to be 
>>>> an important requirement going into Swift 5. But this also has an impact 
>>>> on the ABI: if an enum is "closed", it can be accessed more efficiently by 
>>>> a client. We don't have to do this before ABI stability—we could access 
>>>> all enums the slow way if the library cares about binary compatibility, 
>>>> and add another attribute for this distinction later—but it would be nice™ 
>>>> (an easy model for developers to understand) if "open" vs. "closed" was 
>>>> also the primary distinction between "indirect access" vs. "direct access".
>>>> 
>>>> I've written quite enough at this point. Looking forward to feedback!
>>>> Jordan
>>> 
>> _______________________________________________
>> swift-evolution mailing list
>> swift-evolution@swift.org <mailto:swift-evolution@swift.org>
>> https://lists.swift.org/mailman/listinfo/swift-evolution
> 
> _______________________________________________
> swift-evolution mailing list
> swift-evolution@swift.org <mailto:swift-evolution@swift.org>
> https://lists.swift.org/mailman/listinfo/swift-evolution 
> <https://lists.swift.org/mailman/listinfo/swift-evolution>
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to