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

The mega-thread about SE-0192 is a bit large, and I’d like to talk about one specific point. In the review conversation, there has been significant objection to the idea of requiring a ‘default’ for switches over enums that are non-exhaustive.

This whole discussion is happening because the ABI stability work is introducing a new concept to enums - invisible members/inhabitants (and making them reasonably prominent). A closely related feature is that may come up in the future is "private cases”. Private cases are orthogonal to API evolution and may even occur on one that is defined to be exhaustive.

Private cases and non-exhaustive enums affect the enum in the same way: they say that the enum can have values that a client does not know about. Swift requires switches to process *all* of the dynamically possible values, which is why the original proposal started out with the simplest possible solution: just require ‘default' when processing the cases.

The problems with “unknown case:”

The popular solution to this probably is currently being pitched as a change to the proposal (https://github.com/apple/swift-evolution/pull/777) which introduces a new concept “unknown case” as a near-alias for ‘default’:
https://github.com/jrose-apple/swift-evolution/blob/60d8698d7cde2e1824789b952558bade541415f1/proposals/0192-non-exhaustive-enums.md#unknown-case

In short, I think this is the wrong way to solve the problem. I have several concerns with this:

1) Unlike in C, switch is Swift is a general pattern matching facility - not a way of processing integers. It supports recursive patterns and values, and enums are not necessarily at the top-level of the pattern. https://github.com/apple/swift/blob/master/docs/PatternMatching.rst is a document from early evolution of Swift but contains a good general introduction to this.

2) Swift also has other facilities for pattern matching, including ‘if case’. Making switch inconsistent with them is not great.

3) As pitched, “unknown case” will match *known* cases too, which is (in my opinion :slight_smile: oxymoronic.

4) “unknown case:” changes the basic swift grammar (it isn’t just a modifier on case) because case *requires* a pattern. A better spelling would be “unknown default:” which is closer to the semantic provided anyway.

5) It is entirely reasonable (though rare in practice) to want to handle default and unknown cases in the same switch.

For all the above reasons, ‘unknown case:' becomes a weird wart put on the side of switch/case, not something that fits in naturally with the rest of Swift.

Alternative proposal:

Instead of introducing a new modifier on case/switch, lets just introduce a new pattern matching operator that *matches unknown cases*, called “#unknown” or “.#unknown” or something (I’m not wed to the syntax, better suggestions welcome :).

In the simple case most people are talking about, instead of writing “unknown case:” you’d write “case #unknown:” which isn’t much different. The nice thing about this is that #unknown slots directly into our pattern matching system. Here is a weird example:

switch someIntEnumTuple {
case (1, .X): … matches one combination of int and tuple...
case (2, .Y): … matches another combination of int and tuple...
case (_, #unknown): … matches any int and any unknown enum case ...
case default: … matches anything ...
}

Furthermore, if you have a switch that enumerates all of the known cases and use #unknown, then it falls out of the model that new cases (e.g. due to an SDK upgrade or an updated source package) produces the existing build error. As with the original proposal, you can always choose to use “default:” instead of “case #unknown:” if you don’t like that behavior.

Of course, if you have an exhaustive enum (e.g. one defined in your own module or explicitly marked as such) then #unknown matches nothing, so we should warn about it being pointless.

This addresses my concerns above:

1) This fits into patterns in recursive positions, and slots directly into the existing grammar for patterns. It would be a very simple extension to the compiler instead of a special case added to switch/case.

2) Because it slots into the pattern grammar, it works directly with 'if case’ and the other pattern matching stuff.

3) Doesn’t match known cases.

4) Doesn’t change the case grammar, it just adds a new pattern terminal production.

5) Allows weird cases like the example above.

All that said, the #unknown spelling isn’t great, but I’m sure we can find something else nice.

Thoughts?

-Chris

1 Like

Something like `#unknown` or `#undiscovered` in the pattern matching syntax makes complete sense.

-Richard

···

On Jan 8, 2018, at 22:54, Chris Lattner via swift-evolution <swift-evolution@swift.org> wrote:

The mega-thread about SE-0192 is a bit large, and I’d like to talk about one specific point. In the review conversation, there has been significant objection to the idea of requiring a ‘default’ for switches over enums that are non-exhaustive.

This whole discussion is happening because the ABI stability work is introducing a new concept to enums - invisible members/inhabitants (and making them reasonably prominent). A closely related feature is that may come up in the future is "private cases”. Private cases are orthogonal to API evolution and may even occur on one that is defined to be exhaustive.

Private cases and non-exhaustive enums affect the enum in the same way: they say that the enum can have values that a client does not know about. Swift requires switches to process *all* of the dynamically possible values, which is why the original proposal started out with the simplest possible solution: just require ‘default' when processing the cases.

The problems with “unknown case:”

The popular solution to this probably is currently being pitched as a change to the proposal (https://github.com/apple/swift-evolution/pull/777) which introduces a new concept “unknown case” as a near-alias for ‘default’:
https://github.com/jrose-apple/swift-evolution/blob/60d8698d7cde2e1824789b952558bade541415f1/proposals/0192-non-exhaustive-enums.md#unknown-case

In short, I think this is the wrong way to solve the problem. I have several concerns with this:

1) Unlike in C, switch is Swift is a general pattern matching facility - not a way of processing integers. It supports recursive patterns and values, and enums are not necessarily at the top-level of the pattern. https://github.com/apple/swift/blob/master/docs/PatternMatching.rst is a document from early evolution of Swift but contains a good general introduction to this.

2) Swift also has other facilities for pattern matching, including ‘if case’. Making switch inconsistent with them is not great.

3) As pitched, “unknown case” will match *known* cases too, which is (in my opinion :slight_smile: oxymoronic.

4) “unknown case:” changes the basic swift grammar (it isn’t just a modifier on case) because case *requires* a pattern. A better spelling would be “unknown default:” which is closer to the semantic provided anyway.

5) It is entirely reasonable (though rare in practice) to want to handle default and unknown cases in the same switch.

For all the above reasons, ‘unknown case:' becomes a weird wart put on the side of switch/case, not something that fits in naturally with the rest of Swift.

Alternative proposal:

Instead of introducing a new modifier on case/switch, lets just introduce a new pattern matching operator that *matches unknown cases*, called “#unknown” or “.#unknown” or something (I’m not wed to the syntax, better suggestions welcome :).

In the simple case most people are talking about, instead of writing “unknown case:” you’d write “case #unknown:” which isn’t much different. The nice thing about this is that #unknown slots directly into our pattern matching system. Here is a weird example:

