
pickling.Macros.scala Maven / Gradle / Ivy
The newest version!
package scala.pickling
import scala.pickling.internal._
import scala.reflect.runtime.{universe => ru}
import ir._
// purpose of this macro: implementation of genPickler[T]. i.e. the macro that is selected
// via implicit search and which initiates the process of generating a pickler for a given type T
// NOTE: dispatch is done elsewhere. picklers generated by genPickler[T] only know how to process T
// but not its subclasses or the types convertible to it!
trait PicklerMacros extends Macro with PickleMacros {
import c.universe._
def impl[T: c.WeakTypeTag]: c.Tree = preferringAlternativeImplicits {
import definitions._
val tpe = weakTypeOf[T]
val sym = tpe.typeSymbol.asClass
import irs._
val primitiveSizes = Map(
typeOf[Int] -> 4,
typeOf[Short] -> 2,
typeOf[Long] -> 8,
typeOf[Double] -> 8,
typeOf[Byte] -> 1,
typeOf[Char] -> 2,
typeOf[Float] -> 4,
typeOf[Double] -> 8,
typeOf[Boolean] -> 1
)
def getField(fir: FieldIR): Tree =
if (fir.isPublic) q"picklee.${newTermName(fir.name)}"
else if (fir.javaSetter.isDefined) {
val getter = newTermName("get" + fir.name)
q"picklee.$getter"
} else reflectively("picklee", fir)(fm => q"$fm.get.asInstanceOf[${fir.tpe}]").head //TODO: don't think it's possible for this to return an empty list, so head should be OK
def computeKnownSizeOfObjectOutput(cir: ClassIR): (Option[Tree], List[Tree]) = {
// for now we cannot compute a fixed size for ObjectOutputs
// in the future this will be a possible optimization (faster Externalizables)
None -> List()
}
// this exists so as to provide as much information as possible about the size of the object
// to-be-pickled to the picklers at runtime. In the case of the binary format for example,
// this allows us to remove array copying and allocation bottlenecks
// Note: this takes a "flattened" ClassIR
// returns a tree with the size and a list of trees that have to be checked for null
def computeKnownSizeIfPossible(cir: ClassIR): (Option[Tree], List[Tree]) = {
if (cir.tpe <:< typeOf[Array[_]]) {
val TypeRef(_, _, List(elTpe)) = cir.tpe
val knownSize =
if (elTpe.isEffectivelyPrimitive) Some(q"picklee.length * ${primitiveSizes(elTpe)} + 4")
else None
knownSize -> Nil
} else if (tpe <:< typeOf[java.io.Externalizable]) {
computeKnownSizeOfObjectOutput(cir) match {
case (None, lst) => None -> List()
case _ => c.abort(c.enclosingPosition, "not implemented")
}
} else {
val possibleSizes: List[(Option[Tree], Option[Tree])] = cir.fields map {
case fld if fld.tpe.isEffectivelyPrimitive =>
val isScalar = !(fld.tpe <:< typeOf[Array[_]])
if (isScalar) None -> Some(q"${primitiveSizes(fld.tpe)}")
else {
val TypeRef(_, _, List(elTpe)) = fld.tpe
Some(getField(fld)) -> Some(q"${getField(fld)}.length * ${primitiveSizes(elTpe)} + 4")
}
case _ =>
None -> None
}
val possibleSizes1 = possibleSizes.map(_._2)
val resOpt =
if (possibleSizes1.contains(None) || possibleSizes1.isEmpty) None
else Some(possibleSizes1.map(_.get).reduce((t1, t2) => q"$t1 + $t2"))
val resLst = possibleSizes.flatMap(p => if (p._1.isEmpty) List() else List(p._1.get))
(resOpt, resLst)
}
}
def unifiedPickle = { // NOTE: unified = the same code works for both primitives and objects
val cir = newClassIR(tpe)
// println(s"CIR for ${tpe.toString}: ${cir.fields.mkString(",")}")
val hintKnownSize = computeKnownSizeIfPossible(cir) match {
case (None, lst) => q""
case (Some(tree), lst) =>
val typeNameLen = tpe.key.getBytes("UTF-8").length
val noNullTree = lst.foldLeft[Tree](Literal(Constant(true)))((acc, curr) => q"$acc && ($curr != null)")
q"""
if ($noNullTree) {
val size = $tree + $typeNameLen + 4
builder.hintKnownSize(size)
}
"""
}
val beginEntry = q"""
$hintKnownSize
builder.beginEntry(picklee)
"""
val (nonLoopyFields, loopyFields) = cir.fields.partition(fir => !shouldBotherAboutLooping(fir.tpe))
val putFields =
if (tpe <:< typeOf[java.io.Externalizable]) {
val fieldName = """$ext"""
List(q"""
val out = new scala.pickling.util.GenObjectOutput
picklee.writeExternal(out)
builder.putField($fieldName, b =>
out.pickleInto(b)
)
""")
} else (nonLoopyFields ++ loopyFields).flatMap(fir => {
// for each field, compute a tree for pickling it
// (or empty list, if impossible)
def pickleLogic(fieldValue: Tree): Tree =
if (fir.tpe.typeSymbol.isEffectivelyFinal) q"""
b.hintStaticallyElidedType()
$fieldValue.pickleInto(b)
""" else q"""
val subPicklee: ${fir.tpe} = $fieldValue
if (subPicklee == null || subPicklee.getClass == classOf[${fir.tpe}]) b.hintDynamicallyElidedType()
subPicklee.pickleInto(b)
"""
def putField(getterLogic: Tree) =
q"builder.putField(${fir.name}, b => ${pickleLogic(getterLogic)})"
// we assume getterLogic is a tree of type Try[${fir.tpe}]
def tryPutField(getterLogic: Tree) = {
val tryName = c.fresh(newTermName("tr"))
val valName = c.fresh(newTermName("value"))
q"""
val $tryName = $getterLogic
if ($tryName.isSuccess) {
val $valName = $tryName.get
builder.putField(${fir.name}, b => ${pickleLogic(Ident(valName))})
}
"""
}
if (sym.isModuleClass) {
Nil
} else if (fir.hasGetter) {
if (fir.isPublic) List(putField(q"picklee.${newTermName(fir.name)}"))
else reflectively("picklee", fir)(fm => putField(q"$fm.get.asInstanceOf[${fir.tpe}]"))
} else if (fir.javaSetter.isDefined) {
List(putField(getField(fir)))
} else {
reflectivelyWithoutGetter("picklee", fir)(fvalue =>
tryPutField(q"$fvalue.asInstanceOf[scala.util.Try[${fir.tpe}]]"))
}
})
val endEntry = q"builder.endEntry()"
if (shouldBotherAboutSharing(tpe)) {
q"""
val oid = scala.pickling.internal.`package`.lookupPicklee(picklee)
builder.hintOid(oid)
$beginEntry
if (oid == -1) { ..$putFields }
$endEntry
"""
} else {
q"""
$beginEntry
..$putFields
$endEntry
"""
}
}
def pickleLogic: Tree = tpe match {
case NothingTpe => c.abort(c.enclosingPosition, "cannot pickle Nothing") // TODO: report the serialization path that brought us here
case _ => unifiedPickle
}
//println("trying to generate pickler for type " + tpe.toString)
tpe.normalize match {
case RefinedType(parents, decls) =>
c.abort(c.enclosingPosition, "cannot generate pickler for refined type")
case _ if tpe.typeSymbol.isClass =>
// if class is abstract return instance of `PicklerUnpicklerNotFound`.
// this triggers the generation of a dispatch based on the runtime class of the picklee.
val classSym = tpe.typeSymbol.asClass
if (classSym.isAbstractClass) {
//println("abstract class, returning PicklerUnpicklerNotFound")
return q"new scala.pickling.PicklerUnpicklerNotFound[$tpe]"
}
case _ =>
c.abort(c.enclosingPosition, "cannot generate pickler")
}
val picklerName = c.fresh(syntheticPicklerName(tpe).toTermName)
q"""
implicit object $picklerName extends scala.pickling.SPickler[$tpe] with scala.pickling.Generated {
import scala.pickling._
import scala.pickling.internal._
import scala.pickling.`package`.PickleOps
def pickle(picklee: $tpe, builder: scala.pickling.PBuilder): Unit = $pickleLogic
}
$picklerName
"""
}
def dpicklerImpl[T: c.WeakTypeTag](format: c.Tree): c.Tree = {
val tpe = weakTypeOf[T]
val picklerName = c.fresh((syntheticBaseName(tpe) + "DPickler"): TermName)
val dpicklerPickleImpl = pickleWithTagInto(q"picklee0", q"builder")
q"""
implicit object $picklerName extends scala.pickling.DPickler[$tpe] {
import scala.pickling._
import scala.pickling.internal._
import scala.pickling.`package`.PickleOps
def pickle(picklee0: $tpe, builder: scala.pickling.PBuilder): Unit = $dpicklerPickleImpl
}
$picklerName
"""
}
}
import HasCompat._
// purpose of this macro: implementation of genUnpickler[T]. i.e., the macro that is selected via implicit
// search and which initiates the process of generating an unpickler for a given type T.
// NOTE: dispatch is done elsewhere. unpicklers generated by genUnpickler[T] only know how to process T
// but not its subclasses or the types convertible to it!
trait UnpicklerMacros extends Macro with UnpickleMacros {
def impl[T: c.WeakTypeTag]: c.Tree = preferringAlternativeImplicits {
import c.universe._
import compat._
import definitions._
val tpe = weakTypeOf[T]
val targs = tpe match { case TypeRef(_, _, targs) => targs; case _ => Nil }
val sym = tpe.typeSymbol.asClass
import irs._
def unpicklePrimitive = q"reader.readPrimitive()"
def unpickleObject = {
def readField(name: String, tpe: Type) = {
val readerName = c.fresh(newTermName("reader"))
val readerUnpickleTree = readerUnpickle(tpe, readerName)
q"""
val $readerName = reader.readField($name)
$readerUnpickleTree
"""
}
if (tpe <:< typeOf[java.io.Externalizable]) {
val fieldName = """$ext"""
val readerName = c.fresh(newTermName("reader"))
val objectOutTpe = typeOf[scala.pickling.util.GenObjectOutput]
val readerUnpickleTree = readerUnpickleTopLevel(objectOutTpe, readerName)
q"""
val inst = scala.concurrent.util.Unsafe.instance.allocateInstance(classOf[$tpe]).asInstanceOf[$tpe]
val $readerName = reader.readField($fieldName)
val out = $readerUnpickleTree
val in = out.toInput
inst.readExternal(in)
inst
"""
} else {
// TODO: validate that the tpe argument of unpickle and weakTypeOf[T] work together
// NOTE: step 1) this creates an instance and initializes its fields reified from constructor arguments
val cir = newClassIR(tpe)
val isPreciseType = targs.length == sym.typeParams.length && targs.forall(_.typeSymbol.isClass)
val canCallCtor = cir.canCallCtor
// STEP 2: remove transient fields from the "pending fields", the fields that need to be restored.
// TODO: for ultimate loop safety, pendingFields should be hoisted to the outermost unpickling scope
// For example, in the snippet below, when unpickling C, we'll need to move the b.c assignment not
// just outside the constructor of B, but outside the enclosing constructor of C!
// class С(val b: B)
// class B(var c: C)
// TODO: don't forget about the previous todo when describing the sharing algorithm for the paper
// it's a very important detail, without which everything will crumble
// no idea how to fix that, because hoisting might very well break reading order beyond repair
// so that it becomes hopelessly unsync with writing order
// nevertheless don't despair and try to prove whether this is or is not the fact
// i was super scared that string sharing is going to fail due to the same reason, but it did not :)
// in the worst case we can do the same as the interpreted runtime does - just go for allocateInstance
// pending fields are fields that are restored after instantiation (e.g., through field assignments)
val pendingFields = if (!canCallCtor) cir.fields else cir.fields.filter(fir =>
fir.isNonParam || shouldBotherAboutLooping(fir.tpe) || fir.javaSetter.isDefined
)
val instantiationLogic = {
if (sym.isModuleClass) {
q"${sym.module}"
} else if (cir.javaGetInstance) {
q"""java.lang.Class.forName(${tpe.toString}).getDeclaredMethod("getInstance").invoke(null)"""
} else if (canCallCtor) {
val ctorFirs = cir.fields.filter(_.param.isDefined)
val ctorSig: Map[Symbol, Type] = ctorFirs.map(fir => (fir.param.get: Symbol, fir.tpe)).toMap
if (ctorSig.isEmpty) {
q"new $tpe"
} else {
val ctorSym = ctorSig.head._1.owner.asMethod
val ctorArgs = ctorSym.paramss.map(_.map(f => {
val delayInitialization = pendingFields.exists(_.param.map(_ == f).getOrElse(false))
if (delayInitialization) q"null" else readField(f.name.toString, ctorSig(f))
}))
q"new $tpe(...$ctorArgs)"
}
} else {
q"scala.concurrent.util.Unsafe.instance.allocateInstance(classOf[$tpe]).asInstanceOf[$tpe]"
}
}
// NOTE: step 2) this sets values for non-erased fields which haven't been initialized during step 1
val initializationLogic = {
if (sym.isModuleClass || pendingFields.isEmpty) {
instantiationLogic
} else {
val instance = newTermName(tpe.typeSymbol.name + "Instance")
val initPendingFields = pendingFields.flatMap(fir => {
val readFir = readField(fir.name, fir.tpe)
if (fir.isPublic && fir.hasSetter) List(q"$instance.${newTermName(fir.name)} = $readFir".asInstanceOf[Tree])
else if (fir.javaSetter.isDefined) {
val JavaProperty(name, declaredIn, isAccessible) = fir.javaSetter.get
if (!isAccessible) {
val methodName = "set" + name
// obtain Class of parameter
val className = fir.tpe.toString
val (classTree, readTree) = className match {
case "Byte" => (q"java.lang.Byte.TYPE", q"new java.lang.Byte($readFir)")
case "Short" => (q"java.lang.Short.TYPE", q"new java.lang.Short($readFir)")
case "Char" => (q"java.lang.Character.TYPE", q"new java.lang.Character($readFir)")
case "Int" => (q"java.lang.Integer.TYPE", q"new java.lang.Integer($readFir)")
case "Long" => (q"java.lang.Long.TYPE", q"new java.lang.Long($readFir)")
case "Float" => (q"java.lang.Float.TYPE", q"new java.lang.Float($readFir)")
case "Double" => (q"java.lang.Double.TYPE", q"new java.lang.Double($readFir)")
case "Boolean" => (q"java.lang.Boolean.TYPE", q"new java.lang.Boolean($readFir)")
case _ => (q"Class.forName($className)", readFir)
}
List(q"""
val paramClass = $classTree
val method = Class.forName($declaredIn).getDeclaredMethod($methodName, paramClass)
method.setAccessible(true)
method.invoke($instance, $readTree)
""")
} else {
val setter = newTermName("set" + name)
List(q"$instance.$setter($readFir)")
}
} else if (fir.accessor.isEmpty) List(q"""
try {
val javaField = $instance.getClass.getDeclaredField(${fir.name})
javaField.setAccessible(true)
javaField.set($instance, $readFir)
} catch {
case e: java.lang.NoSuchFieldException => /* do nothing */
}
""")
else reflectively(instance, fir)(fm => q"$fm.set($readFir)".asInstanceOf[Tree])
})
if (shouldBotherAboutSharing(tpe)) {
q"""
val oid = scala.pickling.internal.`package`.preregisterUnpicklee()
val $instance = $instantiationLogic
scala.pickling.internal.`package`.registerUnpicklee($instance, oid)
..$initPendingFields
$instance
"""
} else {
q"""
val $instance = $instantiationLogic
..$initPendingFields
$instance
"""
} }
}
q"$initializationLogic"
}
}
def unpickleLogic = tpe match {
case NullTpe => q"null"
case NothingTpe => c.abort(c.enclosingPosition, "cannot unpickle Nothing") // TODO: report the deserialization path that brought us here
case _ if tpe.isEffectivelyPrimitive || sym == StringClass => q"$unpicklePrimitive"
case _ => q"$unpickleObject"
}
if (tpe.typeSymbol.isClass) {
// if class is abstract return instance of `PicklerUnpicklerNotFound`.
// this triggers the generation of a dispatch based on the runtime class of the picklee.
val classSym = tpe.typeSymbol.asClass
if (classSym.isAbstractClass) return q"new scala.pickling.PicklerUnpicklerNotFound[$tpe]"
}
val unpicklerName = c.fresh(syntheticUnpicklerName(tpe).toTermName)
q"""
implicit object $unpicklerName extends scala.pickling.Unpickler[$tpe] with scala.pickling.Generated {
import scala.language.existentials
import scala.pickling._
import scala.pickling.ir._
import scala.pickling.internal._
def unpickle(tag: => scala.pickling.FastTypeTag[_], reader: scala.pickling.PReader): Any = $unpickleLogic
}
$unpicklerName
"""
}
}
// purpose of this macro: implementation of PickleOps.pickle and pickleInto. i.e., this exists so as to:
// 1) perform dispatch based on the type of the argument
// 2) insert a call in the generated code to the genPickler macro (described above)
trait PickleMacros extends Macro {
import c.universe._
import definitions._
def pickleTo[T: c.WeakTypeTag](output: c.Tree)(format: c.Tree): c.Tree = {
val tpe = weakTypeOf[T]
val q"${_}($pickleeArg)" = c.prefix.tree
val endPickle = if (shouldBotherAboutCleaning(tpe)) q"clearPicklees()" else q"";
q"""
import scala.pickling._
import scala.pickling.internal._
val picklee: $tpe = $pickleeArg
val builder = $format.createBuilder($output)
picklee.pickleInto(builder)
$endPickle
"""
}
def pickle[T: c.WeakTypeTag](format: c.Tree): c.Tree = {
val tpe = weakTypeOf[T]
val q"${_}($pickleeArg)" = c.prefix.tree
val endPickle = if (shouldBotherAboutCleaning(tpe)) q"clearPicklees()" else q"";
q"""
import scala.pickling._
import scala.pickling.internal._
val picklee: $tpe = $pickleeArg
val builder = $format.createBuilder()
picklee.pickleInto(builder)
$endPickle
builder.result()
"""
}
def createPickler(tpe: c.Type, builder: c.Tree): c.Tree = q"""
$builder.hintTag(implicitly[scala.pickling.FastTypeTag[$tpe]])
implicitly[scala.pickling.SPickler[$tpe]]
"""
def createRuntimePickler(builder: c.Tree): c.Tree = q"""
val classLoader = this.getClass.getClassLoader
val tag = scala.pickling.FastTypeTag.mkRaw(clazz, scala.reflect.runtime.universe.runtimeMirror(classLoader))
$builder.hintTag(tag)
scala.pickling.SPickler.genPickler(classLoader, clazz, tag)
"""
def isCaseClass(tpe: c.Type): Boolean = {
val sym = tpe.typeSymbol
sym.isClass && sym.asClass.isCaseClass
}
def isClosed(tpe: c.Type): Boolean = {
val sym = tpe.typeSymbol
sym.isEffectivelyFinal || isCaseClass(tpe) || {
sym.isClass && {
val classSym = sym.asClass
classSym.isSealed && classSym.knownDirectSubclasses.forall(cl => isClosed(cl.asType.toType))
}
}
}
def genDispatchLogic(tpe: c.Type, builder: c.Tree): c.Tree = {
val sym = tpe.typeSymbol
def nonFinalDispatch = {
val compileTimeDispatch = compileTimeDispatchees(tpe) filter (_ != NullTpe) map (subtpe =>
CaseDef(Bind(newTermName("clazz"), Ident(nme.WILDCARD)), q"clazz == classOf[$subtpe]", createPickler(subtpe, builder))
)
//TODO OPTIMIZE: do getClass.getClassLoader only once
val runtimeDispatch = CaseDef(Ident(nme.WILDCARD), EmptyTree, createRuntimePickler(builder))
// TODO: do we still want to use something like HasPicklerDispatch?
q"""
val customPickler = implicitly[scala.pickling.SPickler[$tpe]]
if (customPickler.isInstanceOf[scala.pickling.PicklerUnpicklerNotFound[_]] || customPickler.isInstanceOf[scala.pickling.Generated]) {
val clazz = if (picklee != null) picklee.getClass else null
${Match(q"clazz", compileTimeDispatch :+ runtimeDispatch)}
} else {
// without Generated we would arrive here in two cases:
// 1. we have found a custom pickler that can handle the abstract type tpe
// 2. we have generated a pickler for a concrete superclass!
// in the 2nd case we still have to do the dispatch!
$builder.hintTag(implicitly[scala.pickling.FastTypeTag[$tpe]])
customPickler
}
"""
}
def refinedDispatch(parentTpe: Type) = {
val inferred = c.inferImplicitValue(appliedType(typeOf[SPickler[_]].typeConstructor, List(tpe)))
if (inferred == EmptyTree) {
c.abort(c.enclosingPosition, s"could not find implicit pickler for refined type: $tpe")
} else {
q"""
$builder.hintTag(implicitly[scala.pickling.FastTypeTag[$parentTpe]])
$inferred
"""
}
}
// NOTE: this has zero effect on performance...
// def listDispatch = {
// val List(nullTpe, consTpe, nilTpe) = compileTimeDispatchees(tpe)
// q"""
// import scala.language.existentials
// if (picklee eq Nil) ${createPickler(nilTpe, builder)}
// else if (picklee eq null) ${createPickler(nullTpe, builder)}
// else ${createPickler(consTpe, builder)}
// """
// }
// if (sym == ListClass) listDispatch else
if (c.inferImplicitValue(typeOf[IsStaticOnly]) != EmptyTree) {
if (!isClosed(tpe))
c.abort(c.enclosingPosition, "cannot generate fully static pickler")
}
if (sym.asType.isAbstractType || sym.isEffectivelyFinal) createPickler(tpe, builder)
else tpe.normalize match {
case RefinedType(parents, _) => refinedDispatch(parents.head)
case _ => nonFinalDispatch
}
}
/** Used by the main `pickle` macro. Its purpose is to pickle the object that it's called on *into* the
* the `builder` which is passed to it as an argument.
*/
def pickleInto[T: c.WeakTypeTag](builder: c.Tree): c.Tree = {
val q"${_}($pickleeArg)" = c.prefix.tree
pickleWithTagInto(pickleeArg, builder)
}
def pickleWithTagInto[T: c.WeakTypeTag](picklee: c.Tree, builder: c.Tree): c.Tree = {
val tpe = weakTypeOf[T].widen // to make module classes work
val sym = tpe.typeSymbol
val picklingLogic = if (sym.isClass && sym.asClass.isPrimitive) q"""
val pickler = ${createPickler(tpe, builder)}
pickler.pickle(picklee, $builder)
""" else q"""
if (picklee != null) {
val pickler = ${genDispatchLogic(tpe, builder)}
pickler.asInstanceOf[scala.pickling.SPickler[$tpe]].pickle(picklee, $builder)
} else {
$builder.hintTag(scala.pickling.FastTypeTag.Null)
scala.pickling.SPickler.nullPicklerUnpickler.pickle(null, $builder)
}
"""
q"""
import scala.language.existentials
import scala.pickling._
import scala.pickling.internal._
val picklee: $tpe = $picklee
scala.pickling.internal.GRL.lock()
$picklingLogic
scala.pickling.internal.GRL.unlock()
"""
}
}
// purpose of this macro: implementation of unpickle method of class UnpickleOps, which:
// 1) dispatches to the correct unpickler based on the type of the input;
// 2) inserts a call in the generated code to the genUnpickler macro (described above)
trait UnpickleMacros extends Macro {
// TODO: currently this works with an assumption that sharing settings for unpickling are the same as for pickling
// of course this might not be the case, so we should be able to read settings from the pickle itself
// this is not going to be particularly pretty. unlike the fix for the runtime interpreter, this fix will be a bit of a shotgun one
def pickleUnpickle[T: c.WeakTypeTag]: c.Tree = {
import c.universe._
val tpe = weakTypeOf[T]
val pickleArg = c.prefix.tree
val readerName = c.fresh(newTermName("reader"))
val readerUnpickleTree = readerUnpickleTopLevel(tpe, readerName)
q"""
import scala.language.existentials
import scala.pickling._
import scala.pickling.internal._
val format = implicitly[scala.pickling.PickleFormat]
val pickle = $pickleArg.thePickle.asInstanceOf[format.PickleType]
val $readerName = format.createReader(pickle, scala.pickling.internal.`package`.currentMirror)
$readerUnpickleTree
"""
}
def readerUnpickle(tpe: c.Type, readerName: c.TermName): c.Tree =
readerUnpickleHelper(tpe, readerName)(false)
def readerUnpickleTopLevel(tpe: c.Type, readerName: c.TermName): c.Tree =
readerUnpickleHelper(tpe, readerName)(true)
def readerUnpickleHelper(tpe: c.Type, readerName: c.TermName)(isTopLevel: Boolean = false): c.Tree = {
import c.universe._
import definitions._
val sym = tpe.typeSymbol
def createUnpickler(tpe: Type) = q"implicitly[scala.pickling.Unpickler[$tpe]]"
def finalDispatch = {
if (sym.isNotNullable) createUnpickler(tpe)
else q"""
val tag = scala.pickling.FastTypeTag(typeString)
if (tag.key == scala.pickling.FastTypeTag.Null.key) ${createUnpickler(NullTpe)}
else if (tag.key == scala.pickling.FastTypeTag.Ref.key) ${createUnpickler(RefTpe)}
else ${createUnpickler(tpe)}
"""
}
val customDispatch = CaseDef(Ident(nme.WILDCARD), EmptyTree, q"customUnpickler")
val refDispatch = CaseDef(Literal(Constant(FastTypeTag.Ref.key)), EmptyTree, createUnpickler(typeOf[refs.Ref]))
def nonFinalDispatch = {
val compileTimeDispatch = compileTimeDispatchees(tpe) map (subtpe => {
// TODO: do we still want to use something like HasPicklerDispatch (for unpicklers it would be routed throw tpe's companion)?
CaseDef(Literal(Constant(subtpe.key)), EmptyTree, createUnpickler(subtpe))
})
val runtimeDispatch = CaseDef(Ident(nme.WILDCARD), EmptyTree, q"""
val tag = scala.pickling.FastTypeTag(typeString)
scala.pickling.Unpickler.genUnpickler($readerName.mirror, tag)
""")
q"""
val customUnpickler = implicitly[scala.pickling.Unpickler[$tpe]]
if (customUnpickler.isInstanceOf[scala.pickling.PicklerUnpicklerNotFound[_]] || customUnpickler.isInstanceOf[scala.pickling.Generated]) {
${Match(q"typeString", compileTimeDispatch :+ refDispatch :+ runtimeDispatch)}
} else {
${Match(q"typeString", List(refDispatch) :+ customDispatch)}
}
"""
}
def abstractTypeDispatch =
q"""
val customUnpickler = implicitly[scala.pickling.Unpickler[$tpe]]
${Match(q"typeString", List(refDispatch) :+ customDispatch)}
"""
val staticHint = if (sym.isEffectivelyFinal && !isTopLevel) (q"$readerName.hintStaticallyElidedType()": Tree) else q"";
val dispatchLogic =
if (sym.asType.isAbstractType) abstractTypeDispatch
else if (sym.isEffectivelyFinal) finalDispatch
else nonFinalDispatch
val unpickleeCleanup = if (isTopLevel && shouldBotherAboutCleaning(tpe)) q"clearUnpicklees()" else q""
q"""
scala.pickling.internal.GRL.lock()
$readerName.hintTag(implicitly[scala.pickling.FastTypeTag[$tpe]])
$staticHint
val typeString = $readerName.beginEntryNoTag()
val unpickler = $dispatchLogic
val result = unpickler.unpickle({ scala.pickling.FastTypeTag(typeString) }, $readerName)
$readerName.endEntry()
$unpickleeCleanup
scala.pickling.internal.GRL.unlock()
result.asInstanceOf[$tpe]
"""
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy