SE-0192 — Non-Exhaustive Enums

Hello I would like to give a feedback on this SE,

In the proposal, it stats that
In the initial discussion, multiple people were unhappy with the loss of compiler warnings for switches over non-exhaustive enums that comes with using default—they wanted to be able to handle all cases that exist today, and have the compiler tell them when new ones were added. Ultimately I decided not to include this in the proposal with the expectation is that switches over non-exhaustive enums should be uncommon.

This may not be true for every Swift based app. My scenario is that we are a service company who have a open source library for our customer to integrate our service into their app. However we also have an official application which also uses these libraries too. And since we adopted Swift features as much as possible, we do have a lot of public enum types in our libraries and use them in our app heavily. This PR will make our code base harder to maintain since we lost the complier warning for switches over non-exhaustive enums

I understand that most of the Swift based apps may not suffer from this heavily like us but when they do, it’s really a huge pain to maintain this.

Best regards,
Pitiphong P.

···

On 20 Dec BE 2560, at 05:58, Ted Kremenek <kremenek@apple.com> wrote:

The review of "SE 0192 - Non-Exhaustive Enums" begins now and runs through January 3, 2018.

The proposal is available here:

https://github.com/apple/swift-evolution/blob/master/proposals/0192-non-exhaustive-enums.md
Reviews are an important part of the Swift evolution process. All review feedback should be sent to the swift-evolution mailing list at:

https://lists.swift.org/mailman/listinfo/swift-evolution
or, if you would like to keep your feedback private, directly to the review manager.

When replying, please try to keep the proposal link at the top of the message:

Proposal link: https://github.com/apple/swift-evolution/blob/master/proposals/0192-non-exhaustive-enums.md
...
Reply text
...
Other replies
What goes into a review of a proposal?

The goal of the review process is to improve the proposal under review through constructive criticism and, eventually, determine the direction of Swift.

When reviewing a proposal, here are some questions to consider:

What is your evaluation of the proposal?

Is the problem being addressed significant enough to warrant a change to Swift?

Does this proposal fit well with the feel and direction of Swift?

If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?

How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

Thanks,
Ted Kremenek
Review Manager
_______________________________________________
swift-evolution-announce mailing list
swift-evolution-announce@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution-announce

* What is your evaluation of the proposal?

+1.

I strongly prefer all additions to service ABI follow progressive
disclosure, but it’s not reason alone to make the annotation be mystery
meat. Like the inlining proposal, the spelling needs some consideration
by the core team to provide an ergonomic through-line through the
language. I liked reusing `final` way back when the proposal first came
up, but that ship appears to have sailed. If an annotation is our
preference, I do like `@frozen` if we see it come up in interesting ways
elsewhere.

* Is the problem being addressed significant enough to warrant a
   change to Swift?

I see this proposal and its discussion mostly as prevarication on the
spelling and the default behavior. Those aside, it solves a bug, so bar-
none something must be done. In a perfect world, no annotation or
closed/open would be needed, but this is unrealistic, and a solution
that involves compile-time diagnostics over runtime failures is
preferable.

* Does this proposal fit well with the feel and direction of Swift?

ABI stability is a high-level goal, and this fits within it.

The Core Team and Apple could do a better job with messaging its
compatibility impact. Though I understand it now, changing a feature’s
behavior is indeed surprising at this point. My understanding is that
nothing will change for creating enums in the single target happy path,
with a stretch goal being to extend that to all source targets that
produce a single (in Apple terminology) bundle. I know y’all can’t
discuss future dev tools directions, but it may have helped us to know
the plan for what constitutes the compiler “seeing” an enum’s source to
make a resilience decision.

* If you have used other languages or libraries with a similar
   feature, how do you feel that this proposal compares to those?

I’m most familiar if the impact of this problem on ObjC, where it was
only really solved for Apple. We’re forging new territory for solving
this at the ABI level for any binary module, so some uncertainty is to
be expected.

* How much effort did you put into your review? A glance, a quick
   reading, or an in-depth study?

I’ve followed the proposal in depth since its early stages.

Sincerely,
  Zachary Waldowski
zach@waldowski.me

···

On Tue, Dec 19, 2017, at 5:58 PM, Ted Kremenek wrote:

The proposal is available here:
https://github.com/apple/swift-evolution/blob/master/proposals/0192-non-exhaustive-enums.md
What is your evaluation of the proposal?

-1

I would much prefer the solution proposed by Andrew Bennett in another thread which solves all problems very nicely including the testability of future cases by giving them a placeholder name:

From Andrew’s mail:

public enum HomeworkExcuse {
  case eatenByPet
  case thoughtItWasDueNextWeek
  fallback unknown // NEW
}

Then I believe you would be able to have an exhaustive switch like this:

switch thing {
  case eatenByPet: break
  case thoughtItWasDueNextWeek: break
  case unknown: break
}

Which would still allow compile-time errors if new cases are introduced, while providing a concise way to show something is not exhaustible.

This would also support existing enums with "unknown" equivalent cases would be able to explicitly label those fields as fallback without needing to make large code changes.

I see no reason why you shouldn't be able to use ".unknown", which should still allow this to be testable.

i.e. Andrew’s idea is to introduce a placeholder case instead of marking the enum as exhaustive/non-exhaustive. This gives the future cases a handle to be switched on and to be tested against. Very elegant.

Is the problem being addressed significant enough to warrant a change to Swift?

Yes, but the proposed solution is not as good as it should be, neglecting to provide compile-time errors if new cases are introduced.

Does this proposal fit well with the feel and direction of Swift?

No, due to its shortcomings.

If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?

None, but see Andrew Bennett’s idea above.

How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

Followed most of the discussion and review threads.

-Thorsten

I have been busy over the holidays and have been considering the arguments in this thread so my review is late in coming.

What is your evaluation of the proposal?

I agree that we need a solution to the problem described. I also agree that non-exhaustive is most in keeping with the overall design of Swift at module boundaries. However, I believe this proposal should be modified before being accepted

My primary concern (shard with many others) is that this proposal takes away an important tool of exhaustiveness checking in cases where we need to switch over an enum vended by a library which was not declared exhaustive.

This is likely to be a relatively rare need mostly encountered by 3rd party libraries but it will happen. When it does happen it would be really unfortunate to be forced to use a `default` clause rather than something like a `future` clause which will produce an error when compiled against an SDK where the enum includes cases that are not covered. I can imagine cases where this catch-all case would need to do something other than abort the program so I do not like the `switch!` suggestion that has been discussed. The programmer should still be responsible for determining the behavior of unknown cases.

I find the argument of untestable code to be completely unconvincing. As has been noted several times in this thread, when all known cases are covered in a switch statement the code is equally untestable whether the “other" pattern is spelled `default` or `future`. There has been some discussion of finding a way for the compiler to support an extra test-only value somehow. A tool like this could be quite useful regardless of how the “other” pattern is spelled but the presence or absence of this tool should not influence the decision to include “future” or not.