switch someIntEnumTuple {
case (1, .X): … matches one combination of int and tuple...
case (2, .Y): … matches another combination of int and tuple...
case (_, #unknown): … matches any int and any unknown enum case ...
case default: … matches anything ...
}

Furthermore, if you have a switch that enumerates all of the known cases and use #unknown, then it falls out of the model that new cases (e.g. due to an SDK upgrade or an updated source package) produces the existing build error. As with the original proposal, you can always choose to use “default:” instead of “case #unknown:” if you don’t like that behavior.

Of course, if you have an exhaustive enum (e.g. one defined in your own module or explicitly marked as such) then #unknown matches nothing, so we should warn about it being pointless.

This addresses my concerns above:

1) This fits into patterns in recursive positions, and slots directly into the existing grammar for patterns. It would be a very simple extension to the compiler instead of a special case added to switch/case.

2) Because it slots into the pattern grammar, it works directly with 'if case’ and the other pattern matching stuff.

3) Doesn’t match known cases.

4) Doesn’t change the case grammar, it just adds a new pattern terminal production.

5) Allows weird cases like the example above.

All that said, the #unknown spelling isn’t great, but I’m sure we can find something else nice.

Thoughts?

-Chris

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

Hi Chris,

This is great. Thanks for spending time on this! I am in favor of `case
#unknown` to only match unknown cases.

1) Would #uknown be available to RawRepresentable structs?

2) How is the #uknown pattern accomplished? Are you suggesting to capture
all the compile time known cases so you can do a diff during
runtime? (Sorry this is not obvious to me)

Previously I suggested having something like `case #known` that would only
match known cases by capturing known cases at compile time ( maybe using a
compile-time variation of
https://github.com/apple/swift-evolution/blob/master/proposals/0194-derived-collection-of-enum-cases.md).
This would give us the equivalent of `case _` == `case #known` + `case
#unknown`. The only issue I found with `case #known` is that it would be
the same as `case _` when working with an exhaustive enum.

switch myEnum{
case .X : //
case .Y : //
case #unknown : // Matches all runtime unknown cases
case #known : // Matches all compile known cases
}

···

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

The mega-thread about SE-0192 is a bit large, and I’d like to talk about
one specific point. In the review conversation, there has been significant
objection to the idea of requiring a ‘default’ for switches over enums that
are non-exhaustive.

This whole discussion is happening because the ABI stability work is
introducing a new concept to enums - invisible members/inhabitants (and
making them reasonably prominent). A closely related feature is that may
come up in the future is "private cases”. Private cases are orthogonal to
API evolution and may even occur on one that is defined to be exhaustive.

Private cases and non-exhaustive enums affect the enum in the same way:
they say that the enum can have values that a client does not know about.
Swift requires switches to process *all* of the dynamically possible
values, which is why the original proposal started out with the simplest
possible solution: just require ‘default' when processing the cases.

*The problems with “unknown case:”*

The popular solution to this probably is currently being pitched as a
change to the proposal (https://github.com/apple/swift-evolution/pull/777)
which introduces a new concept “unknown case” as a near-alias for ‘default’:
https://github.com/jrose-apple/swift-evolution/blob/
60d8698d7cde2e1824789b952558bade541415f1/proposals/0192-non-
exhaustive-enums.md#unknown-case

In short, I think this is the wrong way to solve the problem. I have
several concerns with this:

1) Unlike in C, switch is Swift is a general pattern matching facility -
not a way of processing integers. It supports recursive patterns and
values, and enums are not necessarily at the top-level of the pattern.
https://github.com/apple/swift/blob/master/docs/PatternMatching.rst is a
document from early evolution of Swift but contains a good general
introduction to this.

2) Swift also has other facilities for pattern matching, including ‘if
case’. Making switch inconsistent with them is not great.

3) As pitched, “unknown case” will match *known* cases too, which is (in
my opinion :slight_smile: oxymoronic.

4) “unknown case:” changes the basic swift grammar (it isn’t just a
modifier on case) because case *requires* a pattern. A better spelling
would be “unknown default:” which is closer to the semantic provided anyway.

5) It is entirely reasonable (though rare in practice) to want to handle
default and unknown cases in the same switch.

For all the above reasons, ‘unknown case:' becomes a weird wart put on the
side of switch/case, not something that fits in naturally with the rest of
Swift.

*Alternative proposal:*

Instead of introducing a new modifier on case/switch, lets just introduce
a new pattern matching operator that *matches unknown cases*, called
#unknown” or “.#unknown” or something (I’m not wed to the syntax, better
suggestions welcome :).

In the simple case most people are talking about, instead of writing
“unknown case:” you’d write “case #unknown:” which isn’t much different.
The nice thing about this is that #unknown slots directly into our pattern
matching system. Here is a weird example:

switch someIntEnumTuple {
case (1, .X): … matches one combination of int and tuple...
case (2, .Y): … matches another combination of int and tuple...
case (_, #unknown): … matches any int and any unknown enum case ...
case default: … matches anything ...
}

Furthermore, if you have a switch that enumerates all of the known cases
and use #unknown, then it falls out of the model that new cases (e.g. due
to an SDK upgrade or an updated source package) produces the existing build
error. As with the original proposal, you can always choose to use
“default:” instead of “case #unknown:” if you don’t like that behavior.

Of course, if you have an exhaustive enum (e.g. one defined in your own
module or explicitly marked as such) then #unknown matches nothing, so we
should warn about it being pointless.

This addresses my concerns above:

1) This fits into patterns in recursive positions, and slots directly into
the existing grammar for patterns. It would be a very simple extension to
the compiler instead of a special case added to switch/case.

2) Because it slots into the pattern grammar, it works directly with 'if
case’ and the other pattern matching stuff.

3) Doesn’t match known cases.

4) Doesn’t change the case grammar, it just adds a new pattern terminal
production.

5) Allows weird cases like the example above.

All that said, the #unknown spelling isn’t great, but I’m sure we can find
something else nice.

Thoughts?

-Chris

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

Thanks for writing this up, Chris! I addressed the idea of making this an arbitrary pattern in the revised proposal <https://github.com/jrose-apple/swift-evolution/blob/non-exhaustive-enums-2/proposals/0192-non-exhaustive-enums.md#unknown-case-patterns>, and the idea of not matching known cases in a "considered alternative" <https://github.com/jrose-apple/swift-evolution/blob/non-exhaustive-enums-2/proposals/0192-non-exhaustive-enums.md#mixing-unknown-case-and-default>, but in short:

- Matching in arbitrary pattern positions is harder to implement and harder to produce good diagnostics for. (Specifically, the former comes from having to actually check the existing cases in the generated code rather than just having an "else" branch.) I'm not inherently opposed to it if we can all agree on what it means, but I don't think I have time to implement it for Swift 5, so I'm not including it in the proposal.

- Matching known cases is a feature, not a limitation, to avoid existing code changing meaning when you recompile. I'll admit that's not the strongest motivation, though, since other things can change the meaning of existing code when you recompile already.

