How to create a TypeTag manually?
I'm interested in creating a TypeTag manually (since 2.10M5):
object X {
import reflect.runtime.universe._
def tt[A : TypeTag](a: A) = typeTag[A] // how to do this manually?
val t = tt(List("")(_))
}
scalac -Xprint:typer <file>.scala
results in
package <empty> {
object X extends scala.AnyRef {
def <init>(): X.type = {
X.super.<init>();
()
};
import scala.reflect.runtime.`package`.universe._;
def tt[A >: Nothing <: Any](a: A)(implicit evidence$1: reflect.runtime.universe.TypeTag[A]): reflect.runtime.universe.TypeTag[A] = scala.reflect.runtime.`package`.universe.typeTag[A](evidence$1);
private[this] val t: reflect.runtime.universe.TypeTag[Int => String] = X.this.tt[Int => String](((x$1: Int) => immutable.this.List.apply[String]("").apply(x$1)))({
val $u: reflect.runtime.universe.type = scala.this.reflect.runtime.`package`.universe;
val $m: $u.Mirror = scala.this.reflect.runtime.`package`.universe.runtimeMirror(this.getClass().getClassLoader());
$u.TypeTag.apply[Int => String]($m, {
final class $typecreator1 extends TypeCreator {
def <init>(): $typecreator1 = {
$typecreator1.super.<init>();
()
};
def apply[U >: Nothing <: scala.reflect.base.Universe with Singleton]($m$untyped: scala.reflect.base.MirrorOf[U]): U#Type = {
val $u: U = $m$untyped.universe;
val $m: $u.Mirror = $m$untyped.asInstanceOf[$u.Mirror];
$u.TypeRef.apply($u.ThisType.apply($m.staticModule("scala").asModuleSymbol.moduleClass), $m.staticClass("scala.Function1"), scala.collection.immutable.List.apply[$u.Type]($m.staticClass("scala.Int").asTypeSymbol.asTypeConstructor, $m.staticClass("java.lang.String").asTypeSymbol.asTypeConstructor))
}
};
new $typecreator1()
})
});
<stable> <accessor> def t: reflect.runtime.universe.TypeTag[Int => String] = X.this.t
}
}
It seems to be completely compiler magic because the types are hardcoded. Nevertheless is there a way to do this manually?
Solution 1:
In M3 you could create a type tag in a very simple way, e.g.: TypeTag[Int](TypeRef(<scala package>, <symbol of scala.Int>, Nil))
. It basically meant that once a type tag is created, it is forever bound to a certain classloader (namely the one that was used to load a symbol of scala.Int in the example above).
Back then it was fine, because we considered that we could have a one-size-fits-all mirror that accomodates all classloaders. That was convenient, because you could just write implicitly[TypeTag[T]]
and the compiler would use that global mirror to instantiate a Type you requested.
Unfortunately later, based on feedback, we realized that this isn't going to work, and that we need multiple mirrors - each having its own classloader.
And then it became apparent that type tags need to be flexible, because once you write that implicitly[TypeTag[T]]
the compiler doesn't have enough information what classloader you want to use. Basically there were two alternatives: 1) make type tags path-dependent on mirrors (so that one would be forced to explicitly provide a mirror every time a type tag is requested from the compiler), 2) transform type tags into type factories, capable of instantiating themselves in any mirror. Long story short, the first option didn't work, so we are where we are.
So currently type tags need to be created in quite a roundabout way, as outlined in https://github.com/scala/scala/blob/master/src/library/scala/reflect/base/TypeTags.scala#L143. You call a factory method defined in the companion TypeTag
and provide two arguments: 1) a default mirror, where the type tag will be instantiated, 2) a type factory that can instantiate a type tag in arbitrary mirror. This is basically what the compiler did in the printout you've attached above.
Why I called this roundabout? Because to provide a type factory you need to manually subclass the scala.reflect.base.TypeFactory
class. That's an unfortunate limitation of Scala type system on the border between function and dependently-typed methods.
Solution 2:
Based on Get TypeTag[A] from Class[A]:
import scala.reflect.runtime.universe._
def typeToTypeTag[T](
tpe: Type,
mirror: reflect.api.Mirror[reflect.runtime.universe.type]
): TypeTag[T] = {
TypeTag(mirror, new reflect.api.TypeCreator {
def apply[U <: reflect.api.Universe with Singleton](m: reflect.api.Mirror[U]) = {
assert(m eq mirror, s"TypeTag[$tpe] defined in $mirror cannot be migrated to $m.")
tpe.asInstanceOf[U#Type]
}
})
}
For example this can be used to get the TypeTag
for a part of another TypeTag
:
def inside[A, B](tag: TypeTag[(A, B)]): (TypeTag[A], TypeTag[B]) = {
val tpes = tag.tpe.asInstanceOf[TypeRefApi].args
val tagA = typeToTypeTag[A](tpes(0), tag.mirror)
val tagB = typeToTypeTag[B](tpes(1), tag.mirror)
return (tagA, tagB)
}
This works in Scala 2.10.2:
scala> inside(typeTag[(Int, Double)])
res0: (reflect.runtime.universe.TypeTag[Int], reflect.runtime.universe.TypeTag[Double]) = (TypeTag[Int],TypeTag[Double])
The limitation of being tied to a particular Mirror
is likely not a problem as long as you don't have multiple ClassLoader
s.
Solution 3:
Currently, there are three ways to create a TypeTag
that supports generics manually. The first two depends on the scala-compiler
, and with them you get type checking just like in compile time, as it is an actual code compilation:
import scala.reflect.runtime.universe._
import scala.reflect.runtime.currentMirror
import scala.tools.reflect.ToolBox
val toolbox = currentMirror.mkToolBox()
def createTypeTag(tp: String): TypeTag[_] = {
val ttree = toolbox.parse(s"scala.reflect.runtime.universe.typeTag[$tp]")
toolbox.eval(ttree).asInstanceOf[TypeTag[_]]
}
If you need a serializable TypeTag
and performance isn't your main concern, the first method is the most succinct. OTOH, if your use case needs to be very performant, beware that on-the-fly compilation might take a couple of seconds to finish.
The second method performs a little better:
def createTypeTag(tp: String): TypeTag[_] = {
val ttagCall = s"scala.reflect.runtime.universe.typeTag[$tp]"
val tpe = toolbox.typecheck(toolbox.parse(ttagCall), toolbox.TYPEmode).tpe.resultType.typeArgs.head
TypeTag(currentMirror, new reflect.api.TypeCreator {
def apply[U <: reflect.api.Universe with Singleton](m: reflect.api.Mirror[U]) = {
assert(m eq mirror, s"TypeTag[$tpe] defined in $mirror cannot be migrated to $m.")
tpe.asInstanceOf[U#Type]
}
}
}
This second mode creates a typed tree and fetches the concrete types marked in the TypeTag
, i.e., if your target is a List[String]
, it will be translated to scala.collection.immutable.List[String]
, since scala.List
is only a type alias. That's actually the behavior expected from typeTag[List[String]]
.
The last method is to create a Type
manually and use it to create the TypeTag
. This method is error-prone, there is limited type checking, and it uses internal API. This is, however, the fastest way to manually obtain a TypeTag
.
def createTypeTag(tp: String): TypeTag[_] = {
val typs = // ... manipulate the string to extract type and parameters
val typSym = currentMirror.staticClass(typs[0])
val paramSym = currentMirror.staticClass(typs[1])
val tpe = universe.internal.typeRef(NoPrefix, typSym, List(paramSym.selfType))
val ttag = TypeTag(currentMirror, new TypeCreator {
override def apply[U <: Universe with Singleton](m: Mirror[U]): U#Type = {
assert(m == currentMirror, s"TypeTag[$tpe] defined in $currentMirror cannot be migrated to $m.")
tpe.asInstanceOf[U#Type]
}
})
}
Solution 4:
The function definition
def tt[A : TypeTag](a: A) = typeTag[A]
is just another way of writing
def tt(a: A)(implicit tag: TypeTag[A]) = tag
which means that the instance of a tag is being created implicitly by the compiler. The reasoning behind this is that Scala compiler solves the JVM's type erasure problem by hardcoding the otherwise erased type information.
In case you're not familiar with type erasure issue, it is that JVM doesn't store the type parameters information, for instance a type Seq[Set[Int]]
would be seen simply as Seq[_]
by the JVM and there would be no way for you to find out the missing type information with reflection.