SE-0192 — Non-Exhaustive Enums

Hey, everyone. SE-0192 is not dead! Here's what's been going on.

Over the last few weeks I've been discussing this with the core team, who pretty much all agreed that this is not the right direction for third-party libraries. That is, for this table in the proposal:

Use Case Frozen Non-frozen
Multi-module app The desired behavior. Compiler can find all clients if the enum becomes non-frozen. Compiler can find all clients if the enum becomes frozen.
Open-source library (SwiftPM) Changing to non-frozen is a source-breaking change; it produces errors in any clients. Changing to frozen produces warnings in any clients.
ABI-stable library (Apple OSs) Cannot change to non-frozen; it would break binary compatibility. Changing to frozen produces warnings in clients (probably dependent on deployment target).

the first two rows are being unnecessarily inconvenienced by the frozen/non-frozen distinction.

This was always a controversial part of the proposal, since it meant that all enum that anyone had ever written in Swift would be affected. For C enums and the standard library, the benefit is clear, but for third-party libraries it was merely about whether a particular change (adding a case) would break source compatibility, and for an app that had just been broken up into multiple modules, there was almost no point at all. The core team, unlike me, did not feel that this trade-off was worth it. So, the frozen/non-frozen distinction will only apply to (1) C enums and (2) enums from the standard library and overlays.

This was the biggest part of the proposal, but the rest of it is mostly still relevant. In particular:

  • Switching over a C enum will require a catch-all clause, unless the enum is marked specially. (The current Clang attribute that controls that is spelled __attribute__((enum_extensibility(closed))).) This will be an error in Swift 5 mode, but we're still deciding whether it should be a warning in Swift 4 mode or just left un-diagnosed. (In either case, you'll be able to add the catch-all clause in Swift 4 mode without the compiler complaining.)

  • Switching over a Swift enum in the standard library or overlays will require a catch-all clause, unless that enum is marked specially. The attribute that distinguishes frozen enums from non-frozen enums will be underscored (something like @_frozen), indicating that it is not ready for general use yet; in order to discuss it properly, we need to be able to talk about the differences between "libraries with a stable binary interface" and "libraries without a stable binary interface", and SE-0192 is big enough already.

  • If an enum is explicitly marked frozen, using any sort of catch-all case will get an unreachable code warning, as it does today.

  • The community has made it clear that they want some form of catch-all case that will still result in compiler warnings whenever it's reachable with a known enum case. This is what I've been calling unknown case and what's being discussed over in Handling unknown cases in enums [RE: SE-0192] as well.

I'm working on cutting down the proposal text (and the implementation) to match this feedback from the core team, but there's also one more issue that needs to be worked out about unknown case—and no, I'm not just talking about the name. I'm going to bring that up in Handling unknown cases in enums [RE: SE-0192].

Once that's all done, the plan is to do another formal round of review (though likely a short one), since much of the community may not have had the stamina to keep up with these very long discussions. I may also start landing some of the implementation ahead of that just to keep from having to rebase it all the time (with approval from Ted), but all the checking will be behind a flag.

Thanks as always to everyone for their participation, patience, and thoughtful feedback.

18 Likes