Warning when omitting default case for imported enums


(Tanner Nelson) #1

Hello Swift Evolution,

I'd like to propose that a warning be emitted when default cases are
omitted for enums from other modules.

What this would look like:

OtherModule:

public enum SomeEnum {
    case one
    case two
}

public let global: SomeEnum = .one

executable:

import OtherModule

switch OtherModule.global {
    case .one: break
    case .two: break
    ^~~~~ ⚠︎ Warning: Default case recommended for imported enums. Fix-it:
Add `default: break`
}

Why:

Allowing the omission of a default case in an exhaustive switch makes the
addition of a new case to the enum a breaking change.
In other words, if you're exhaustively switching on an enum from an
imported library, the imported library can break your code by adding a new
case to that enum (which the library authors may erroneously view as an
additive/minor-bump change).

Background:

As a maintainer of a Swift framework, public enums have been a pain point
in maintaining semver. They've made it difficult to implement additive
features and have necessitated the avoidance of enums in our future public
API plans.

Related Twitter thread:
https://twitter.com/tanner0101/status/796860273760104454

Looking forward to hearing your thoughts.

Best,
Tanner

Tanner Nelson
Vapor
+1 (435) 773-2831


(Michael Ilseman) #2

BTW, this will likely be part of the eventual design of “open”/resilient enums ala https://github.com/apple/swift/blob/master/docs/LibraryEvolution.rst#enums. There, the goal is to reduce both ABI and source breakage caused by this sort of thing. It seems like for your purposes, you’re less inclined to care about ABI breakage than source breakage, though that may change in the future.

···

On Feb 7, 2017, at 7:12 AM, Tanner Nelson via swift-evolution <swift-evolution@swift.org> wrote:

Hello Swift Evolution,

I'd like to propose that a warning be emitted when default cases are omitted for enums from other modules.

What this would look like:

OtherModule:

public enum SomeEnum {
    case one
    case two
}

public let global: SomeEnum = .one

executable:

import OtherModule

switch OtherModule.global {
    case .one: break
    case .two: break
    ^~~~~ ⚠︎ Warning: Default case recommended for imported enums. Fix-it: Add `default: break`
}

Why:

Allowing the omission of a default case in an exhaustive switch makes the addition of a new case to the enum a breaking change.
In other words, if you're exhaustively switching on an enum from an imported library, the imported library can break your code by adding a new case to that enum (which the library authors may erroneously view as an additive/minor-bump change).

Background:

As a maintainer of a Swift framework, public enums have been a pain point in maintaining semver. They've made it difficult to implement additive features and have necessitated the avoidance of enums in our future public API plans.

Related Twitter thread: https://twitter.com/tanner0101/status/796860273760104454

Looking forward to hearing your thoughts.

Best,
Tanner

Tanner Nelson
Vapor
+1 (435) 773-2831

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


(Matthew Johnson) #3

I lean +1, but this answer on its own seems incomplete. Exhaustiveness is an important property, and it’s not clear what happens here now when you fall through a “complete” case tree without matching a pattern, and in that sense this plan solves a problem. But it also would hinder a “future-you” from going back and dealing with the ramifications of swapping a dependency for a newer version, in that with a default now covering the rest of the cases the compiler is unable to tell you which cases you are actually missing post-upgrade.

Library Evolution <https://github.com/apple/swift/blob/master/docs/LibraryEvolution.rst> includes what I think is the more complete response here: An @closed attribute for enums where a switch is guaranteed to be exhaustive. (Though, after the open discussion, I wonder if that keyword can’t be reused in some way to provide the inverse restriction instead).

I agree that we probably need to allow both and the question is whether we have a default or not, and if we do, which one is the default.

Personally, if we have a default I would prefer for the default to be closed. One of the most important attributes of enums is the fact that they encompass a fixed set of known cases. I *want* my code to break if a new case is added to an enum of another module I depend on. As the Library Evolution document notes "adding new cases should not be done lightly. Any clients attempting to do an exhaustive switch over all enum cases will likely not handle new cases well.”. Perhaps if a module author is not willing to commit to a fixed set of cases maybe publicly exposing an enum’s cases is not the best way to model the API in question.

I understand that making closed the default is the is slightly contrary to the principle that a module author explicitly opts-in to public API contracts. With that in mind, I would also consider it acceptable to decide that there should be *no* default, as we did in the `open` discussion.

It’s also worth noting that in the context of enums there are two ways in which an enum might be considered open - open to future extension by the module itself and open to extension by other modules. The latter most closely matches the meaning of the `open` access modifier as it is currently used and I believe it has been requested on the list once or twice. With that in mind, I don’t think we should reuse the `open` keyword to mean “open to extension in a future version of the module, but not open to extension by users of the module”. That is basically equivalent to the meaning of `public` for classes.

This leads me to conclude that maybe the right answer here is to require library authors to specify `closed` *or* `public` when exposing an enum outside the module. This follows the principle of library authors explicitly opting in to public API contracts, but also does not penalize the library author with additional verbosity for the contract which is more likely to lead to correct client code.

···

On Feb 7, 2017, at 1:45 PM, Robert Widmann via swift-evolution <swift-evolution@swift.org> wrote:

On Feb 7, 2017, at 10:12 AM, Tanner Nelson via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Hello Swift Evolution,

I'd like to propose that a warning be emitted when default cases are omitted for enums from other modules.

What this would look like:

OtherModule:

public enum SomeEnum {
    case one
    case two
}

public let global: SomeEnum = .one

executable:

import OtherModule

switch OtherModule.global {
    case .one: break
    case .two: break
    ^~~~~ ⚠︎ Warning: Default case recommended for imported enums. Fix-it: Add `default: break`
}

Why:

Allowing the omission of a default case in an exhaustive switch makes the addition of a new case to the enum a breaking change.
In other words, if you're exhaustively switching on an enum from an imported library, the imported library can break your code by adding a new case to that enum (which the library authors may erroneously view as an additive/minor-bump change).

Background:

As a maintainer of a Swift framework, public enums have been a pain point in maintaining semver. They've made it difficult to implement additive features and have necessitated the avoidance of enums in our future public API plans.

