How does 'switch' work? Not the same as if/else with pattern matching operator

this comes out of an @MJTsai post about pattern matching with error codes.

https://mjtsai.com/blog/2023/03/13/pattern-matching-on-swift-error-codes/

He comments that it doesn't work when codes use a protocol, and gives the following example:

setup:

protocol MyErrorProtocol<Code>: Error {
    associatedtype Code: MyCodeProtocol
    var code: Code { get }
}

protocol MyCodeProtocol: RawRepresentable where RawValue == Int {
}

enum MyCode: Int, MyCodeProtocol {
    case a
    case b
}

struct MyError: MyErrorProtocol {
    let code: MyCode
}

func ~=<Code: MyCodeProtocol> (code: Code, error: any Error) -> Bool {
    guard let myError = error as? any MyErrorProtocol,
          let myCode = myError.code as? Code else { return false }
    return myCode == code
}

the following doesn't work. The switch gives a warning

let error: Error = MyError(code: .a)
switch error {

case MyCode.a://Warning: Cast from 'any Error' to unrelated type 'MyCode' always fails
    print("a" )
default:
    print ("other")
}

however - writing the same logic as an if/else with the pattern matching operator does work correctly

if (MyCode.a ~= error) {
//no error - this branch is triggered
    print("a" )
}
else {
    print ("other")
}

what's going on?
clearly the switch isn't using the pattern match operator in the same way as the if/else code.

Any insights appreciated :)

3 Likes

meanwhile - a simple protocol case with no generics works correctly.

protocol HasString {
    var string:String {get}
}

struct Foo:HasString {
    var string:String
}

func ~=(match: String, input: HasString) -> Bool {
    return match == input.string
}

let foo = Foo(string: "Hello")
switch foo {
case "Hello"://This works
    print("match")
default:
    break
}

Protocols aren't relevant; it's a problem with enumerations. I haven't seen it before.

struct S { static let instance = Self() }
enum E { case instance }

func ~= (_: S, _: Void) -> Bool { true }
func ~= (_: E, _: Void) -> Bool { true }

S.instance ~= () // compiles
if case S.instance = () { } // compiles
E.instance ~= () // compiles
if case E.instance = () { } // Cannot convert value of type '()' to specified type 'E'

Edit: Oh, interesting. I don't see how what you're trying to do can work, unless this gets fixed:

2 Likes