4

I've stumbled upon a strange situation where having reflect.runtime.universe._ imported causes reflect.runtime.universe.RuntimeClass to be inferred where it seems Nothing would be more appropriate.

Consider this simple method and List:

import scala.reflect.ClassTag

def find[A : ClassTag](l: List[Any]): Option[A] =
    l collectFirst { case a: A => a }

val list = List(1, "a", false)

I can use it to find the first element in the List of some type, and it works well, as expected.

scala> find[String](list)
res1: Option[String] = Some(a)

scala> find[Long](list)
res2: Option[Long] = None

If I do not supply the type parameter, then A is inferred as Nothing, so I get Option[Nothing], also as expected.

scala> find(list)
res3: Option[Nothing] = None

But, if I import scala.reflect.runtime.universe._ and again do not supply the type parameter, A is now inferred as reflect.runtime.universe.RuntimeClass instead of Nothing.

scala> find(list)
res4: Option[reflect.runtime.universe.RuntimeClass] = None
                ^ What?

This isn't a huge problem, since I can hardly imagine much of a use-case for the find method without supplying the type parameter manually, but why does this happen? The ClassTag seems partially to blame, as removing it once again causes Nothing to be inferred (though completely breaks the method due to erasure). What's going on here?

Michael Zajac
  • 55,144
  • 7
  • 113
  • 138

2 Answers2

4

That looks like some completely unintended side effect of how the runtime Universe is designed internally.

The scala.reflect.runtime.universe has type scala.reflect.api.JavaUniverse.

By importing all its members, you import - in particular - a bunch of implicit ClassTag values defined in scala.reflect.api.ImplicitTags trait which is extended by the universe.

The ImplicitTags trait introduces about 90 different implicit ClassTag values. Among them, there is this one:

implicit val RuntimeClassTag: ClassTag[RuntimeClass]

It looks like the compiler likes it more than others since it decided to use it when inferring arbitrary ClassTag[A]. Why is that? This is because RuntimeClassTag is overridden in scala.reflect.api.JavaUniverse and implicit values defined in subclasses have priority over implicit values defined in superclasses (as specified somewhere in overloading resolution rules - SLS 6.26.3)

So, summarizing, the compiler inferred RuntimeClass for A with ClassTag context bound because it saw this thing in scope (introduced by wildcard import on universe):

trait JavaUniverse extends Universe { self =>
  type RuntimeClass = java.lang.Class[_]
  implicit val RuntimeClassTag: ClassTag[RuntimeClass] = 
    ClassTag[RuntimeClass](classOf[RuntimeClass])
  ...
}
ghik
  • 10,706
  • 1
  • 37
  • 50
0

@ghik's answer is correct, but I think there is one important thing to add. Normally, when we require an implicit ClassTag[A], we expect the compiler to generate the ClassTag for us. I was expecting ClassTag[Nothing] to be generated when omitting the explicit type parameter. But having the imported ClassTag[RuntimeClass] in scope appears to stop that from happening.

We can make this happen by simply introducing another implicit ClassTag without importing runtime.universe._.

scala> implicit val ct = classTag[Long]
ct: scala.reflect.ClassTag[Long] = Long

scala> find(list)
res8: Option[Long] = None

The compiler sees that I only have one implicit ClassTag[Long] in scope, so when I call find(list), the compiler assumes that must be what I wanted and A must be Long, because there is no other ClassTag available, and it will not generate another ClassTag for me.

In the precise context of my question then, I'm importing a boat-load of implicit ClassTags, and the compiler deems that ClassTag[RuntimeClass] is the most appropriate of those.

Michael Zajac
  • 55,144
  • 7
  • 113
  • 138