Pattern variable binding cannot appear in an expression

Hi, I've encountered this error which I've never seen before when switching over a type with a custom pattern-matching overload - "Pattern variable binding cannot appear in an expression".

I overloaded the pattern-matching operator (~=) like this:

func ~= (pattern: SearchAction, value: CurrentValueSubject<SearchAction, Never>) -> Bool {
    return pattern == value.value
}

However, now in my switch statements I'm not able to bind associated values of enums when using this overload:

switch actionStream { in // CurrentValueSubject<SearchAction, Never>
    case let .search(text): // SearchAction  // Pattern variable binding cannot appear in an expression
        return actionStream
            .debounce(for: 2, scheduler: env.scheduler)
            .delay(for: 1, scheduler: env.scheduler)
            .eraseToEffect()
    default:
        return .none
    }

Is anyone familiar with this error, and what it may mean?

A full gist of what I'm trying to achieve can be found here!

I think the stray in makes compiler think you're inside a closure, and not a switch.

Try removing the in and see if the error changes to something else

1 Like

Unfortunately, that didn't work - I did this:

.passthrough(default: .clearSearch) {
    switch $0 {
    case let .search(text): // Pattern variable binding cannot appear in an expression
        return $0
            .debounce(for: 2, scheduler: $1.scheduler)
            .delay(for: 1, scheduler: $1.scheduler)
            .eraseToEffect()
    default:
        return .none
    }
}

Also, I don't think the closure is the issue, since I get the same error in a normal function:

func passthrough(actionStream: CurrentValueSubject<SearchAction, Never>, env: SearchEnvironment) -> Effect<SearchAction, Never> {
    switch actionStream {
    case let .search(text): // Pattern variable binding cannot appear in an expression
        return actionStream
            .debounce(for: 2, scheduler: env.scheduler)
            .delay(for: 1, scheduler: env.scheduler)
            .eraseToEffect()
    default:
        return .none
    }
}

I tried it out in playgrounds, with the switch statement as a top-level expression, and it didn't work too.

You can't do a variable binding pattern match with a custom ~=. When you implemented the custom ~= operator, the compiler is looking for fully formed values of SearchAction to check for == with the switched over value (in this case, actionStream). So using case let .search(text) there is nonsensical as there is no instance to compare against.

What would work would be case .search("text") or case .search("bla") or whatever, as this expression produces a fully-formed instance of SearchAction to do the equality check with.

But what are trying to express is not possible with the language today, you'll have to switch over actionStream.value explicitly instead.

2 Likes

I see, thanks a lot for the in-depth explanation! Switching over actionStream.value will do, I guess. Would support for this be realistically possible in the future? I think it would unlock some really expressive APIs, with Swift's pattern matching already being so powerful.

I guess a feature to enable custom pattern matching on any type is theoretically achievable. I don't think it's on any roadmaps or manifestos though at the moment (note: I'm just an outside user / enthusiast).

2 Likes

Agreed – it's definitely not as important nor needed as ownership and improved generics, so I think pattern matching will be perfected in a few years once those big arcs have been solved. Is there anything else you'd like to see improved in pattern matching?