Handling unknown cases in enums [RE: SE-0192]

I’m definitely in the error camp, but I will go along with a warning if everyone feels that is better.

Thanks,
Jon

···

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

Okay, I went back to `unknown case` in the proposal, but mentioned Chris's point very specifically: if the compiler emits an error, we should go with `case #unknown` instead. (I'm very strongly in the "warning" camp, though.)

I think the revised proposal is in good shape! (https://github.com/apple/swift-evolution/pull/777) I think I've addressed everyone's feedback either in the proposal or on-list, if not necessarily convinced them. If there are no other major comments I'll let Ted know that it's ready to re-run on Monday.

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

A question about the new #unknown behavior. Is it intended to be used for error handling too ?
Will it be possible to use in catch clause ?

If we go with the #unknown approach, then yes of course it will work in catch clauses. They are patterns, so it naturally falls out.

It will not work in catch clauses because you need to have a static type that's an enum. Catch clauses always (today…) have a static type of 'Error'.

If we go with the “unknown default:” / “unknown case:" approach, then no, this has nothing to do with error handling.

IMO, this pivots on the desired semantics for “unknown cases in enums”: if you intentionally try to match on this, do we get a warning or error if you don’t handle all the cases? If we can get to consensus on that point, then the design is pretty obvious IMO.

That's fair. I'm strongly in favor of a warning, though, because again, people don't edit their dependencies.

A warning is better than nothing :), but for third party libraries you include in the app you are responsible for not updating to a library that would break your build and between compiler and automation catch this error before merging a PR and putting it in production. It is also your responsibility to reasonable choose a library that is properly maintained and vetted.

···

Sent from my iPhone

On 12 Jan 2018, at 18:25, Jordan Rose via swift-evolution <swift-evolution@swift.org> wrote:

On Jan 11, 2018, at 23:30, Chris Lattner <clattner@nondot.org> wrote:

On Jan 11, 2018, at 11:15 PM, Jean-Daniel via swift-evolution <swift-evolution@swift.org> wrote:

Jordan

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

Thanks!

Out of curiosity, why not “unknown default:”? The “warning” behavior is a customization of default, so this seems like a more logical model. It also fits into the existing Swift grammar, unlike “unknown case:” which requires adding a new special case production.

-Chris

···

On Jan 12, 2018, at 3:08 PM, Jordan Rose <jordan_rose@apple.com> wrote:

Okay, I went back to `unknown case` in the proposal, but mentioned Chris's point very specifically: if the compiler emits an error, we should go with `case #unknown` instead. (I'm very strongly in the "warning" camp, though.)

1 Like

A question about the new #unknown behavior. Is it intended to be used for error handling too ?
Will it be possible to use in catch clause ?

If we go with the #unknown approach, then yes of course it will work in catch clauses. They are patterns, so it naturally falls out.
If we go with the “unknown default:” / “unknown case:" approach, then no, this has nothing to do with error handling.
IMO, this pivots on the desired semantics for “unknown cases in enums”: if you intentionally try to match on this, do we get a warning or error if you don’t handle all the cases? If we can get to consensus on that point, then the design is pretty obvious IMO.

For me the other question is what "all the cases" means for enum with private cases(if we'll have them). I.e. if switch contains all the "public" cases of frozen enum - does this mean "all the cases" were processed? As I understand, the answer is no, because we *can* have 'private' case value here and so we need to react to this. How switch will look in this case?

switch frozenEnumWithPrivateCases {
case .one: ..
case .two: ..
unknown default: .. // or 'case #unknown:' depending on our decision, or 'unknown case:' etc
}
?
But then such switch looks exactly as switch for non-frozen enum value, no? It looks like we are reacting on future new cases, while enum is frozen.

Moreover. How the switch for non-frozed enum with private cases should looks like?

switch nonfrozenEnumWithPrivateCases {
case .one: ..
case .two: ..
unknown default: .. // or 'case #unknown:' depending on our decision, or 'unknown case:' etc
}
? But then, is that 'unknown default' for reacting on "future" cases we didn't know about during the compilation OR it is for reacting on private cases?

Or the main idea that we don't want to separate "future" cases and "private" cases?

I think treating both as the same thing is the right idea. You also need to handle "future private" cases and "private cases that become public in the future". These are all unknown cases in the context of the switch.

So an enum with private cases can't be switched exhaustively outside of its module. Thus, @frozen would need to forbid private cases... or we need @exhaustive to forbid private cases so they can be allowed by @frozen.

As mentioned in "Future directions", my recommendation to anyone planning to write a proposal for non-public cases is to go with the former, which would keep it from infecting the design.

Thank you for the comment!
From proposal:
"Were such a proposal to be written, I advise that a frozen enum not be permitted to have non-public cases."

OK. Seems logically for frozen enum(imported from another module) to not have non-public cases, as such cases most likely will be added later during the evaluation of the library(external module) - so such enum should not be frozen then.

I'd like to discuss how current decision will fit into the (possible) future 'private cases' in enum.

1. Non-frozen enum with private cases in the same module.

It seems like we'll need to write
switch val {
  case .one : ..
  case .two : ..
  unknown default: .. // for private cases, even 'val' can't have 'future' cases
}

So, 'unknown default' will mean not just 'future cases', but 'future cases and private cases'.

2. Non-frozen enum with private cases in another module.

In this case, if we want exhaustive switch, we need to use 'unknown default'. But I feel like known but private cases are not the same as future public cases for the 'consumer' of that enum, no?

I mean, when making a decision what to do inside 'unknown default' - isn't it important to know what is the "event" - new public case or private(even "known") case? I'm thinking about something like this:

let val = getSomeNonFrozenEnumResultFromLibrary()
switch val {
   case .one : ... // process the val on our side
   case .two : ... // process the val on our side

   private default : sendValueBackToLibraryToProcess(val) // not interested, calling some special handler
   future default : .. // somehow react on important new case introduced in library. for example, show "please update the app" for the user and cancels the current operation
}

I don't think we need to think about "future private" cases, as we don't have access to them in any case, so current and future private cases are not distinguishable for us.

I'm not sure if we should distinct future cases and private cases on 'switch' side, but I think we should discuss this now for taking a correct decision regarding 'unknown default' etc.

P.S. FWIW I agree with Chris Lattner that 'unknown default' fits into Swift syntax and mental model much better than 'unknown case'. This handler is for default reacting on _number_ of unknown cases, not for reacting on some _specific_ case, like other 'case xxx:' handlers. Are we going to discuss this and select the better name? Or most of us not agree that 'unknown case' is the best?

Vladimir.

···

On 12.01.2018 21:38, Jordan Rose wrote:

On Jan 12, 2018, at 06:49, Michel Fortin via swift-evolution <swift-evolution@swift.org >> <mailto:swift-evolution@swift.org>> wrote:

Le 12 janv. 2018 à 4:44, Vladimir.S via swift-evolution <swift-evolution@swift.org >>> <mailto:swift-evolution@swift.org>> a écrit :
On 12.01.2018 10:30, Chris Lattner via swift-evolution wrote:

On Jan 11, 2018, at 11:15 PM, Jean-Daniel via swift-evolution <swift-evolution@swift.org >>>>> <mailto:swift-evolution@swift.org>> wrote:

Jordan

I included that, but also I again don't think that's the correct design. There's a case where that's useful, but that shouldn't be the default, and I don't think it's important enough to do in Swift 5.

Jordan

···

On Jan 12, 2018, at 15:15, Dave DeLong <swift@davedelong.com> wrote:

Unless I’m missing something, this is still missing the discussion on being able to treat all enums of internally-packaged libraries as frozen.

IE, that frozen vs unfrozen is only an issue for enums that come from modules that are not packaged with your app.

Dave

On Jan 12, 2018, at 4:08 PM, Jordan Rose via swift-evolution <swift-evolution@swift.org> wrote:

Okay, I went back to `unknown case` in the proposal, but mentioned Chris's point very specifically: if the compiler emits an error, we should go with `case #unknown` instead. (I'm very strongly in the "warning" camp, though.)