While library authors have a legitimate need to reserve the right to introduce new cases for some enums this need can be met without taking away a useful tool for generating static compiler errors when code does not align with intent (in this case, the intent being to cover all known cases). Switch statements working with these kinds of enums should be required to cover unknown cases but should be able to do so while still being statically checked with regards to known cases.

People are going to write these kinds of switch statements whether the language helps them out or not. Instead of taking a moral standpoint that says “that is bad, don’t do it” the language should continue to provide assistance to the degree it can. If the core team remains unconvinced about use cases for these kinds of "pseudo-exhaustive” switches I suggest a brief extension to the review with a specific request for example use cases. I believe several good examples could be quickly identified in existing open source libraries.

A secondary concern I have is the @exhaustive keyword itself. I strongly believe Swift will be better suited by a more general keyword that is also applicable in other contexts.

I authored earlier discussions about how exhaustiveness could fit into the access control system. This is discussed as an alternative under “closed” and “open”. I think “closed” as a “greater than public” availability with respect to switch statements is perfectly sensible. In this design the access control hierarchy would have two branches in different directions that are both "greater than public”. This direction had some proponents but didn’t appear to gain traction with the core team. In case the core team discusses this option I do still prefer it.

Another direction that has been discussed in this thread is to use a keyword that is shared with fixed layout structs. Both of these feel like better options than a special case, enum-only attribute. On the other hand, this is a relatively minor concern.

Is the problem being addressed significant enough to warrant a change to Swift?

Yes.

Does this proposal fit well with the feel and direction of Swift?

For the most part. However, it does introduce a special case attribute specifically for enums. I would prefer if we try to avoid that and believe it is in keeping with the direction of Swift to try and avoid these.

If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?

I have not worked with a language that supported both exhaustive and non-exhaustive enums.

How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

In-depth study of the proposal and all of the threads on the list.

First, this is an extremely well-written proposal, that explains itself well and tries to walk a difficult tightrope. So, A+ for that.

That said, I think it needs modification before acceptance:

* I agree with Dave DeLong that @exhaustive does not actually "do anything", it relies on library authors to do the right thing but library authors cannot be trusted. I am not even sure Optional will continue to have its two cases based on the track record of the swift-evolution process :-P
* I agree with Vladimir S that app developers need to be able to use compile-time checks that they handle all the "known cases" for an arbitrary enum (e.g. exhaustive or non-exhaustive), for example with "future" or some other mechanism. Lack of testability does not actually concern me, but I feel it could be addressed by allowing the assignment of Any to a non-exhaustive enum, perhaps gated via a warning or an @testable.

I feel that a better solution to the underlying dilemma would be the following:

* As an app developer, I can use switch! or @import! to mean that I am vendoring this SDK and the runtime library will definitely be the same library I am linking against. So it does not matter if the library author intends to someday add more cases – from my point of of view they are exhaustive, because this is the library I am linking, accept no substitutes. Cases are checked at import time, and there is a runtime exception if somebody swaps the binary for one with "new" cases, may god have mercy on their soul
* As an app developer, in the absence of one of those opt-in mechanisms an imported enum is assumed to be open and I must handle either a default case (which is not compile-time checked for exhaustion) or a future case (which is). I prefer "undefined" for "future" as a keyword because it seems to me library authors can also remove cases, regardless of what this proposal says.

This solution is a nod to @clattner's "the difference between source packages and binary packages that are updated outside your control (e.g. the OS, or a dynamic library that is updated independently of your app like a 3rd party plugin)." But he is wrong that the difference is somehow tied together with a notion of binary and source: it is a difference between whether the library is vendored or nonvendored, that is whether it is shipped with the application or the OS. If it is shipped with your application, you control the updates and so all enums can be exhaustive, if it is shipped with the OS it is updated independently and who knows what cases will appear at runtime. But there is no law that says I have the sourcecode for all my application's libraries or that OS vendors only ship binaries, so I use "vendored" and "non-vendored", and "import-time" for "compile-time" to be precise in this distinction.

As a library author, I am not sure that the @exhaustive promise is meaningful. Unlike resilience more generally where a library author can provide some fallback behavior for client who calls a deprecated method, there is really not much that can be done to support older clients who are unaware of my new enum case. I suppose we could introduce compile-time checks to prevent passing that enum case to an older client, for example

public enum Foo {
case old
@introduced (1.1) case new
}

public final enum Fixed {
case one
@introduced (1.1) case two //error: Can't add a new case to a final enum, drop @introduced or drop final
}

public func bar() -> Foo {
return .new //error: Not all clients support case new, use if #available or @available
}

This sort of thing might be a justification for supporting a "final" or "exhaustive" declaration, but the motivation in this listing is to support library authors within their own compilation unit, rather than exposing a signal to app developers that may or may not be reliable moving forward.

As shown in this listing, I find "final" more natural and more in the spirit of Swift than @exhaustive.

Drew

···

On December 19, 2017 at 4:58:14 PM, Ted Kremenek (kremenek@apple.com) wrote:

The review of "SE 0192 - Non-Exhaustive Enums" begins now and runs through January 3, 2018.

The proposal is available here:

Reviews are an important part of the Swift evolution process. All review feedback should be sent to the swift-evolution mailing list at:

https://lists.swift.org/mailman/listinfo/swift-evolution
or, if you would like to keep your feedback private, directly to the review manager.

When replying, please try to keep the proposal link at the top of the message:

Proposal link: swift-evolution/0192-non-exhaustive-enums.md at master · apple/swift-evolution · GitHub
...
Reply text
...
Other replies
What goes into a review of a proposal?

The goal of the review process is to improve the proposal under review through constructive criticism and, eventually, determine the direction of Swift.

When reviewing a proposal, here are some questions to consider:

What is your evaluation of the proposal?

Is the problem being addressed significant enough to warrant a change to Swift?

Does this proposal fit well with the feel and direction of Swift?

If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?

How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

Thanks,
Ted Kremenek
Review Manager
_______________________________________________
swift-evolution-announce mailing list
swift-evolution-announce@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution-announce

1 Like

[Proposal: https://github.com/apple/swift-evolution/blob/master/proposals/0192-non-exhaustive-enums.md\]

Whew! Thanks for your feedback, everyone. On the lighter side of feedback—naming things—it seems that most people seem to like '@frozen', and that does in fact have the connotations we want it to have. I like it too.

More seriously, this discussion has convinced me that it's worth including what the proposal discusses as a 'future' case. The key point that swayed me is that this can produce a warning when the switch is missing a case rather than an error, which both provides the necessary compiler feedback to update your code and allows your dependencies to continue compiling when you update to a newer SDK. I know people on both sides won't be 100% satisfied with this, but does it seem like a reasonable compromise?

The next question is how to spell it. I'm leaning towards `unexpected case:`, which (a) is backwards-compatible, and (b) also handles "private cases", either the fake kind that you can do in C (as described in the proposal), or some real feature we might add to Swift some day. `unknown case:` isn't bad either.

I too would like to just do `unknown:` or `unexpected:` but that's technically a source-breaking change:

switch foo {
case bar:
  unknown:
  while baz() {
    while garply() {
      if quux() {
        break unknown
      }
    }
  }
}

Another downside of the `unexpected case:` spelling is that it doesn't work as part of a larger pattern. I don't have a good answer for that one, but perhaps it's acceptable for now.

I'll write up a revision of the proposal soon and make sure the core team gets my recommendation when they discuss the results of the review.

···

---

I'll respond to a few of the more intricate discussions tomorrow, including the syntax of putting a new declaration inside the enum rather than outside. Thank you again, everyone, and happy new year!

Jordan

hello,
im not sure I like this. I think new cases to switches should always break an app. :)