- FWIW I can't actually think of a use case for using this with `if case` or anything else. I'm not against it, but I don't know why you would ever do it, just like I don't know why you would mix `case #unknown` with `default` when matching against a single value.

That said, it sounds like people are happier with `case #unknown` than `unknown case`, and that leaves things a little more consistent if we ever do expand it to other pattern positions, so I'll change the proposal revision to use that spelling. (And if anyone comes up with a nicer spelling, great!)

Jordan

···

On Jan 8, 2018, at 22:54, Chris Lattner via swift-evolution <swift-evolution@swift.org> wrote:

The mega-thread about SE-0192 is a bit large, and I’d like to talk about one specific point. In the review conversation, there has been significant objection to the idea of requiring a ‘default’ for switches over enums that are non-exhaustive.

This whole discussion is happening because the ABI stability work is introducing a new concept to enums - invisible members/inhabitants (and making them reasonably prominent). A closely related feature is that may come up in the future is "private cases”. Private cases are orthogonal to API evolution and may even occur on one that is defined to be exhaustive.

Private cases and non-exhaustive enums affect the enum in the same way: they say that the enum can have values that a client does not know about. Swift requires switches to process *all* of the dynamically possible values, which is why the original proposal started out with the simplest possible solution: just require ‘default' when processing the cases.

The problems with “unknown case:”

The popular solution to this probably is currently being pitched as a change to the proposal (https://github.com/apple/swift-evolution/pull/777) which introduces a new concept “unknown case” as a near-alias for ‘default’:
https://github.com/jrose-apple/swift-evolution/blob/60d8698d7cde2e1824789b952558bade541415f1/proposals/0192-non-exhaustive-enums.md#unknown-case

In short, I think this is the wrong way to solve the problem. I have several concerns with this:

1) Unlike in C, switch is Swift is a general pattern matching facility - not a way of processing integers. It supports recursive patterns and values, and enums are not necessarily at the top-level of the pattern. https://github.com/apple/swift/blob/master/docs/PatternMatching.rst is a document from early evolution of Swift but contains a good general introduction to this.

2) Swift also has other facilities for pattern matching, including ‘if case’. Making switch inconsistent with them is not great.

3) As pitched, “unknown case” will match *known* cases too, which is (in my opinion :slight_smile: oxymoronic.

4) “unknown case:” changes the basic swift grammar (it isn’t just a modifier on case) because case *requires* a pattern. A better spelling would be “unknown default:” which is closer to the semantic provided anyway.

5) It is entirely reasonable (though rare in practice) to want to handle default and unknown cases in the same switch.

For all the above reasons, ‘unknown case:' becomes a weird wart put on the side of switch/case, not something that fits in naturally with the rest of Swift.

Alternative proposal:

Instead of introducing a new modifier on case/switch, lets just introduce a new pattern matching operator that *matches unknown cases*, called “#unknown” or “.#unknown” or something (I’m not wed to the syntax, better suggestions welcome :).

In the simple case most people are talking about, instead of writing “unknown case:” you’d write “case #unknown:” which isn’t much different. The nice thing about this is that #unknown slots directly into our pattern matching system. Here is a weird example:

switch someIntEnumTuple {
case (1, .X): … matches one combination of int and tuple...
case (2, .Y): … matches another combination of int and tuple...
case (_, #unknown): … matches any int and any unknown enum case ...
case default: … matches anything ...
}

Furthermore, if you have a switch that enumerates all of the known cases and use #unknown, then it falls out of the model that new cases (e.g. due to an SDK upgrade or an updated source package) produces the existing build error. As with the original proposal, you can always choose to use “default:” instead of “case #unknown:” if you don’t like that behavior.

Of course, if you have an exhaustive enum (e.g. one defined in your own module or explicitly marked as such) then #unknown matches nothing, so we should warn about it being pointless.

This addresses my concerns above:

1) This fits into patterns in recursive positions, and slots directly into the existing grammar for patterns. It would be a very simple extension to the compiler instead of a special case added to switch/case.

2) Because it slots into the pattern grammar, it works directly with 'if case’ and the other pattern matching stuff.

3) Doesn’t match known cases.

4) Doesn’t change the case grammar, it just adds a new pattern terminal production.

5) Allows weird cases like the example above.

All that said, the #unknown spelling isn’t great, but I’m sure we can find something else nice.

Thoughts?

Hi Chris, thank you for the new idea!

FWIW, after first reading, it looks like more elegant solution than "unknown case" in initial proposal. Swift's enums deserve powerful and flexible solution!

I really like the syntax of

switch val {
case .one: ...
case .two: ...
case #unknown: ...
}

instead of

switch val {
case .one: ...
case .two: ...
unknown case: ...
}

Also the possibility to use the #unknown in pattern matching is awesome.

Please find some comments/questions inline:

The mega-thread about SE-0192 is a bit large, and I’d like to talk about one specific point. In the review conversation, there has been significant objection to the idea of requiring a ‘default’ for switches over enums that are non-exhaustive.

This whole discussion is happening because the ABI stability work is introducing a new concept to enums - invisible members/inhabitants (and making them reasonably prominent). A closely related feature is that may come up in the future is "private cases”. Private cases are orthogonal to API evolution and may even occur on one that is defined to be exhaustive.

Private cases and non-exhaustive enums affect the enum in the same way: they say that the enum can have values that a client does not know about. Swift requires switches to process *all* of the dynamically possible values, which is why the original proposal started out with the simplest possible solution: just require ‘default' when processing the cases.

*The problems with “unknown case:”*

The popular solution to this probably is currently being pitched as a change to the proposal (https://github.com/apple/swift-evolution/pull/777) which introduces a new concept “unknown case” as a near-alias for ‘default’:
https://github.com/jrose-apple/swift-evolution/blob/60d8698d7cde2e1824789b952558bade541415f1/proposals/0192-non-exhaustive-enums.md#unknown-case

In short, I think this is the wrong way to solve the problem. I have several concerns with this:

1) Unlike in C, switch is Swift is a general pattern matching facility - not a way of processing integers. It supports recursive patterns and values, and enums are not necessarily at the top-level of the pattern. https://github.com/apple/swift/blob/master/docs/PatternMatching.rst is a document from early evolution of Swift but contains a good general introduction to this.

2) Swift also has other facilities for pattern matching, including ‘if case’. Making switch inconsistent with them is not great.

3) As pitched, “unknown case” will match *known* cases too, which is (in my opinion :slight_smile: oxymoronic.

Are you saying here about situation, when new case is added in non-frozen enum, but we are compiling the old code with absent new case but with "unknown case" in switch? As stated in proposal, in this case we'll have a warning exactly for source compatibility reason: "..unknown case matches any value. ... the compiler will produce a warning if all known elements of the enum have not already been matched. This is a warning rather than an error so that adding new elements to the enum remains a source-compatible change." Do you suggest to change that warning into error?

Or you are saying about another situation when “unknown case” will match known cases too in the initial proposal ?

4) “unknown case:” changes the basic swift grammar (it isn’t just a modifier on case) because case *requires* a pattern. A better spelling would be “unknown default:” which is closer to the semantic provided anyway.

