Is this considered a bug in match exhaustiveness check?

I think this is a bug. The enum only has two options, but the compiler does not recognize this as being exhaustive.

enum S[T](t: T) {
  case SA extends S("str")
  case SB extends S(10)
}

case class Merged[T](a: S[T], b: T)

def f(m: Merged[?]): Unit =
  m match {
    case Merged(S.SA, s) => println(s)
    case Merged(S.SB, x) => println(x)
  }

I had a hunch this had to do with the use of a wildcard type parameter, and indeed, if you define f as def f[T](m: Merged[T]), the warning goes away.

(I’m using Scala 3.8.2, by the way.)

Is the behavior with the wildcard a bug? I’m not sure, but note that this doesn’t warn:

case class Merged[T](a: S[T])
       
def f(m: Merged[?]): Unit =
  m match {
    case Merged(S.SA) =>
    case Merged(S.SB) =>
  }

If this doesn’t warn, it seems like your example shouldn’t warn either…?

Also for the record, the counterexample is

It would fail on pattern case: Merged(_, _)

I recall that there is something finicky in how pattern matching decides whether to warn? 20 minutes later … I see I looked at Scala 2 patmat a few years ago, where it doesn’t warn if a type is “switchable”. I don’t know (to coin a phrase), but maybe not knowing the type of s or x leads to warning, even though the “variable pattern” matches all values.

It warns “normally” if b has a specific type.

i’m just thinking loud and guessing as i have no knowledge of scala compiler internals.

is the compiler doing ‘multi-path’ exhaustiveness checks? here if the compiler starts doing exhaustiveness checking by picking b and checking if the cases exhaustively match all possible values then that will fail. the compiler then would need to backtrack and pick a and then do exhaustiveness check and this time it would succeed.

btw:
i’ve found a (hopefully) non-intrusive hack to make it compile :slight_smile:

enum S[T](t: T) {
  case SA extends S("str")
  case SB extends S(10)
}

type Id[T] = T

case class Merged[T](a: S[T], b: Id[T])

def f(m: Merged[?]): Unit =
  m match {
    case Merged(S.SA, s) => println(s)
    case Merged(S.SB, x) => println(x)
  }
  

just wrapping b: T in Id[T] i.e. a no-op. if this works then the original version should work too, i.e. the miscompilation seems like a bug then.