Swift Pattern Matching Operator: Compiler Error when Specifying Enum Type and Case Name?

Today’s WTF moment with Swift is related to switch-case and the pattern matching operator, ~=.

Defining operator overloads for special cases can help to keep case statements readable. And I thought it’d be simple enough to quickly check the setup, but I was in for a surprise!

So let’s say you have an enum that is RawRepresentable and backed by some integer type. Take UInt16, for example, because we’re dealing with a C API today that stores some type attributes as unsigned short and we want a nice Swift API for that:

enum CType: UInt16 {
    case two = 2
}

Now you can compare CType.two.rawValue == 2, and you can replace the literal 2 with the C unsigned short reference, which is exposed to Swift as UInt16.

But the .rawValue suffix gets old quickly when you have many cases in CType and want to switch over the C struct’s type directly.

So you reach for the pattern matching operator to make this shorter:

func ~= (pattern: CType, value: UInt16) -> Bool {
    return pattern.rawValue == value
}

With this operator overload, you can now write CType.two ~= 2, and perform pattern matching in if-case and guard-case and switch-case statements.

The weird part if how Swift allows you to do that.

So my line of thought was to write case CType.two: to be verbose and help the Swift compiler find the correct operator overload.

switch UInt16(2) {
case 1: // ...
case CType.two: print("Matched!")
default: // ...
}

The thing is: this doesn’t even compile!

error: enum case ‘two’ is not a member of type ‘UInt16’

Well, yes, that’s right – the case is a member of CType, as I am plainly telling in the code.

I tried other variants to help the compiler figure out what this pattern I want to match against is. It turned out that variables work just fine with Xcode 13.1 RC, as do static variables on the UInt16 type:

let cTypeVar = CType.two

extension UInt16 {
    static var cTypeStatic: CType { .two }
}

switch UInt16(2) {
case 2: fallthrough                   // ✓  Compiles fine
case cTypeVar: fallthrough            // ✓  Compiles fine
case UInt16.cTypeStatic: fallthrough  // ✓  Compiles fine
case CType.two: fallthrough           // ✗  Does not compile
default: break
}

Basically everything works except the enum wrapper for known values I have. Even a static variable that returns the same type!

Some time later on Slack, Ian S. told me to try just case .two:

switch UInt16(2) {
case 2: fallthrough                   // ✓  Compiles fine
case cTypeVar: fallthrough            // ✓  Compiles fine
case UInt16.cTypeStatic: fallthrough  // ✓  Compiles fine
case /*CType*/.two: fallthrough       // ✓  Compiles fine?!?
default: break

For all intents and purposes, this appears to be a bug in Swift – the pattern matching operator overload works when I don’t specify the type name, as you can see in the last code example, but the compiler produces an error when the .two case is written with its fully qualified type? That doesn’t make sense; if anything, I could understand if the opposite were the case: if the compiler cannot figure out what .two refers to, but has a much easier time compiling the pattern matching case statement when I type out case CType.two:...

Have reported this as FB9724060.