5) It is entirely reasonable (though rare in practice) to want to handle default and unknown cases in the same switch.

Are you suggesting to have this?:

switch val {
case .one: ...
case .two: ...
case #unknown: ...
default: ..
}

So, all new cases, introduced in external module after compilation of that code will fall into "case #unknown: ..." bucket, but all "known"(at the moment of compilation) cases but not .one/.two - into "default:..." ? Personally I like that, so if I need this - I will be able to express this, but that rule seems like complicated to understand.

But this can be really helpful in situation, when you *at compilation time" thinks that you are not interested in anything but .one/.two cases of that enum, but in case of new values - you want to show(for example) a warning for app user.

For all the above reasons, ‘unknown case:' becomes a weird wart put on the side of switch/case, not something that fits in naturally with the rest of Swift.

*Alternative proposal:*

Instead of introducing a new modifier on case/switch, lets just introduce a new pattern matching operator that *matches unknown cases*, called “#unknown” or “.#unknown” or something (I’m not wed to the syntax, better suggestions welcome :).

In the simple case most people are talking about, instead of writing “unknown case:” you’d write “case #unknown:” which isn’t much different. The nice thing about this is that #unknown slots directly into our pattern matching system. Here is a weird example:

switch someIntEnumTuple {
case (1, .X): … matches one combination of int and tuple...
case (2, .Y): … matches another combination of int and tuple...
case (_, #unknown): … matches any int and any unknown enum case ...
case default: … matches anything ...
}

Furthermore, if you have a switch that enumerates all of the known cases and use #unknown, then it falls out of the model that new cases (e.g. due to an SDK upgrade or an updated source package) produces the existing build error. As with the original proposal, you can always choose to use “default:” instead of “case #unknown:” if you don’t like that behavior.

Do I understand correctly, that you suggest the same behavior as in original(updated) proposal:
* if we have a switch on non-frozen enum value, and enumerated some cases, and have "case #unknown" - we'll have a warning(error?) if (at the moment of compilation) not all known enums are processed in "switch" ?
Just to be sure.

Btw, should this be valid? :
switch val {
   case #unknown:... // I'm only interested if new values were added to that external enum
   default: ... // as I understand, "default" will be required here
}

Of course, if you have an exhaustive enum (e.g. one defined in your own module or explicitly marked as such) then #unknown matches nothing, so we should warn about it being pointless.

I have some concerns(applied to the initial proposal also) about the warnings, that depend on how the enum was imported(same module / other module).

Is the warning really needed in this case? Yes, we have #unknown case, we understand that enum can(and probably will not) be changed, so the code probably will never be called.
Probably we want to use the same source file with this "switch" both in separate module and in the same module. Then we'll have unnecessary warning(can't silence it) when compiling in the same module.
Or we just want to write "general"("common") switch code not thinking about how enum will be imported later, so always write #unknown to complete the logic. No? At least probably we then need a way to silence the warning to keep "case #unknown" in the source in the same module with enum declaration.

Also, regarding the possible "private cases" in enum. IIUC, in case of private cases we'll need "case #unknown" in switch even for enums declared in the same module. Otherwise how we'll separate "default"(known "public" cases we are aware of but not interested in) and "private cases of that enum we know nothing about". But then how to separate "future public cases" and "private cases of enum" in switch?
Or some "case #private" will work better here? But then why "case #unknown" and not "case #future" ?
So, may we want to have this? :
switch extern_enum_val {
   case .one: ..
   case .two: ..

   case #private: .. // decision for some private value of enum
   case #unknown: .. // decision for some "new"(future) public value of enum
   default: .. // decision for known *at compile* public values other than listed above
}
Or probably I understand the concept of "private cases" wrong, then sorry.
(Sorry for such a "flow of questions" :slight_smile: Just want to understand the proposed solutions better)

This addresses my concerns above:

1) This fits into patterns in recursive positions, and slots directly into the existing grammar for patterns. It would be a very simple extension to the compiler instead of a special case added to switch/case.

2) Because it slots into the pattern grammar, it works directly with 'if case’ and the other pattern matching stuff.

3) Doesn’t match known cases.

4) Doesn’t change the case grammar, it just adds a new pattern terminal production.

5) Allows weird cases like the example above.

All that said, the #unknown spelling isn’t great, but I’m sure we can find something else nice.

Thoughts?

Personally I like #unknown - similar like other stuff that depends on compiler magic and '#' clearly separates '#unknown' from other 'usual' cases. (Actually I like #future more, especially if we are going to have something like #private to clearly separate unknown future public cases, and unknown private cases)

Vladimir.

···

On 09.01.2018 9:54, Chris Lattner via swift-evolution wrote:

-Chris

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

- FWIW I can't actually think of a use case for using this with `if case` or anything else. I'm not against it, but I don't know why you would ever do it, just like I don't know why you would mix `case #unknown` with `default` when matching against a single value.

  if case #unknown = someEnum {
    throw MyError.unknownValue
  }
  …

That said, it sounds like people are happier with `case #unknown` than `unknown case`, and that leaves things a little more consistent if we ever do expand it to other pattern positions, so I'll change the proposal revision to use that spelling. (And if anyone comes up with a nicer spelling, great!)

I don't love the spelling of `#unknown`—particularly since some enums in the Apple frameworks actually *have* an ordinary case called "unknown"—but I think it's a nice, pragmatic solution which slots into pattern matching very nicely.

(And if we ever *do* decide to support @testable enum parameters, we'll already have the syntax to specify unknown values.)

···

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

--
Brent Royal-Gordon
Architechies

2) Swift also has other facilities for pattern matching, including ‘if case’. Making switch inconsistent with them is not great.
3) As pitched, “unknown case” will match *known* cases too, which is (in my opinion :slight_smile: oxymoronic.

Are you saying here about situation, when new case is added in non-frozen enum, but we are compiling the old code with absent new case but with "unknown case" in switch? As stated in proposal, in this case we'll have a warning exactly for source compatibility reason: "..unknown case matches any value. ... the compiler will produce a warning if all known elements of the enum have not already been matched. This is a warning rather than an error so that adding new elements to the enum remains a source-compatible change." Do you suggest to change that warning into error?

Or you are saying about another situation when “unknown case” will match known cases too in the initial proposal ?

In the proposal, ‘unknown case’ is a synonym for default with changes to warning generation. This means that it matches everything, including cases known at static compile time.

4) “unknown case:” changes the basic swift grammar (it isn’t just a modifier on case) because case *requires* a pattern. A better spelling would be “unknown default:” which is closer to the semantic provided anyway.
5) It is entirely reasonable (though rare in practice) to want to handle default and unknown cases in the same switch.

