7
votes

I've got a case class hierarchy to encode some request and processing errors:

  sealed trait OpError
  sealed trait RequestErrorType
  sealed trait ProcessingErrorType

  final case class InvalidEndpoint(reason: String) extends RequestErrorType
  final case class InvalidParameters(reason: String) extends RequestErrorType

  final case class InvalidFormat(response: String) extends ProcessingErrorType
  final case class EntityNotFound(id: Long) extends ProcessingErrorType

  final case class RequestError(errorType: RequestErrorType) extends OpError
  final case class ProcessingError(errorType: ProcessingErrorType) extends OpError

If I write a simple match across all patterns:

  def printMatches(error: OpError): Unit = error match {
    case RequestError(InvalidEndpoint(reason)) => //print something
    case RequestError(InvalidParameters(reason)) => //print something
    case ProcessingError(InvalidFormat(format)) => //print something
    case ProcessingError(EntityNotFound(entityId)) => //print something
  }

the compiler gives me a warning about missing match:

 match may not be exhaustive.
 It would fail on the following input: ProcessingError(_)
 def printMatches(error: OpError): Unit = error match {

But ProcessingError takes in a ProcessingErrorType with only two extensions: InvalidFormat and EntityNotFound, both which are accounted for in the pattern match. What am I missing?

Even more curious is that if I change the parameter type of InvalidParameters or InvalidEndpoint to a String*, I don't get the error:

final case class InvalidParameters(reason: String*) extends RequestErrorType

Any ideas?

4
printMatches(ProcessingError(new ProcessingErrorType{})) it is not match any - 余杰水
this example violates the sealed contract. - pedrofurla
In terms of the curious behaviour with the String* parameter, it sounds like Scala turns of exhaustive checking when a case class has a varargs parameter: issues.scala-lang.org/browse/… - ssanj

4 Answers

5
votes

This is a confirmed bug. I filed a bug report for this and it has been since fixed for Scala 2.12.0-M4.

1
votes

Very interesting! Unfortunately, I haven't found an answer. I've been revolving around http://www.scala-lang.org/files/archive/spec/2.11/08-pattern-matching.html#constructor-patterns but I haven't really found a valid explanation for what's going on.

Here's a simpler demo (hope you don't mind):

sealed abstract class ClassOne
case class ClassOneImpl() extends ClassOne

sealed abstract class ClassTwo()
case class ClassTwoImpl() extends ClassTwo

sealed abstract class Foo
case class FooOne(x: ClassOne) extends Foo
case class FooTwo(x: ClassTwo) extends Foo

def printMatches(st: Foo): Unit = st match {
  case FooOne(ClassOneImpl()) => println()
  case FooTwo(ClassTwoImpl()) => println()
}

I've observed that each of the following two modifications removes the warning:
1) Changing FooOne and FooTwo signatures so that instead of taking ClassOne and ClassTwo they take ClassOneImpl and ClassTwoImpl
2) Removing FooOne or FooTwo so that there's only one case class extending Foo (which leads to only one case in pattern matching).

Perhaps we could submit an issue and see what they say?

0
votes

You can help the compiler with an unchecked annotation:

... = (error: @unchecked) match ...

but you should be sure, your match is exhaustive.

0
votes

I think exhaustiveness matching works on a single inheritance level. RequestErrorType and ProcessingErrorType are part of the constructor where that exhaustiveness is not checked.

You can see it from the reading of the code, but it seem that compiler does not.