4

I am trying to use pattern matching to detect a generic type of my own custom type based on this answer.

The code provided by author works as expected:

import scala.reflect.runtime.{universe => ru}

def matchList[A: ru.TypeTag](list: List[A]) = list match {
  case strlist: List[String @unchecked] if ru.typeOf[A] =:= ru.typeOf[String] => println("A list of strings!")
  case intlist: List[Int @unchecked] if ru.typeOf[A] =:= ru.typeOf[Int] => println("A list of ints!")
}

matchList(List("a", "b", "c"))
matchList(List(1,2,3))

correctly displays:

A list of strings!
A list of ints!

Now based on this I am trying to apply same pattern to detect the generic type of my custom class Foo. The code below is copy-pased, except it uses Foo instead of List:

import scala.reflect.runtime.{universe => ru}

class Foo[T](val data: T)

def matchFoo[A: ru.TypeTag](foo: Foo[A]) = {
  println("Is string = " + (ru.typeOf[A] =:= ru.typeOf[String]))
  println("Is int = " + (ru.typeOf[A] =:= ru.typeOf[Int]))
  foo match {
    case fooStr: Foo[String @unchecked] if ru.typeOf[A] =:= ru.typeOf[String] => println("Found String")
    case fooInt: Foo[Int @unchecked] if ru.typeOf[A] =:= ru.typeOf[Int] => println("Found Int")
  }
}

matchFoo(new Foo[String]("str"))
println("------------")
matchFoo(new Foo[Int](123))

Only this time it outputs not what I was expecting:

Is string = true
Is int = false
Found String
------------
Is string = false
Is int = true
Found String  // wth?

How can the second call matchFoo(new Foo[Int](123)) display Found String? As you can see I even explicitly printed out the match conditions, and they are fine.

The code online: http://goo.gl/b5Ta7h

EDIT:

I got it working by extracting match conditions into a variable:

def matchFoo[A: ru.TypeTag](foo: Foo[A]) = {
    val isString: Boolean = ru.typeOf[A] =:= ru.typeOf[String]
    val isInt: Boolean = ru.typeOf[A] =:= ru.typeOf[Int]
    println("Is string = " + isString)
    println("Is int = " + isInt)
    foo match {
      case fooStr: Foo[String @unchecked] if isString => println("Found String")
      case fooInt: Foo[Int @unchecked] if isInt => println("Found Int")
    }
}

Code online: http://goo.gl/mLxYY2

But in my opinion the original version should also work. I don't think I'm missing operator precedence here, since wrapping conditions into parenthesis also doesn't help.

Is it a bug in Scala? I'm using Scala SDK v. 2.11.5 and JDK v. 1.8.0_25. The online CodingGround uses Scala SDK v. 2.10.3.

EDIT 2:

I've opened an issue in Scala's bugtracker for this. You can vote for it here.

Community
  • 1
  • 1
Maciej Sz
  • 11,151
  • 7
  • 40
  • 56

1 Answers1

0

This looks very much like a bug in the compiler which does not resolve the correct implicit (could be the presence of the @unchecked?).

case fooStr: Foo[String @unchecked] if ru.typeOf[A] =:= ru.typeOf[String] =>
   println(implicitly[TypeTag[String]]) // will print TypeTag[Int]

By looking at the byte code, the compiler uses the TypeTag passed to the method ($evidence).

A (limited) workaround could be to use ru.definitions.IntTpe:

case fooStr: Foo[Int @unchecked] if ru.typeOf[A] =:= ru.definitions.IntTpe =>
   println("That's an Int")
Bruno Bieth
  • 2,317
  • 20
  • 31