SE-0192 — Non-Exhaustive Enums

Actually, from the other email thread about this same topic (thank god forums are almost here), I see the proposed syntax “final switch” for what I referred to as “switch!”, which I prefer.

···

On Dec 28, 2017, at 12:17 AM, Riley Testut via swift-evolution <swift-evolution@swift.org> wrote:

-1.

I agree this is a problem, but I think this is the wrong solution. I think the solution should be on the client side, not on the framework author’s side.

I would be fine if enums from imported modules are non-exhaustive, as long as I can choose to treat them as exhaustive if I want to. And in that case, if a new case is introduced, I think a fatal error is a reasonable result.

The proposed “switch!” command would do just this, and I think that is the better answer for this. Adding an @exhaustive attribute doesn’t actually prevent someone from adding a case anyway, which I think is a big (and not really solvable) issue :man_shrugging:

I know much has been said about this, but it’s just my 2c.

On Dec 27, 2017, at 9:42 AM, Thorsten Seitz via swift-evolution <swift-evolution@swift.org> 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
_______________________________________________
swift-evolution mailing list
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

Could this also be used on functions to make them inlinable?
i.e. The body of the function has been frozen.

@frozen
public func a()

@available(*, frozen)
public func b()

@available(swift, introduced: 4.1, frozen: 5.0)
public func c()

<https://github.com/apple/swift-evolution/blob/master/proposals/0192-non-exhaustive-enums.md&gt;
<https://github.com/apple/swift-evolution/blob/master/proposals/0193-cross-module-inlining-and-specialization.md&gt;

-- Ben

···

On 21 Dec 2017, at 03:32, John McCall wrote:

On Dec 20, 2017, at 10:16 PM, Brent Royal-Gordon wrote:

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

  • What is your evaluation of the proposal?

I am pleased with the broad strokes of this design. I have quibbles with three areas:

1. The `@exhaustive` attribute may be confusing because the term doesn't suggest versioning. My best alternative suggestion is `@frozen`, which matches existing programming terminology: something that has been frozen will not be changed in the future.

I rather like @frozen. We could use that across language features, so that we don't end up with a keyword per kind of declaration.

YES :clap:. This is the conclusion I’ve been coming to as well as I’ve been mulling it over.

Exhaustiveness is *only* an issue for modules that are updated independently of my app (ie, via an OS or other externally-linked library changing). Modules that are shipped with my app are unaffected with this.

I’d really love it if the build system could know that “this module is bundled with your app and therefore switches on its enums don't need a default case but this other module isn’t bundled with the app, and so therefore switches on its enums DO needs a default case”.

Personally, I’m falling on the side of “all enums from external modules are non-exhaustive”, but I recognize there are some cases where that’s semantically incorrect (Optional<T>, for one; NSComparisonResult for another). In those cases, I think I’d prefer to have an attribute on the enum that is overly long and scary in order to emphasize that, once shipped, this enum must never change again. Something like:

@mustNeverEverEverChangeInAFutureRelease public enum Optional<T> {
  case none
  case some(T)
}

Dave

···

On Jan 1, 2018, at 10:47 PM, Chris Lattner via swift-evolution <swift-evolution@swift.org> wrote:

On Dec 31, 2017, at 12:14 PM, Matthew Johnson via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

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

Thanks for writing this up - you’ve explained a common concern in an interesting way:

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.

..

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.

I think that this could be the crux of some major confusion, the root of which is 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). Consider:

1) When dealing with independently updated binary packages, your code *has* to implement some behavior for unexpected cases if the enum is non-exhaustive. It isn’t acceptable to not handle that case, and it isn’t acceptable to abort because then your app will start crashing when a new OS comes out. You have to build some sort of fallback into your app.

2) When dealing with a source package that contributes to your app (e.g. through SwiftPM), *YOU* control when you update that package, and therefore it is entirely reasonable to exhaustively handle enums even if that package owner didn’t “intend” for them to be exhaustive. When *you* chose to update the package, you get the “unhandled case” error, and you have maximal “knowability” about the package’s behavior.

It seems that your concern stems from the fact that the feature as proposed is aligned around module boundaries, and therefore overly punishes source packages like #2. I hope you agree that in case #1, that the feature as proposed is the right and only thing we can do: you really do have to handle unknown future cases somehow.

If I’m getting this right, then maybe there is a variant of the proposal that ties the error/warning behavior to whether or not a module is a source module vs a binary module. The problem with that right now is that we have no infrastructure in the language to know this…

1) When dealing with independently updated binary packages, your code
*has* to implement some behavior for unexpected cases if the enum is
non-exhaustive. It isn’t acceptable to not handle that case, and it isn’t
acceptable to abort because then your app will start crashing when a new OS
comes out. You have to build some sort of fallback into your app.

Yes, absolutely.

2) When dealing with a source package that contributes to your app (e.g.
through SwiftPM), *YOU* control when you update that package, and therefore
it is entirely reasonable to exhaustively handle enums even if that package
owner didn’t “intend” for them to be exhaustive. When *you* chose to
update the package, you get the “unhandled case” error, and you have
maximal “knowability” about the package’s behavior.

I agree 100%

It seems that your concern stems from the fact that the feature as
proposed is aligned around module boundaries, and therefore overly punishes
source packages like #2. I hope you agree that in case #1, that the
feature as proposed is the right and only thing we can do: you really do
have to handle unknown future cases somehow.

I take slight exception to the word “only” here: the proposal as written
does not offer compile-time feedback to inform client programmers that
external enums on which they “switch” have new cases, because they are
required to include a “default” clause.

Yes, those client programmers need to handle unknown cases, but they *also*
deserve the compiler’s assistance in identifying when such cases have been
added. I don’t know if a “future” keyword is the best approach, but we
ought to include some way to warn (not error) when compiling a switch
statement that the author intends to be exhaustive-over-all-known-cases
while also handling unknown ones.

If I’m getting this right, then maybe there is a variant of the proposal
that ties the error/warning behavior to whether or not a module is a source
module vs a binary module. The problem with that right now is that we have
no infrastructure in the language to know this…

-Chris

If we can make that work, this proposal will be much more palatable.

Nevin

···

On Tue, Jan 2, 2018 at 12:47 AM, Chris Lattner via swift-evolution < swift-evolution@swift.org> wrote:

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