Related Twitter thread: https://twitter.com/tanner0101/status/796860273760104454

Looking forward to hearing your thoughts.

Best,
Tanner

Tanner Nelson
Vapor
+1 (435) 773-2831

_______________________________________________
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


(Robert Widmann) #4

I lean +1, but this answer on its own seems incomplete. Exhaustiveness is an important property, and it’s not clear what happens here now when you fall through a “complete” case tree without matching a pattern, and in that sense this plan solves a problem. But it also would hinder a “future-you” from going back and dealing with the ramifications of swapping a dependency for a newer version, in that with a default now covering the rest of the cases the compiler is unable to tell you which cases you are actually missing post-upgrade.

Library Evolution <https://github.com/apple/swift/blob/master/docs/LibraryEvolution.rst> includes what I think is the more complete response here: An @closed attribute for enums where a switch is guaranteed to be exhaustive. (Though, after the open discussion, I wonder if that keyword can’t be reused in some way to provide the inverse restriction instead).

···

On Feb 7, 2017, at 10:12 AM, Tanner Nelson via swift-evolution <swift-evolution@swift.org> wrote:

Hello Swift Evolution,

I'd like to propose that a warning be emitted when default cases are omitted for enums from other modules.

What this would look like:

OtherModule:

public enum SomeEnum {
    case one
    case two
}

public let global: SomeEnum = .one

executable:

import OtherModule

switch OtherModule.global {
    case .one: break
    case .two: break
    ^~~~~ ⚠︎ Warning: Default case recommended for imported enums. Fix-it: Add `default: break`
}

Why:

Allowing the omission of a default case in an exhaustive switch makes the addition of a new case to the enum a breaking change.
In other words, if you're exhaustively switching on an enum from an imported library, the imported library can break your code by adding a new case to that enum (which the library authors may erroneously view as an additive/minor-bump change).

Background:

As a maintainer of a Swift framework, public enums have been a pain point in maintaining semver. They've made it difficult to implement additive features and have necessitated the avoidance of enums in our future public API plans.

Related Twitter thread: https://twitter.com/tanner0101/status/796860273760104454

Looking forward to hearing your thoughts.

Best,
Tanner

Tanner Nelson
Vapor
+1 (435) 773-2831

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


(Matthew Johnson) #5

BTW, this will likely be part of the eventual design of “open”/resilient enums ala https://github.com/apple/swift/blob/master/docs/LibraryEvolution.rst#enums. There, the goal is to reduce both ABI and source breakage caused by this sort of thing. It seems like for your purposes, you’re less inclined to care about ABI breakage than source breakage, though that may change in the future.

Source breakage is generally *desirable* in the context of switch statements over the cases of an enum. This is why I am strongly opposed to the idea of making open enums the default.

It feels like this would only be a desirable API contract for enums which it would be unusual for user code to switch over. Any time user code might reasonably switch over the cases it feels like the wrong API contract. How is the user code supposed to handle the unknown future cases correctly?

I think it’s also worth asking the question: if user code is not supposed to switch over the cases of the enum, why is the type exposed publicly as an enum at all? This means the cases are only intended to be used by the user to create values of the type. Maybe a better API design would be something like the following:

public struct Foo {
    private enum Value {
        case foo1
        case foo2(String)
    }

    private let value: Value
    private init(_ value: Value) {
        self.value = value
    }

    public static var foo1: Foo {
        return Foo(.foo1)
    }
    public static func foo2(_ value: String) -> Foo {
        return Foo(.foo2(value))
    }
}

public func apiFunctionTakingFoo(_ foo: Foo) {}

// user code:
apiFunctionTakingFoo(.foo1)
apiFunctionTakingFoo(.foo2(""))

This provides the same syntactic convenience for users when constructing values and allows adding new values (i.e. “cases”), retains the ability to use an enum internally in the implementation, and intentionally prevents users from switching over the open set of values in a fragile way (i.e. handling unknown future values / cases in a `default` clause).

This requires some boilerplate but it feels like it may often be a better API design when the library author is not willing to commit to the contract of a closed enum. It feels like it should be unusual when a library author expects users to switch over the cases, but also wants to add new cases in the future.

···

On Feb 7, 2017, at 3:01 PM, Michael Ilseman via swift-evolution <swift-evolution@swift.org> wrote:

On Feb 7, 2017, at 7:12 AM, Tanner Nelson via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Hello Swift Evolution,

I'd like to propose that a warning be emitted when default cases are omitted for enums from other modules.

What this would look like:

OtherModule:

public enum SomeEnum {
    case one
    case two
}

public let global: SomeEnum = .one

executable:

import OtherModule

switch OtherModule.global {
    case .one: break
    case .two: break
    ^~~~~ ⚠︎ Warning: Default case recommended for imported enums. Fix-it: Add `default: break`
}

Why:

Allowing the omission of a default case in an exhaustive switch makes the addition of a new case to the enum a breaking change.
In other words, if you're exhaustively switching on an enum from an imported library, the imported library can break your code by adding a new case to that enum (which the library authors may erroneously view as an additive/minor-bump change).

Background:

As a maintainer of a Swift framework, public enums have been a pain point in maintaining semver. They've made it difficult to implement additive features and have necessitated the avoidance of enums in our future public API plans.

Related Twitter thread: https://twitter.com/tanner0101/status/796860273760104454

Looking forward to hearing your thoughts.

Best,
Tanner

Tanner Nelson
Vapor
+1 (435) 773-2831

_______________________________________________
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


(Charlie Monroe) #6

-1

Not having the default case allows you to rely on the compiler to handle new options once they are added. Most of my apps consist nowadays from multiple modules and this would be massively inconvenient.

The possible future features may be non-breaking if we consider that we will not allow enums to be extended by default, but would need to be marked explicitely as extendable. Similar to public vs. open classes.

···

On Feb 7, 2017, at 4:12 PM, Tanner Nelson via swift-evolution <swift-evolution@swift.org> wrote:

Hello Swift Evolution,

I'd like to propose that a warning be emitted when default cases are omitted for enums from other modules.

