12

Consider the following broken function:

def sum (list : Seq[Int]) : Int = list match {
  case Nil => 0
  case head :: tail => head + sum(tail)
}

Here, the function was supposed to work with a List[Int], but was refactored to accept Seq[Int] instead, thus becoming broken without the compiler noticing.

This gaping hole in Scala's incomplete pattern match detection makes it next to useless.

I want to have a way of systematically detecting such problems. Specifically, I'd like the compiler to emit an error/warning on every instanceof-guided pattern match, i.e. I only want to allow pattern matches on sealed hierarchies and on custom matchers.

Are there existing compiler options/plugins for doing conservative (as opposed to arbitrary) checks of pattern matching safety?

Rotsor
  • 13,655
  • 6
  • 43
  • 57
  • I use this match example with `List` very often, too. My advice in this case is to use `list.toList match ...`(because you know in advance you probably will refactor some code (perhaps not this method) later on and the match cases are only working with `List`), then you can refactor the method parameter to `Seq` without broken code. – Peter Schmitz Jan 18 '12 at 14:48

2 Answers2

3

Have a look at this answer by M. Odersky.

Summary

Checks on matches of non-sealed hierarchies are doable, not trivial and not (yet) implemented.

Community
  • 1
  • 1
ziggystar
  • 28,410
  • 9
  • 72
  • 124
  • 2
    That misses the point. I don't ask the compiler to be omniscient or even clever. I just want the check to become conservative, meaning that out of "Safe / Unsafe / Unknown" analyses only the first one would be admitted without warnings. – Rotsor Jan 18 '12 at 15:31
3

Nil and :: are clearly ways to construct a List, but not all Sequences happen to be Lists, so one would expect the Scala type checker to reject this program as ill-typed. Right?

Wrong. Try this and you'll see what I mean:

def sum (list : Seq[Int]) : Int = list match {
  case Nil => 0
  case head :: tail => head + sum(tail)
  case _ => -1
}

> sum(Array(1,2,3).toSeq)
res1: Int = -1
> sum(List(1,2,3))
res2: Int = 6

So you see, some Sequences might be able to be deconstructed with Nil and ::, so those which can, will. Those which can't will fail the pattern match and move on, trying the next match. Nil and :: are sufficient to cover all the possibilities for List, but not for Seq. There is a tradeoff here between subtyping, convenience, and type safety. The solution for now is: be more careful when you refactor.

Dan Burton
  • 53,238
  • 27
  • 117
  • 198