···

Am 19.12.2017 um 14:58 schrieb Ted Kremenek via swift-evolution <swift-evolution@swift.org>:

The review of "SE 0192 - Non-Exhaustive Enums" begins now and runs through January 3, 2018.

The proposal is available here:

https://github.com/apple/swift-evolution/blob/master/proposals/0192-non-exhaustive-enums.md
Reviews are an important part of the Swift evolution process. All review feedback should be sent to the swift-evolution mailing list at:

https://lists.swift.org/mailman/listinfo/swift-evolution
or, if you would like to keep your feedback private, directly to the review manager.

When replying, please try to keep the proposal link at the top of the message:

Proposal link: https://github.com/apple/swift-evolution/blob/master/proposals/0192-non-exhaustive-enums.md
...
Reply text
...
Other replies
What goes into a review of a proposal?

The goal of the review process is to improve the proposal under review through constructive criticism and, eventually, determine the direction of Swift.

When reviewing a proposal, here are some questions to consider:

What is your evaluation of the proposal?

Is the problem being addressed significant enough to warrant a change to Swift?

Does this proposal fit well with the feel and direction of Swift?

If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?

How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

Thanks,
Ted Kremenek
Review Manager
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

1 Like

This would introduce an inconsistency between enums and structs. Structs will not be fixed-contents by default (there’s a proposal coming for that), which means you will be able to add stored properties after the fact. For this reason it makes more sense to me to also make enums non-exhaustive by default so that new cases can be added.

Slava

···

On Dec 19, 2017, at 3:31 PM, Kevin Ballard via swift-evolution <swift-evolution@swift.org> wrote:

So I guess I’m saying, I want more thought put on the topic of whether enums defined in Swift should actually default to non-exhaustive, and I’m now leaning towards the idea that they should remain exhaustive (but Obj-C enums will still default to non-exhaustive).

GitHub - apple/swift-evolution: This maintains proposals for changes and user-visible enhancements to the Swift Programming Language.
proposals/0192-non-exhaustive-enums.md

   -

   What is your evaluation of the proposal?

Overall I like it, but I have some issues.

I’m not completely sold yet on the idea that public enums should be
non-exhaustive by default, as I believe every single public enum I’ve ever
written definitely wants to be exhaustive. I’m tempted to say that enums
exposed by frameworks that need to maintain future-compatibility (such as
Apple’s frameworks) want non-exhaustive by default, but enums exposed by
third-party frameworks that follow semantic versioning are likely to want
exhaustive by default.

In fact, this brings to mind another difference between enums in Apple
frameworks and enums in third-party Swift frameworks, which is the former
is exclusively C-compatible enums whereas the latter is going to have a lot
of enums that carry associated values. I have no data to support this but
I’m inclined to think that it’s more likely for C-compatible enums to want
to be non-exhaustive than it is for enums carrying associated values to
want to be non-exhaustive.

So I guess I’m saying, I want more thought put on the topic of whether
enums defined in Swift should actually default to non-exhaustive, and I’m
now leaning towards the idea that they should remain exhaustive (but Obj-C
enums will still default to non-exhaustive).

I think the proposal represents a very good summary of the pros and cons of
each approach. At the pre-proposal stage, all possible solutions (there are
only three: no default, default to exhaustive, or default to nonexhaustive)
were thoroughly explored, and the proposal as revised here represents a
thoughtful synthesis of the conversation and has a good justification for
the default proposed. I highly doubt that any more back-and-forth on this
will surface new arguments not already covered.

--

I don’t like that modules using @testable imports treat all enums as
exhaustive. I can see why you want to do that, but being forced to handle
non-exhaustive enums in unit tests seems like a valuable guard against
accidentally publishing non-exhaustive enums that should be exhaustive.

On the contrary, I think that the proposal's behavior for `@testable` is
fully consistent and the best solution possible. It preserves the behavior
that `@testable` imports are treated as though internal, and it allows for
unit tests to have compile-time errors when unexpected cases are added. Any
time you want to test nonexhaustive behavior, you can import without
`@testable`.

···

On Tue, Dec 19, 2017 at 6:31 PM, Kevin Ballard via swift-evolution < swift-evolution@swift.org> wrote:

On Dec 19, 2017, at 2:58 PM, Ted Kremenek <kremenek@apple.com> wrote:

--

I’m really not a fan of using a ‘default’ case in non-exhaustive enums.
This means that if new cases are added, the compiler can’t help me find my
switches. I’d really like to have an alternative way of saying “here’s how
to behave in the event of an unknown enum case” without also making that a
catch-all for unspecified-but-known cases. You already brought up this
issue in the alternatives section, where you stated

Ultimately I decided not to include this in the proposal with the
expectation is that switches over non-exhaustive enums should be uncommon.

But since non-exhaustive enums will be the default, I think this _will_
become quite common, if for no other reason than you’re using a library
written by someone who forgot to mark enums as @exhaustive that really
should be.

Regarding the comment that a ‘future’ case is impossible to test, so is a
‘default’ case on a switch that is otherwise exhaustive. In the latter you
can test it by commenting out one of your existing cases. But you can do
that with ‘future’ too, just by renaming it to ‘default’ first.

This, too, I think, has been discussed quite thoroughly, and I think the
proposal synthesizes the discussion and presents the best possible solution.

In the “Use-side” section it says that a switch without a default case
will produce a warning in Swift 4 mode. But in Swift 4 mode all enums are
exhaustive by default and the @exhaustive annotation does nothing. These
two things can’t both be true or else Swift 4 mode will emit warnings on
switches over enums that are very clearly supposed to be exhaustive enums.

This is curious. I wonder if C enums will be nonexhaustive in both Swift 4
and 5 mode, thereby triggering such warnings.

   -

   Is the problem being addressed significant enough to warrant a change
   to Swift?

Yes

   -

   Does this proposal fit well with the feel and direction of Swift?

Yes

   -

   How much effort did you put into your review? A glance, a quick
   reading, or an in-depth study?

A quick reading, followed by a few re-readings as I composed this email.

-Kevin Ballard

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

As an aside: there seems to be increasingly comments about proposals that
say:

  1. This was discussed at the evaluation stage and rejected.
  2. This is how it is implemented in the patch.

And other comments along those lines. Neither the pre-proposal discussions
nor the proposed implementation are intended to limit the scope of the
review. Therefore I don’t think people should raise this as reasons. You
should remember that the process is deliberately staged this way and
different people may well be commenting (in fact the process rather assumes
that people in the formal review will be a wider set of people).

