13

This file:

object Test extends App {
    val obj = List(1,2,3) : Object
    val res = obj match {
       case Seq(1,2,3) => "first"
       case _ => "other"
    }
    println(res)
}

Gives this warning:

Test.scala:6: warning: non variable type-argument A in type pattern Seq[A]  
is unchecked since it is eliminated by erasure
   case Seq(1,2,3) => "first"

Scala version 2.9.0.1.

I don't see how an erased type parameter is needed to perform the match. That first case clause is meant to ask if obj is a Seq with 3 elements equal to 1, 2, and 3.

I would understand this warning if I had written something like:

case strings : Seq[String] => ...

Why do I get the warning, and what is a good way to make it go away?

By the way, I do want to match against something with static type of Object. In the real code I'm parsing something like a Lisp datum - it might be an String, sequence of datums, Symbol, Number, etc.

Qantas 94 Heavy
  • 15,750
  • 31
  • 68
  • 83
Rob N
  • 15,024
  • 17
  • 92
  • 165
  • On an interesting note, Scala version 2.8.1 doesn't give this warning in this case – thoredge Sep 02 '11 at 05:57
  • @thoredge, my guess is the warning in 2.9.0 was probably considered an improvement. I don't know if this particular case was foreseen. – huynhjl Sep 02 '11 at 06:44

2 Answers2

6

Here is some insight to what happens behind the scene. Consider this code:

class Test {
  new Object match { case x: Seq[Int] => true }
  new Object match { case Seq(1) => true }
}

If you compile with scalac -Xprint:12 -unchecked, you'll see the code just before the erasure phase (id 13). For the first type pattern, you will see something like:

<synthetic> val temp1: java.lang.Object = new java.lang.Object();
if (temp1.isInstanceOf[Seq[Int]]()) 

For the Seq extractor pattern, you will see something like:

<synthetic> val temp3: java.lang.Object = new java.lang.Object();
if (temp3.isInstanceOf[Seq[A]]()) {
  <synthetic> val temp4: Seq[A] = temp3.asInstanceOf[Seq[A]]();
  <synthetic> val temp5: Some[Seq[A]] = collection.this.Seq.unapplySeq[A](temp4);
  // ...
}

In both cases, there is a type test to see if the object is of type Seq (Seq[Int] and Seq[A]). Type parameters will be eliminated during the erasure phase. Thus the warning. Even though the second may be unexpected, it does make sense to check the type since if object is not of type Seq that clause won't match and the JVM can proceed to the next clause. If the type does match, then the object can be casted to Seq and unapplySeq can be called.


RE: thoredge comment on the type check. May be we are talking about different things. I was merely saying that:

(o: Object) match {
  case Seq(i) => println("seq " + i)
  case Array(i) => println("array " + i)
}

translates to something like:

if (o.isInstanceOf[Seq[_]]) { // type check
  val temp1 = o.asInstanceOf[Seq[_]] // cast
  // verify that temp1 is of length 1 and println("seq " + temp1(0))
} else if (o.isInstanceOf[Array[_]]) { // type check
  val temp1 = o.asInstanceOf[Array[_]] // cast
  // verify that temp1 is of length 1 and println("array " + temp1(0))
}

The type check is used so that when the cast is done there is no class cast exception.

Whether the warning non variable type-argument A in type pattern Seq[A] is unchecked since it is eliminated by erasure is justified and whether there would be cases where there could be class cast exception even with the type check, I don't know.

Edit: here is an example:

object SeqSumIs10 {
  def unapply(seq: Seq[Int]) = if (seq.sum == 10) Some(seq) else None
}

(Seq("a"): Object) match {
  case SeqSumIs10(seq) => println("seq.sum is 10 " + seq) 
}
// ClassCastException: java.lang.String cannot be cast to java.lang.Integer
huynhjl
  • 41,520
  • 14
  • 105
  • 158
  • 1
    Is it a bug then? The type check is just an optimisation in this case. – thoredge Sep 02 '11 at 06:10
  • @thoredge, without the type check, you'd get a *ClassCastException* when the object is not of type `Seq` versus trying to match against subsequent clauses. – huynhjl Sep 02 '11 at 06:30
  • I don't get a ClassCaseException if I change the object to Map(1 -> 2, 2 -> 3), Map("1" -> "2", "2" -> "3"); or "" for that matter – thoredge Sep 02 '11 at 08:24
  • With -Xprint I get `case collection.this.Seq.unapplySeq[A]() (Array[A]{1, 2, 3}) =>` ... I don't know what that is, but there is no instanceof check. – Rob N Sep 02 '11 at 11:27
  • @thoredge, you don't get a *ClassCastException* because the compiler added the *isInstanceOf* check. – huynhjl Sep 02 '11 at 15:25
  • @Rob N, the `isInstanceOf` check should be before the `unapplySeq` call (may be a couple lines before). – huynhjl Sep 02 '11 at 15:28
  • @huynhjl, Now I see it. I had to change the -Xprint:12 to 14. – Rob N Sep 02 '11 at 15:48
  • @huynhjl, I'm confused. If the compiler gives a warning that the pattern Seq[A] is unchecked for A, then we should see a ClassCastException in some case; right? – thoredge Sep 04 '11 at 12:26
  • @thoredge, added some thoughts in my answer related to your question. – huynhjl Sep 05 '11 at 13:47
  • I still can't get the point: 1. What a problem or a potential problem do these warnings warn about? 2. Why "unchecked" if the type is checked? 3. How to rewrite the code to eliminate the warning (and the problem it is meant to signalize)? – Ivan Feb 23 '12 at 16:47
4

Declaring the match object outside at least makes it go away, but I'm not sure why:

class App
  object Test extends App {
  val obj = List(1,2,3) : Object
  val MatchMe = Seq(1,2,3)
  val res = obj match {
    case MatchMe => "first"
    case _ => "other"
  }
  println(res)
}
thoredge
  • 12,237
  • 1
  • 40
  • 55
  • 2
    Yes good find. This is because in this case, it's using a *stable identifier pattern*, and those compile just to an equality test (per the language spec), no casting needed. If you want to bind variables, you're out of luck though. – huynhjl Sep 02 '11 at 06:27
  • Good to know, but yes, I need to bind variables. – Rob N Sep 02 '11 at 11:24
  • 1
    What if I match against a type (like `Seq[Int]`) and any integer sequence is to be considered a valid match (not just (1 2, 3))? – Ivan Feb 23 '12 at 22:29