Suggestion: warning for pattern match cases of non-immediate traits

I would really like to have a new warning type for pattern matches in which the matched case looks for traits that are not an immediate type of the unwrapped type.

For example:

class Foo

object Foo1 extends Foo
object Foo2 extends Foo

case class Bar(foo: Foo)

Bar(Foo1) match {
  case Bar(Seq(1)) => "compiles no warning"
  case Bar(s: Seq[_]) => "compiles no warning"
  case Bar(List(1)) => "compiles with warning"
  case Bar(Foo2) => "oh no :("
  case Bar(Foo1) => "hooray!"
}

The pattern match on Bar(List(1)) yields a “fruitless type test” warning as List is a class and Foo does not extend from it.

Seq on the other hand is a trait, which means that even though Foo does not extend it by definition, it can always be anonymously mixed in via new Foo with Seq[Any]. This results in no warning whatsoever on the pattern match.

It would be useful to have such a warning, especially in cases where one wishes to refactor a case class that previously held a Seq (or any other trait) and now holds a different type:

// from
case class Foo(items: Seq[String])

// to
case class Items(...)
case class Foo(items: Items)

Without such a warning, one has to manually find all instances of pattern matching on the case class with Seq:

foo match {
  case Foo(Seq("1")) => ...
  ...
}

Which is prone to error that is only discoverable at runtime!

I’m really unsure whether this warning should be enabled by default, but in any case it would be extremely helpful to back-port it to earlier versions (i.e not just 2.13).

2 Likes

Wouldn’t a better solution here be to respect sealed in this case. As for the more real world example, if you annotate your Items class as final, you get an compile error for the wrong cases.

4 Likes

That is a good solution for when one has control over the definition of the new class, but it can’t be applied to situations where one doesn’t have such control (using stdlib / third-party class).

1 Like