No, previous discussion don't limit the scope of review per se, but it's
not helpful to rehash the same arguments again. We want to encourage
everyone to contribute their most thought-out comments as early in the
process as possible to give those who propose ideas the fullest chance to
flesh out any revisions. However, everyone has finite time to contribute,
and if the same discussions are merely replayed at every stage of review,
then the process actively discourages thoughtful early participation. After
all, why bother defending ideas at the pre-proposal stage if I'm going to
have to spend the time repeating myself in a few months' time anyway?

Of course, if a wider set of people have _new_ comments, those are welcome
something already, then _I_ should say it whether or not the same viewpoint
has already been aired. In my view, such an approach should be actively
discouraged for the reasons above. Although a strong consensus within the
community should certainly be accounted for, this list--even at the formal
review stage--doesn't even come close to approximating the community of
Swift users at large. Thus, review is not a vote-counting exercise to
maximize the number of people who chime in, but rather it is meant to
maximize the number of ideas and perspectives that are aired. If it's
already been said, it doesn't need to be said again, even if _I_ haven't
said it myself.

···

On Tue, Dec 19, 2017 at 11:15 PM, Howard Lovatt via swift-evolution < swift-evolution@swift.org> wrote:
at a later stage. But, there seems to be a sense that if _I_ haven't said

Anyway gripe over.

Proposal link: https://github.com/apple/swift-evolution/blob/
master/proposals/0192-non-exhaustive-enums.md

   -

   What is your evaluation of the proposal?

   +1/2

   I only give this a half because whilst it is important I can see three
   issues:

     1. It doesn’t seem very Swift like to have a different rule, default
   non-exhaustive, for public as opposed to non-public.

     2. It doesn’t seem very Swift like to have the default the unsafe
   case.

     3. Other languages have better solutions - see below under other
   languages
   -

   Is the problem being addressed significant enough to warrant a change
   to Swift?

   Yes, Swift ABI compatibility going forwards is important
   -

   Does this proposal fit well with the feel and direction of Swift?

   No. As mentioned above different rules for public and a non-safe
   default don’t see that Swift like.
   -

   If you have used other languages or libraries with a similar feature,
   how do you feel that this proposal compares to those?

   Both Java and Scala have a better solution. In these languages enums
   (Scala calls them case classes) can implement protocols and the user of an
   enum rarely writes a switch statement, instead they call protocol methods.
   Enums in these languages are a fixed set of derived classes; i.e. normal OO
   programming rather than functional programming, which works well in the
   case of wanting to expand later the number of enum cases.
   -

   How much effort did you put into your review? A glance, a quick
   reading, or an in-depth study?
   Have followed the discussions. Used enums in Swift and other languages
   extensively.

-- Howard.

On 19 Dec 2017, at 12:58 pm, Ted Kremenek <kremenek@apple.com> wrote:

When replying, please try to keep the proposal link at the top of the
message:

Proposal link: GitHub - apple/swift-evolution: This maintains proposals for changes and user-visible enhancements to the Swift Programming Language.
proposals/0192-non-exhaustive-enums.md
...
Reply text
...
Other replies

What goes into a review of a proposal?

The goal of the review process is to improve the proposal under review
through constructive criticism and, eventually, determine the direction of
Swift.

When reviewing a proposal, here are some questions to consider:

   -

   What is your evaluation of the proposal?
   -

   Is the problem being addressed significant enough to warrant a change
   to Swift?
   -

   Does this proposal fit well with the feel and direction of Swift?
   -

   If you have used other languages or libraries with a similar feature,
   how do you feel that this proposal compares to those?
   -

   How much effort did you put into your review? A glance, a quick
   reading, or an in-depth study?

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

The expectation is that switches over non-exhaustive enums are uncommon.

Basically every time I interact with an enum, I switch over it, to make sure I don’t forget anything, and to make sure I reconsider my code if the library changes. Since most SDK enums will become non-exhaustive, this is the 99% case for me.

Is that typical of your interactions with, say, `UIViewAnimationTransition`?

There are three categories of enums in the SDK:

  1) Those which you switch over and which are inherently exhaustive. For example, `ComparisonResult` will never, ever have a case besides `orderedAscending`, `orderedSame`, and `orderedDescending`. All possible values fit into one of these three categories (other than those for which comparison is not a valid operation). You want an exhaustive switch for these.

  2) Those which you switch over but which could have cases added in the future. For example, `SKPaymentTransactionState` (representing the state of an in-app purchase) may sometimes need additional cases; one was already added in iOS 8 for a parental permission feature. You want a nonexhaustive switch for these, with a `default` clause containing some fallback behavior (like skipping the associated entry or showing an error message).

  3) Those which you almost never switch over at all; they are simply used as opaque inputs to APIs in the same framework. For example, very few pieces of code ever need to examine or use a `UIViewAnimationTransition`; they just need to pass one into the proper UIKit animation APIs. (Error types are another example—you should catch specific errors you know you want to handle specially, but you should never imagine that you have thought of all possible errors.) You basically don't care about the switch behavior of these enums; the compiler could even ban switching over them and you would probably never notice.

Category 1 enums should be exhaustive; category 2 and 3 should be nonexhaustive. So the question is, do category 1 enums outnumber category 2 and 3? You seem to suggest that they do, but I very much doubt that. My suspicion is that category 3 is the most common, followed by category 2, with category 1 bringing up the rear. The authors of the proposal say they surveyed Foundation enums to test this theory:

To see how this distinction will play out in practice, I investigated the public headers of Foundation in the macOS SDK. Out of all 60 or so NS_ENUMs in Foundation, only 6 of them are clearly exhaustive:

  • ComparisonResult
  • NSKeyValueChange / NSKeyValueSetMutationKind
  • NSRectEdge
  • FileManager.URLRelationship
  • maybe Decimal.CalculationError

...with a handful more that could go either way, such as Stream.Status. This demonstrates that there is a clear default for public enums, at least in Objective-C.

Maybe a survey of another library like Alamofire would show different results; if you think it would, by all means feel free to demonstrate it.

Having said that, I too think that `future` or `unknown` is a good idea, and I think we should strongly consider adding it. Doing so would restore many of the correctness benefits of exhaustiveness checking to nonexhaustive enums, significantly reducing the painful parts of this feature.

···

On Dec 20, 2017, at 1:00 AM, Sebastian Hagedorn via swift-evolution <swift-evolution@swift.org> wrote:

--
Brent Royal-Gordon
Architechies

I agree this would be useful. At the moment I have to hack around it with things like `var isFoo: Bool { if case .foo = self …`* with cases I commonly need, but this is definitely a feature that has come up before and I support. It is potentially related to getting the values through an accessor, which has also come up several times.

Sidenote, your `switch` example is actually trivial with existing syntax:

switch enumeration {
case .a(.c(let param)): // or just .a(.c) if you don't need the value
    print(param)
default:
    break
}

I use this from time to time switching over, e.g., optional enums.

*: ugliest syntax ever, and it can't even be used as a standalone expression.

···

On Dec 20, 2017, at 8:44 AM, Ethan Diamond via swift-evolution <swift-evolution@swift.org> wrote:

Hello everyone,

One major pain point I've run into with Swift is the inability to evaluate the case of an enum that has associated values in a way that just returns a bool. We've been given the ability in a switch statement:

enum Enum {
   case a(param: String)
   case b(param: String)
}

let enumeration: Enum = a(param: "Hi")
switch enumeration {
    case a:
      // Do something
    case b:
      // Do something
}

We'e been given the ability in the context of an if statement:

enum Enum {
   case a(param: String)
   case b(param: String)
}

let enumeration: Enum = a(param: "Hi")

if case .a = enumeration {
    // Do something
}

But without a basic was of getting a bool for if an enum is a given case, here's a list of things I can't do:

Where statements:

enum Enum {
   case a(param: Enum2)
   case b(param: Enum2)
}

enum Enum2 {
    case c(param: String)
    case d(param: String)
}

let enumeration: Enum = a(param: "Hi")
switch enumeration {
    case a(let inner) where [INNER CASE IS .c]
}

---------

Filter an array for a certain case:

Expertly explained by Erica Sadun here: Challenge: Filtering associated value enumeration arrays — Erica Sadun

---------

Nicely set a UIButton to hidden if an enum is a certain case:

enum State {
    case `default`
    case searching(results: [Result])
}

myButton.isHidden = [STATE IS .searching]

---------

I've run into this issue a ton of times because I tend to represent my views a State enums. I haven't seen anything on the board for plans for solving this issue, thought. Has there been any discussion about addressing it? Ideally I'd be able to do this:

enum Enum {
   case a(param: String)
   case b(param: String)
}

let enumeration: Enum = a(param: "Hi")

case .a = enumeration // Bool
case .a(let param) = enumeration // Bool, assigns "Hi" to "param"

Thanks!
Ethan

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

Thanks for the links, Colin. I think neither of these approaches are appropriate for Swift, largely for the same reason: they can't be used to define a library's API. Polymorphic variants are ad-hoc union types (much like tuples are ad-hoc structs) which—apart from having other problems in Swift previously discussed on this list—means that you can't add new cases to them. That is, any API which takes `(red(r: Int) | green(g: Int) | blue(b: Int))` today can't add `alpha(a: Int)` in the future, because that would change the type. ML-style exceptions have the opposite problem; they are completely unconstrained and so a client can add new "cases" without the library author being prepared. (Swift's error-handling model in general behaves a lot like this, except it doesn't get ML's knowledge of which errors might be thrown.)

I'd sum this up by saying Swift is differing from ML and from most other languages because it is solving a different problem. Swift is designed such that the compiler does not require whole-program knowledge to produce a correct, working, type-safe program. It will use any cross-module knowledge it can for optimization purposes, but the language semantics do not depend on it. And this is critical, as you note, for libraries with binary compatibility concerns.

Jordan

···

On Dec 20, 2017, at 10:13, Colin Barrett via swift-evolution <swift-evolution@swift.org> wrote:

I very much agree with the motivations for this proposal. It identifies a clear, urgent need.

I disagree that the proposed solution is a good solution. It makes a very significant, and confusing, change to the language that does not have much precedent in other languages. This goes against the stated design guidelines for the Swift language.

I would much rather see Swift follow the lead of other ML languages and introduce something like “polymorphic variants”[1] or Standard ML’s exceptions (which are "open” in this way by default)

Thanks for everyone's hard work, particularly the authors,
-Colin

[1]: https://realworldocaml.org/v1/en/html/variants.html#polymorphic-variants
[2]: https://www.cs.cmu.edu/~rwh/introsml/core/exceptions.htm

On Dec 19, 2017, at 5:58 PM, Ted Kremenek via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

The review of "SE 0192 - Non-Exhaustive Enums" begins now and runs through January 3, 2018.

The proposal is available here:

https://github.com/apple/swift-evolution/blob/master/proposals/0192-non-exhaustive-enums.md
Reviews are an important part of the Swift evolution process. All review feedback should be sent to the swift-evolution mailing list at:

https://lists.swift.org/mailman/listinfo/swift-evolution
or, if you would like to keep your feedback private, directly to the review manager.

When replying, please try to keep the proposal link at the top of the message:

Proposal link: https://github.com/apple/swift-evolution/blob/master/proposals/0192-non-exhaustive-enums.md
...
Reply text
...
Other replies
What goes into a review of a proposal?

The goal of the review process is to improve the proposal under review through constructive criticism and, eventually, determine the direction of Swift.

When reviewing a proposal, here are some questions to consider:

What is your evaluation of the proposal?

Is the problem being addressed significant enough to warrant a change to Swift?

Does this proposal fit well with the feel and direction of Swift?

If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?

How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

Thanks,
Ted Kremenek
Review Manager
_______________________________________________
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
https://lists.swift.org/mailman/listinfo/swift-evolution

The review of "SE 0192 - Non-Exhaustive Enums" begins now and runs through January 3, 2018.

The proposal is available here:

https://github.com/apple/swift-evolution/blob/master/proposals/0192-non-exhaustive-enums.md+1, it needs to happen (and ASAP, since it _will_ introduce source-breaking changes one way or the other).

I think non-exhaustive is the correct default. However, does this not mean that, by default, enums will be boxed because the receiver doesn’t know their potential size?

It's not always boxing, but yes, there will be more indirection if the compiler can't see the contents of the enum. (More on that below.)

That would mean that the best transition path for multi-module Apps would be to make your enums @exhaustive, rather than adding “default” statements (which is unfortunate, because I imagine when this change hits, the way you’ll notice will be complaints about missing “default” statements).

Yep, that's going to be the recommendation. The current minimal-for-review implementation does not do this but I'd like to figure out how to improve that; at the very least it might be a sensible thing to do in the migrator.

I do have some thoughts about how we could ease the transition (for this and other resilience-related changes), but it’s best to leave that to a separate discussion.

The one thing I’m still not overly fond of is the name - I would like us to keep the set of resilience/optimisation related keywords to a minimum. “exhaustive” for enums feels an awful lot like “fixed_contents” for structs - couldn’t we come up with a single name which could be used for both? I don’t think anybody’s going to want to use “exhaustive” for structs.

The core team was very focused on this too, but I contend that "exhaustive" is not about optimization and really isn't even about "resilience" (i.e. the ability to evolve a library's API while preserving binary compatibility). It's a semantic feature of an enum, much like 'open' or 'final' is for classes, and it affects what a client can or can't do with an enum. For libaries compiled from source, it won't affect performance at all—the compiler still knows the full set of cases in the current version of the library even if the programmer is forced to consider future versions.

I'm working on the fixed-contents proposal now, though it won't be ready for a while, and the same thing applies there: for structs compiled from source, the compiler can still do all the same optimizations. It's only when the library has binary compatibility concerns that we need to use extra indirection, and then "fixed-contents" becomes important. (As currently designed, it doesn't affect what clients can do with the struct at all.) This means that I don't expect a "normal" package author to write "fixed-contents" at all (however it ends up being spelled), whereas "exhaustive" is a fairly normal thing to consider whenever you make an enum public.