Thanks for writing this up - you’ve explained a common concern in an interesting way:

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.

..

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.

I think that this could be the crux of some major confusion, the root of which is 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). Consider:

1) When dealing with independently updated binary packages, your code *has* to implement some behavior for unexpected cases if the enum is non-exhaustive. It isn’t acceptable to not handle that case, and it isn’t acceptable to abort because then your app will start crashing when a new OS comes out. You have to build some sort of fallback into your app.

2) When dealing with a source package that contributes to your app (e.g. through SwiftPM), *YOU* control when you update that package, and therefore it is entirely reasonable to exhaustively handle enums even if that package owner didn’t “intend” for them to be exhaustive. When *you* chose to update the package, you get the “unhandled case” error, and you have maximal “knowability” about the package’s behavior.

It seems that your concern stems from the fact that the feature as proposed is aligned around module boundaries, and therefore overly punishes source packages like #2. I hope you agree that in case #1, that the feature as proposed is the right and only thing we can do: you really do have to handle unknown future cases somehow.

If I’m getting this right, then maybe there is a variant of the proposal that ties the error/warning behavior to whether or not a module is a source module vs a binary module. The problem with that right now is that we have no infrastructure in the language to know this…

Hi Chris, thanks for your reply.

The concern you describe isn’t exactly what I was describing but it is related. John McCall recently posted a sketch of a solution to the concern you describe which looked great to me. I don’t have time to look up the link this morning but I think it was in this review thread.

The actual concern I am describing is where a 3rd party library (or app) wants to switch over a non-exhaustive enum provided by a module that is a binary (not source) dependency. The author of the 3rd party library may have a legitimate reason to switch over an enum despite the author of the binary module reserving the right to add additional cases.

When this circumstance arises they will do it using the tools provided by the language. Regardless of the final language solution they obviously need to cover unknown cases - their library could be shipping on a device which receives an update to the binary dependency that contains a new case. I agree with you that a language-defined crash is not appropriate. The author of the switch must take responsibility for the behavior of unknown cases.

I am arguing that these “pseudo-exhaustive” switch statements will exist in the wild. The crucial point of contention is whether or not the language provides assistance to the author of the 3rd party library in updating their library when the enum provided by the binary dependency changes. Is the author forced to use a `default` case which turns of exhaustiveness checking? Or are they able to use an alternative mechanism for handling unknown cases which does not turn off exhaustiveness checking - all statically known cases must be covered. The most common example of such a mechanism is the `future` (or perhaps `unknown`) case which would only be used for cases that are not statically known.

This facility will of course help authors of these switch statements make the necessary updates as the enum vended by the binary dependency changes. It will also help alert authors of apps that depend on that 3rd party library (which will usually be a source dependency). If the author of the app attempts to rebuild the dependency against a new SDK with added cases the library will fail to build, alerting the user that they should update the 3rd party library.

My position is that if there are reasonable use cases for these kinds of “pseudo-exhaustive” switches then the language should provide exhaustiveness checking of statically known cases via some mechanism that authors can opt-in to using. It’s ok with me if this is a relatively esoteric feature. It won’t be commonly needed, but when it is necessary it will provide significant value.

IIRC there were some reasonable examples of these kinds of switches posted in earlier threads on this topic. I don’t have time to look those up right now either but it would be good for the core team to be aware of them before making a final decision.

I am only aware of two arguments against this kind of “pseudo-exhaustive” switch. One is that users should not attempt to switch over an enum that a library author does not intend to be exhaustive (i.e. it is an “input-only” enum). The other is that a `future` or `unknown` case is not testable.

The first argument is a moral one which I believe should not carry much weight relative to concrete, pragmatic counter-examples such as those that (IIRC) were provided on this list in the past.

The second argument doesn’t make sense to me: as I noted in my review post, the code path is equally untestable when a `default` case is used, but all statically known cases are matched in earlier patterns. The testability problem of non-exhaustive enums is orthogonal to the issue of language support for “pseudo-exhaustive” switches.

This is the line of reasoning which leads me to conclude that we should dig up the concrete examples which have been provided and evaluate them for merit. If we can’t discard them as a bad coding practice for which a better solution is available then we should strongly consider providing language support for these use cases. The existence of such use cases should also motivate a solution to the testability problem.

- Matthew

···

Sent from my iPad

On Jan 1, 2018, at 11:47 PM, Chris Lattner <clattner@nondot.org> wrote:
On Dec 31, 2017, at 12:14 PM, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:

-Chris

We use enums also when modeling JSON responses from our servers. To allow the server side to add new cases without breaking existing clients, we always add an undefined case to our enums.

Binary frameworks might do the same when exporting enums. When clients compile to a newer version of the framework, new cases will be added and checked by the compiler for exhausiveness. The new version will still contain undefined though for the next binary / server-side change.

Cheers
Marc

···

Von: swift-evolution@swift.org

Gesendet: 2. Januar 2018 6:47 vorm.

An: matthew@anandabits.com

Antworten: clattner@nondot.org

Cc: swift-evolution@swift.org

Betreff: Re: [swift-evolution] [Review] SE 0192 - Non-Exhaustive Enums

On Dec 31, 2017, at 12:14 PM, Matthew Johnson via swift-evolution swift-evolution@swift.org wrote:

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

Thanks for writing this up - you’ve explained a common concern in an interesting way:

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.

..

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.

I think that this could be the crux of some major confusion, the root of which is 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). Consider:

  1. When dealing with independently updated binary packages, your code has to implement some behavior for unexpected cases if the enum is non-exhaustive. It isn’t acceptable to not handle that case, and it isn’t acceptable to abort because then your app will start crashing when a new OS comes out. You have to build some sort of fallback into your app.

  2. When dealing with a source package that contributes to your app (e.g. through SwiftPM), YOU control when you update that package, and therefore it is entirely reasonable to exhaustively handle enums even if that package owner didn’t “intend” for them to be exhaustive. When you chose to update the package, you get the “unhandled case” error, and you have maximal “knowability” about the package’s behavior.

It seems that your concern stems from the fact that the feature as proposed is aligned around module boundaries, and therefore overly punishes source packages like #2. I hope you agree that in case #1, that the feature as proposed is the right and only thing we can do: you really do have to handle unknown future cases somehow.