Are you suggesting to have this?:

switch val {
case .one: ...
case .two: ...
case #unknown: ...
default: ..
}

Yes.

So, all new cases, introduced in external module after compilation of that code will fall into "case #unknown: ..." bucket, but all "known"(at the moment of compilation) cases but not .one/.two - into "default:..." ? Personally I like that, so if I need this - I will be able to express this, but that rule seems like complicated to understand.
But this can be really helpful in situation, when you *at compilation time" thinks that you are not interested in anything but .one/.two cases of that enum, but in case of new values - you want to show(for example) a warning for app user.

Exactly. It is a narrow case, but it is important to me that we provide primitives that compose properly.

Do I understand correctly, that you suggest the same behavior as in original(updated) proposal:
* if we have a switch on non-frozen enum value, and enumerated some cases, and have "case #unknown" - we'll have a warning(error?) if (at the moment of compilation) not all known enums are processed in "switch” ?

Yes.

Just to be sure.

Btw, should this be valid? :
switch val {
case #unknown:... // I'm only interested if new values were added to that external enum
default: ... // as I understand, "default" will be required here
}

Yes, this would be very weird to write, but would be valid: the default matches all known cases at compile time. I’d suggest that you write that as a “if case #unknown” with an else though.

-Chris

···

On Jan 9, 2018, at 12:27 PM, Vladimir.S <svabox@gmail.com> wrote:

Hi Chris,

This is great. Thanks for spending time on this! I am in favor of `case #unknown` to only match unknown cases.

1) Would #uknown be available to RawRepresentable structs?

I haven’t considered this, so I’m not sure how that would work. I had assumed that this would be an enum specific concept.

What do you have in mind? If your raw representation is itself an enum, then of course this would work.

2) How is the #uknown pattern accomplished? Are you suggesting to capture all the compile time known cases so you can do a diff during
runtime? (Sorry this is not obvious to me)

The internals of the compiler would handle this, it is difficult to explain unless you know the bowels of silgen and irgen :slight_smile:

-Chris

···

On Jan 9, 2018, at 3:07 PM, Jose Cheyo Jimenez <cheyo@masters3d.com> wrote:

Thanks for writing this up, Chris! I addressed the idea of making this an arbitrary pattern in the revised proposal <https://github.com/jrose-apple/swift-evolution/blob/non-exhaustive-enums-2/proposals/0192-non-exhaustive-enums.md#unknown-case-patterns>, and the idea of not matching known cases in a "considered alternative" <https://github.com/jrose-apple/swift-evolution/blob/non-exhaustive-enums-2/proposals/0192-non-exhaustive-enums.md#mixing-unknown-case-and-default>, but in short:

- Matching in arbitrary pattern positions is harder to implement and harder to produce good diagnostics for. (Specifically, the former comes from having to actually check the existing cases in the generated code rather than just having an "else" branch.) I'm not inherently opposed to it if we can all agree on what it means, but I don't think I have time to implement it for Swift 5, so I'm not including it in the proposal.

I’m not sure what you’re saying here. This does slot directly into the existing pattern design: it is just a new terminal pattern node. Are you saying the SILGen/IRGen is more difficult and therefore you’re not interested in doing it even if it is the better design?

If that is the case, then the conservatively correct thing to do (in my opinion) is to add no feature here: no “unknown case:” and no “case #unknown:’.

Alternatively are you saying that you intend to support only the “case #unknown:” form exactly, but not the recursive cases?

- Matching known cases is a feature, not a limitation, to avoid existing code changing meaning when you recompile. I'll admit that's not the strongest motivation, though, since other things can change the meaning of existing code when you recompile already.

I’m not sure I understand this.

The whole motivation for this feature is to notify people if they are not handling a “newly known” case. If they don’t care about this, they can just use default.

- FWIW I can't actually think of a use case for using this with `if case` or anything else. I'm not against it, but I don't know why you would ever do it, just like I don't know why you would mix `case #unknown` with `default` when matching against a single value.

Brent gave a simple example down thread. That said, I’m more concerned about keeping pattern matching simple and composable than I am about particular use cases (I agree the top level case in a switch on enum is the disproportionately important case).

At some point we will hopefully extend pattern matching to support regexes and other things, we want to keep the design consistent.

That said, it sounds like people are happier with `case #unknown` than `unknown case`, and that leaves things a little more consistent if we ever do expand it to other pattern positions, so I'll change the proposal revision to use that spelling. (And if anyone comes up with a nicer spelling, great!)

Thanks!

-Chris

···

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

I'm not in favor to distinguish #unknown and #known, as it may lead to
surprises, like something that previously was unknown becomes known on a
newer iOS version for instance. And version-dependant code is clearer if
solely handled by the `@available()` syntax.

I like the new pattern matching syntax proposal, but I feel it would also
work by naming it `default` instead of `unknown`:

    case (_, default): … matches any int and any unknown enum case ...

···

Le mer. 10 janv. 2018 à 08:46, Jordan Rose via swift-evolution < swift-evolution@swift.org> a écrit :

On Jan 8, 2018, at 22:54, Chris Lattner via swift-evolution < > swift-evolution@swift.org> wrote:

The mega-thread about SE-0192 is a bit large, and I’d like to talk about
one specific point. In the review conversation, there has been significant
objection to the idea of requiring a ‘default’ for switches over enums that
are non-exhaustive.

This whole discussion is happening because the ABI stability work is
introducing a new concept to enums - invisible members/inhabitants (and
making them reasonably prominent). A closely related feature is that may
come up in the future is "private cases”. Private cases are orthogonal to
API evolution and may even occur on one that is defined to be exhaustive.

Private cases and non-exhaustive enums affect the enum in the same way:
they say that the enum can have values that a client does not know about.
Swift requires switches to process *all* of the dynamically possible
values, which is why the original proposal started out with the simplest
possible solution: just require ‘default' when processing the cases.

*The problems with “unknown case:”*

The popular solution to this probably is currently being pitched as a
change to the proposal (https://github.com/apple/swift-evolution/pull/777)
which introduces a new concept “unknown case” as a near-alias for ‘default’:

https://github.com/jrose-apple/swift-evolution/blob/60d8698d7cde2e1824789b952558bade541415f1/proposals/0192-non-exhaustive-enums.md#unknown-case

In short, I think this is the wrong way to solve the problem. I have
several concerns with this:

1) Unlike in C, switch is Swift is a general pattern matching facility -
not a way of processing integers. It supports recursive patterns and
values, and enums are not necessarily at the top-level of the pattern.
https://github.com/apple/swift/blob/master/docs/PatternMatching.rst is a
document from early evolution of Swift but contains a good general
introduction to this.

