In one case ordering, get warning. In a different ordering, no warning. Even though in both certain case will never be reached:
enum Thing {
case one, two
}
func printThing000(_ thing: Thing?) {
// case's in this order, get warnings
switch thing {
case .none:
print("000 .none")
case .some(let me):
print("000 .some of \(me)")
case .one: // Warning: Case is already handled by previous patterns; consider removing it
print("000 .one")
case .two: // Warning: Case is already handled by previous patterns; consider removing it
print("000 .two")
}
}
func printThing111(_ thing: Thing?) {
// case's in this order, no warning even though case .some() will never be reached
switch thing {
case .none:
print("111 .none")
case .one: // no warning
print("111 .one")
case .two: // no warning
print("111 .two")
case .some(let me): // will never be matched to here
print("111 .some of \(me)")
}
}
Your enum isn’t frozen to have only two cases forever (which would be the case if it were explicitly declared as such in a separate, ABI-stable library). If you add a new case three to your enum, the last two cases in the first example will still never be reached, but the last case in the second example will become reachable.
@frozen is an opt-out from certain restrictions for libraries compiled in library evolution mode. It was first available for the C overlay, standard library, and libraries shipped by Apple as of SE-0192, and then was extended to user-defined types in libraries compiled in library evolution mode in SE-0260. The attribute has no effect when library evolution mode is off. See the Swift Evolution proposals for more details.
Interesting, though, I think you're onto something in terms of a discrepancy:
var sign: FloatingPointSign? = .plus
switch sign {
case .none: fallthrough
case .minus: fallthrough
case .plus: print("Hi")
default: fatalError() // warning: default will never be executed
}
switch sign {
case .none: fallthrough
case .minus: fallthrough
case .plus: print("Hi")
case .some(_): fatalError() // no warning, hmm...
}
Not sure if this is exactly identical, but here's an earlier similar bug. You'll notice a lot more weirdness in this area, like you're not even allowed explicitly mention the enum name and you have to use implicit member expressions:
var sign: FloatingPointSign? = .plus
switch sign {
case .none: fallthrough
case FloatingPointSign.minus: fallthrough // error: enum case 'minus' is not a member of type 'FloatingPointSign?'
case .plus: print("Hi")
}
Not really. If you remove the optionality, the compiler will treat your two cases .one and .two as exhaustive, so there is clearly a bug or limitation here, where it can't figure it out correctly when stashed inside an optional.