I think the revised proposal is in good shape! (https://github.com/apple/swift-evolution/pull/777) I think I've addressed everyone's feedback either in the proposal or on-list, if not necessarily convinced them. If there are no other major comments I'll let Ted know that it's ready to re-run on Monday.

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

I assume you’re referring to this?
Implicitly treat enums without binary compatibility concerns as @frozen

Several people questioned whether it was necessary to make this distinction for libraries without binary compatibility concerns, i.e. those that are shipped with their client apps. While there may be a need to do something to handle enums shipped with Apple's OS SDKs, it's arguable whether this is worth it for "source libraries", such as SwiftPM packages.

This question can be rephrased as "is adding a case to an enum a source-breaking change?"

I don’t follow on how this is a source-breaking change.

Let’s say I’m writing an app “A” that embeds a module “M”, which defines and enum “E”.

If E is frozen, the everything’s fine and all is well.
If E is *not* frozen, then the compiler is going to force me to put in an “unknown case”, because it has falsely concluded that E may change out from underneath my app.

The will be a mistake on the compiler’s part. I do not need to include the “unknown case” handler while enumerating on E, because there is no possibility of ever getting an unknown enum value from M, because it is statically copied in to my app and will never change.

Dave

···

On Jan 12, 2018, at 4:16 PM, Jordan Rose <jordan_rose@apple.com> wrote:

I included that, but also I again don't think that's the correct design. There's a case where that's useful, but that shouldn't be the default, and I don't think it's important enough to do in Swift 5.

Jordan

On Jan 12, 2018, at 15:15, Dave DeLong <swift@davedelong.com <mailto:swift@davedelong.com>> wrote:

Unless I’m missing something, this is still missing the discussion on being able to treat all enums of internally-packaged libraries as frozen.

IE, that frozen vs unfrozen is only an issue for enums that come from modules that are not packaged with your app.

Dave

On Jan 12, 2018, at 4:08 PM, Jordan Rose via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Okay, I went back to `unknown case` in the proposal, but mentioned Chris's point very specifically: if the compiler emits an error, we should go with `case #unknown` instead. (I'm very strongly in the "warning" camp, though.)

I think the revised proposal is in good shape! (https://github.com/apple/swift-evolution/pull/777) I think I've addressed everyone's feedback either in the proposal or on-list, if not necessarily convinced them. If there are no other major comments I'll let Ted know that it's ready to re-run on Monday.

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

I’m definitely in the error camp, but I will go along with a warning if everyone feels that is better.

I see advantages both ways. The fact that the error approach generalizes much cleaner is definitely a plus.

The primary downside to that approach is losing the ability to rebuild with the same version of a source dependency that has used #unkown when matching a non-frozen enum vended by a common dependency to which a case has been added since the source dependency was written. Requiring this behavior can be viewed as a pretty significant downside. It tightens the coupling between versions of dependencies. This can make it difficult to update dependencies in a large project. The warning approach allows people to opt-in to this behavior by treating warnings as errors without requiring everyone to make the same tradeoff.

It’s really a tough tradeoff.

···

On Jan 12, 2018, at 6:31 PM, Jonathan Hull via swift-evolution <swift-evolution@swift.org> wrote:

Thanks,
Jon

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

Okay, I went back to `unknown case` in the proposal, but mentioned Chris's point very specifically: if the compiler emits an error, we should go with `case #unknown` instead. (I'm very strongly in the "warning" camp, though.)