I hope that convinces you that "fixed-contents" and "exhaustive" don't need to have the same name. I don't think anyone loves the particular name "exhaustive", but as you see in the "Alternatives considered" we didn't manage to come up with anything significantly better. If reviewers all prefer something else we'd consider changing it.

Thanks for responding!
Jordan

···

On Dec 20, 2017, at 05:36, Karl Wagner via swift-evolution <swift-evolution@swift.org> wrote:

On 19. Dec 2017, at 23:58, Ted Kremenek via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Thanks for your feedback! I think you're missing the "libraries in the OS" aspect of this: if there are Swift libraries in iOS, and Apple ships a software update, some enums will get new cases. That means you can always end up in a default case if you were switching over those enums, and that's why 'switch!' has a '!' in it.

I sympathize with the "exhaustiveness diagnostics must be preserved" idea, but it simply doesn't account for package-based development. If libraries are allowed to add new cases without breaking source stability, they must not introduce new errors into clients. An app author cares about that because they want to use the latest Apple SDKs right away, even before any of their third-party library dependencies have released an update.

I do actually expect a number of third-party libraries to both mark their enums as exhaustive and add new cases in major releases (the ones that are allowed to be source-breaking). It's also a source-compatible change to mark a public enum as "exhaustive" that wasn't before, so I also expect an early flurry of bug reports when people realize this is important in Swift 5. But I think people will get the hang of it.

To be clear, I'm open to ideas in this space. It's just that both of the ideas in "Alternatives Considered" feel incomplete to me, and unfortunately we do have to do something in the near term for both imported enums and for Apple APIs (the overlays).

Jordan

···

On Dec 20, 2017, at 01:44, Jérôme Duquennoy via swift-evolution <swift-evolution@swift.org> wrote:

Hi guys,

I am part of the people mentioned in "Preserve exhaustiveness diagnostics for non-exhaustive enums" : I see the completeness check as a major feature of enums, that makes a difference between it and a list of constants in multiple cases.
So much that in the coding rules of the company I work for, the use of default case requires some specific justification at review time.

I have a positive opinion on this evolution if it comes with a solution to preserve exhaustiveness diagnostics. Otherwise, I think it lowers the ease of maintenance of code using external libraries.

One of the risk I fear is that libraries builders might just forget to add the @exhaustive keyword. So it might end up being a pretty common case to have to specify "I want this switch to be exhaustive", just to not loose on maintainability. This could lead to a situation similar to the "fileprivate", that ended up being used much more than expected.

It is not exactly in the scope of that review, but in the two solution for enforcing exhaustiveness drafted in that proposal, I would rather avoid the second one (the switch!). It would contradict the fact that as of today, the use of the exclamation mark in swift is a pretty clear sign that you are disabling some compiler-provided security at your own risk, and that it might lead to runtime crash (force unwrap, force cast, ...).
Here, requesting a switch to be exhaustive is adding one more compiler check. It cannot lead to a runtime crash.
I will be happy to discuss it further in a future review.

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

Thanks for the feedback, Dave! Responses inline.

The review of "SE 0192 - Non-Exhaustive Enums" begins now and runs through January 3, 2018.

The proposal is available here:

https://github.com/apple/swift-evolution/blob/master/proposals/0192-non-exhaustive-enums.md
When reviewing a proposal, here are some questions to consider:

What is your evaluation of the proposal?

A very strong -1. I do not believe this is the appropriate solution to the problem.

• While the goal of the proposal is to ensure the correctness of *client* code, it does nothing to enforce the correctness of evolving *library* code. As a library author, I can declare a public enum as “exhaustive”, yet still add a new case in the next release. Nothing in the proposal prevents me from doing this, yet doing so would obviously break any clients of my library.

There are some ideas listed to prevent this occurence in the proposal. Additionally, such a change may be desirable for a major version release of a library distributed as source. Nothing prevents someone from changing the type of an 'open' method either, but that can also be a breaking change.

• The name “exhaustive” is misleading for uninformed library authors. An author creates an enum and then thinks “is that all of the cases? Yep! OK, it’s @exhaustive”. Then the next evolution of the library occurs, new cases arise, and now the enum isn’t exhaustive to handle the new cases. So a case gets added, and the formerly-but-not-actually-exhaustive enum is re-stamped as exhaustive, because it once again handles all known cases. “Exhaustive” is not a strong enough name. It does not contain the idea of eternal permanence. Once an enum gets branded as exhaustive and shipped as such, *it can never change*. “Exhaustive” does not imply that, and the lack of that implication will confuse library authors.

I don't think anyone loves the current name, but we haven't come up with a better one (cf. "Alternatives considered"). Any suggestions?

(As mentioned above, I also don't think this is an impossible situation either. But we'd certainly want people to not do it by accident.)

• This proposal does not address the case of “publicly exhaustive enums with private cases”. Consider NSExpression.ExpressionType: when creating NSPredicates from format strings, it is easy to create sub-expressions whose expression types are not one of the publicly listed cases. Based on the proposal, NSExpression.ExpressionType would be correctly imported as a non-exhaustive enum. HOWEVER. There is nothing *stopping* a library author from declaring a publicly exhaustive enum (like NSExpression.ExpressionType), but having private cases that get leaked (accidentally or not) past the public barrier and end up in client code. This proposal does nothing to prevent that.

Swift does not currently support enums with private cases (as mentioned in the proposal), but if it did, such an enum could not be marked "exhaustive". On the C side, there's no way to check that no one's secretly using a private case, so we have to trust the author of the type to use the equivalent annotation responsibly.

The summary of these objections is this: you fundamentally cannot trust libraries that are not bundled with your application to not change in unexpected ways. Or in other words, if you don’t have the source code, you cannot trust it. And even if you do have the source code, it’s still questionable once you start bridging things in from other languages where this sort of safety is not enforced.

This is fair, and we should consider leaving an implicit "default: fatalError()" in even for supposedly exhaustive enums (at least those imported from C).

Jordan

···

On Dec 20, 2017, at 09:23, Dave DeLong via swift-evolution <swift-evolution@swift.org> wrote:

On Dec 19, 2017, at 3:58 PM, Ted Kremenek via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

To summarize the summary: Leaving a judgement of “exhaustive or not” up to fallible library authors isn’t safe.

To summarize the summary of the summary: people are a problem.

Is the problem being addressed significant enough to warrant a change to Swift?

Yes, the problem is significant, but in my opinion this is the wrong answer.

Does this proposal fit well with the feel and direction of Swift?

No. Implementing this proposal would give the appearance of safety while still leaving developers subtly but dangerously vulnerable to imperfectly written libraries (ie, all of them).

How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

I’ve been following the email threads, and I’ve spent years as a library author, both on Apple frameworks and my own personal libraries.

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

As an aside: there seems to be increasingly comments about proposals that say:

  1. This was discussed at the evaluation stage and rejected.
  2. This is how it is implemented in the patch.

And other comments along those lines. Neither the pre-proposal discussions nor the proposed implementation are intended to limit the scope of the review. Therefore I don’t think people should raise this as reasons. You should remember that the process is deliberately staged this way and different people may well be commenting (in fact the process rather assumes that people in the formal review will be a wider set of people).

