Ability to match a condition to multiple associated values from merged switch cases

I just had a small issue with a switch statement where I for some reason assumed that the where clause was already applied to every associated value of a merged switch case.

enum Permission {
  case notSupported(UInt8)
  case readOnly(UInt8)
  case readWrite
}

let permission = Permission.notSupported(0)

switch permission {
case .notSupported(let code),
     .readOnly(let code) where code > 0:
  /* create the right error value */
default:
  /* create the default error value */
}

The above example actually will succeed with in the first case instead of landing in the default branch, because the where clause is only applied to the second readOnly case.

There is also a diagnostics bug (SR-8567) in the above switch which prevents from triggering an expected warning that might have helped me finding the issue in that pattern. If we restructure the switch statement then the right diagnostics is triggered and the compiler emits a warning:

case .notSupported(let code), .readOnly(let code) where code > 0: // 'where' only applies to the second pattern match in this case

In my use case the solution was to remove the where clause entirely and add a guard code > 0 else { break } below the case since default case was also breaking out the current control flow.

The issue I'm trying to describe next is trying to avoid the same situation and reduce some of the boilerplate code wile providing a more flexible way of expressing a global condition.
In general you may want to merge multiple cases in a switch statement and match them from left to right, top to bottom. You may also wish to extract an associated value from any merged case. The only constraint that is required from the developer to be applied, is that the associated value has to have the same instance name and be of the same type. If you also need to apply a where clause to the extracted value in order to return the control flow to the switch so it matches the next case you have to replicate the same where clause over and over again:

switch something {
case .a(let value) where checkCondition(of: value),
     .b(let value) where checkCondition(of: value),
     .c(let value) where checkCondition(of: value): 
   ...
case .otherCase:
   ...
default:
   ....
}

To reduce the repetition of the where clause and allow to return the control flow to the switch statement so it can match next case, I propose a new way we can express the where clause in switch statements that is automatically applied to every associated value in the current branch.

switch something {
case where checkCondition(of: value) in .a(let value), .b(let value), .c(let value): 
   ...
case .otherCase:
   ...
default:
   ...
}

If this idea reaches some traction and positive feedback, I'd happily write a draft proposal and see if I can find someone willing to implement that feature.

Thoughts?


Edit: The above solution introduces a syntax by reusing the where clause in an unambiguous way which leads us to the

case where condition in cases

syntax. However I think we can also go a step further and eliminate the where clause entirely. Then we'd get

case condition in cases

which is quite similar to

for value in sequence /* where condition */
2 Likes

we can also go a step further and eliminate the where clause entirely, case condition in cases

I would vote to leave the where keyword in there. Yes, case condition in cases is attractively similar to for value in sequence, but perhaps... it shouldn't be?

Considering: for value in sequence and case eval(of: condition) in .a(let value), .b(let value), it is true that both in sequence and in .a(let value), .b(let value) are sequences of values, but for value is concerned with value assignment and case eval(of: condition) is concerned with condition evaluation. How to clearly set them apart? It is the where keyword that usually "extends" a statement with condition evaluation, as in for value in sequence where eval(of: condition). An analogous situation happens with .first(where: { eval(of: condition) }) or even extension LHSType where Self: RHSType - seeing the where keyword immediately implies that a condition follows.

All programmers used to case .a(let value) where eval(of: condition) will likely immediately understand case where eval(of: condition) in .a(let value), .b(let value), without Reading The Fine Manual.

1 Like

Well I personally like either way because both reads well, but those are good points to be aware of. Thank you for sharing your opinion.