I think the revised proposal is in good shape! (https://github.com/apple/swift-evolution/pull/777) I think I've addressed everyone's feedback either in the proposal or on-list, if not necessarily convinced them. If there are no other major comments I'll let Ted know that it's ready to re-run on Monday.

Jordan
_______________________________________________
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

I don’t understand why #unknown wouldn’t work in catch clauses. In the absence of typed throws you can’t match on an enums case without the enums base: you can’t use .foo, you have to use MyEnum.foo.

Similarly, catch wouldn’t allow .#unknown, it would require MyEnum.#unknown. This is perfectly well defined and just falls out of the model.

That said, I agree that the issue of source dependencies that might use this is a significant problem. IMO, that argues strongly for “unknown default:” producing a warning.

-Chris

···

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

On Jan 12, 2018, at 3:08 PM, Jordan Rose <jordan_rose@apple.com> wrote:

Okay, I went back to `unknown case` in the proposal, but mentioned Chris's point very specifically: if the compiler emits an error, we should go with `case #unknown` instead. (I'm very strongly in the "warning" camp, though.)

Thanks!

Out of curiosity, why not “unknown default:”? The “warning” behavior is a customization of default, so this seems like a more logical model. It also fits into the existing Swift grammar, unlike “unknown case:” which requires adding a new special case production.

-Chris

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

I would like to add a syntax sugar .casesBelow for enum value to be used in
switch sentence to avoid default case.

enum MyEnum {
    case a
    case b
    case c
}

let myEnum: MyEnum = .a

//Normally we need default case
switch myEnum {
    case .a: print("a")
    default: print("other value")
}

//Now use syntax sugar
switch myEnum.casesBelow {
    case .a: print("a")
}

This would look more intuitive to me than other solutions but I am not sure
how much effort we need for this.

···

On Mon, Jan 15, 2018 at 4:36 AM Vladimir.S via swift-evolution < swift-evolution@swift.org> wrote:

On 12.01.2018 21:38, Jordan Rose wrote:
>
>
>> On Jan 12, 2018, at 06:49, Michel Fortin via swift-evolution < > swift-evolution@swift.org > >> <mailto:swift-evolution@swift.org>> wrote:
>>
>>> Le 12 janv. 2018 à 4:44, Vladimir.S via swift-evolution < > swift-evolution@swift.org > >>> <mailto:swift-evolution@swift.org>> a écrit :
>>>
>>> On 12.01.2018 10:30, Chris Lattner via swift-evolution wrote:
>>>>> On Jan 11, 2018, at 11:15 PM, Jean-Daniel via swift-evolution < > swift-evolution@swift.org > >>>>> <mailto:swift-evolution@swift.org>> wrote:
>>>>>
>>>>> A question about the new #unknown behavior. Is it intended to be
used for error handling too ?
>>>>> Will it be possible to use in catch clause ?
>>>> If we go with the #unknown approach, then yes of course it will work
in catch clauses. They are patterns, so it
>>>> naturally falls out.
>>>> If we go with the “unknown default:” / “unknown case:" approach,
then no, this has nothing to do with error handling.
>>>> IMO, this pivots on the desired semantics for “unknown cases in
enums”: if you intentionally try to match on this,
>>>> do we get a warning or error if you don’t handle all the cases? If
we can get to consensus on that point, then the
>>>> design is pretty obvious IMO.
>>>
>>> For me the other question is what "all the cases" means for enum with
private cases(if we'll have them). I.e. if
>>> switch contains all the "public" cases of frozen enum - does this mean
"all the cases" were processed? As I
>>> understand, the answer is no, because we *can* have 'private' case
value here and so we need to react to this. How
>>> switch will look in this case?
>>>
>>> switch frozenEnumWithPrivateCases {
>>> case .one: ..
>>> case .two: ..
>>> unknown default: .. // or 'case #unknown:' depending on our
decision, or 'unknown case:' etc
>>> }
>>> ?
>>> But then such switch looks exactly as switch for non-frozen enum
value, no? It looks like we are reacting on future
>>> new cases, while enum is frozen.
>>>
>>> Moreover. How the switch for non-frozed enum with private cases should
looks like?
>>>
>>> switch nonfrozenEnumWithPrivateCases {
>>> case .one: ..
>>> case .two: ..
>>> unknown default: .. // or 'case #unknown:' depending on our
decision, or 'unknown case:' etc
>>> }
>>> ? But then, is that 'unknown default' for reacting on "future" cases
we didn't know about during the compilation OR
>>> it is for reacting on private cases?
>>>
>>> Or the main idea that we don't want to separate "future" cases and
"private" cases?
>>
>> I think treating both as the same thing is the right idea. You also
need to handle "future private" cases and "private
>> cases that become public in the future". These are all unknown cases in
the context of the switch.
>>
>> So an enum with private cases can't be switched exhaustively outside of
its module. Thus, @frozen would need to forbid
>> private cases... or we need @exhaustive to forbid private cases so they
can be allowed by @frozen.
>
> As mentioned in "Future directions", my recommendation to anyone
planning to write a proposal for non-public cases is to
> go with the former, which would keep it from infecting the design.
>

Thank you for the comment!
From proposal:
"Were such a proposal to be written, I advise that a frozen enum not be
permitted to have non-public cases."

OK. Seems logically for frozen enum(imported from another module) to not
have non-public cases, as such cases most
likely will be added later during the evaluation of the library(external
module) - so such enum should not be frozen then.

I'd like to discuss how current decision will fit into the (possible)
future 'private cases' in enum.

1. Non-frozen enum with private cases in the same module.

It seems like we'll need to write
switch val {
  case .one : ..
  case .two : ..
  unknown default: .. // for private cases, even 'val' can't have 'future'
cases
}

So, 'unknown default' will mean not just 'future cases', but 'future cases
and private cases'.

2. Non-frozen enum with private cases in another module.

In this case, if we want exhaustive switch, we need to use 'unknown
default'. But I feel like known but private cases
are not the same as future public cases for the 'consumer' of that enum,
no?

I mean, when making a decision what to do inside 'unknown default' - isn't
it important to know what is the "event" -
new public case or private(even "known") case? I'm thinking about
something like this:

let val = getSomeNonFrozenEnumResultFromLibrary()
switch val {
   case .one : ... // process the val on our side
   case .two : ... // process the val on our side

   private default : sendValueBackToLibraryToProcess(val) // not
interested, calling some special handler
   future default : .. // somehow react on important new case introduced
in library. for example, show "please update
the app" for the user and cancels the current operation
}

I don't think we need to think about "future private" cases, as we don't
have access to them in any case, so current and
future private cases are not distinguishable for us.

I'm not sure if we should distinct future cases and private cases on
'switch' side, but I think we should discuss this
now for taking a correct decision regarding 'unknown default' etc.

P.S. FWIW I agree with Chris Lattner that 'unknown default' fits into
Swift syntax and mental model much better than
'unknown case'. This handler is for default reacting on _number_ of
unknown cases, not for reacting on some _specific_
case, like other 'case xxx:' handlers. Are we going to discuss this and
select the better name? Or most of us not agree
that 'unknown case' is the best?

Vladimir.

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

I'm not sure how this fits more into the existing grammar. Both of them require custom parsing with one token's worth of lookahead. You're right that they suggest different natural modelings in the AST, but that's an implementation detail.

To me, `unknown case` is more meaningful than `unknown default`: it matches cases (as in, enum elements declared with `case`) that the developer doesn't know about. "unknown default" is talking about two different things; the "default" part talks about the switch and the code that will be executed, while "unknown" is talking about the input. But this isn't something I feel too strongly about; it will go to review, people will express their opinions, and then you and the rest of the core team will pick one name or the other based on feedback.

(It's also shorter, which gives it 0.3 extra aesthetic points. But unlike attributes or modifiers, it's less common to put things on the same line as switch cases, so that matters a lot less.)

Jordan

···

On Jan 12, 2018, at 22:49, Chris Lattner <clattner@nondot.org> wrote:

On Jan 12, 2018, at 3:08 PM, Jordan Rose <jordan_rose@apple.com> wrote:

Okay, I went back to `unknown case` in the proposal, but mentioned Chris's point very specifically: if the compiler emits an error, we should go with `case #unknown` instead. (I'm very strongly in the "warning" camp, though.)