Anyway gripe over.

Proposal link: https://github.com/apple/swift-evolution/blob/master/proposals/0192-non-exhaustive-enums.md
What is your evaluation of the proposal <x-apple-data-detectors://7>?

+1/2

I only give this a half because whilst it is important I can see three issues:

  1. It doesn’t seem very Swift like to have a different rule, default non-exhaustive, for public as opposed to non-public.

I guess people are still misunderstanding this. Within a module, switches must still be exhaustive, always, because the switch can never get out of sync with the enum definition. The "exhaustive" annotation only applies to clients across module boundaries.

  2. It doesn’t seem very Swift like to have the default the unsafe case.

I find it interesting that you call this the "unsafe" case. From my point of view <https://imgur.com/4OWRavD&gt;, it is very much in line with Swift's current design to have the default be "force clients to consider all possibilities" (like Optional), as well as "let library authors decide what a client should be able to rely on" (like 'open').

  3. Other languages have better solutions - see below under other languages

Is the problem being addressed significant enough to warrant a change to Swift?

Yes, Swift ABI compatibility going forwards is important

Does this proposal fit well with the feel and direction of Swift?

No. As mentioned above different rules for public and a non-safe default don’t see that Swift like.

If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?

Both Java and Scala have a better solution. In these languages enums (Scala calls them case classes) can implement protocols and the user of an enum rarely writes a switch statement, instead they call protocol methods. Enums in these languages are a fixed set of derived classes; i.e. normal OO programming rather than functional programming, which works well in the case of wanting to expand later the number of enum cases.

This is an interesting point that I probably should have expanded more in the "comparison with other languages" section. Swift has protocols too, and you can build most of the features of enums with protocols. (Set aside the inferior pattern-matching for now; that's something we could add to Swift.) This is currently much more heavyweight than enums, but let's roll with it.

Next we have to deal with C enums, which can have "private cases" or newly-added cases. Okay, maybe those get imported as structs instead, like NS_OPTIONS. That's not too bad, is it?

Now we're back in a place where 'enum' always means "exhaustive". That's good, right? Except…now we have a kind of type where the recommendation will be "don't use these in your library's public API; they'll be stuck that way forever", a trap waiting for library authors. That's a problem C has with structs, and it's something we don't want to bring into Swift. Making a library necessarily requires more care than making an app, but we don't want there to be techniques that only make sense within a module, and are discouraged when writing a library.

So we can implement this world in Swift. I just don't think it'll be a better one. When someone makes a library, the default should be safe for them. That means that they reserve the ability to add cases, and also to make the enum "exhaustive" in the future if it turns out that's the right thing to do.

(You're free to continue to disagree with me on this; thanks for getting me to write it out.)

Jordan

···

On Dec 19, 2017, at 20:15, Howard Lovatt via swift-evolution <swift-evolution@swift.org> wrote:

How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

Have followed the discussions. Used enums in Swift and other languages extensively.

-- Howard.

On 19 Dec 2017, at 12:58 pm, Ted Kremenek <kremenek@apple.com <mailto:kremenek@apple.com>> wrote:

When replying, please try to keep the proposal link at the top of the message:

Proposal link: https://github.com/apple/swift-evolution/blob/master/proposals/0192-non-exhaustive-enums.md
...
Reply text
...
Other replies
What goes into a review of a proposal?

The goal of the review process is to improve the proposal under review through constructive criticism and, eventually, determine the direction of Swift.

When reviewing a proposal, here are some questions to consider:

What is your evaluation of the proposal?

Is the problem being addressed significant enough to warrant a change to Swift?

Does this proposal fit well with the feel and direction of Swift?

If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?

How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

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

1 Like

Thanks for the response, Kevin. Xiaodi and others covered a lot of the points I would make but I'll add a few notes of my own.

https://github.com/apple/swift-evolution/blob/master/proposals/0192-non-exhaustive-enums.md
What is your evaluation of the proposal?

Overall I like it, but I have some issues.

I’m not completely sold yet on the idea that public enums should be non-exhaustive by default, as I believe every single public enum I’ve ever written definitely wants to be exhaustive. I’m tempted to say that enums exposed by frameworks that need to maintain future-compatibility (such as Apple’s frameworks) want non-exhaustive by default, but enums exposed by third-party frameworks that follow semantic versioning are likely to want exhaustive by default.

In fact, this brings to mind another difference between enums in Apple frameworks and enums in third-party Swift frameworks, which is the former is exclusively C-compatible enums whereas the latter is going to have a lot of enums that carry associated values. I have no data to support this but I’m inclined to think that it’s more likely for C-compatible enums to want to be non-exhaustive than it is for enums carrying associated values to want to be non-exhaustive.

So I guess I’m saying, I want more thought put on the topic of whether enums defined in Swift should actually default to non-exhaustive, and I’m now leaning towards the idea that they should remain exhaustive (but Obj-C enums will still default to non-exhaustive).

The main argument here for me isn't about frequency, although I like Brent and Sebastian's discussion about "three types of enums" (inherently exhaustive, non-exhaustive but useful to switch on, and things that are just inputs). What's most important here is that a library author who doesn't think about enum exhaustivity doesn't need to make a major-version release if they realize either

- they need a new case
- they meant to make the enum exhaustive

Both of these are non-source-breaking changes as currently designed. In a world where enums are exhaustive by default, they would both be source-breaking. This is similar to 'open': if a library author just does the usual thing the language lets you do, they don't have to worry about clients doing something they didn't mean to allow.

--

I don’t like that modules using @testable imports treat all enums as exhaustive. I can see why you want to do that, but being forced to handle non-exhaustive enums in unit tests seems like a valuable guard against accidentally publishing non-exhaustive enums that should be exhaustive.

This one's fair and I could go either way on it, but in practice my view is the same as Xiaodi's that it would be more annoying than useful. You can always import the library without using '@testable' in a different unit test source file.

--

I’m really not a fan of using a ‘default’ case in non-exhaustive enums. This means that if new cases are added, the compiler can’t help me find my switches. I’d really like to have an alternative way of saying “here’s how to behave in the event of an unknown enum case” without also making that a catch-all for unspecified-but-known cases. You already brought up this issue in the alternatives section, where you stated

Ultimately I decided not to include this in the proposal with the expectation is that switches over non-exhaustive enums should be uncommon.

But since non-exhaustive enums will be the default, I think this _will_ become quite common, if for no other reason than you’re using a library written by someone who forgot to mark enums as @exhaustive that really should be.

Regarding the comment that a ‘future’ case is impossible to test, so is a ‘default’ case on a switch that is otherwise exhaustive. In the latter you can test it by commenting out one of your existing cases. But you can do that with ‘future’ too, just by renaming it to ‘default’ first.

Neither of those are tests that can be automated, though, right? I mean, I guess you could set up a "fallthrough if set" global or something…

My response to Jerome goes into this a bit too: I'm not totally happy with what we have, but I'm not yet convinced the alternatives sufficiently solve the problem either.

--

In the “Use-side” section it says that a switch without a default case will produce a warning in Swift 4 mode. But in Swift 4 mode all enums are exhaustive by default and the @exhaustive annotation does nothing. These two things can’t both be true or else Swift 4 mode will emit warnings on switches over enums that are very clearly supposed to be exhaustive enums.

The change of C enums from exhaustive to non-exhaustive will apply to Swift 4 mode as well (and even Swift 3 mode, if that sticks around). The reason for this is that C enums with private cases are very broken right now: if an unexpected value makes its way into a switch in today's Swift, the behavior is undefined (as in, no guaranteed trap, no memory safety).

If reviewers or the core team object to this part of the proposal, we would leave C enums as 'exhaustive' in Swift 4 mode, but at least make a switch over them deterministically trap if an invalid value shows up.

Jordan

···

On Dec 19, 2017, at 15:31, Kevin Ballard via swift-evolution <swift-evolution@swift.org> wrote:

On Dec 19, 2017, at 2:58 PM, Ted Kremenek <kremenek@apple.com <mailto:kremenek@apple.com>> wrote:

Is the problem being addressed significant enough to warrant a change to Swift?

Yes

Does this proposal fit well with the feel and direction of Swift?

Yes

How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

A quick reading, followed by a few re-readings as I composed this email.

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

This would be easily solved if pattern matching was available as an expression, such as in Haskell, OCaml / Standard ML, and Scala / Kotlin. :-)

···

On Dec 20, 2017, at 11:44 AM, Ethan Diamond via swift-evolution <swift-evolution@swift.org> wrote:

Hello everyone,

One major pain point I've run into with Swift is the inability to evaluate the case of an enum that has associated values in a way that just returns a bool. We've been given the ability in a switch statement:

enum Enum {
   case a(param: String)
   case b(param: String)
}

let enumeration: Enum = a(param: "Hi")
switch enumeration {
    case a:
      // Do something
    case b:
      // Do something
}

We'e been given the ability in the context of an if statement:

enum Enum {
   case a(param: String)
   case b(param: String)
}

let enumeration: Enum = a(param: "Hi")

if case .a = enumeration {
    // Do something
}

But without a basic was of getting a bool for if an enum is a given case, here's a list of things I can't do:

Where statements:

enum Enum {
   case a(param: Enum2)
   case b(param: Enum2)
}

enum Enum2 {
    case c(param: String)
    case d(param: String)
}

let enumeration: Enum = a(param: "Hi")
switch enumeration {
    case a(let inner) where [INNER CASE IS .c]
}

---------

Filter an array for a certain case:

Expertly explained by Erica Sadun here: Challenge: Filtering associated value enumeration arrays — Erica Sadun

---------

Nicely set a UIButton to hidden if an enum is a certain case:

enum State {
    case `default`
    case searching(results: [Result])
}

myButton.isHidden = [STATE IS .searching]

---------

I've run into this issue a ton of times because I tend to represent my views a State enums. I haven't seen anything on the board for plans for solving this issue, thought. Has there been any discussion about addressing it? Ideally I'd be able to do this:

enum Enum {
   case a(param: String)
   case b(param: String)
}

let enumeration: Enum = a(param: "Hi")

case .a = enumeration // Bool
case .a(let param) = enumeration // Bool, assigns "Hi" to "param"

Thanks!
Ethan

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

Thanks for your thoughts.

I think 3) is indeed a very common case, but also not affected by this proposal or the general issue at all. Unless I’m missing something, I think it makes sense to limit the discussion to those enums that are typically switched over in client code.

