'pattern type is incompatible with expected type' and subtyping

1,155 views
Skip to first unread message

Johannes Rudolph

unread,
Jun 12, 2012, 4:14:22 AM6/12/12
to scala-l...@googlegroups.com
Hi all,

here's a small snippet I expected to compile correctly (this is 2.9.2
but failed with trunk from a few days ago as well):

scala> trait C[T]
defined trait C

scala> case class Val[T](c: C[T], v: T)
defined class Val

scala> object Test extends C[Int]
defined module Test

scala> (null: AnyRef) match { case Val(Test, x) => x; case _ => -1 }
<console>:12: error: pattern type is incompatible with expected type;
found : object Test
required: C[Any]
Note: Int <: Any (and object Test <: C[Int]), but trait C is invariant
in type T.
You may wish to define T as +T instead. (SLS 4.5)
(null: AnyRef) match { case Val(Test, x) => x; case _ => -1 }


The problem is that here for some reason `C[Any]` is expected so that
the variance notice is generated (which I think is a red herring to
the issue). It works if you explicitly use an existential for the
scrutinee type:

scala> (null: Val[_]) match { case Val(Test, x) => x; case _ => -1 }
res2: Int = -1

It looks like the typer implies that `Val[Any]` is the only possible
`Val` type instantiation that is <:< AnyRef (or any other supertype)
when instead it should be `Val[_]`.

--
Johannes

-----------------------------------------------
Johannes Rudolph
http://virtual-void.net

Paul Phillips

unread,
Jun 12, 2012, 10:24:45 AM6/12/12
to scala-l...@googlegroups.com

On Tue, Jun 12, 2012 at 1:14 AM, Johannes Rudolph <johannes...@googlemail.com> wrote:
here's a small snippet I expected to compile correctly (this is 2.9.2
but failed with trunk from a few days ago as well):

What you're looking at is approximately the same issue which underlies our failures calculating recursive bounds.  In both cases it is trying to solve for T in a system like T <: C[T].  It does not do this task well.  See the "Any"s in the below? That is a similar failure.  In that case it is more egregious because it infers an invalid type when valid ones exist.

scala> List(List, Stream)
<console>:8: error: type mismatch;
 found   : scala.collection.immutable.List.type
 required: scala.collection.generic.SeqFactory[_ >: scala.collection.immutable.Stream with List <: scala.collection.immutable.LinearSeq with scala.collection.AbstractSeq with scala.collection.LinearSeqOptimized[Any,scala.collection.immutable.LinearSeq[Any] with scala.collection.AbstractSeq[Any] with scala.collection.LinearSeqOptimized[scala.collection.LinearSeqOptimized[A,scala.collection.immutable.Stream[A]],scala.collection.LinearSeqOptimized[A,scala.collection.immutable.Stream[A]]]]]
              List(List, Stream)
                   ^

Here is some more in a similar vein.

scala> trait D[T <: D[T]]
defined trait D

scala> case class Foo[T <: D[T]](x: T, y: D[T])
defined class Foo

// really, List[Any], that's all we know?
scala> def f(x: Any) = x match { case Foo(x, y) => List(x, y) }
f: (x: Any)List[Any]

// type arguments what in the what now?
scala> def f(x: Any) = x match { case Foo(x, y) => y }
<console>:10: error: type arguments [Any] do not conform to trait D's type parameter bounds [T <: D[T]]
       def f(x: Any) = x match { case Foo(x, y) => y }
           ^

Johannes Rudolph

unread,
Jun 12, 2012, 11:26:44 AM6/12/12
to scala-l...@googlegroups.com
On Tue, Jun 12, 2012 at 4:24 PM, Paul Phillips <pa...@improving.org> wrote:
> What you're looking at is approximately the same issue which underlies our
> failures calculating recursive bounds.  In both cases it is trying to solve
> for T in a system like T <: C[T].  It does not do this task well.  See the
> "Any"s in the below? That is a similar failure.  In that case it is more
> egregious because it infers an invalid type when valid ones exist.

I don't see yet if (and why) this is necessarily related.

> Here is some more in a similar vein.
> [...]

This one is similar for sure.

Interestingly, when I hand-craft the `unapply` method for the case
class the error goes away (I simplified my example btw):