Thanks!

Out of curiosity, why not “unknown default:”? The “warning” behavior is a customization of default, so this seems like a more logical model. It also fits into the existing Swift grammar, unlike “unknown case:” which requires adding a new special case production.

A question about the new #unknown behavior. Is it intended to be used for error handling too ?
Will it be possible to use in catch clause ?

If we go with the #unknown approach, then yes of course it will work in catch clauses. They are patterns, so it naturally falls out.
If we go with the “unknown default:” / “unknown case:" approach, then no, this has nothing to do with error handling.
IMO, this pivots on the desired semantics for “unknown cases in enums”: if you intentionally try to match on this, do we get a warning or error if you don’t handle all the cases? If we can get to consensus on that point, then the design is pretty obvious IMO.

For me the other question is what "all the cases" means for enum with private cases(if we'll have them). I.e. if switch contains all the "public" cases of frozen enum - does this mean "all the cases" were processed? As I understand, the answer is no, because we *can* have 'private' case value here and so we need to react to this. How switch will look in this case?

switch frozenEnumWithPrivateCases {
case .one: ..
case .two: ..
unknown default: .. // or 'case #unknown:' depending on our decision, or 'unknown case:' etc
}
?
But then such switch looks exactly as switch for non-frozen enum value, no? It looks like we are reacting on future new cases, while enum is frozen.

Moreover. How the switch for non-frozed enum with private cases should looks like?

switch nonfrozenEnumWithPrivateCases {
case .one: ..
case .two: ..
unknown default: .. // or 'case #unknown:' depending on our decision, or 'unknown case:' etc
}
? But then, is that 'unknown default' for reacting on "future" cases we didn't know about during the compilation OR it is for reacting on private cases?

Or the main idea that we don't want to separate "future" cases and "private" cases?

I think treating both as the same thing is the right idea. You also need to handle "future private" cases and "private cases that become public in the future". These are all unknown cases in the context of the switch.

So an enum with private cases can't be switched exhaustively outside of its module. Thus, @frozen would need to forbid private cases... or we need @exhaustive to forbid private cases so they can be allowed by @frozen.

As mentioned in "Future directions", my recommendation to anyone planning to write a proposal for non-public cases is to go with the former, which would keep it from infecting the design.

Thank you for the comment!
From proposal:
"Were such a proposal to be written, I advise that a frozen enum not be permitted to have non-public cases."

OK. Seems logically for frozen enum(imported from another module) to not have non-public cases, as such cases most likely will be added later during the evaluation of the library(external module) - so such enum should not be frozen then.

I'd like to discuss how current decision will fit into the (possible) future 'private cases' in enum.

1. Non-frozen enum with private cases in the same module.

It seems like we'll need to write
switch val {
case .one : ..
case .two : ..
unknown default: .. // for private cases, even 'val' can't have 'future' cases
}

So, 'unknown default' will mean not just 'future cases', but 'future cases and private cases'.

Interesting. I didn't think about that; in the original proposal without any warnings, you just had to use 'default'. But it does make sense, and whoever proposes private cases will have to mention that.

2. Non-frozen enum with private cases in another module.

In this case, if we want exhaustive switch, we need to use 'unknown default'. But I feel like known but private cases are not the same as future public cases for the 'consumer' of that enum, no?

I mean, when making a decision what to do inside 'unknown default' - isn't it important to know what is the "event" - new public case or private(even "known") case? I'm thinking about something like this:

let val = getSomeNonFrozenEnumResultFromLibrary()
switch val {
case .one : ... // process the val on our side
case .two : ... // process the val on our side

private default : sendValueBackToLibraryToProcess(val) // not interested, calling some special handler
future default : .. // somehow react on important new case introduced in library. for example, show "please update the app" for the user and cancels the current operation
}

I don't think we need to think about "future private" cases, as we don't have access to them in any case, so current and future private cases are not distinguishable for us.

I'm not sure if we should distinct future cases and private cases on 'switch' side, but I think we should discuss this now for taking a correct decision regarding 'unknown default' etc.

I think this distinction is both too fine-grained to be useful and not possible to implement. You don't get to see whether an enum has private cases or not, so every switch on a non-frozen enum from another module would have to have both of these, and then when the library is actually updated but your app isn't recompiled, you can't tell whether the case you're just now seeing is a new private case or a new public case.

P.S. FWIW I agree with Chris Lattner that 'unknown default' fits into Swift syntax and mental model much better than 'unknown case'. This handler is for default reacting on _number_ of unknown cases, not for reacting on some _specific_ case, like other 'case xxx:' handlers. Are we going to discuss this and select the better name? Or most of us not agree that 'unknown case' is the best?

*shrug* That's on the core team to decide when the second review period is over. Nobody has convinced me yet that `unknown default` is better than `unknown case`, but the particular spelling here isn't a key part of the proposal. (I just replied to Chris with my reasoning for preferring `unknown case`.)

Jordan

···

On Jan 14, 2018, at 09:36, Vladimir.S <svabox@gmail.com> wrote:
On 12.01.2018 21:38, Jordan Rose wrote:

On Jan 12, 2018, at 06:49, Michel Fortin via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Le 12 janv. 2018 à 4:44, Vladimir.S via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> a écrit :
On 12.01.2018 10:30, Chris Lattner via swift-evolution wrote:

On Jan 11, 2018, at 11:15 PM, Jean-Daniel via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

No, it's "Revision-locked imports".

A source-breaking change is one in which updating the library at compile time will break clients at compile time. That's relevant for libraries distributed with an app as well as for libraries that are part of the OS. You may not care, but I do, and I think other package authors will too.

Jordan

···

On Jan 12, 2018, at 15:20, Dave DeLong <swift@davedelong.com> wrote:

I assume you’re referring to this?
Implicitly treat enums without binary compatibility concerns as @frozen

Several people questioned whether it was necessary to make this distinction for libraries without binary compatibility concerns, i.e. those that are shipped with their client apps. While there may be a need to do something to handle enums shipped with Apple's OS SDKs, it's arguable whether this is worth it for "source libraries", such as SwiftPM packages.

This question can be rephrased as "is adding a case to an enum a source-breaking change?"

I don’t follow on how this is a source-breaking change.

Let’s say I’m writing an app “A” that embeds a module “M”, which defines and enum “E”.

If E is frozen, the everything’s fine and all is well.
If E is *not* frozen, then the compiler is going to force me to put in an “unknown case”, because it has falsely concluded that E may change out from underneath my app.

The will be a mistake on the compiler’s part. I do not need to include the “unknown case” handler while enumerating on E, because there is no possibility of ever getting an unknown enum value from M, because it is statically copied in to my app and will never change.

Dave

On Jan 12, 2018, at 4:16 PM, Jordan Rose <jordan_rose@apple.com <mailto:jordan_rose@apple.com>> wrote:

I included that, but also I again don't think that's the correct design. There's a case where that's useful, but that shouldn't be the default, and I don't think it's important enough to do in Swift 5.

Jordan

On Jan 12, 2018, at 15:15, Dave DeLong <swift@davedelong.com <mailto:swift@davedelong.com>> wrote:

Unless I’m missing something, this is still missing the discussion on being able to treat all enums of internally-packaged libraries as frozen.

IE, that frozen vs unfrozen is only an issue for enums that come from modules that are not packaged with your app.

Dave

On Jan 12, 2018, at 4:08 PM, Jordan Rose via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Okay, I went back to `unknown case` in the proposal, but mentioned Chris's point very specifically: if the compiler emits an error, we should go with `case #unknown` instead. (I'm very strongly in the "warning" camp, though.)