I don’t think 1) outnumbers 2), which is exactly why I think it is important to have some sort of exhaustive-checking for 2). If 2) was an edge cases, I wouldn’t mind, because 1) behaves the same way with or without the implementation of this proposal.

What I was trying to say is: 99% of the times I switch over an enum, I do want to consider all available cases in my code, which also means I want to be informed (by a warning) if I’m missing something. Without anything like `future`, I’ll end up with switches that used to be exhaustive, but are not anymore when the library author adds a case, and I don’t learn about it until I hit such a case at runtime (documentation aside), which may rarely happen. I’m aware that there may be a period of time during which I haven’t updated my code and my code has to deal with unexpected cases, but as soon as I get to compile my code with the current SDK, I’d like to learn about new cases.

···

On 20. Dec 2017, at 11:39, Brent Royal-Gordon <brent@architechies.com> wrote:

On Dec 20, 2017, at 1:00 AM, Sebastian Hagedorn via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

The expectation is that switches over non-exhaustive enums are uncommon.

Basically every time I interact with an enum, I switch over it, to make sure I don’t forget anything, and to make sure I reconsider my code if the library changes. Since most SDK enums will become non-exhaustive, this is the 99% case for me.

Is that typical of your interactions with, say, `UIViewAnimationTransition`?

There are three categories of enums in the SDK:

  1) Those which you switch over and which are inherently exhaustive. For example, `ComparisonResult` will never, ever have a case besides `orderedAscending`, `orderedSame`, and `orderedDescending`. All possible values fit into one of these three categories (other than those for which comparison is not a valid operation). You want an exhaustive switch for these.

  2) Those which you switch over but which could have cases added in the future. For example, `SKPaymentTransactionState` (representing the state of an in-app purchase) may sometimes need additional cases; one was already added in iOS 8 for a parental permission feature. You want a nonexhaustive switch for these, with a `default` clause containing some fallback behavior (like skipping the associated entry or showing an error message).

  3) Those which you almost never switch over at all; they are simply used as opaque inputs to APIs in the same framework. For example, very few pieces of code ever need to examine or use a `UIViewAnimationTransition`; they just need to pass one into the proper UIKit animation APIs. (Error types are another example—you should catch specific errors you know you want to handle specially, but you should never imagine that you have thought of all possible errors.) You basically don't care about the switch behavior of these enums; the compiler could even ban switching over them and you would probably never notice.

Category 1 enums should be exhaustive; category 2 and 3 should be nonexhaustive. So the question is, do category 1 enums outnumber category 2 and 3? You seem to suggest that they do, but I very much doubt that. My suspicion is that category 3 is the most common, followed by category 2, with category 1 bringing up the rear. The authors of the proposal say they surveyed Foundation enums to test this theory:

To see how this distinction will play out in practice, I investigated the public headers of Foundation in the macOS SDK. Out of all 60 or so NS_ENUMs in Foundation, only 6 of them are clearly exhaustive:

  • ComparisonResult
  • NSKeyValueChange / NSKeyValueSetMutationKind
  • NSRectEdge
  • FileManager.URLRelationship
  • maybe Decimal.CalculationError

...with a handful more that could go either way, such as Stream.Status. This demonstrates that there is a clear default for public enums, at least in Objective-C.

Maybe a survey of another library like Alamofire would show different results; if you think it would, by all means feel free to demonstrate it.

Having said that, I too think that `future` or `unknown` is a good idea, and I think we should strongly consider adding it. Doing so would restore many of the correctness benefits of exhaustiveness checking to nonexhaustive enums, significantly reducing the painful parts of this feature.

--
Brent Royal-Gordon
Architechies