If I’m getting this right, then maybe there is a variant of the proposal that ties the error/warning behavior to whether or not a module is a source module vs a binary module. The problem with that right now is that we have no infrastructure in the language to know this…

-Chris

I think this is a good summary.

I agree that we need to handle unexpected cases from changes to a binary somehow. The main issue is that, when forcing developers to use ‘default’ to do it, it also brings along unwanted semantics that prevent warnings in the very common use case of a 3rd party library being updated. The proposal is close to what we want, but unacceptable in it’s current form IMHO.

I see two possible solutions to this:

1) A new ‘unexpected:’ construct in switches, which handle cases not known at compile time. This is my favorite option because I think it gives us the most freedom to evolve the language in the future. For example, if we end up adding subclassable enums in Swift 12 or something, we will already have a mechanism in place to deal with the issues that arise.

2) We version the annotation, and then require any additions to map themselves back an existing case for code which doesn’t know about the new case.

I also feel like framing the issue as being about exhaustiveness of enums is confusing. It really doesn’t have to be about exhaustiveness at all. I would much prefer an annotation like @frozen which can be used for structs as well. With @exhaustive you have to start talking about module boundaries, because all enums are already exhaustive within the module boundary. You may find people adding @exhaustive for the wrong reasons (i.e. they want their local enum to be exhaustive). Framing the issue as something where we promise not to change the API is a much easier mental model to explain to people.

Thanks,
Jon

···

On Jan 1, 2018, at 9:48 PM, Chris Lattner via swift-evolution <swift-evolution@swift.org> wrote:

On Dec 31, 2017, at 12:14 PM, Matthew Johnson via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

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

Thanks for writing this up - you’ve explained a common concern in an interesting way:

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.

..

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.

I think that this could be the crux of some major confusion, the root of which is 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). Consider:

1) When dealing with independently updated binary packages, your code *has* to implement some behavior for unexpected cases if the enum is non-exhaustive. It isn’t acceptable to not handle that case, and it isn’t acceptable to abort because then your app will start crashing when a new OS comes out. You have to build some sort of fallback into your app.

2) When dealing with a source package that contributes to your app (e.g. through SwiftPM), *YOU* control when you update that package, and therefore it is entirely reasonable to exhaustively handle enums even if that package owner didn’t “intend” for them to be exhaustive. When *you* chose to update the package, you get the “unhandled case” error, and you have maximal “knowability” about the package’s behavior.

It seems that your concern stems from the fact that the feature as proposed is aligned around module boundaries, and therefore overly punishes source packages like #2. I hope you agree that in case #1, that the feature as proposed is the right and only thing we can do: you really do have to handle unknown future cases somehow.

If I’m getting this right, then maybe there is a variant of the proposal that ties the error/warning behavior to whether or not a module is a source module vs a binary module. The problem with that right now is that we have no infrastructure in the language to know this…

-Chris

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

