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

The latest news: SE-0192 — Non-Exhaustive Enums - #337 by jrose

I’m working on cutting down the proposal text (and the implementation) to match this feedback from the core team, but there’s also one more issue that needs to be worked out about unknown case—and no, I’m not just talking about the name.

During these discussions, @Chris_Lattner3 and I realized we were talking past each other because of one crucial point:

It is an error to use unknown case with a non-enum-typed value.

Chris pointed out that this makes it much more annoying to use switches with patterns that contain non-frozen enums.

switch (excuse, notifiedTeacherBeforeDeadline) {
case (.eatenByPet, false):
  // 1
case (_, false):
  // 2
case (let excuse, true):
  switch excuse {
  case .eatenByPet: // 3
  case .thoughtItWasDueNextWeek: // 4
  unknown case: // 5
  }
}

Okay, this one's particularly contrived, but you see the point: if you want to keep your exhaustiveness diagnostics, you need to pull the enum value out into its own switch. That was something I had always considered a necessary evil, since it would come up relatively rarely, but Chris proposed an alternative rule: unknown default can match any pattern that contains a non-frozen enum.

switch (excuse, notifiedTeacherBeforeDeadline) {
case (.eatenByPet, false):
  // 1
case (_, false):
  // 2
case (.eatenByPet, true):
  // 3
case (.thoughtItWasDueNextWeek, true):
  // 4
unknown default:
  // 5
}

I personally see this as convenient but confusing; it's no longer obvious what relation the "unknown" has to the value being matched, and it can catch multiple unknown values if you have a pattern containing multiple enums. Or an enum with another enum as a payload.

(The other features that work like this are try, which applies to an entire subexpression rather than just the immediate call so that you can do things like try riskyThingOne().riskyThingTwo(), and pattern-based let, so that you can say case let (x, y) instead of case (let x, let y).)

Whatever we pick, however, this is really the thing that was keeping me and Chris from agreeing on a name. If this new kind of catch-all can only match enum values, then unknown or unknown case is a good name. If it can match any value that contains a non-frozen enum, then unknown, unknown default, or default unknown is a good name. (@dabrahams in particular had a strong distaste for unknown default since it isn't the "default" that's "unknown".)

(Yes, unknown is back on the table, because we think we can give good enough diagnostics if someone is using it as a label.)

So, what do you all think? Should the new catch-all be allowed to match "patterns containing non-frozen enums" or "non-frozen enums only", and why?

(Please save comments on the name until we see if there's a clear preference for one or the other of these choices.)