I think the revised proposal is in good shape! (https://github.com/apple/swift-evolution/pull/777) I think I've addressed everyone's feedback either in the proposal or on-list, if not necessarily convinced them. If there are no other major comments I'll let Ted know that it's ready to re-run on Monday.

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

No, it's "Revision-locked imports”.

Ah, yeah I can see how the rev-locked imports is a variation on “I’ve copied in this library”.

A source-breaking change is one in which updating the library at compile time will break clients at compile time. That's relevant for libraries distributed with an app as well as for libraries that are part of the OS. You may not care, but I do, and I think other package authors will too.

??? Of course I care about getting warnings when I’ve chosen to update a dependency. Where did I ever imply otherwise?

What I’m saying is that if I’m embedding such a library in my app *now* and I’m importing it *now*, I shouldn’t get a warning about handling unknown cases off non-frozen enums *now*.

And if I am going to see such warnings now, then the document needs to include how those “false positives” will be eliminated in the future.

Dave

···

On Jan 12, 2018, at 4:22 PM, Jordan Rose <jordan_rose@apple.com> wrote:

Is this equivalent to `default: break`? `default: fatalError()`? I think it's better to write such things explicitly.

Jordan

···

On Jan 14, 2018, at 21:51, Tim Wang <shenghai.wang@bigtincan.com> wrote:

I would like to add a syntax sugar .casesBelow for enum value to be used in switch sentence to avoid default case.

enum MyEnum {
    case a
    case b
    case c
}

let myEnum: MyEnum = .a

//Normally we need default case
switch myEnum {
    case .a: print("a")
    default: print("other value")
}

//Now use syntax sugar
switch myEnum.casesBelow {
    case .a: print("a")
}

This would look more intuitive to me than other solutions but I am not sure how much effort we need for this.

On Mon, Jan 15, 2018 at 4:36 AM Vladimir.S via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On 12.01.2018 21:38, Jordan Rose wrote:
>
>
>> On Jan 12, 2018, at 06:49, Michel Fortin via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org> > >> <mailto:swift-evolution@swift.org <mailto:swift-evolution@swift.org>>> wrote:
>>
>>> Le 12 janv. 2018 à 4:44, Vladimir.S via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org> > >>> <mailto:swift-evolution@swift.org <mailto:swift-evolution@swift.org>>> a écrit :
>>>
>>> On 12.01.2018 10:30, Chris Lattner via swift-evolution wrote:
>>>>> On Jan 11, 2018, at 11:15 PM, Jean-Daniel via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org> > >>>>> <mailto:swift-evolution@swift.org <mailto:swift-evolution@swift.org>>> wrote:
>>>>>
>>>>> A question about the new #unknown behavior. Is it intended to be used for error handling too ?
>>>>> Will it be possible to use in catch clause ?
>>>> If we go with the #unknown approach, then yes of course it will work in catch clauses. They are patterns, so it
>>>> naturally falls out.
>>>> If we go with the “unknown default:” / “unknown case:" approach, then no, this has nothing to do with error handling.
>>>> IMO, this pivots on the desired semantics for “unknown cases in enums”: if you intentionally try to match on this,
>>>> do we get a warning or error if you don’t handle all the cases? If we can get to consensus on that point, then the
>>>> design is pretty obvious IMO.
>>>
>>> For me the other question is what "all the cases" means for enum with private cases(if we'll have them). I.e. if
>>> switch contains all the "public" cases of frozen enum - does this mean "all the cases" were processed? As I
>>> understand, the answer is no, because we *can* have 'private' case value here and so we need to react to this. How
>>> switch will look in this case?
>>>
>>> switch frozenEnumWithPrivateCases {
>>> case .one: ..
>>> case .two: ..
>>> unknown default: .. // or 'case #unknown:' depending on our decision, or 'unknown case:' etc
>>> }
>>> ?
>>> But then such switch looks exactly as switch for non-frozen enum value, no? It looks like we are reacting on future
>>> new cases, while enum is frozen.
>>>
>>> Moreover. How the switch for non-frozed enum with private cases should looks like?
>>>
>>> switch nonfrozenEnumWithPrivateCases {
>>> case .one: ..
>>> case .two: ..
>>> unknown default: .. // or 'case #unknown:' depending on our decision, or 'unknown case:' etc
>>> }
>>> ? But then, is that 'unknown default' for reacting on "future" cases we didn't know about during the compilation OR
>>> it is for reacting on private cases?
>>>
>>> Or the main idea that we don't want to separate "future" cases and "private" cases?
>>
>> I think treating both as the same thing is the right idea. You also need to handle "future private" cases and "private
>> cases that become public in the future". These are all unknown cases in the context of the switch.
>>
>> So an enum with private cases can't be switched exhaustively outside of its module. Thus, @frozen would need to forbid
>> private cases... or we need @exhaustive to forbid private cases so they can be allowed by @frozen.
>
> As mentioned in "Future directions", my recommendation to anyone planning to write a proposal for non-public cases is to
> go with the former, which would keep it from infecting the design.
>

Thank you for the comment!
From proposal:
"Were such a proposal to be written, I advise that a frozen enum not be permitted to have non-public cases."

OK. Seems logically for frozen enum(imported from another module) to not have non-public cases, as such cases most
likely will be added later during the evaluation of the library(external module) - so such enum should not be frozen then.

I'd like to discuss how current decision will fit into the (possible) future 'private cases' in enum.

1. Non-frozen enum with private cases in the same module.

It seems like we'll need to write
switch val {
  case .one : ..
  case .two : ..
  unknown default: .. // for private cases, even 'val' can't have 'future' cases
}

So, 'unknown default' will mean not just 'future cases', but 'future cases and private cases'.

2. Non-frozen enum with private cases in another module.

In this case, if we want exhaustive switch, we need to use 'unknown default'. But I feel like known but private cases
are not the same as future public cases for the 'consumer' of that enum, no?

I mean, when making a decision what to do inside 'unknown default' - isn't it important to know what is the "event" -
new public case or private(even "known") case? I'm thinking about something like this:

let val = getSomeNonFrozenEnumResultFromLibrary()
switch val {
   case .one : ... // process the val on our side
   case .two : ... // process the val on our side

   private default : sendValueBackToLibraryToProcess(val) // not interested, calling some special handler
   future default : .. // somehow react on important new case introduced in library. for example, show "please update
the app" for the user and cancels the current operation
}

I don't think we need to think about "future private" cases, as we don't have access to them in any case, so current and
future private cases are not distinguishable for us.

I'm not sure if we should distinct future cases and private cases on 'switch' side, but I think we should discuss this
now for taking a correct decision regarding 'unknown default' etc.

P.S. FWIW I agree with Chris Lattner that 'unknown default' fits into Swift syntax and mental model much better than
'unknown case'. This handler is for default reacting on _number_ of unknown cases, not for reacting on some _specific_
case, like other 'case xxx:' handlers. Are we going to discuss this and select the better name? Or most of us not agree
that 'unknown case' is the best?

Vladimir.

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

I don’t understand why #unknown wouldn’t work in catch clauses. In the absence of typed throws you can’t match on an enums case without the enums base: you can’t use .foo, you have to use MyEnum.foo.

Similarly, catch wouldn’t allow .#unknown, it would require MyEnum.#unknown. This is perfectly well defined and just falls out of the model.

I did not think about this. You're right, we could allow that syntax. We don't currently have any hash-prefixed constructs that aren't "top-level", but that's not a rule or anything.

Jordan

···

On Jan 13, 2018, at 18:33, Chris Lattner <clattner@nondot.org> wrote:

That said, I agree that the issue of source dependencies that might use this is a significant problem. IMO, that argues strongly for “unknown default:” producing a warning.

-Chris

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

On Jan 12, 2018, at 3:08 PM, Jordan Rose <jordan_rose@apple.com> wrote:

Okay, I went back to `unknown case` in the proposal, but mentioned Chris's point very specifically: if the compiler emits an error, we should go with `case #unknown` instead. (I'm very strongly in the "warning" camp, though.)

Thanks!

Out of curiosity, why not “unknown default:”? The “warning” behavior is a customization of default, so this seems like a more logical model. It also fits into the existing Swift grammar, unlike “unknown case:” which requires adding a new special case production.

-Chris

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

The parser has fairly general support for declmodifiers, and my proposal fits directly into that. The only extension is that ‘default’ isn’t a decl, and I don’t think we have a statement modifier yet. That said, we’ve always planned to have them when the need arose.

-Chris

···

On Jan 16, 2018, at 10:24 AM, Jordan Rose <jordan_rose@apple.com> wrote:

On Jan 12, 2018, at 3:08 PM, Jordan Rose <jordan_rose@apple.com <mailto:jordan_rose@apple.com>> wrote:

Okay, I went back to `unknown case` in the proposal, but mentioned Chris's point very specifically: if the compiler emits an error, we should go with `case #unknown` instead. (I'm very strongly in the "warning" camp, though.)

