In this post:
Is it possible to convert a TypeTag to a Manifest?
It is indicated that a TypeTag can be converted into a Manifest using the following code:
def toManifest[T:TypeTag]: Manifest[T] = {
val t = typeTag[T]
val mirror = t.mirror
def toManifestRec(t: Type): Manifest[_] = {
val clazz = ClassTag[T](mirror.runtimeClass(t)).runtimeClass
if (t.typeArgs.length == 1) {
val arg = toManifestRec(t.typeArgs.head)
ManifestFactory.classType(clazz, arg)
} else if (t.typeArgs.length > 1) {
val args = t.typeArgs.map(x => toManifestRec(x))
ManifestFactory.classType(clazz, args.head, args.tail: _*)
} else {
ManifestFactory.classType(clazz)
}
}
toManifestRec(t.tpe).asInstanceOf[Manifest[T]]
}
It doesn't work, as demonstrated in the following test case:
object TypeTag2Manifest {
class Example {
type T = Map[String, Int]
}
val example = new Example
}
class TypeTag2Manifest extends FunSpec {
import org.apache.spark.sql.catalyst.ScalaReflection.universe._
import TypeTag2Manifest._
it("can convert") {
val t1 = implicitly[TypeTag[example.T]]
val v1 = toManifest(t1)
val v2 = implicitly[Manifest[example.T]]
assert(v1 == v2)
}
}
Output:
scala.collection.immutable.Map did not equal scala.collection.immutable.Map[java.lang.String, Int]
ScalaTestFailureLocation: com.tribbloids.spike.scala_spike.reflection.TypeTag2Manifest at (TypeTag2Manifest.scala:52)
Expected :scala.collection.immutable.Map[java.lang.String, Int]
Actual :scala.collection.immutable.Map
Evidently this indicates that type erasure has been plaguing the conversion, and TypeTag, despite being designed to avoid type erasure, can only resolve to the dependent type example.T
without getting the correct type arguments from underlying type Map[String, Int]
.
So what is the way to convert TypeTag and Manifest into each other that doesn't suck?
If you replace
toManifestRec(t.tpe).asInstanceOf[Manifest[T]]
with
toManifestRec(t.tpe.dealias).asInstanceOf[Manifest[T]]
this improves toManifest
but not completely:
scala.collection.immutable.Map[java.lang.String, int]
vs.
scala.collection.immutable.Map[java.lang.String, Int]
i.e. Java primitive int
vs. scala.Int
.
The problem is in the second .runtimeClass
in
val clazz = ClassTag[T](mirror.runtimeClass(t)).runtimeClass
mirror.runtimeClass(t)
is int
, ClassTag[T](mirror.runtimeClass(t))
is Int
but ClassTag[T](mirror.runtimeClass(t)).runtimeClass
is again int.
So try to improve toManifest
more
def toManifest[T: TypeTag]: Manifest[T] = {
val t = typeTag[T]
val mirror = t.mirror
def toManifestRec(t: Type): Manifest[_] = {
ClassTag[T](mirror.runtimeClass(t)) match {
case ClassTag.Byte => Manifest.Byte
case ClassTag.Short => Manifest.Short
case ClassTag.Char => Manifest.Char
case ClassTag.Int => Manifest.Int
case ClassTag.Long => Manifest.Long
case ClassTag.Float => Manifest.Float
case ClassTag.Double => Manifest.Double
case ClassTag.Boolean => Manifest.Boolean
case ClassTag.Unit => Manifest.Unit
case ClassTag.Object => Manifest.Object
case ClassTag.Nothing => Manifest.Nothing
case ClassTag.Null => Manifest.Null
case classTag =>
val clazz = classTag.runtimeClass
if (t.typeArgs.length >= 1) {
val args = t.typeArgs.map(x => toManifestRec(x))
ManifestFactory.classType(clazz, args.head, args.tail: _*)
} else {
ManifestFactory.classType(clazz)
}
}
}
toManifestRec(t.tpe.dealias).asInstanceOf[Manifest[T]]
}
val t1 = implicitly[TypeTag[example.T]] //TypeTag[TypeTag2Manifest.example.T]
val v1 = toManifest(t1) //scala.collection.immutable.Map[java.lang.String, Int]
val v2 = implicitly[Manifest[example.T]] //scala.collection.immutable.Map[java.lang.String, Int]
v1 == v2 //true