2) Swift also has other facilities for pattern matching, including ‘if
case’. Making switch inconsistent with them is not great.

3) As pitched, “unknown case” will match *known* cases too, which is (in
my opinion :slight_smile: oxymoronic.

4) “unknown case:” changes the basic swift grammar (it isn’t just a
modifier on case) because case *requires* a pattern. A better spelling
would be “unknown default:” which is closer to the semantic provided anyway.

5) It is entirely reasonable (though rare in practice) to want to handle
default and unknown cases in the same switch.

For all the above reasons, ‘unknown case:' becomes a weird wart put on the
side of switch/case, not something that fits in naturally with the rest of
Swift.

*Alternative proposal:*

Instead of introducing a new modifier on case/switch, lets just introduce
a new pattern matching operator that *matches unknown cases*, called
#unknown” or “.#unknown” or something (I’m not wed to the syntax, better
suggestions welcome :).

In the simple case most people are talking about, instead of writing
“unknown case:” you’d write “case #unknown:” which isn’t much different.
The nice thing about this is that #unknown slots directly into our pattern
matching system. Here is a weird example:

switch someIntEnumTuple {
case (1, .X): … matches one combination of int and tuple...
case (2, .Y): … matches another combination of int and tuple...
case (_, #unknown): … matches any int and any unknown enum case ...
case default: … matches anything ...
}

Furthermore, if you have a switch that enumerates all of the known cases
and use #unknown, then it falls out of the model that new cases (e.g. due
to an SDK upgrade or an updated source package) produces the existing build
error. As with the original proposal, you can always choose to use
“default:” instead of “case #unknown:” if you don’t like that behavior.

Of course, if you have an exhaustive enum (e.g. one defined in your own
module or explicitly marked as such) then #unknown matches nothing, so we
should warn about it being pointless.

This addresses my concerns above:

1) This fits into patterns in recursive positions, and slots directly into
the existing grammar for patterns. It would be a very simple extension to
the compiler instead of a special case added to switch/case.

2) Because it slots into the pattern grammar, it works directly with 'if
case’ and the other pattern matching stuff.

3) Doesn’t match known cases.

4) Doesn’t change the case grammar, it just adds a new pattern terminal
production.

5) Allows weird cases like the example above.

All that said, the #unknown spelling isn’t great, but I’m sure we can find
something else nice.

Thoughts?

Thanks for writing this up, Chris! I addressed the idea of making this an
arbitrary pattern in the revised proposal
<https://github.com/jrose-apple/swift-evolution/blob/non-exhaustive-enums-2/proposals/0192-non-exhaustive-enums.md#unknown-case-patterns>,
and the idea of not matching known cases in a "considered alternative"
<https://github.com/jrose-apple/swift-evolution/blob/non-exhaustive-enums-2/proposals/0192-non-exhaustive-enums.md#mixing-unknown-case-and-default>,
but in short:

- Matching in arbitrary pattern positions is harder to implement and
harder to produce good diagnostics for. (Specifically, the former comes
from having to actually check the existing cases in the generated code
rather than just having an "else" branch.) I'm not inherently opposed to it
if we can all agree on what it means, but I don't think I have time to
implement it for Swift 5, so I'm not including it in the proposal.

- Matching known cases is a feature, not a limitation, to avoid existing
code changing meaning when you recompile. I'll admit that's not the
strongest motivation, though, since other things can change the meaning of
existing code when you recompile already.

- FWIW I can't actually think of a use case for using this with `if case`
or anything else. I'm not against it, but I don't know why you would ever
do it, just like I don't know why you would mix `case #unknown` with
`default` when matching against a single value.

That said, it sounds like people are happier with `case #unknown` than
`unknown case`, and that leaves things a little more consistent if we ever
do expand it to other pattern positions, so I'll change the proposal
revision to use that spelling. (And if anyone comes up with a nicer
spelling, great!)

Jordan

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

And what about, as an alternative:

case (_, #unknown): … matches any int and any unknown enum case ...
case _: … matches anything ...

Then it clearly goes the "pattern matching" way.

···

Le mer. 10 janv. 2018 à 14:17, Chris Lattner via swift-evolution < swift-evolution@swift.org> a écrit :

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

Thanks for writing this up, Chris! I addressed the idea of making this an
arbitrary pattern in the revised proposal
<https://github.com/jrose-apple/swift-evolution/blob/non-exhaustive-enums-2/proposals/0192-non-exhaustive-enums.md#unknown-case-patterns>,
and the idea of not matching known cases in a "considered alternative"
<https://github.com/jrose-apple/swift-evolution/blob/non-exhaustive-enums-2/proposals/0192-non-exhaustive-enums.md#mixing-unknown-case-and-default>,
but in short:

- Matching in arbitrary pattern positions is harder to implement and
harder to produce good diagnostics for. (Specifically, the former comes
from having to actually check the existing cases in the generated code
rather than just having an "else" branch.) I'm not inherently opposed to it
if we can all agree on what it means, but I don't think I have time to
implement it for Swift 5, so I'm not including it in the proposal.

I’m not sure what you’re saying here. This does slot directly into the
existing pattern design: it is just a new terminal pattern node. Are you
saying the SILGen/IRGen is more difficult and therefore you’re not
interested in doing it even if it is the better design?

If that is the case, then the conservatively correct thing to do (in my
opinion) is to add no feature here: no “unknown case:” and no “case
#unknown:’.

Alternatively are you saying that you intend to support only the “case
#unknown:” form exactly, but not the recursive cases?

- Matching known cases is a feature, not a limitation, to avoid existing
code changing meaning when you recompile. I'll admit that's not the
strongest motivation, though, since other things can change the meaning of
existing code when you recompile already.

I’m not sure I understand this.

The whole motivation for this feature is to notify people if they are not
handling a “newly known” case. If they don’t care about this, they can
just use default.

- FWIW I can't actually think of a use case for using this with `if case`
or anything else. I'm not against it, but I don't know why you would ever
do it, just like I don't know why you would mix `case #unknown` with
`default` when matching against a single value.

Brent gave a simple example down thread. That said, I’m more concerned
about keeping pattern matching simple and composable than I am about
particular use cases (I agree the top level case in a switch on enum is the
disproportionately important case).

At some point we will hopefully extend pattern matching to support regexes
and other things, we want to keep the design consistent.

That said, it sounds like people are happier with `case #unknown` than
`unknown case`, and that leaves things a little more consistent if we ever
do expand it to other pattern positions, so I'll change the proposal
revision to use that spelling. (And if anyone comes up with a nicer
spelling, great!)

Thanks!

-Chris

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

- FWIW I can't actually think of a use case for using this with `if case` or anything else. I'm not against it, but I don't know why you would ever do it, just like I don't know why you would mix `case #unknown` with `default` when matching against a single value.

  if case #unknown = someEnum {
    throw MyError.unknownValue
  }
  …