Thanks!

Out of curiosity, why not “unknown default:”? The “warning” behavior is a customization of default, so this seems like a more logical model. It also fits into the existing Swift grammar, unlike “unknown case:” which requires adding a new special case production.

I'm not sure how this fits more into the existing grammar. Both of them require custom parsing with one token's worth of lookahead. You're right that they suggest different natural modelings in the AST, but that's an implementation detail.

I feel like we’re talking past each other right now, so I’ll give a concrete example:

Let’s say MyAwesomeFramework.framework defines this enum:

enum Access {
  case none
  case readOnly
  case readWrite
}

This framework is compiled, built, and embedded in to MyAwesomeApp.app/Frameworks.

Somewhere in MyAwesomeApp, I switch on the access of a thing:

switch aThing.access {
  case .none: break
  case .readOnly: print(“I can read”)
  case .readWrite: print(“I can write”)
}

Let’s here observe some things:

1️⃣ the enum “Access” is *not* declared as frozen, because it might get a new case in the future (such as “writeOnly”)
2️⃣ the switch on an Access value is exhaustive
3️⃣ under the proposal, I’m going to get a warning on the switch because Access is not frozen. That warning will tell me to add “unknown case:”

This is the problem I’m talking about. The warning I’ll get on the switch is technically correct (I’m switching on a non-frozen enum), but semantically incorrect (it’s ok because the framework CANNOT introduce a new case without recompilation of the switch statement, because they are packaged together). The compiler will tell me to add an unknown case statement to the switch when it will be completely unnecessary. Since MyAwesomeFramework.framework is embedded in my app, there is no binary compatibility concern here, and I will never come across an unknown case.