What this would look like:

OtherModule:

public enum SomeEnum {
    case one
    case two
}

public let global: SomeEnum = .one

executable:

import OtherModule

switch OtherModule.global {
    case .one: break
    case .two: break
    ^~~~~ ⚠︎ Warning: Default case recommended for imported enums. Fix-it: Add `default: break`
}

Why:

Allowing the omission of a default case in an exhaustive switch makes the addition of a new case to the enum a breaking change.
In other words, if you're exhaustively switching on an enum from an imported library, the imported library can break your code by adding a new case to that enum (which the library authors may erroneously view as an additive/minor-bump change).

Background:

As a maintainer of a Swift framework, public enums have been a pain point in maintaining semver. They've made it difficult to implement additive features and have necessitated the avoidance of enums in our future public API plans.

Related Twitter thread: https://twitter.com/tanner0101/status/796860273760104454

Looking forward to hearing your thoughts.

Best,
Tanner

Tanner Nelson
Vapor
+1 (435) 773-2831

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


(Rien) #7

-1

Reason 1: the “negative” behaviour you describe is actually exactly what I want to happen.
Reason 2: Introducing a warning would also necessitate a warning suppression in order to have your code compile without warnings. But when you suppress, the purpose of the warning is nul and void.

PS: I would suggest not to use an enum in cases where this is really annoying and replace the enums with constants.

Regards,
Rien

Site: http://balancingrock.nl
Blog: http://swiftrien.blogspot.com
Github: http://github.com/Balancingrock
Project: http://swiftfire.nl

···

On 07 Feb 2017, at 16:12, Tanner Nelson via swift-evolution <swift-evolution@swift.org> wrote:

Hello Swift Evolution,

I'd like to propose that a warning be emitted when default cases are omitted for enums from other modules.

What this would look like:

OtherModule:

public enum SomeEnum {
    case one
    case two
}

public let global: SomeEnum = .one

executable:

import OtherModule

switch OtherModule.global {
    case .one: break
    case .two: break
    ^~~~~ ⚠︎ Warning: Default case recommended for imported enums. Fix-it: Add `default: break`
}

Why:

Allowing the omission of a default case in an exhaustive switch makes the addition of a new case to the enum a breaking change.
In other words, if you're exhaustively switching on an enum from an imported library, the imported library can break your code by adding a new case to that enum (which the library authors may erroneously view as an additive/minor-bump change).

Background:

As a maintainer of a Swift framework, public enums have been a pain point in maintaining semver. They've made it difficult to implement additive features and have necessitated the avoidance of enums in our future public API plans.

Related Twitter thread: https://twitter.com/tanner0101/status/796860273760104454

Looking forward to hearing your thoughts.

Best,
Tanner

Tanner Nelson
Vapor
+1 (435) 773-2831

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


#8

I lean +1, but this answer on its own seems incomplete. Exhaustiveness is an important property, and it’s not clear what happens here now when you fall through a “complete” case tree without matching a pattern, and in that sense this plan solves a problem. But it also would hinder a “future-you” from going back and dealing with the ramifications of swapping a dependency for a newer version, in that with a default now covering the rest of the cases the compiler is unable to tell you which cases you are actually missing post-upgrade.

I have not read the ABI stability stuff, so not sure if adding a new case to an enum inside a library can be made backward compatible but that would be nice. Handling this unexpected new value in the currently compiled user code should be as important as telling the owner of the user code that he must handle a new case.

Would a new pseudo-default keyword (unexpected, undefined) help here? Such keyword would require the switch statement to be exhaustive so it doesn't act like 'default' for switch exhaustiveness status at compile time, but does act like 'default' at runtime.