Yes, you can write that, but why would you do it? What are you going to do below now that you know 'someEnum' is a known value but the compiler doesn't?

(Also, it doesn't play well with recompiling…)

···

On Jan 9, 2018, at 21:39, Brent Royal-Gordon <brent@architechies.com> wrote:

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

That said, it sounds like people are happier with `case #unknown` than `unknown case`, and that leaves things a little more consistent if we ever do expand it to other pattern positions, so I'll change the proposal revision to use that spelling. (And if anyone comes up with a nicer spelling, great!)

I don't love the spelling of `#unknown`—particularly since some enums in the Apple frameworks actually *have* an ordinary case called "unknown"—but I think it's a nice, pragmatic solution which slots into pattern matching very nicely.

(And if we ever *do* decide to support @testable enum parameters, we'll already have the syntax to specify unknown values.)

--
Brent Royal-Gordon
Architechies

Thanks for writing this up, Chris! I addressed the idea of making this an arbitrary pattern in the revised proposal <https://github.com/jrose-apple/swift-evolution/blob/non-exhaustive-enums-2/proposals/0192-non-exhaustive-enums.md#unknown-case-patterns>, and the idea of not matching known cases in a "considered alternative" <https://github.com/jrose-apple/swift-evolution/blob/non-exhaustive-enums-2/proposals/0192-non-exhaustive-enums.md#mixing-unknown-case-and-default>, but in short:

- Matching in arbitrary pattern positions is harder to implement and harder to produce good diagnostics for. (Specifically, the former comes from having to actually check the existing cases in the generated code rather than just having an "else" branch.) I'm not inherently opposed to it if we can all agree on what it means, but I don't think I have time to implement it for Swift 5, so I'm not including it in the proposal.

I’m not sure what you’re saying here. This does slot directly into the existing pattern design: it is just a new terminal pattern node. Are you saying the SILGen/IRGen is more difficult and therefore you’re not interested in doing it even if it is the better design?

If that is the case, then the conservatively correct thing to do (in my opinion) is to add no feature here: no “unknown case:” and no “case #unknown:’.

Alternatively are you saying that you intend to support only the “case #unknown:” form exactly, but not the recursive cases?

- Matching known cases is a feature, not a limitation, to avoid existing code changing meaning when you recompile. I'll admit that's not the strongest motivation, though, since other things can change the meaning of existing code when you recompile already.

I’m not sure I understand this.

The whole motivation for this feature is to notify people if they are not handling a “newly known” case. If they don’t care about this, they can just use default.

Notify, yes. Error, no. It's a design goal that adding a new case does not break source compatibility in addition to not breaking binary compatibility (because people don't like editing their dependencies) and therefore the behavior has to be defined when they recompile with no changes.

(This should also cover why it's more difficult to implement both diagnostics and SILGen for it.)

Jordan

···

On Jan 9, 2018, at 22:17, Chris Lattner <clattner@nondot.org> wrote:
On Jan 9, 2018, at 4:46 PM, Jordan Rose <jordan_rose@apple.com <mailto:jordan_rose@apple.com>> wrote:

- FWIW I can't actually think of a use case for using this with `if case` or anything else. I'm not against it, but I don't know why you would ever do it, just like I don't know why you would mix `case #unknown` with `default` when matching against a single value.

Brent gave a simple example down thread. That said, I’m more concerned about keeping pattern matching simple and composable than I am about particular use cases (I agree the top level case in a switch on enum is the disproportionately important case).

At some point we will hopefully extend pattern matching to support regexes and other things, we want to keep the design consistent.

That said, it sounds like people are happier with `case #unknown` than `unknown case`, and that leaves things a little more consistent if we ever do expand it to other pattern positions, so I'll change the proposal revision to use that spelling. (And if anyone comes up with a nicer spelling, great!)

Thanks!

-Chris

> Hi Chris,
>
> This is great. Thanks for spending time on this! I am in favor of `case
#unknown` to only match unknown cases.
>
> 1) Would #uknown be available to RawRepresentable structs?

I haven’t considered this, so I’m not sure how that would work. I had
assumed that this would be an enum specific concept.

What do you have in mind? If your raw representation is itself an enum,
then of course this would work.

I was just curious since a RawRepresentable structs ( and OptionSet ) are
very similar to non exhaustive enums in that they would require handling
unknown cases. Currently RawRepresentable is required to use a default even
if all cases are accounted for.

struct Directions: OptionSet {
    let rawValue: UInt8
    static let up = Directions(rawValue: 1 << 0)
}

let myDir = Directions.up

switch myDir
{
    case .up : print("matched")
    default: print("default required")
}

···

On Tue, Jan 9, 2018 at 10:10 PM, Chris Lattner <clattner@nondot.org> wrote:

On Jan 9, 2018, at 3:07 PM, Jose Cheyo Jimenez <cheyo@masters3d.com> > wrote:

> 2) How is the #uknown pattern accomplished? Are you suggesting to
capture all the compile time known cases so you can do a diff during
> runtime? (Sorry this is not obvious to me)

The internals of the compiler would handle this, it is difficult to
explain unless you know the bowels of silgen and irgen :slight_smile:

-Chris

That said, it sounds like people are happier with `case #unknown` than `unknown case`, and that leaves things a little more consistent if we ever do expand it to other pattern positions, so I'll change the proposal revision to use that spelling. (And if anyone comes up with a nicer spelling, great!)

Thanks!

Updated! https://github.com/apple/swift-evolution/pull/777. Also tried to clarify some of the points on why I'm leery about #unknown as an arbitrary pattern, at least for now.

Jordan

Since an option set has 2^N possible values, not just N, we don't expect anyone to be switching over them! And it wouldn't be correct to try to do so exhaustively anyway.

Jordan

···

On Jan 10, 2018, at 12:04, Jose Cheyo Jimenez via swift-evolution <swift-evolution@swift.org> wrote:

On Tue, Jan 9, 2018 at 10:10 PM, Chris Lattner <clattner@nondot.org <mailto:clattner@nondot.org>> wrote:
On Jan 9, 2018, at 3:07 PM, Jose Cheyo Jimenez <cheyo@masters3d.com <mailto:cheyo@masters3d.com>> wrote:
> Hi Chris,
>
> This is great. Thanks for spending time on this! I am in favor of `case #unknown` to only match unknown cases.
>
> 1) Would #uknown be available to RawRepresentable structs?

I haven’t considered this, so I’m not sure how that would work. I had assumed that this would be an enum specific concept.

What do you have in mind? If your raw representation is itself an enum, then of course this would work.

I was just curious since a RawRepresentable structs ( and OptionSet ) are very similar to non exhaustive enums in that they would require handling unknown cases. Currently RawRepresentable is required to use a default even if all cases are accounted for.