[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!

I do like this spelling of `@frozen`, and `unknown case` looks perfectly cromulent to me. If this is the path to go down, I'd urge more explicit design as to what happens when `unknown case` and `default` are mixed. I would imagine the most consistent design would be:

`unknown case` should allow `default` to be omitted if the switch is otherwise exhaustive, obviously.
`default` should allow `unknown case` to be omitted, just like any other case may then be omitted.
`unknown case` before `default` should be allowed, just like any other case before `default`; in that case, only known cases not otherwise matched reach the `default`.
`default` before `unknown case` makes the latter unreachable, just like any other case after `default`.

The issue here remains that of testability. I wonder if, for such purposes, unknown case should be instantiable when testably imported, with some grammar. In its simplest and yet most exotic form, we could imagine code that testably imports the enum to be allowed to instantiate any made-up case whatsoever (e.g., `@testable import Foo.MyEnum; let x = MyEnum.asdfasdfasdfNonexistent`).

What should happen when an unknown case instantiated via a testability mechanism is passed to the library that vended the enum (which is able to truly exhaustively switch over the enum)? I would like to see a solution to the testability problem and answering this question seems to be the most difficult part of finding a solution. The best answer is not obvious to me.

···

On Jan 2, 2018, at 8:45 PM, Xiaodi Wu via swift-evolution <swift-evolution@swift.org> wrote:
On Tue, Jan 2, 2018 at 8:07 PM, Jordan Rose via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

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

[Proposal: https://github.com/apple/swift-evolution/blob/mas
ter/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

+1 to warning instead of error
+1 to unknown/unexpected case
+1 to “@frozen” or any other reasonable spelling, they are all fine by me.

+1 to “@tangled” because abi is complicated

The one remaining problem to solve is making sure multi-module apps can
leave out the unknown/unexpected case on enums from modules which are part
of the app itself and thus cannot be updated independently of it. John
McCall’s version-locking plan sounds promising, though we should explore
the available options before finalizing a course.

Perhaps we need a concept of submodules, or supermodules, or some other
way to demarcate the boundaries of a resilience domain.

Nevin

i would support a proper submodule system over some verson-locking system
that only the most advanced users will probably know about. i think modules
should be one level higher than what they’re currently being used for right
now for lack of a better alternative (one application should never have to
define more than one capital M Module). submodules shouldn’t be that hard
to implement, though the submodule names should be part of ABI to avoid
name mangling problems

···

On Tue, Jan 2, 2018 at 11:45 PM, Nevin Brackett-Rozinsky via swift-evolution <swift-evolution@swift.org> wrote:

On Tue, Jan 2, 2018 at 9:07 PM, Jordan Rose via swift-evolution < > swift-evolution@swift.org> wrote:

IMO this is still too large of a hammer for this problem.

This whole “unexpected case” thing is only a problem when you’re linking libraries that are external to/shipped independently of your app. Right now, the *only* case where this might exist is Swift on the server. We *might* run in to this in the future once the ABI stabilizes and we have the Swift libraries shipping as part of iOS/macOS/Linux. Other than this, unexpected enum cases won’t really be a problem developers have to deal with.

Because this will be such a relatively rare problem, I feel like a syntax change like what’s being proposed is a too-massive hammer for such a small nail.

What feels far more appropriate is:

:a: Teaching the compiler/checker/whatever about the linking semantics of modules. For modules that are packaged inside the final built product, there is no need to deal with any unexpected cases, because we already have the exhaustiveness check appropriate for that scenario (regardless of whether the module is shipped as a binary or compiled from source). The app author decides when to update their dependencies, and updating those dependencies will produce new warnings/errors as the compiler notices new or deprecated cases. This is the current state of things and is completely orthogonal to the entire discussion.

John McCall sketched out a vision of what a solution to this might look like here: https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20171218/042333.html\.

and

:b: Adding an attribute (@frozen, @tangled, @moana, @whatever) that can be used to decorate an enum declaration. This attribute would only need to be consulted on enums where the compiler can determine that the module will *not* be part of the final built product. (Ie, it’s an “external” module, in my nomenclature). This, then, is a module that can update independently of the final app, and therefore there are two possible cases:

  1️⃣ If the enum is decorated with @frozen, then I, as an app author, have the assurance that the enum case will not change in future releases of the library, and I can safely switch on all known cases and not have to provide a default case.

  2️⃣ If the enum is NOT decorated with @frozen, then I, as an app author, have to account for the possibility that the module may update from underneath my app, and I have to handle an unknown case. This is simple: the compiler should require me to add a “default:” case to my switch statement. This warning is produced IFF: the enum is coming from an external module, and the enum is not decorated with @frozen.

This does not help people who need to write a switch statement over an enum vended by a module that ships with the OS keep their code up to date as the module adds new cases. I find the example of `SKPaymentTransactionState` provided by Brent Royal-Gordon here: [swift-evolution] Enums and Source Compatibility to be compelling. There are rare but legitimate reasons to switch over all known cases of a non-@frozen enum that ship with the OS. These use cases deserve proper language support. I think Jordan’s solution strikes a good balance.

···

On Jan 3, 2018, at 11:07 AM, Dave DeLong via swift-evolution <swift-evolution@swift.org> wrote:

==========

With this proposal, we only have one thing to consider: the spelling of @frozen/@moana/@whatever that we decorate enums in external modules with. Other than that, the existing behavior we currently have is completely capable of covering the possibilities: we just keep using a “default:” case whenever the compiler can’t guarantee that we can be exhaustive in our switching.

Where the real work would be is teaching the compiler about internally-vs-externally linked modules.

Dave

On Jan 2, 2018, at 7:07 PM, Jordan Rose via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

[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

_______________________________________________
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

This is a very nice revision. One bikeshedding thought:

Since "unknown case" is presented as a special kind of "default", can't be
mixed with "default", and can't be used in case patterns, why not "default
unknown" (or "unknown default") instead of "unknown case"?

···

On Wed, Jan 3, 2018 at 8:05 PM, Jordan Rose via swift-evolution < swift-evolution@swift.org> wrote:

On Jan 2, 2018, at 18:07, Jordan Rose <jordan_rose@apple.com> wrote:

[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!

I ended up doing these in the opposite order, writing up the new proposal
first and not yet responding to the discussion that's further out. You can
read my revisions at https://github.com/apple/swift-evolution/pull/777\.

In particular, I want to at least address:
- Dave D and Drew C's points about versioned libraries / linking semantics
of modules.
- Jason M's point about migration
and I'll do one more pass over the thread to see if there's anything else
I didn't address directly. (That doesn't mean everyone who disagrees, just
messages where I think there's more I can do to explain why the proposal is
the way it is.)

Jordan

P.S. Enjoying the Disney references. Thanks, Nevin and Dave. :-)

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

Hi, Dave. You're right, all these points are worth addressing. I'm going to go in sections.

This whole “unexpected case” thing is only a problem when you’re linking libraries that are external to/shipped independently of your app. Right now, the *only* case where this might exist is Swift on the server. We *might* run in to this in the future once the ABI stabilizes and we have the Swift libraries shipping as part of iOS/macOS/Linux. Other than this, unexpected enum cases won’t really be a problem developers have to deal with.

I wish this were the case, but it is not. Regardless of what we do for Swift enums, we are in dire need of a fix for C enums. Today, if a C enum doesn't have one of the expected values, the behavior is undefined in the C sense (as in, type-unsafe, memory-unsafe, may invoke functions that shouldn't be invoked, may not invoke functions that should be invoked, etc).

Obviously that's an unacceptable state of affairs; even without this proposal we would fix it so that the program will deterministically trap instead. This isn't perfect because it results in a (tiny) performance and code size hit compared to C, but it's better than leaving such a massive hole in Swift's safety story.

The trouble is that many enums—maybe even most enums—in the Apple SDK really are expected to grow new cases, and the Apple API authors rely on this. Many of those—probably most of them—are the ones that Brent Royal-Gordon described as "opaque inputs", like UIViewAnimationTransition, which you're unlikely to switch over but which the compiler should handle correctly if you do. Then there are the murkier ones like SKPaymentTransactionState.

I'm going to come dangerously close to criticizing Apple and say I have a lot of sympathy for third-party developers in the SKPaymentTransactionState case. As Karl Wagner said, there wasn't really any way an existing app could handle that case well, even if they had written an 'unknown case' handler. So what could the StoreKit folks have done instead? They can't tell themselves whether your app supports the new case, other than the heavy-handed "check what SDK they compiled against" that ignores the possibility of embedded binary frameworks. So maybe they should have added a property "supportsDeferredState" or something that would have to be set before the new state was returned.

(I'll pause to say I don't know what consideration went into this API and I'm going to avoid looking it up to avoid perjury. This is all hypothetical, for the next API that needs to add a case.)

Let's say we go with that, a property that controls whether the new case is ever passed to third-party code. Now the new case exists, and new code needs to switch over it. At the same time, old code needs to continue working. The new enum case exists, and so even if it shouldn't escape into old code that doesn't know how to handle it, the behavior needs to be defined if it does. Furthermore, the old code needs to continue working without source changes, because updating to a new SDK must not break existing code. (It can introduce new warnings, but even that is something that should be considered carefully.)

So: this proposal is designed to handle the use cases both for Swift library authors to come and for C APIs today, and in particular Apple's Objective-C SDKs and how they've evolved historically.

There's another really interesting point in your message, which Karl, Drew Crawford, and others also touched on.

Teaching the compiler/checker/whatever about the linking semantics of modules. For modules that are packaged inside the final built product, there is no need to deal with any unexpected cases, because we already have the exhaustiveness check appropriate for that scenario (regardless of whether the module is shipped as a binary or compiled from source). The app author decides when to update their dependencies, and updating those dependencies will produce new warnings/errors as the compiler notices new or deprecated cases. This is the current state of things and is completely orthogonal to the entire discussion.

This keeps sneaking into discussions and I hope to have it formalized in a proposal soon. On the library side, we do want to make a distinction between "needs binary compatibility" and "does not need binary compatibility". Why? Because we can get much better performance if we know a library is never going to change. A class will not acquire new dynamic-dispatch members; a stored property will not turn into a computed property; a struct will not gain new stored properties. None of those things affect how client code is written, but they do affect what happens at run-time.

Okay, so should we use this as an indicator of whether an enum can grow new cases? (I'm going to ignore C libraries in this section, both because they don't have this distinction and because they can always lie anyway.)

- If a library really is shipped separately from the app, enums can grow new cases, except for the ones that can't. So we need some kind of annotation here. This is your "B" in the original email, so we're all agreed here.

- If a library is shipped with the app, there's no chance of the enum growing a new case at run time. Does that mean we don't need a default case? (Or "unknown case" now.)

The answer here is most easily understood in terms of semantic versioning <https://semver.org/&gt;\. If adding a new enum case is a source-breaking change, then it's a source-breaking change, requiring a major version update. The app author decides when to update their dependencies, and might hold off on getting a newer version of a library because it's not compatible with what they have.

If adding a new enum case is not a source-breaking change, then it can be done in a minor version release of a library. Like deprecations, this can produce new warnings, but not new errors, and it should not (if done carefully) break existing code. This isn't a critical feature for a language to have, but I would argue (and have argued) that it's a useful one for library developers. Major releases still exist; this just makes one particular kind of change valid for minor releases as well.

(It also feels very subtle to me that 'switch' behaves differently based on where the enum came from. I know this whole proposal adds complexity to the language, and I'd like to keep it as consistent as possible.)

Okay, so what if we did this based on the 'import' rather than on how the module was compiled—Karl's `@static import`? That feels a little better to me because you can see it in your code. (Let's ignore re-exported modules for now.) But now we have two types of 'import', only one of which can be used with system libraries. That also makes me uncomfortable. (And to be fair, it's also something that can be added after the fact without disturbing the rest of the language.)

Finally, it's very important that whatever you do in your code doesn't necessarily apply to your dependencies. We've seen in practice that people are not willing to edit their dependencies, even to handle simple SDK changes or language syntax changes (of which there are hopefully no more). That's why I'm pushing the source compatibility aspect so hard, even for libraries that won't be shipped separately from an app.

Overall, I think we're really trying to keep from breaking Swift into different dialects, and making this feature dependent on whether or not the library is embedded in the app would work at cross-purposes to that. Everyone would still be forced to learn about the feature if they used C enums anyway, so we're not even helping out average developers. Instead, it's better that we have one, good model for dealing with other people's enums, which in practice can and do grow new cases regardless of how they are linked.

Jordan

···

On Jan 3, 2018, at 09:07, Dave DeLong <swift@davedelong.com> wrote:

IMO this is still too large of a hammer for this problem.

This whole “unexpected case” thing is only a problem when you’re linking libraries that are external to/shipped independently of your app. Right now, the *only* case where this might exist is Swift on the server. We *might* run in to this in the future once the ABI stabilizes and we have the Swift libraries shipping as part of iOS/macOS/Linux. Other than this, unexpected enum cases won’t really be a problem developers have to deal with.

Because this will be such a relatively rare problem, I feel like a syntax change like what’s being proposed is a too-massive hammer for such a small nail.

What feels far more appropriate is:

:a: Teaching the compiler/checker/whatever about the linking semantics of modules. For modules that are packaged inside the final built product, there is no need to deal with any unexpected cases, because we already have the exhaustiveness check appropriate for that scenario (regardless of whether the module is shipped as a binary or compiled from source). The app author decides when to update their dependencies, and updating those dependencies will produce new warnings/errors as the compiler notices new or deprecated cases. This is the current state of things and is completely orthogonal to the entire discussion.

and

:b: Adding an attribute (@frozen, @tangled, @moana, @whatever) that can be used to decorate an enum declaration. This attribute would only need to be consulted on enums where the compiler can determine that the module will *not* be part of the final built product. (Ie, it’s an “external” module, in my nomenclature). This, then, is a module that can update independently of the final app, and therefore there are two possible cases:

  1️⃣ If the enum is decorated with @frozen, then I, as an app author, have the assurance that the enum case will not change in future releases of the library, and I can safely switch on all known cases and not have to provide a default case.

  2️⃣ If the enum is NOT decorated with @frozen, then I, as an app author, have to account for the possibility that the module may update from underneath my app, and I have to handle an unknown case. This is simple: the compiler should require me to add a “default:” case to my switch statement. This warning is produced IFF: the enum is coming from an external module, and the enum is not decorated with @frozen.

==========

With this proposal, we only have one thing to consider: the spelling of @frozen/@moana/@whatever that we decorate enums in external modules with. Other than that, the existing behavior we currently have is completely capable of covering the possibilities: we just keep using a “default:” case whenever the compiler can’t guarantee that we can be exhaustive in our switching.

Where the real work would be is teaching the compiler about internally-vs-externally linked modules.

Dave

On Jan 2, 2018, at 7:07 PM, Jordan Rose via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

[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

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

1 Like

They would not be boxed, but there would be additional indirection required at runtime, using the same mechanism currently used for unspecialized generics.

Slava

···

On Dec 19, 2017, at 6:42 PM, Kevin Ballard <kevin@sb.org> wrote:

Isn’t this going to turn both structs and non-C-like enums into types that need to be auto-boxed (as the client won’t be able to rely on knowing the fixed size anymore)? This seems like a performance hazard.

-Kevin Ballard

On Dec 19, 2017, at 5:35 PM, Slava Pestov <spestov@apple.com <mailto:spestov@apple.com>> wrote:

On Dec 19, 2017, at 3:31 PM, Kevin Ballard via swift-evolution <swift-evolution@swift.org <mailto: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).

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

It is precisely in line with the `public`/`open` split. They both come from the principle that the default behavior should bind library authors as little as possible. The default for public classes is non-subclassable because going from non-subclassable to subclassable doesn't break anything, but the opposite direction does; the default for enums should be non-exhaustive because going from non-exhaustive to exhaustive doesn't break anything, but the opposite direction does.

···

On Dec 20, 2017, at 12:54 PM, Charlie Monroe via swift-evolution <swift-evolution@swift.org> wrote:

the choice to make them non-exhaustive by default is not in line with everything else in Swift - everything else is generally closed by default - public (-> final in other modules), no access modified (-> internal), ...

--
Brent Royal-Gordon
Architechies

I think that the main confusion here stems from the word library as we are addressing something that can be divided further (and this is IMHO as many macOS/iOS devs see it):

- libraries that come with the OS - here, it absolutely makes sense to make the enums non-exhaustive as the apps are linked against these libraries and the user installs a binary that will load these at launch and they are not bundled with the app - the developer can't control future OS releases and he wants the app to run on a future OS release.
- libraries that are bundled with the app - be it PM, CocoaPods or something else - you typically update your dependencies once in a while and they change. And you want to be notified by the compiler about possible changes - extended enums, in this case. Because let's be honest - if your app has a dozen dependencies and you come to the app after a year of no development, Swift 5 came along during that period, you want to update these libraries to Swift-5-compatible versions. And no one has the time to go through all change logs - even if they were kept up-to-date and thorough, which I can't say that I've seen in many instances.

I know that this is a limited view from the perspective of an app developer and that potentially, e.g. on Linux, there may be libraries written in Swift that you may want to install via package managers and depend on them once the ABI is stable, but the choice to make them non-exhaustive by default is not in line with everything else in Swift - everything else is generally closed by default - public (-> final in other modules), no access modified (-> internal), ...

For me, it's a -1 as it is now. I'd prefer exhaustive-by-default, ObjC/C-import non-exhaustive by default (the way ObjC classes are open by default vs. public). When it comes to the switch statement, there definitely needs to be an option to make an exhaustive switch over all compile-time-known values with a warning shall a new one be added. Without that, the code will become incredibly prone to errors and hard to maintain.

This does bring up another option, which is to differentiate Apple-provided libraries (and in general, libraries with binary compatibility concerns, i.e. libraries that may be different at run-time from what you compiled against) from bundled / built-from-source libraries. Slava and I have been very leery of this because it fragments the language into dialects, and has the potential to confuse people when they try to write code that behaves like an Apple framework, but I suppose it is an option.

Jordan

···

On Dec 20, 2017, at 12:54, Charlie Monroe <charlie@charliemonroe.net> wrote:

On Dec 20, 2017, at 9:35 PM, Karl Wagner via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On 20. Dec 2017, at 19:54, Jordan Rose <jordan_rose@apple.com <mailto:jordan_rose@apple.com>> wrote:

On Dec 20, 2017, at 05:36, Karl Wagner via swift-evolution <swift-evolution@swift.org <mailto: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:

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

When you say “libraries compiled from source”, what do you mean?

As for whether its a resilience feature: actually it is completely a resilience feature. The effects on switching are only side-effects; really what “exhaustive” or “nonexhaustive” are saying is literally that cases may be added later. Even if we added private cases, you wouldn’t need to mark those enums as specially exhaustive or not; that would be implied. It’s an accommodation for things which don’t exist yet, so really, it is all about resilience IMO.

Anyway, as I see it, library authors in general ought to be happy about this:
+ Their libraries become safer by default, so they can make changes in the future without having to worry about breakage
+ It doesn’t affect your code inside of a module, so it only affects types they already explicitly marked “public”

The only people who lose are multi-module App developers, because they are “library authors” who don’t need to care about evolution, and now need to add attributes to things they wouldn’t have to before, or suffer language and performance penalties. Their libraries become less reusable and not resilient-by-default.

For example, I have an App for which I wrote a cross-platform model framework in Swift. When I compile it as a framework inside my App, it is bundled there forever. However, I use the same code to build libraries for Linux, which I would like to ship in binary form to 3rd-parties. Am I supposed to litter my code with annotations to mark those types as final, just to make the App fast and convenient to code? What happens when I need to fix a bug and distribute an updated copy, this means the 3rd-parties need to recompile (which they won’t do…).

Typically, for such a problem, I would recommend using a static library instead. But we don’t have those, and anyway they’re not always the best thing these days. So that’s why I started a new thread about creating a “@static” import, so App developers can go back to all the conveniences they had before.

- Karl

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

As a perhaps more long-term design note, I think modules ought to have the ability to version-lock themselves to one or more of their dependencies. They would still be required to obey access control as if they were outside those dependencies, but we would suppress some of the semantic consequences of being outside the module, such as the need to assume non-exhaustiveness by default.

That is, there would be two independent axes of library dependency: source vs. binary and version-compatible vs. version-locked:
  - a source dependency allows the compiler to take advantage of the implementation of public entities when generating code
  - a version-locked dependency allows the compiler to take advantage of the implementation of public entities when enforcing semantics

Apps would generally elect to primarily use version-locked source dependencies because they're just pulling down source libraries (e.g. from github) and are comfortable with updating their code if the library changes.

Source libraries on github would generally want to use version-compatible source dependencies because version-locking would put their clients in "library hell" if the locking didn't all agree.

Binary dependencies could reasonably use either.

John.

···

On Dec 21, 2017, at 2:03 PM, Jordan Rose via swift-evolution <swift-evolution@swift.org> wrote:

On Dec 20, 2017, at 12:35, Karl Wagner <razielim@gmail.com <mailto:razielim@gmail.com>> wrote:

On 20. Dec 2017, at 19:54, Jordan Rose <jordan_rose@apple.com <mailto:jordan_rose@apple.com>> wrote:

On Dec 20, 2017, at 05:36, Karl Wagner via swift-evolution <swift-evolution@swift.org <mailto: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:

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

When you say “libraries compiled from source”, what do you mean?

- Other targets in your project
- Source packages built through SwiftPM / CocoaPods / Carthage / other

And I was being imprecise with the terminology, but also

- Libraries built by someone else but designed to be embedded into an app, so that there's no chance of a different version showing up at run-time.

As for whether its a resilience feature: actually it is completely a resilience feature. The effects on switching are only side-effects; really what “exhaustive” or “nonexhaustive” are saying is literally that cases may be added later. Even if we added private cases, you wouldn’t need to mark those enums as specially exhaustive or not; that would be implied. It’s an accommodation for things which don’t exist yet, so really, it is all about resilience IMO.

"Resilience", as an admittedly fuzzily-defined term in the Swift project, specifically refers to what changes can be made without breaking binary compatibility <https://github.com/apple/swift/blob/master/docs/Lexicon.rst&gt;\. It does not refer to every change you can make to a library. (For comparison, adding a field to a struct is not source-breaking in Swift. We would like to make it not ABI-breaking either; that proposal's coming soon.)

Anyway, as I see it, library authors in general ought to be happy about this:
+ Their libraries become safer by default, so they can make changes in the future without having to worry about breakage
+ It doesn’t affect your code inside of a module, so it only affects types they already explicitly marked “public”

That's the intent.

The only people who lose are multi-module App developers, because they are “library authors” who don’t need to care about evolution, and now need to add attributes to things they wouldn’t have to before, or suffer language and performance penalties. Their libraries become less reusable and not resilient-by-default.

For example, I have an App for which I wrote a cross-platform model framework in Swift. When I compile it as a framework inside my App, it is bundled there forever. However, I use the same code to build libraries for Linux, which I would like to ship in binary form to 3rd-parties. Am I supposed to litter my code with annotations to mark those types as final, just to make the App fast and convenient to code? What happens when I need to fix a bug and distribute an updated copy, this means the 3rd-parties need to recompile (which they won’t do…).

Typically, for such a problem, I would recommend using a static library instead. But we don’t have those, and anyway they’re not always the best thing these days. So that’s why I started a new thread about creating a “@static” import, so App developers can go back to all the conveniences they had before.

There won't be a perf penalty, but yes, I do expect multi-module apps to use 'exhaustive' on most of their enums, because they don't need the futureproofing. Maybe this should have been mentioned more explicitly in the proposal.

Hi Dave,

The broader problem is being considered holistically, and we’re designing and implementing various pieces of the solution as part of the ABI stability effort for Swift 5. Some of these pieces impact the language, hence you’ll be seeing some evolution proposals. Non-exhaustive enums is one part, the inlinable proposal out for review right now is another part. There are a few more related proposals in the pipeline also.

Perhaps we need to do a better job of explaining how these things all tie together. In the mean time you can read about the overall design here: https://github.com/apple/swift/blob/master/docs/LibraryEvolution.rst

Slava

···

On Dec 23, 2017, at 11:17 AM, Dave DeLong via swift-evolution <swift-evolution@swift.org> wrote:

Continuing on this vein, there is another huge reason this proposal is incomplete and won’t solve the problem.

This proposal, as I outlined previously, is all about binary compatibility and functional resilience in the face of changing libraries. However, it falls woefully short by only considering the binary implications of changing enums.

The problem of binary compatibility extends *far* beyond the mutation of enums between library versions. By only considering the “enum problem”, we are ignoring a much larger and worse problem that will be even easier for libraries to succumb to. It’s easy as a library author to inadvertently rename a method or change the type of a parameter, or add a parameter, or change the return type, or change the implementation semantics, or remove a type, or remove a function, in such a way that a linking app cannot handle. This is why the burden of binary compatibility rightfully rests on library authors, and not app authors.

The thing that changes has the responsibility to maintain compatibility for the thing that hasn’t changed.

We should absolutely not implement this proposal; it only addresses a single portion of a very large problem. Instead, we should figure out what the broader binary compatibility story is. If we can answer *that* and provide the necessary features to address it, then enum resilience will naturally fall out of it. Focusing on the enum problem is focusing on the wrong problem.

Dave

On Dec 21, 2017, at 11:02 AM, Dave DeLong <swift@davedelong.com <mailto:swift@davedelong.com>> wrote:

I realized further why we should not implement this proposal: It forces the problem of binary compatibility on the app developer, when experience has shown us that problem is better handled by the libraries.

Binary Compatibility

“Binary compatibility” is the notion that, even if the libraries you link against change, your app will still behave as it did when it was first compiled. For Swift apps these days, we don’t really have this problem, *yet*. We do have the problem of “binary compatibility” with Apple-provided frameworks, but those are all written in Objective-C, and so the question of “Swift” binary compatibility is still up-in-the-air.

Post-ABI stability, we still won’t have much issue with swift binary compatibility on Apple platforms, because there isn’t a mechanism to ship a framework to your users independently of an app update. So from the app POV, none of this will be a problem for Apple platform developers.

It will be a problem for non-Apple platform developers. As a simple example, let’s say I write a web app in Swift, and deploy it to a server that has some built-in Swift-on-the-server libraries. Here, my Swift app is decoupled from the libraries, and either one can update independently of each other. If the server owner decides to update from Swift 6 to Swift 6.1, that’s cool. But my web app should continue to run and behave *as if* the server were still running Swift 6, because it has not been re-compiled to use Swift 6.1 features.

This is the situation today on Apple platforms. Every app deployed to an Apple device includes a little piece of information in the executable file indicating which SDK was used to compile the app. At runtime, the system frameworks read this value and then alter their behavior accordingly. This is why apps written against the iOS 9 SDK continue to work on iOS 11 devices; UIKit and friends are altering their behavior to provide iOS 9 semantics.

This is “binary compatibility”: the binary (your app) continues to be compatible with the dynamically linked frameworks present on the system, even though those frameworks may change.

When you have a setup where the frameworks do NOT provide binary compatibility, you end up in DLL Hell [1]. This is the same frustrating scenario when you’re in when you’re doing stuff with homebrew and find that this package you want has multiple dependencies, but these dependencies want different versions of the same library. [2]

Exhaustive Enums

All of the discussion around exhaustive enums has been from the point-of-view of “what should the behavior be when the libraries change”. Thinking back, I’m actually surprised this is a question at all, because Apple answered this *years* ago: THE BEHAVIOR SHOULD REMAIN UNCHANGED. It is up to the library authors to ensure that they’re doing what the compiled-and-unchanging application is expecting them to do.

This discussion around exhaustive enums is basically saying “can we force the developers to deal with binary incompatibility?”. We absolutely should not. There are far more app developers than library developers, and it would be a massively wasteful expenditure of engineering effort to force each and every app developer to deal with binary incompatibility, when the library can do it for them. That is the entire *purpose* of having libraries: abstract out a problem so that I, as an app developer, don’t have to spend the effort to do it myself.

Where We Should Go

Instead of forcing developers to deal with incompatible libraries, we should be discussing ways to make binary compatibility easier to implement in libraries.

One of the major problems I struggled with as a UIKit engineer was the presence of huge numbers of “if … else” checks in the code to deal with binary compatibility. It exploded the cyclomatic complexity of the classes and was a major source of technical debt that I struggled to not add to.

I would love to see some sort of formal API versioning that we could do instead in libraries, along with easy runtime support for checking the linked version of libraries, making it easy to strategize implementations based on version, etc.

But forcing developers to deal with binary incompatibility is a solution we’ve long known to be a bad one. We should not perpetuate that sin in Swift.

Dave

[1]: DLL Hell - Wikipedia
[2]: Dependency hell - Wikipedia

On Dec 20, 2017, at 10:23 AM, Dave DeLong via swift-evolution <swift-evolution@swift.org <mailto: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:

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.

• 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.

• 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.

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.

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 <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
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?

+1 except for the name. @frozenExposed @fixedMembers @frozenMembers.
preferably something that aligns with the other notion of not being able to add public members to structs. This will help treat structs with static members in the same way which would be ideal. I don't think enums should have their own attitude.

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

don't know. im not a library author. ill defer to other library authors.

I want to revise my review here. While I am not a library author I am a library consumer.

Having the ability treat a non exhaustive enum as exhaustive should be introduced with this. I like the idea of a
`final switch`

I think it communicate clearly that I want this to be treated as exhaustive even if it is already exhaustive. Having something like future, unknowns would be weird to me.

Another option would be being able to cast a enum as exhaustive. I am not sure how that would work. I do not like switch!

Preferably I’d like to say:

switch (@exhaustive x){...}

Would this be allowed?

let @exhaustive myEnum= x

typealias @exhaustive Y = X

if let @exhaustive x = x {
     switch x {...} // exhaustive here.
}

Could this be addressed in the proposal?

I would also expect case _ to only match known cases since _ acts like a wild char.

switch x { // x is non-exhaustive here.
  case _ : fatalError("compile time error when missing cases ") // known cases at compile time
  default: fatalError("runtime error when missing cases") // unknown cases.
}

It makes sense for default and case _ to do the same thing for exhaustive enums but not for non exhaustive.

Could this be addressed in the proposal too? Thanks in advance.

···

On Dec 25, 2017, at 9:14 AM, Cheyo Jimenez via swift-evolution <swift-evolution@swift.org> wrote:

On Dec 22, 2017, at 8:49 AM, Cheyo Jose Jimenez <cheyo@masters3d.com> wrote:

On Dec 20, 2017, at 11:12 PM, Cheyo Jimenez <cheyo@masters3d.com> wrote:

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

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

yes.

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

n/a

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

followed the previous discussion. read the proposal.

Thanks,
Ted Kremenek
Review Manager
_______________________________________________
swift-evolution mailing list
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

Hello everyone,

My name is Eneko Alonso, iOS developer, new here on the list.

Is there a good summary anywhere that condenses the pros and cons of this new feature that have been discussed so far?

It is not clear to me why non-exhaustive would be the default, requiring adding `@exhaustive` otherwise. Has anyone discussed doing it the other way around, this is, defaulting to exhaustive (no changes with prior Swift versions) and adding a `@nonExhaustive` tag instead as needed?

Apologies if this has been covered already.

Regards and thank you everyone for making Swift better!
Eneko

···

On Dec 27, 2017, at 10:26 PM, Riley Testut via swift-evolution <swift-evolution@swift.org> wrote:

Actually, from the other email thread about this same topic (thank god forums are almost here), I see the proposed syntax “final switch” for what I referred to as “switch!”, which I prefer.

On Dec 28, 2017, at 12:17 AM, Riley Testut via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

-1.

I agree this is a problem, but I think this is the wrong solution. I think the solution should be on the client side, not on the framework author’s side.

I would be fine if enums from imported modules are non-exhaustive, as long as I can choose to treat them as exhaustive if I want to. And in that case, if a new case is introduced, I think a fatal error is a reasonable result.

The proposed “switch!” command would do just this, and I think that is the better answer for this. Adding an @exhaustive attribute doesn’t actually prevent someone from adding a case anyway, which I think is a big (and not really solvable) issue :man_shrugging:

I know much has been said about this, but it’s just my 2c.

On Dec 27, 2017, at 9:42 AM, Thorsten Seitz via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> 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
_______________________________________________
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

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

   • What is your evaluation of the proposal?

I am pleased with the broad strokes of this design. I have quibbles with three areas:

1. The `@exhaustive` attribute may be confusing because the term doesn't suggest versioning. My best alternative suggestion is `@frozen`, which matches existing programming terminology: something that has been frozen will not be changed in the future.

I rather like @frozen. We could use that across language features, so that we don't end up with a keyword per kind of declaration.

Could this also be used on functions to make them inlinable?
i.e. The body of the function has been frozen.

@frozen
public func a()

@available(*, frozen)
public func b()

@available(swift, introduced: 4.1, frozen: 5.0)
public func c()

My understanding is that frozen / exhaustible is guaranteed by the compiler while inlineable is more of a strong suggestion to the compiler. It would be confusing to use the same word for both.

···

On Dec 31, 2017, at 8:59 AM, Ben Rimmington via swift-evolution <swift-evolution@swift.org> wrote:

On 21 Dec 2017, at 03:32, John McCall wrote:

On Dec 20, 2017, at 10:16 PM, Brent Royal-Gordon wrote:
On Dec 19, 2017, at 2:58 PM, Ted Kremenek wrote:

<https://github.com/apple/swift-evolution/blob/master/proposals/0192-non-exhaustive-enums.md&gt;
<https://github.com/apple/swift-evolution/blob/master/proposals/0193-cross-module-inlining-and-specialization.md&gt;

-- Ben

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