trait C[T]
case class D[T](c: C[T])
object E {
def unapply[T](x: D[T]): Option[C[T]] = None
}

object IntC extends C[Int]

object Test {
(null: AnyRef) match { case E(IntC) => }
}

`-Xprint-typer` now shows almost the same unapply method, so the
question is what's the difference:

in D:
case <synthetic> def unapply[T >: Nothing <: Any](x$0: D[T]):
Option[C[T]] = // ...

in E:
def unapply[T >: Nothing <: Any](x: D[T]): Option[C[T]] = // ...

Is typer generating case class companions? Is it generating them
before type checking or does it assume something before type-checking
and then generates the real thing only afterwards?

Johannes Rudolph

unread,
Jun 12, 2012, 11:29:03 AM6/12/12
to scala-l...@googlegroups.com
Aha, that seems to be the difference:

* (5) Convert constructors in a pattern as follows:
* (5.1) If constructor refers to a case class factory, set
tree's type to the unique
* instance of its primary constructor that is a subtype of
the expected type.
* (5.2) If constructor refers to an extractor, convert to application of
* unapply or unapplySeq method.

so it seems like 5.1 is flawed.

Johannes Rudolph

unread,
Jun 12, 2012, 11:41:23 AM6/12/12
to scala-l...@googlegroups.com
Anyway, it got a nice number: https://issues.scala-lang.org/browse/SI-5900

On Tue, Jun 12, 2012 at 5:29 PM, Johannes Rudolph

Paul Phillips

unread,
Jun 12, 2012, 4:01:16 PM6/12/12
to scala-l...@googlegroups.com


On Tue, Jun 12, 2012 at 8:26 AM, Johannes Rudolph <johannes...@googlemail.com> wrote:
I don't see yet if (and why) this is necessarily related.

case Val(Test, x) => ...

It has to infer what is T, where Val is defined as

  case class Val[T](c: C[T], v: T)

The type it is trying to infer (T) itself appears as a type parameter in the system it is trying to solve.  It responds to this recursion by throwing up its hands and putting Any there.  I don't know if that clarifies the relationship, if not can you be more specific.

Johannes Rudolph

unread,
Jun 13, 2012, 5:09:20 AM6/13/12
to scala-l...@googlegroups.com
On Tue, Jun 12, 2012 at 10:01 PM, Paul Phillips <pa...@improving.org> wrote:
> The type it is trying to infer (T) itself appears as a type parameter in the
> system it is trying to solve.  It responds to this recursion by throwing up
> its hands and putting Any there.

I basically don't understand completely (or misunderstand) what you
mean by recursion. In your original example you had a type parameter
which was bounded by type which contained the type itself, that's what
I understood as the recursion you meant (`trait D[T <: D[T]]`).

Especially looking at the last simplified example I gave it's not
obviously clear to me where there's recursion involved (`trait C[T];
case class D[T](c: C[T])`). Do you mean there's recursion because that
`T` is here constrained (as a more general form of being explicitly
bounded) by something containing `T` itself?

What I'm still wondering about is why extractors are working where
case class constructor matching is not. Is there a deeper cause for
both being handled differently? It seems like extractors would provide
a superset of the functionality of case class matching. (I think I
found a difference: type inference for extractors doesn't allow for
the functionality of "case 2" of the paragraph "Type parameter
inference for typed patterns" in the spec which is to allow
implementing methods like this with pattern matching: `def f[B](t:
Term[B]): B`) Wouldn't it be desirable / possible to unify both?

Adriaan Moors

unread,
Jun 13, 2012, 5:47:00 AM6/13/12
to scala-l...@googlegroups.com
Type inference in patterns flows a bit differently from what we're used to in normal expressions.

For a constructor pattern, it's actually the expected type of the pattern that drives inference.
Concretely, that would be AnyRef in your first example, and Val[_] in the second one.
The types of the patterns that are "passed as arguments" to the constructor do not contribute to type inference.
(Except for determining in which variance positions the undetermined parameters occur.)

I agree the unapply and case class scenarios should behave in the same way.
Technically, I think we should retract "failed" inference results (such as Any) using adjustTypeArgs,
and replace them by skolems, as we do for unapply's.
Reply all
Reply to author
Forward
0 new messages