I'm trying to demonstrate to someone that you can use a custom operator ~= for pattern-matching. Yet, for some reason, when I try to run the code below, I always get an error "pattern of type 'Parity' cannot match 'Int'":
It doesn't matter if I put the func ~= as a static member of Parity, as a static member in an extension Int, or as a global function. It just doesn't work. I also tried switching the operands around in case I have those backwards, but that wasn't it either. What is going on here?
I think what's happening here is that enum cases are treated specially in pattern matching rather than 'just' being values. This is in some sense necessary to be able to do things like match a case with an associated value using only the case name, without materializing a full value of the enum (or match against enums which don't conform to Equatable). The pattern matching operator is only used in the case of expression patterns, but since enum cases form enum patterns we never get around to using the expression matching.
Your example compiles for me if I switch to a struct:
struct Parity: Equatable {
private var isEven: Bool
static let even = Parity(isEven: true)
static let odd = Parity(isEven: false)
static func ~= (lhs: Parity, rhs: Int) -> Bool {
return (lhs == .even) == rhs.isMultiple(of: 2)
}
}
let str = switch 2 {
case Parity.even: "even"
case Parity.odd: "odd"
default: fatalError("No exhaustivity checking :(")
}
print(str)
Some workarounds for this longstanding issue (all of them are "make this not look like a simple member access to an enum case"):
Add .self to each case (case Parity.even.self)
Wrap in an identity function call (case id(Parity.even), if you've defined func id)
Wrap in a closure (case {Parity.even}())
Delete the type qualification; weirdly, using case .even is sufficient to force a context-sensitive search (but only because Int doesn't have an even static member)
Once we hit the fallback to expression pattern matching, the compiler looks up all definitions for ~= for the given matchee. This finds the generic ~=<T: Equatable> overload, which obviously doesn't work since there's no Int.even as you mention, but the custom (Parity, Int) overload is notionally calling .even ~= 2, so the implicit member lookup happens with Parity type context, not Int type context!
Dropping the type context also didn't occur to me but it's a clever way to sidestep the 'enum case' special treatment because that logic relies on being able up-front identify "this is a reference to an enum case". By delaying that until overload resolution time we can ensure that the pattern is resolved as an expression pattern rather than an enum case pattern!