switch data {
  case .one: { print("one') }
  case .two: { print("two") }
  undefined: { print("unexpected value") }
}

Replacing the library with one providing .three, will cause this previously compile code to print "unexpected value", and would cause the code to fail to compile against this same new library.

Dany

···

Le 7 févr. 2017 à 14:45, Robert Widmann via swift-evolution <swift-evolution@swift.org> a écrit :

Library Evolution includes what I think is the more complete response here: An @closed attribute for enums where a switch is guaranteed to be exhaustive. (Though, after the open discussion, I wonder if that keyword can’t be reused in some way to provide the inverse restriction instead).

On Feb 7, 2017, at 10:12 AM, Tanner Nelson via swift-evolution <swift-evolution@swift.org> wrote:

Hello Swift Evolution,

I'd like to propose that a warning be emitted when default cases are omitted for enums from other modules.

What this would look like:

OtherModule:

public enum SomeEnum {
    case one
    case two
}

public let global: SomeEnum = .one

executable:

import OtherModule

switch OtherModule.global {
    case .one: break
    case .two: break
    ^~~~~ ⚠︎ Warning: Default case recommended for imported enums. Fix-it: Add `default: break`
}

Why:

Allowing the omission of a default case in an exhaustive switch makes the addition of a new case to the enum a breaking change.
In other words, if you're exhaustively switching on an enum from an imported library, the imported library can break your code by adding a new case to that enum (which the library authors may erroneously view as an additive/minor-bump change).

Background:

As a maintainer of a Swift framework, public enums have been a pain point in maintaining semver. They've made it difficult to implement additive features and have necessitated the avoidance of enums in our future public API plans.

Related Twitter thread: https://twitter.com/tanner0101/status/796860273760104454

Looking forward to hearing your thoughts.

Best,
Tanner

Tanner Nelson
Vapor
+1 (435) 773-2831

_______________________________________________
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


(Tanner Nelson) #9

I like this a lot. +1

···

Sent from my iPhone

On Feb 7, 2017, at 21:22, Matthew Johnson <matthew@anandabits.com> wrote:

On Feb 7, 2017, at 1:45 PM, Robert Widmann via swift-evolution <swift-evolution@swift.org> wrote:

I lean +1, but this answer on its own seems incomplete. Exhaustiveness is an important property, and it’s not clear what happens here now when you fall through a “complete” case tree without matching a pattern, and in that sense this plan solves a problem. But it also would hinder a “future-you” from going back and dealing with the ramifications of swapping a dependency for a newer version, in that with a default now covering the rest of the cases the compiler is unable to tell you which cases you are actually missing post-upgrade.

Library Evolution includes what I think is the more complete response here: An @closed attribute for enums where a switch is guaranteed to be exhaustive. (Though, after the open discussion, I wonder if that keyword can’t be reused in some way to provide the inverse restriction instead).

I agree that we probably need to allow both and the question is whether we have a default or not, and if we do, which one is the default.

Personally, if we have a default I would prefer for the default to be closed. One of the most important attributes of enums is the fact that they encompass a fixed set of known cases. I *want* my code to break if a new case is added to an enum of another module I depend on. As the Library Evolution document notes "adding new cases should not be done lightly. Any clients attempting to do an exhaustive switch over all enum cases will likely not handle new cases well.”. Perhaps if a module author is not willing to commit to a fixed set of cases maybe publicly exposing an enum’s cases is not the best way to model the API in question.

I understand that making closed the default is the is slightly contrary to the principle that a module author explicitly opts-in to public API contracts. With that in mind, I would also consider it acceptable to decide that there should be *no* default, as we did in the `open` discussion.

It’s also worth noting that in the context of enums there are two ways in which an enum might be considered open - open to future extension by the module itself and open to extension by other modules. The latter most closely matches the meaning of the `open` access modifier as it is currently used and I believe it has been requested on the list once or twice. With that in mind, I don’t think we should reuse the `open` keyword to mean “open to extension in a future version of the module, but not open to extension by users of the module”. That is basically equivalent to the meaning of `public` for classes.

This leads me to conclude that maybe the right answer here is to require library authors to specify `closed` *or* `public` when exposing an enum outside the module. This follows the principle of library authors explicitly opting in to public API contracts, but also does not penalize the library author with additional verbosity for the contract which is more likely to lead to correct client code.

On Feb 7, 2017, at 10:12 AM, Tanner Nelson via swift-evolution <swift-evolution@swift.org> wrote:

Hello Swift Evolution,

I'd like to propose that a warning be emitted when default cases are omitted for enums from other modules.

What this would look like:

OtherModule:

public enum SomeEnum {
    case one
    case two
}

public let global: SomeEnum = .one

executable:

import OtherModule

switch OtherModule.global {
    case .one: break
    case .two: break
    ^~~~~ ⚠︎ Warning: Default case recommended for imported enums. Fix-it: Add `default: break`
}

Why:

Allowing the omission of a default case in an exhaustive switch makes the addition of a new case to the enum a breaking change.
In other words, if you're exhaustively switching on an enum from an imported library, the imported library can break your code by adding a new case to that enum (which the library authors may erroneously view as an additive/minor-bump change).

Background:

As a maintainer of a Swift framework, public enums have been a pain point in maintaining semver. They've made it difficult to implement additive features and have necessitated the avoidance of enums in our future public API plans.

Related Twitter thread: https://twitter.com/tanner0101/status/796860273760104454

Looking forward to hearing your thoughts.

Best,
Tanner

Tanner Nelson
Vapor
+1 (435) 773-2831

_______________________________________________
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


(Tanner Nelson) #10

That's awesome. It looks like `(planned) Open and closed enums` is exactly what I'm looking for.

Would it help if I created a concrete proposal for that or is it something the Swift team already has brewing?

···

Sent from my iPhone

On Feb 7, 2017, at 22:01, Michael Ilseman <milseman@apple.com> wrote:

BTW, this will likely be part of the eventual design of “open”/resilient enums ala https://github.com/apple/swift/blob/master/docs/LibraryEvolution.rst#enums. There, the goal is to reduce both ABI and source breakage caused by this sort of thing. It seems like for your purposes, you’re less inclined to care about ABI breakage than source breakage, though that may change in the future.

On Feb 7, 2017, at 7:12 AM, Tanner Nelson via swift-evolution <swift-evolution@swift.org> wrote:

Hello Swift Evolution,

I'd like to propose that a warning be emitted when default cases are omitted for enums from other modules.

What this would look like:

OtherModule:

public enum SomeEnum {
    case one
    case two
}

public let global: SomeEnum = .one

executable:

import OtherModule

switch OtherModule.global {
    case .one: break
    case .two: break
    ^~~~~ ⚠︎ Warning: Default case recommended for imported enums. Fix-it: Add `default: break`
}

Why:

Allowing the omission of a default case in an exhaustive switch makes the addition of a new case to the enum a breaking change.
In other words, if you're exhaustively switching on an enum from an imported library, the imported library can break your code by adding a new case to that enum (which the library authors may erroneously view as an additive/minor-bump change).

Background:

As a maintainer of a Swift framework, public enums have been a pain point in maintaining semver. They've made it difficult to implement additive features and have necessitated the avoidance of enums in our future public API plans.

Related Twitter thread: https://twitter.com/tanner0101/status/796860273760104454

Looking forward to hearing your thoughts.

Best,
Tanner

Tanner Nelson
Vapor
+1 (435) 773-2831

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


(Tino) #11

Allowing the omission of a default case in an exhaustive switch makes the addition of a new case to the enum a breaking change.

Depending on the context, even with a default the change might be breaking — but without giving notice to the framework client!
Additionally, it would feel very strange for me to have a default clause for bool-like enums* or other examples where there can't be any additional cases.

When there are two types of enums, the proposal makes sense for the "open"-ones; but I'm not sure if I like the concept of having two kinds of enum.

- Tino

* tiny anecdote: I actually have seen the equivalent of this

func reallyExhaustive(input: Bool) {
  if input == true {
    print("true")
  } else if input != true {
    print ("not true")
  } else {
    print("just to be save")
  }
}

in the wild… but I don't remember the comment I wrote :wink:


(Michael Ilseman) #12

That's awesome. It looks like `(planned) Open and closed enums` is exactly what I'm looking for.

Would it help if I created a concrete proposal for that or is it something the Swift team already has brewing?

Note that open enums also have to solve ABI stability across versions.

Basically, it sounds like you have an enum for which there is a reasonable default handling in client code for any cases added in future versions of your framework. Note that this is not true of all enums. The current behavior of breaking clients when new cases are added forces them to think about this new variant and their assumptions associated therein. This is very desirable for many enums, even public ones.

An intermediary approach could be to come up with an attribute to convey your intent ala https://github.com/apple/swift-evolution/blob/master/proposals/0047-nonvoid-warn.md. The idea is to keep default behavior the same, but have something like (straw man syntax) @defaultCaseRequired.

···

On Feb 7, 2017, at 1:21 PM, Tanner Nelson <tanner@qutheory.io> wrote:

Sent from my iPhone

On Feb 7, 2017, at 22:01, Michael Ilseman <milseman@apple.com <mailto:milseman@apple.com>> wrote:

BTW, this will likely be part of the eventual design of “open”/resilient enums ala https://github.com/apple/swift/blob/master/docs/LibraryEvolution.rst#enums. There, the goal is to reduce both ABI and source breakage caused by this sort of thing. It seems like for your purposes, you’re less inclined to care about ABI breakage than source breakage, though that may change in the future.

On Feb 7, 2017, at 7:12 AM, Tanner Nelson via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Hello Swift Evolution,

I'd like to propose that a warning be emitted when default cases are omitted for enums from other modules.

What this would look like:

OtherModule:

public enum SomeEnum {
    case one
    case two
}

public let global: SomeEnum = .one

executable:

import OtherModule

switch OtherModule.global {
    case .one: break
    case .two: break
    ^~~~~ ⚠︎ Warning: Default case recommended for imported enums. Fix-it: Add `default: break`
}

Why:

Allowing the omission of a default case in an exhaustive switch makes the addition of a new case to the enum a breaking change.
In other words, if you're exhaustively switching on an enum from an imported library, the imported library can break your code by adding a new case to that enum (which the library authors may erroneously view as an additive/minor-bump change).

Background:

As a maintainer of a Swift framework, public enums have been a pain point in maintaining semver. They've made it difficult to implement additive features and have necessitated the avoidance of enums in our future public API plans.

Related Twitter thread: https://twitter.com/tanner0101/status/796860273760104454

Looking forward to hearing your thoughts.

Best,
Tanner

Tanner Nelson
Vapor
+1 (435) 773-2831

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


(Tanner Nelson) #13

I don't understand the part about warning suppression. The warning would go away when you add the default case.

···

Sent from my iPhone

On Feb 7, 2017, at 16:25, Rien <Rien@Balancingrock.nl> wrote:

-1

Reason 1: the “negative” behaviour you describe is actually exactly what I want to happen.
Reason 2: Introducing a warning would also necessitate a warning suppression in order to have your code compile without warnings. But when you suppress, the purpose of the warning is nul and void.

PS: I would suggest not to use an enum in cases where this is really annoying and replace the enums with constants.

Regards,
Rien

Site: http://balancingrock.nl
Blog: http://swiftrien.blogspot.com
Github: http://github.com/Balancingrock
Project: http://swiftfire.nl

On 07 Feb 2017, at 16:12, Tanner Nelson via swift-evolution <swift-evolution@swift.org> wrote:

Hello Swift Evolution,

I'd like to propose that a warning be emitted when default cases are omitted for enums from other modules.

What this would look like:

OtherModule:

public enum SomeEnum {
   case one
   case two
}

public let global: SomeEnum = .one

executable:

import OtherModule

switch OtherModule.global {
   case .one: break
   case .two: break
   ^~~~~ ⚠︎ Warning: Default case recommended for imported enums. Fix-it: Add `default: break`
}

Why:

Allowing the omission of a default case in an exhaustive switch makes the addition of a new case to the enum a breaking change.
In other words, if you're exhaustively switching on an enum from an imported library, the imported library can break your code by adding a new case to that enum (which the library authors may erroneously view as an additive/minor-bump change).

Background:

As a maintainer of a Swift framework, public enums have been a pain point in maintaining semver. They've made it difficult to implement additive features and have necessitated the avoidance of enums in our future public API plans.

Related Twitter thread: https://twitter.com/tanner0101/status/796860273760104454

Looking forward to hearing your thoughts.

Best,
Tanner

Tanner Nelson
Vapor
+1 (435) 773-2831

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


(Tanner Nelson) #14

You'd still get the compiler helping you with new cases when working with enums within your module.

There's less use in having the compiler help you find new cases when they can only be added between major versions of a package. The benefit to cost ratio here is imbalanced. Enum cases are forever frozen from version x.0.0 of a package forward (until the next major bump). No other programming language I know of prevents adding enum cases in a minor release. This makes using enums in public API (which are incredibly useful) very difficult to do while following semver.

Could you explain more what you mean by public vs open enums?

Best,
Tanner

···

Sent from my iPhone

On Feb 7, 2017, at 16:21, Charlie Monroe <charlie@charliemonroe.net> wrote:

-1

Not having the default case allows you to rely on the compiler to handle new options once they are added. Most of my apps consist nowadays from multiple modules and this would be massively inconvenient.

The possible future features may be non-breaking if we consider that we will not allow enums to be extended by default, but would need to be marked explicitely as extendable. Similar to public vs. open classes.

On Feb 7, 2017, at 4:12 PM, Tanner Nelson via swift-evolution <swift-evolution@swift.org> wrote:

Hello Swift Evolution,

I'd like to propose that a warning be emitted when default cases are omitted for enums from other modules.

What this would look like:

OtherModule:

public enum SomeEnum {
    case one
    case two
}

public let global: SomeEnum = .one

executable:

import OtherModule

switch OtherModule.global {
    case .one: break
    case .two: break
    ^~~~~ ⚠︎ Warning: Default case recommended for imported enums. Fix-it: Add `default: break`
}

Why:

Allowing the omission of a default case in an exhaustive switch makes the addition of a new case to the enum a breaking change.
In other words, if you're exhaustively switching on an enum from an imported library, the imported library can break your code by adding a new case to that enum (which the library authors may erroneously view as an additive/minor-bump change).

Background:

As a maintainer of a Swift framework, public enums have been a pain point in maintaining semver. They've made it difficult to implement additive features and have necessitated the avoidance of enums in our future public API plans.

Related Twitter thread: https://twitter.com/tanner0101/status/796860273760104454

Looking forward to hearing your thoughts.

Best,
Tanner

Tanner Nelson
Vapor
+1 (435) 773-2831

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


(Slava Pestov) #15

I lean +1, but this answer on its own seems incomplete. Exhaustiveness is an important property, and it’s not clear what happens here now when you fall through a “complete” case tree without matching a pattern, and in that sense this plan solves a problem. But it also would hinder a “future-you” from going back and dealing with the ramifications of swapping a dependency for a newer version, in that with a default now covering the rest of the cases the compiler is unable to tell you which cases you are actually missing post-upgrade.

I have not read the ABI stability stuff, so not sure if adding a new case to an enum inside a library can be made backward compatible but that would be nice.

The plan of record currently is to have a ‘closed’ attribute for enums which allows the default case to be omitted for an exhaustive switch. Closed enums cannot have new cases added after the fact. Enums that are not marked ‘closed’ will require a default case when used outside of their defining module, and adding new cases will be permitted without breaking ABI.

(If anyone wants to take a look, this is sort of implemented today, except you have to enable the resilient ABI explicitly with the -enable-resilience frontend flag, and @closed is spelled ‘@_fixed_layout’. Also 100% unsupported and undocumented).

···

On Feb 7, 2017, at 6:16 PM, Dany St-Amant via swift-evolution <swift-evolution@swift.org> wrote:
Le 7 févr. 2017 à 14:45, Robert Widmann via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> a écrit :

Handling this unexpected new value in the currently compiled user code should be as important as telling the owner of the user code that he must handle a new case.

Would a new pseudo-default keyword (unexpected, undefined) help here? Such keyword would require the switch statement to be exhaustive so it doesn't act like 'default' for switch exhaustiveness status at compile time, but does act like 'default' at runtime.

switch data {
  case .one: { print("one') }
  case .two: { print("two") }
  undefined: { print("unexpected value") }
}

Replacing the library with one providing .three, will cause this previously compile code to print "unexpected value", and would cause the code to fail to compile against this same new library.

Dany

Library Evolution <https://github.com/apple/swift/blob/master/docs/LibraryEvolution.rst> includes what I think is the more complete response here: An @closed attribute for enums where a switch is guaranteed to be exhaustive. (Though, after the open discussion, I wonder if that keyword can’t be reused in some way to provide the inverse restriction instead).

On Feb 7, 2017, at 10:12 AM, Tanner Nelson via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Hello Swift Evolution,

I'd like to propose that a warning be emitted when default cases are omitted for enums from other modules.

What this would look like:

OtherModule:

public enum SomeEnum {
    case one
    case two
}

public let global: SomeEnum = .one

executable:

import OtherModule

switch OtherModule.global {
    case .one: break
    case .two: break
    ^~~~~ ⚠︎ Warning: Default case recommended for imported enums. Fix-it: Add `default: break`
}

Why:

Allowing the omission of a default case in an exhaustive switch makes the addition of a new case to the enum a breaking change.
In other words, if you're exhaustively switching on an enum from an imported library, the imported library can break your code by adding a new case to that enum (which the library authors may erroneously view as an additive/minor-bump change).

Background:

As a maintainer of a Swift framework, public enums have been a pain point in maintaining semver. They've made it difficult to implement additive features and have necessitated the avoidance of enums in our future public API plans.

Related Twitter thread: https://twitter.com/tanner0101/status/796860273760104454

Looking forward to hearing your thoughts.

Best,
Tanner

Tanner Nelson
Vapor
+1 (435) 773-2831

_______________________________________________
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 <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution


(Derrick Ho) #16

-1
I prefer to have enums enforce that all cases are met otherwise use a
default value.

If an API changes and adds new cases I want to know about it so that I can
do something about it! If we hide it then weird and mysterious bugs might
creep up

···

On Tue, Feb 7, 2017 at 4:22 PM Tanner Nelson via swift-evolution < swift-evolution@swift.org> wrote:

That's awesome. It looks like `(planned) Open and closed enums` is
exactly what I'm looking for.

Would it help if I created a concrete proposal for that or is it something
the Swift team already has brewing?

Sent from my iPhone

On Feb 7, 2017, at 22:01, Michael Ilseman <milseman@apple.com> wrote:

BTW, this will likely be part of the eventual design of “open”/resilient
enums ala
https://github.com/apple/swift/blob/master/docs/LibraryEvolution.rst#enums.
There, the goal is to reduce both ABI and source breakage caused by this
sort of thing. It seems like for your purposes, you’re less inclined to
care about ABI breakage than source breakage, though that may change in the
future.

On Feb 7, 2017, at 7:12 AM, Tanner Nelson via swift-evolution < > swift-evolution@swift.org> wrote:

Hello Swift Evolution,

I'd like to propose that a warning be emitted when default cases are
omitted for enums from other modules.

What this would look like:

OtherModule:

public enum SomeEnum {
    case one
    case two
}

public let global: SomeEnum = .one

executable:

import OtherModule

switch OtherModule.global {
    case .one: break
    case .two: break
    ^~~~~ ⚠︎ Warning: Default case recommended for imported enums. Fix-it:
Add `default: break`
}

Why:

Allowing the omission of a default case in an exhaustive switch makes the
addition of a new case to the enum a breaking change.
In other words, if you're exhaustively switching on an enum from an
imported library, the imported library can break your code by adding a new
case to that enum (which the library authors may erroneously view as an
additive/minor-bump change).

Background:

As a maintainer of a Swift framework, public enums have been a pain point
in maintaining semver. They've made it difficult to implement additive
features and have necessitated the avoidance of enums in our future public
API plans.

Related Twitter thread:
https://twitter.com/tanner0101/status/796860273760104454

Looking forward to hearing your thoughts.

Best,
Tanner

Tanner Nelson
Vapor
+1 (435) 773-2831

_______________________________________________
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


(Tanner Nelson) #17

To give a concrete example, some of the issues have arisen from using enums as Swift.Errors.

public enum SocketError: Error {
    case closed
}

Then we find some new error that was unaccounted for and want to add a new case.

public enum SocketError: Error {
    case closed
    case brokenPipe
}

What should have been a minor or possibly even a patch bump now requires a major bump.

Looking back, it would have been easy to use a different data structure for these types of errors. But there's something so elegant about handling error enums like:

do {
    try someSocketThing()
} catch let error as SocketError {
    switch error {
        case .closed:
            print("the socket closed")
        default:
            print("there was an error with the socket")
    }
}

In this situation, a sensible default is easy and expected.

On the other hand, something like the following obviously should be closed and allow exhaustive switching.

public enum AutonomousCarAction {
    case turnLeft
    case turnRight
}

What I'm getting at here is I think it makes a lot of sense to allow the library developer to choose open or closed. I'd lean toward closed being the default since it seems to be the safer option--at least in terms of applications at runtime. Adding new options to a closed enum will cause the code not to compile, but at least consumers of the API aren't forced into adding "I don't know what to do here" default cases.

Tanner Nelson
Vapor
+1 (435) 773-2831

···

On Feb 7, 2017, at 10:32 PM, Michael Ilseman <milseman@apple.com> wrote:

On Feb 7, 2017, at 1:21 PM, Tanner Nelson <tanner@qutheory.io <mailto:tanner@qutheory.io>> wrote:

That's awesome. It looks like `(planned) Open and closed enums` is exactly what I'm looking for.

Would it help if I created a concrete proposal for that or is it something the Swift team already has brewing?

Note that open enums also have to solve ABI stability across versions.

Basically, it sounds like you have an enum for which there is a reasonable default handling in client code for any cases added in future versions of your framework. Note that this is not true of all enums. The current behavior of breaking clients when new cases are added forces them to think about this new variant and their assumptions associated therein. This is very desirable for many enums, even public ones.

An intermediary approach could be to come up with an attribute to convey your intent ala https://github.com/apple/swift-evolution/blob/master/proposals/0047-nonvoid-warn.md. The idea is to keep default behavior the same, but have something like (straw man syntax) @defaultCaseRequired.

Sent from my iPhone

On Feb 7, 2017, at 22:01, Michael Ilseman <milseman@apple.com <mailto:milseman@apple.com>> wrote:

BTW, this will likely be part of the eventual design of “open”/resilient enums ala https://github.com/apple/swift/blob/master/docs/LibraryEvolution.rst#enums. There, the goal is to reduce both ABI and source breakage caused by this sort of thing. It seems like for your purposes, you’re less inclined to care about ABI breakage than source breakage, though that may change in the future.

On Feb 7, 2017, at 7:12 AM, Tanner Nelson via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Hello Swift Evolution,

I'd like to propose that a warning be emitted when default cases are omitted for enums from other modules.

What this would look like:

OtherModule:

public enum SomeEnum {
    case one
    case two
}

public let global: SomeEnum = .one

executable:

import OtherModule

switch OtherModule.global {
    case .one: break
    case .two: break
    ^~~~~ ⚠︎ Warning: Default case recommended for imported enums. Fix-it: Add `default: break`
}

Why:

Allowing the omission of a default case in an exhaustive switch makes the addition of a new case to the enum a breaking change.
In other words, if you're exhaustively switching on an enum from an imported library, the imported library can break your code by adding a new case to that enum (which the library authors may erroneously view as an additive/minor-bump change).

Background:

As a maintainer of a Swift framework, public enums have been a pain point in maintaining semver. They've made it difficult to implement additive features and have necessitated the avoidance of enums in our future public API plans.

Related Twitter thread: https://twitter.com/tanner0101/status/796860273760104454

Looking forward to hearing your thoughts.

Best,
Tanner

Tanner Nelson
Vapor
+1 (435) 773-2831

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


(Slava Pestov) #18

Allowing the omission of a default case in an exhaustive switch makes the addition of a new case to the enum a breaking change.

Depending on the context, even with a default the change might be breaking — but without giving notice to the framework client!
Additionally, it would feel very strange for me to have a default clause for bool-like enums* or other examples where there can't be any additional cases.

When there are two types of enums, the proposal makes sense for the "open"-ones; but I'm not sure if I like the concept of having two kinds of enum.

Why not? Bool-like enums would be declared ‘closed’, and would not require a default case (but adding a new case would then break ABI).

For better or worse we need the ability to define enums that admit new cases without breaking ABI. Whether or not this is the default for all enums, or enabled with a special attribute can be designed later when we send out evolution proposals for resilience-related features.

Slava

···

On Feb 8, 2017, at 1:34 AM, Tino Heth via swift-evolution <swift-evolution@swift.org> wrote:

- Tino

* tiny anecdote: I actually have seen the equivalent of this

func reallyExhaustive(input: Bool) {
  if input == true {
    print("true")
  } else if input != true {
    print ("not true")
  } else {
    print("just to be save")
  }
}

in the wild… but I don't remember the comment I wrote :wink:
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Michael Ilseman) #19

To give a concrete example, some of the issues have arisen from using enums as Swift.Errors.

public enum SocketError: Error {
    case closed
}

Then we find some new error that was unaccounted for and want to add a new case.

public enum SocketError: Error {
    case closed
    case brokenPipe
}

What should have been a minor or possibly even a patch bump now requires a major bump.

Looking back, it would have been easy to use a different data structure for these types of errors. But there's something so elegant about handling error enums like:

do {
    try someSocketThing()
} catch let error as SocketError {
    switch error {
        case .closed:
            print("the socket closed")
        default:
            print("there was an error with the socket")
    }
}

In this situation, a sensible default is easy and expected.

On the other hand, something like the following obviously should be closed and allow exhaustive switching.

public enum AutonomousCarAction {
    case turnLeft
    case turnRight
}

What I'm getting at here is I think it makes a lot of sense to allow the library developer to choose open or closed. I'd lean toward closed being the default since it seems to be the safer option--at least in terms of applications at runtime. Adding new options to a closed enum will cause the code not to compile, but at least consumers of the API aren't forced into adding "I don't know what to do here" default cases.

This kind of enum usage is a common source of API fragility. A potential workaround is to use a struct. E.g.:

public struct SocketError: Error {
  let id: Int
  private init(_ id: Int) { self.id = id }

  // .. common functionality goes here
  static let broken: SocketError { return SocketError(0) }
  static let closed: SocketError { return SocketError(1) }
  ...
}

And the use site would be:

do {…}
catch let error as SocketError {
  if error == .broken {
    …
  } else {
    … default handling ...
  }
}

(this is very similar to how the NS_[EXTENSIBLE]_ENUMs are imported into Swift; you get layout-equivalent with C for free)

Another alternative is to have “SocketError” be a protocol, and “BrokenPipe” be an uninhabitable type that conforms to it (i.e. case-less enum). Such errors could then also be phantom types.

···

On Feb 7, 2017, at 1:44 PM, Tanner Nelson <tanner@qutheory.io> wrote:

Tanner Nelson
Vapor
+1 (435) 773-2831

On Feb 7, 2017, at 10:32 PM, Michael Ilseman <milseman@apple.com <mailto:milseman@apple.com>> wrote:

On Feb 7, 2017, at 1:21 PM, Tanner Nelson <tanner@qutheory.io <mailto:tanner@qutheory.io>> wrote:

That's awesome. It looks like `(planned) Open and closed enums` is exactly what I'm looking for.

Would it help if I created a concrete proposal for that or is it something the Swift team already has brewing?

Note that open enums also have to solve ABI stability across versions.

Basically, it sounds like you have an enum for which there is a reasonable default handling in client code for any cases added in future versions of your framework. Note that this is not true of all enums. The current behavior of breaking clients when new cases are added forces them to think about this new variant and their assumptions associated therein. This is very desirable for many enums, even public ones.

An intermediary approach could be to come up with an attribute to convey your intent ala https://github.com/apple/swift-evolution/blob/master/proposals/0047-nonvoid-warn.md. The idea is to keep default behavior the same, but have something like (straw man syntax) @defaultCaseRequired.

Sent from my iPhone

On Feb 7, 2017, at 22:01, Michael Ilseman <milseman@apple.com <mailto:milseman@apple.com>> wrote:

BTW, this will likely be part of the eventual design of “open”/resilient enums ala https://github.com/apple/swift/blob/master/docs/LibraryEvolution.rst#enums. There, the goal is to reduce both ABI and source breakage caused by this sort of thing. It seems like for your purposes, you’re less inclined to care about ABI breakage than source breakage, though that may change in the future.

On Feb 7, 2017, at 7:12 AM, Tanner Nelson via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Hello Swift Evolution,

I'd like to propose that a warning be emitted when default cases are omitted for enums from other modules.

What this would look like:

OtherModule:

public enum SomeEnum {
    case one
    case two
}

public let global: SomeEnum = .one

executable:

import OtherModule

switch OtherModule.global {
    case .one: break
    case .two: break
    ^~~~~ ⚠︎ Warning: Default case recommended for imported enums. Fix-it: Add `default: break`
}

Why:

Allowing the omission of a default case in an exhaustive switch makes the addition of a new case to the enum a breaking change.
In other words, if you're exhaustively switching on an enum from an imported library, the imported library can break your code by adding a new case to that enum (which the library authors may erroneously view as an additive/minor-bump change).

Background:

As a maintainer of a Swift framework, public enums have been a pain point in maintaining semver. They've made it difficult to implement additive features and have necessitated the avoidance of enums in our future public API plans.

Related Twitter thread: https://twitter.com/tanner0101/status/796860273760104454

Looking forward to hearing your thoughts.

Best,
Tanner

Tanner Nelson
Vapor
+1 (435) 773-2831

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


(Rien) #20

If you don’t want the default case, and if you like a warning free compilation, you need a way to suppress the warning.

Regards,
Rien

Site: http://balancingrock.nl
Blog: http://swiftrien.blogspot.com
Github: http://github.com/Balancingrock
Project: http://swiftfire.nl

···

On 07 Feb 2017, at 19:42, Tanner Nelson <tanner@qutheory.io> wrote:

I don't understand the part about warning suppression. The warning would go away when you add the default case.

Sent from my iPhone

On Feb 7, 2017, at 16:25, Rien <Rien@Balancingrock.nl> wrote:

-1

Reason 1: the “negative” behaviour you describe is actually exactly what I want to happen.
Reason 2: Introducing a warning would also necessitate a warning suppression in order to have your code compile without warnings. But when you suppress, the purpose of the warning is nul and void.

PS: I would suggest not to use an enum in cases where this is really annoying and replace the enums with constants.

Regards,
Rien

Site: http://balancingrock.nl
Blog: http://swiftrien.blogspot.com
Github: http://github.com/Balancingrock
Project: http://swiftfire.nl

On 07 Feb 2017, at 16:12, Tanner Nelson via swift-evolution <swift-evolution@swift.org> wrote:

Hello Swift Evolution,

I'd like to propose that a warning be emitted when default cases are omitted for enums from other modules.

What this would look like:

OtherModule:

public enum SomeEnum {
  case one
  case two
}

public let global: SomeEnum = .one

executable:

import OtherModule

switch OtherModule.global {
  case .one: break
  case .two: break
  ^~~~~ ⚠︎ Warning: Default case recommended for imported enums. Fix-it: Add `default: break`
}

Why:

Allowing the omission of a default case in an exhaustive switch makes the addition of a new case to the enum a breaking change.
In other words, if you're exhaustively switching on an enum from an imported library, the imported library can break your code by adding a new case to that enum (which the library authors may erroneously view as an additive/minor-bump change).

Background:

As a maintainer of a Swift framework, public enums have been a pain point in maintaining semver. They've made it difficult to implement additive features and have necessitated the avoidance of enums in our future public API plans.

Related Twitter thread: https://twitter.com/tanner0101/status/796860273760104454

Looking forward to hearing your thoughts.

Best,
Tanner

Tanner Nelson
Vapor
+1 (435) 773-2831

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