I think this false positive is going to be very common, because lots of people embed frameworks in their apps, and those frameworks declare enums. Until the frameworks are updated to freeze the enums, the app authors will be forced to add “unknown case” statements to their switches for places where it is entirely unnecessary. (This brings up the question: does this proposal include a fixit to remove an “unknown case” statement from switches where it’s unnecessary?)

So, what I’m asking for is a part in the proposal (likely under “Future Directions”) where you acknowledge this problem (the “false positive problem for embedded modules”) and list it as something we’d like to solve in the future.

Dave

···

On Jan 12, 2018, at 4:27 PM, Dave DeLong via swift-evolution <swift-evolution@swift.org> wrote:

On Jan 12, 2018, at 4:22 PM, Jordan Rose <jordan_rose@apple.com <mailto:jordan_rose@apple.com>> wrote:

No, it's "Revision-locked imports”.

Ah, yeah I can see how the rev-locked imports is a variation on “I’ve copied in this library”.

A source-breaking change is one in which updating the library at compile time will break clients at compile time. That's relevant for libraries distributed with an app as well as for libraries that are part of the OS. You may not care, but I do, and I think other package authors will too.

??? Of course I care about getting warnings when I’ve chosen to update a dependency. Where did I ever imply otherwise?

What I’m saying is that if I’m embedding such a library in my app *now* and I’m importing it *now*, I shouldn’t get a warning about handling unknown cases off non-frozen enums *now*.

And if I am going to see such warnings now, then the document needs to include how those “false positives” will be eliminated in the future.

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

1 Like

‘case’ isn’t a decl in switches either. Nor is it a statement, the way things are modeled today. But this is all implementation detail; it’s not interesting to discuss that.

Jordan

···

On Jan 17, 2018, at 14:41, Chris Lattner <clattner@nondot.org> wrote:

On Jan 16, 2018, at 10:24 AM, Jordan Rose <jordan_rose@apple.com <mailto:jordan_rose@apple.com>> wrote:

On Jan 12, 2018, at 3:08 PM, Jordan Rose <jordan_rose@apple.com <mailto:jordan_rose@apple.com>> wrote:

Okay, I went back to `unknown case` in the proposal, but mentioned Chris's point very specifically: if the compiler emits an error, we should go with `case #unknown` instead. (I'm very strongly in the "warning" camp, though.)

Thanks!

Out of curiosity, why not “unknown default:”? The “warning” behavior is a customization of default, so this seems like a more logical model. It also fits into the existing Swift grammar, unlike “unknown case:” which requires adding a new special case production.

