Why does pattern matching work differently when type parameter comes from an enclosing method as opposed to an enclosing class? For example,
trait Base[T]
case class Derived(v: Int) extends Base[Int]
class Test[A] {
def method(arg: Base[A]) = {
arg match {
case Derived(_) => 42
}
}
}
gives error
constructor cannot be instantiated to expected type;
found : A$A87.this.Derived
required: A$A87.this.Base[A]
case Derived(_) => 42
^
whilst it successfully compiles when A
is method type parameter
class Test {
def method[A](arg: Base[A]) = {
arg match {
case Derived(_) => 42
}
}
}
The question is based on Daniel's analysis, which I used to attempt to provide answer to similar question.
I don't have the 100% complete answer, but I have a pointer that might be sufficient for you.
Scala compiler deals with GADTs (Generalized Algebraic Data Types) in a very particular way. Some cases are solved with special handling, some cases are unsolved. Dotty is trying to fill most of the holes, and it has already solved a lot of related issues, however there are still quite a few open ones as well.
Typical example of special GADT handling in Scala 2 compiler is very related to your use case. If we take a look at:
def method[A](arg: Base[A]) = {
arg match {
case Derived(_) => 42
}
}
and we explicitly declare the return type to be A
:
def method[A](arg: Base[A]): A
it will compile just fine. Your IDE might complain, but the compiler will let it through. Method says it returns an A
, but the pattern matching case evaluates into an Int
, which theoretically shouldn't compile. However, special handling of GADTs in the compiler says it's fine, because in that particular pattern matching branch A
has been "fixed" to be an Int
(because we matched on Derived
which is a Base[Int]
).
Generic type parameter for the GADT (in our case A
) has to be declared somewhere. And here's the interesting part - special compiler handling only works when it's declared as the type parameter of the enclosing method. If it's coming from a type member or a type parameter of the enclosing trait/class, it doesn't compile, as you witnessed yourself.
This is why I said it's not a 100% complete answer - I cannot point to a concrete place (such as official specification) which documents this properly. Sources on handling of GADTs in Scala come down to a couple of blogposts, which are great by the way, but if you want more than that you will have to dig into the compiler code yourself. I tried doing exactly that, and I think it comes down to this method, but if you really want to go deeper, you might want to ping someone more experienced with the Scala compiler codebase.