struct Directions: OptionSet {
    let rawValue: UInt8
    static let up = Directions(rawValue: 1 << 0)
}

let myDir = Directions.up

switch myDir
{
    case .up : print("matched")
    default: print("default required")
}

Hi Jordan,

After a long and hard reading, I will conditionally +1 this:

I agree that this is a problem that “needs" to be solved. (“Needs” is subjective, because as you correctly point out, there are other languages that don’t do this and seem to be relatively OK with that)
I am ok with the @frozen moniker on enums
I am ok with the “#unknown” syntax
I am therefore generally ok with the proposed solution

BUT:

I think the application of the warnings is still overly broad. The frozenness of an enum is only a problem for enums that come from dynamically linked modules that are external to my built project.

Therefore I’d like to see stuff regarding:

future directions for how we can refine the behavior and tooling around frozen enums, specifically
“statically” linking libraries (ie, the “import Module1 @ 12.1.2” stuff, aka “version locking"), because statically linking should eliminate frozenness concerns
embedded modules not producing warnings in the future, because embedded modules only change when the app author decides
ruminations on improving tooling for yelling at a developer if they “unfreeze” an enum in between versions (ie, a reduction of the SemVer conversation)

Because version locking and knowledge of embedding modules doesn’t currently exist, we’re forced to deal with the over-applicability of frozenness that shouldn’t be necessary. Getting those in would go a long way towards getting this feature scoped down to where it properly belongs in the app development workflow.

Dave

···

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

That said, it sounds like people are happier with `case #unknown` than `unknown case`, and that leaves things a little more consistent if we ever do expand it to other pattern positions, so I'll change the proposal revision to use that spelling. (And if anyone comes up with a nicer spelling, great!)

Thanks!

Updated! https://github.com/apple/swift-evolution/pull/777. Also tried to clarify some of the points on why I'm leery about #unknown as an arbitrary pattern, at least for now.

Ok, if that’s the desired design, then (IMO) the right way to spell it is “unknown default:” and it should have semantics basically aligned with the design you laid out in the revision of the proposal. If this is supposed to be an error, then it should be a pattern production.

Do you have a sense for whether this is what people want? We really should have a review cycle evaluating exactly this sort of tradeoff.

In any case, I’ve said this before off-list, but I find this whole discussion (of how to improve diagnostics for unknown cases) to be separable from the core issue required to get to ABI stability. It seems to me that we could split this (ongoing) design discussion off into a separate SE, allowing you to get on with the relatively uncontroversial and critical parts in SE-0192.

-Chris

···

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

- Matching known cases is a feature, not a limitation, to avoid existing code changing meaning when you recompile. I'll admit that's not the strongest motivation, though, since other things can change the meaning of existing code when you recompile already.

I’m not sure I understand this.

The whole motivation for this feature is to notify people if they are not handling a “newly known” case. If they don’t care about this, they can just use default.

Notify, yes. Error, no. It's a design goal that adding a new case does not break source compatibility in addition to not breaking binary compatibility (because people don't like editing their dependencies) and therefore the behavior has to be defined when they recompile with no changes.

In other words, the current solution will produce a bunch of false positives, and I’d like to see stuff in the proposal about how those false positives will be addressed.

Dave

···

On Jan 10, 2018, at 1:29 PM, Dave DeLong via swift-evolution <swift-evolution@swift.org> wrote:

On Jan 10, 2018, at 1:05 PM, Jordan Rose via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

That said, it sounds like people are happier with `case #unknown` than `unknown case`, and that leaves things a little more consistent if we ever do expand it to other pattern positions, so I'll change the proposal revision to use that spelling. (And if anyone comes up with a nicer spelling, great!)

Thanks!

Updated! https://github.com/apple/swift-evolution/pull/777. Also tried to clarify some of the points on why I'm leery about #unknown as an arbitrary pattern, at least for now.

Hi Jordan,

After a long and hard reading, I will conditionally +1 this:

I agree that this is a problem that “needs" to be solved. (“Needs” is subjective, because as you correctly point out, there are other languages that don’t do this and seem to be relatively OK with that)
I am ok with the @frozen moniker on enums
I am ok with the “#unknown” syntax
I am therefore generally ok with the proposed solution

BUT:

I think the application of the warnings is still overly broad. The frozenness of an enum is only a problem for enums that come from dynamically linked modules that are external to my built project.

Therefore I’d like to see stuff regarding:

future directions for how we can refine the behavior and tooling around frozen enums, specifically
“statically” linking libraries (ie, the “import Module1 @ 12.1.2” stuff, aka “version locking"), because statically linking should eliminate frozenness concerns
embedded modules not producing warnings in the future, because embedded modules only change when the app author decides
ruminations on improving tooling for yelling at a developer if they “unfreeze” an enum in between versions (ie, a reduction of the SemVer conversation)

Because version locking and knowledge of embedding modules doesn’t currently exist, we’re forced to deal with the over-applicability of frozenness that shouldn’t be necessary. Getting those in would go a long way towards getting this feature scoped down to where it properly belongs in the app development workflow.

I am sorry, but I have to disagree here. Having two separate proposals would make it highly more likely that one thing is done and the other one postponed and postponed until it seems no longer relevant in the grand scheme of things... first we lost labels in callbacks / stores functions (has there been at least some Core Team chat about this after last year?) and now we are losing compiler enforced exhaustive switching over enum... not only that, until we improve diagnostic and at least warn users we are now even behind Objective-C as compiler can enforce exhaustive switching and actually mandate no default case for enums through a compiler warning.

···

Sent from my iPhone

On 11 Jan 2018, at 04:31, Chris Lattner via swift-evolution <swift-evolution@swift.org> wrote:

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

- Matching known cases is a feature, not a limitation, to avoid existing code changing meaning when you recompile. I'll admit that's not the strongest motivation, though, since other things can change the meaning of existing code when you recompile already.

I’m not sure I understand this.

The whole motivation for this feature is to notify people if they are not handling a “newly known” case. If they don’t care about this, they can just use default.

Notify, yes. Error, no. It's a design goal that adding a new case does not break source compatibility in addition to not breaking binary compatibility (because people don't like editing their dependencies) and therefore the behavior has to be defined when they recompile with no changes.

Ok, if that’s the desired design, then (IMO) the right way to spell it is “unknown default:” and it should have semantics basically aligned with the design you laid out in the revision of the proposal. If this is supposed to be an error, then it should be a pattern production.

Do you have a sense for whether this is what people want? We really should have a review cycle evaluating exactly this sort of tradeoff.

In any case, I’ve said this before off-list, but I find this whole discussion (of how to improve diagnostics for unknown cases) to be separable from the core issue required to get to ABI stability. It seems to me that we could split this (ongoing) design discussion off into a separate SE, allowing you to get on with the relatively uncontroversial and critical parts in SE-0192.

-Chris

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