Odd/inconsistent compile warning for optional of enum

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)")
    }
}
1 Like

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.

2 Likes

After adding @fronzen to the enum doesn't change anything. The second example still no warning. Is this correct behavior?

Declaring the enum this way is not "frozen"?

@frozen
enum Thing {
    case one, two
}

@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.

3 Likes

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...
}
2 Likes

Filed [SR-14993] Missed warning for redundant .some(_) case in the presence of implicit wrapping · Issue #57335 · apple/swift · GitHub to track fixing this.

1 Like

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")
}

I think they're two different issues. One is related to name lookup being weird and the other is about exhaustivity checking not working correctly.

1 Like

This crash:

switch sign {
case _: fallthrough
//case .minus: fallthrough
//case .plus: print("Hi")
    // does warning: Case is already handled by previous patterns; consider removing it
case .some(_): fatalError() // !!! crash here: error: Execution was interrupted, reason: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0).
}

What do you expect it to do?

:man_facepalming:Sorry

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.