I'm not sure how this fits more into the existing grammar. Both of them require custom parsing with one token's worth of lookahead. You're right that they suggest different natural modelings in the AST, but that's an implementation detail.

The parser has fairly general support for declmodifiers, and my proposal fits directly into that. The only extension is that ‘default’ isn’t a decl, and I don’t think we have a statement modifier yet. That said, we’ve always planned to have them when the need arose.

I feel like we’re talking past each other right now, so I’ll give a concrete example:

Let’s say MyAwesomeFramework.framework defines this enum:

enum Access {
  case none
  case readOnly
  case readWrite
}

This framework is compiled, built, and embedded in to MyAwesomeApp.app/Frameworks.

Somewhere in MyAwesomeApp, I switch on the access of a thing:

switch aThing.access {
  case .none: break
  case .readOnly: print(“I can read”)
  case .readWrite: print(“I can write”)
}

Let’s here observe some things:

1️⃣ the enum “Access” is *not* declared as frozen, because it might get a new case in the future (such as “writeOnly”)
2️⃣ the switch on an Access value is exhaustive
3️⃣ under the proposal, I’m going to get a warning on the switch because Access is not frozen. That warning will tell me to add “unknown case:”

This is the problem I’m talking about. The warning I’ll get on the switch is technically correct (I’m switching on a non-frozen enum), but semantically incorrect (it’s ok because the framework CANNOT introduce a new case without recompilation of the switch statement, because they are packaged together). The compiler will tell me to add an unknown case statement to the switch when it will be completely unnecessary. Since MyAwesomeFramework.framework is embedded in my app, there is no binary compatibility concern here, and I will never come across an unknown case.

I think this false positive is going to be very common, because lots of people embed frameworks in their apps, and those frameworks declare enums. Until the frameworks are updated to freeze the enums, the app authors will be forced to add “unknown case” statements to their switches for places where it is entirely unnecessary. (This brings up the question: does this proposal include a fixit to remove an “unknown case” statement from switches where it’s unnecessary?)

So, what I’m asking for is a part in the proposal (likely under “Future Directions”) where you acknowledge this problem (the “false positive problem for embedded modules”) and list it as something we’d like to solve in the future.

I agree that it's important to discuss this in the proposal.

I think the point that Jordan is trying to make is that this kind of resilience problem can come up as a source-compatibility issue, not just a binary-compatibility issue. Of course an app can be version-locked to its non-OS dependencies and nobody will care. However, it's problematic for a library to be version-locked to its dependencies even if both the library and its dependencies are only ever distributed as source: you ought to be able to upgrade the dependencies without having to revise all the dependent libraries. If our library ecosystem doesn't let you pull a new version of BaseLib without simultaneously pulling new versions of everything else you use that requires BaseLib — or, alternatively, hand-fixing them all yourself if that library hasn't gotten updated yet — that's a pretty frustrating experience.

Conversely, an app ought to be able to be version-locked to its non-OS dependencies whether they're binaries or source. So this really has almost nothing to do with binary vs. source distribution and everything to do with whether version-locking makes sense for a specific project.

John.

···

On Jan 12, 2018, at 6:47 PM, Dave DeLong via swift-evolution <swift-evolution@swift.org> wrote:

Dave

On Jan 12, 2018, at 4:27 PM, Dave DeLong via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Jan 12, 2018, at 4:22 PM, Jordan Rose <jordan_rose@apple.com <mailto:jordan_rose@apple.com>> wrote:

No, it's "Revision-locked imports”.

Ah, yeah I can see how the rev-locked imports is a variation on “I’ve copied in this library”.

A source-breaking change is one in which updating the library at compile time will break clients at compile time. That's relevant for libraries distributed with an app as well as for libraries that are part of the OS. You may not care, but I do, and I think other package authors will too.

??? Of course I care about getting warnings when I’ve chosen to update a dependency. Where did I ever imply otherwise?

What I’m saying is that if I’m embedding such a library in my app *now* and I’m importing it *now*, I shouldn’t get a warning about handling unknown cases off non-frozen enums *now*.

And if I am going to see such warnings now, then the document needs to include how those “false positives” will be eliminated in the future.

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

Okay, I went back to `unknown case` in the proposal, but mentioned Chris's point very specifically: if the compiler emits an error, we should go with `case #unknown` instead. (I'm very strongly in the "warning" camp, though.)

Thanks!

Out of curiosity, why not “unknown default:”? The “warning” behavior is a customization of default, so this seems like a more logical model. It also fits into the existing Swift grammar, unlike “unknown case:” which requires adding a new special case production.

I'm not sure how this fits more into the existing grammar. Both of them require custom parsing with one token's worth of lookahead. You're right that they suggest different natural modelings in the AST, but that's an implementation detail.

The parser has fairly general support for declmodifiers, and my proposal fits directly into that. The only extension is that ‘default’ isn’t a decl, and I don’t think we have a statement modifier yet. That said, we’ve always planned to have them when the need arose.

‘case’ isn’t a decl in switches either. Nor is it a statement, the way things are modeled today.

Bleah, sorry, I forgot that it is a statement at the moment. But I don't think it needs to be modeled as a statement attribute/modifier. A flag on CaseStmt is fine.

Jordan

···

On Jan 17, 2018, at 14:42, Jordan Rose <jordan_rose@apple.com> wrote:

On Jan 17, 2018, at 14:41, Chris Lattner <clattner@nondot.org <mailto:clattner@nondot.org>> wrote:

On Jan 16, 2018, at 10:24 AM, Jordan Rose <jordan_rose@apple.com <mailto:jordan_rose@apple.com>> wrote:

On Jan 12, 2018, at 3:08 PM, Jordan Rose <jordan_rose@apple.com <mailto:jordan_rose@apple.com>> wrote:

But this is all implementation detail; it’s not interesting to discuss that.

Jordan