Compiler warning: "Case is already handled by previous patterns; consider removing it"

I've written the following view model with a switch statement. I'm getting a compiler warning that doesn't make sense to me.

import Cocoa

class ViewModel {
    enum Mode {
        case initializing(listenerUUID: UUID)
        case loading(listenerUUID: UUID, currentIndex: Int)
        case loaded(currentIndex: Int)
        case done
        case error
    }
    var mode = Mode.done
    private static let firstIndex = 1
    private static let stepsize = 25
    
    internal func eventDidPop(_ event: Data, uuid: UUID) {
        let newIndex: Int
        switch self.mode {
        case .initializing(let uuidOrigin), .loading(let uuidOrigin, _):
            // See if this event is for us
            guard uuidOrigin == uuid else {
                return
            }
            fallthrough
        case .initializing(_):
            newIndex = Self.firstIndex
        case .loading(_, let currentIndex):
            newIndex = currentIndex + Self.stepsize
        default:
            return
        }
        
        print("New index = \(newIndex)")
    }
}

On line 24 and 26, I get a compiler warning "Case is already handled by previous patterns; consider removing it". But I don't think that compiler warning is appropriate, and I try and indicate that with the fallthrough.

Who is right, the compiler or me? If it's me, I wish to silence the compiler, does anyone know a way?

The fallthrough keyword simply causes code execution to move to the statements inside the next case, without checking case conditions again.

Which means that in your code, if .loading(let uuidOrigin, _) matches in the first case and the uuid is correct then the statements from case .initializing(_) will be executed next. That is probably not what you want.

What you perhaps want is something like this:

switch self.mode {
case .initializing(let uuidOrigin) where uuidOrigin == uuid:
    newIndex = Self.firstIndex
case .loading(let uuidOrigin, let currentIndex) where uuidOrigin == uuid:
    newIndex = currentIndex + Self.stepsize
default:
    return
}
2 Likes

Also, I think, you can express the where condition directly as

switch self.mode {
case .initializing(uuid) :
    newIndex = Self.firstIndex
case .loading(uuid, let currentIndex) :
    newIndex = currentIndex + Self.stepsize
default:
    return
}
3 Likes

@Martin Thanks so much, very compact code!

@RonAvitzur I'm definitely not used to this syntax O_O

This must be the most compact code way of handling this. Somehow I know I should immediately understand what was happening here, it's just a switch. But still it took me multiple re-reads to figure out what was happening here.

The pattern matching reads more clearly when done with literals where it is unambiguously a pattern for a specific enum payload, e.g.

case .key("A"), .key("B"): ... // action for specific payloads
case .key(let c): ... // general case for any other payloads not handled above

I have a few more tips that can help with switch clarity.


If you want a case that should be genuinely impossible to match unless something changes in the future, mark it with @unknown. This tells the compiler to warn you if it starts being matchable in the future (like when adding a new enumeration case) without forcing you to actually handle it to compile.

If I have a sensible default, but don’t want to risk forgetting to replace it with something more specific in the future, I often use a pattern like this:

case .one, .two:
  // specific action
case .four:
  // specific action
case .three, .five:
  fallthrough
@unknown default:
  // general case

If I add a .six case to the enumeration in the future, I'll get a warning so I know to go back and determine if a specific action is needed. If it isn't, I add it to the fallthrough case.


If you have a case that in theory won’t ever be matched, but technically could be, use assertionFailure(_:file:line:), preconditionFailure(_:file:line:), or fatalError(_:file:line:).