
scala.pickling.ir.IRs.scala Maven / Gradle / Ivy
The newest version!
package scala.pickling
package ir
import scala.reflect.api.Universe
import java.lang.reflect.Modifier
import HasCompat._
class IRs[U <: Universe with Singleton](val uni: U) {
import uni._
import compat._
import definitions._
// step 1: decide which strategy to use: primary ctor or allocateInstance (pure analysis of the ctor).
// - obtain primary ctor (see code)
// - go through its params and see if there's a param without a getter.
// step 2: based on the strategy, we collect the FieldIRs.
// we should have a method for each strategy.
sealed abstract class PickleIR
case class JavaProperty(name: String, declaredIn: String, isSetterPublic: Boolean)
/* If javaSetter.nonEmpty there is both a getter and a setter method.
*/
case class FieldIR(name: String, tpe: Type, param: Option[TermSymbol], accessor: Option[MethodSymbol], javaSetter: Option[JavaProperty] = None) {
def field = accessor.map(_.accessed.asTerm)
def getter = accessor.map(_.getter).flatMap(sym => if (sym != NoSymbol) Some(sym) else None)
def setter = accessor.map(_.setter).flatMap(sym => if (sym != NoSymbol) Some(sym) else None)
def isParam = param.map(_.owner.name == nme.CONSTRUCTOR).getOrElse(false)
def isPublic = param.nonEmpty || accessor.map(_.isPublic).getOrElse(false)
// this part is interesting to picklers
def hasGetter = param.isDefined || getter.isDefined // either case class val or getter
// this part is interesting to unpicklers
def hasSetter = setter.isDefined
def isErasedParam = isParam && accessor.isEmpty // TODO: this should somehow communicate with the constructors phase!
def isNonParam = !isParam
}
case class ClassIR(tpe: Type, parent: ClassIR, fields: List[FieldIR], javaGetInstance: Boolean = false) extends PickleIR {
var canCallCtor: Boolean = true
}
def nonParamFieldIRsOf(tpe: Type): Iterable[FieldIR] = {
val (quantified, rawTpe) = tpe match { case ExistentialType(quantified, rtpe) => (quantified, rtpe); case rtpe => (Nil, rtpe) }
val allAccessors = tpe.declarations.collect { case meth: MethodSymbol if meth.isAccessor || meth.isParamAccessor => meth }
val (filteredAccessors, _) = allAccessors.partition(notMarkedTransient)
val goodAccessorsNotParams = filteredAccessors.filterNot(_.isParamAccessor)
val goodAccessorsNotParamsVars = goodAccessorsNotParams.filter(acc => acc.isSetter && acc.accessed != NoSymbol) // 2.10 compat: !acc.isAbstract
goodAccessorsNotParamsVars.map { symSetter: MethodSymbol =>
val sym = symSetter.getter.asMethod
val rawSymTpe = sym.typeSignatureIn(rawTpe) match { case NullaryMethodType(ntpe) => ntpe; case ntpe => ntpe }
val symTpe = existentialAbstraction(quantified, rawSymTpe)
FieldIR(sym.name.toString, symTpe, None, Some(sym))
}
}
def nonAbstractVars(tpe: Type, quantified: List[Symbol], rawTpeOfOwner: Type, isJava: Boolean): List[FieldIR] = {
val javaFieldIRs = if (isJava) {
// candidates for setter/getter combo
val candidates = tpe.declarations.collect {
case sym: MethodSymbol if sym.name.toString.startsWith("get") => sym.name.toString.substring(3)
}
tpe.declarations.flatMap {
case sym: MethodSymbol if sym.name.toString.startsWith("set") =>
val shortName = sym.name.toString.substring(3)
if (candidates.find(_ == shortName).nonEmpty && shortName.length > 0) {
val rawSymTpe = sym.typeSignatureIn(rawTpeOfOwner) match {
case MethodType(List(param), _) => param.typeSignature
case _ => throw PicklingException("expected method type for method ${sym.name.toString}")
}
val symTpe = existentialAbstraction(quantified, rawSymTpe)
List(FieldIR(shortName, symTpe, None, None, Some(JavaProperty(shortName, tpe.toString, sym.isPublic))))
} else {
List()
}
case _ => List()
}
} else {
List()
}
(tpe.declarations.collect {
case sym: MethodSymbol if !sym.isParamAccessor && sym.isSetter && sym.accessed != NoSymbol =>
val rawSymTpe =
sym.getter.typeSignatureIn(rawTpeOfOwner) match { case NullaryMethodType(ntpe) => ntpe; case ntpe => ntpe }
val symTpe =
existentialAbstraction(quantified, rawSymTpe)
FieldIR(sym.getter.name.toString, symTpe, None, Some(sym.getter.asMethod))
}).toList ++ javaFieldIRs
}
def newClassIR(tpe: Type): ClassIR = {
// create new instance of ClassIR(tpe: Type, parent: ClassIR, fields: List[FieldIR])
// (a) ignore parent (TODO: remove)
// (b) keep track of canCallCtor (for unpickler generation)
// idea:
// param.nonEmpty iff field is param of primary ctor
// accessor.nonEmpty iff field has getter and possibly a setter
val primaryCtor = tpe.declaration(nme.CONSTRUCTOR) match {
case overloaded: TermSymbol => overloaded.alternatives.head.asMethod // NOTE: primary ctor is always the first in the list
case primaryCtor: MethodSymbol => primaryCtor
case NoSymbol => NoSymbol
}
// we need all accessors to filter out transient ctor params
// main diff: members instead of declarations
val allAccessors = tpe.declarations.collect { case meth: MethodSymbol if meth.isAccessor || meth.isParamAccessor => meth }
val (filteredAccessors, transientAccessors) = allAccessors.partition(notMarkedTransient)
val primaryCtorParamsOpt =
if (primaryCtor.isMethod) Some(primaryCtor.asMethod.paramss.flatten)
else None
val canCallCtor =
primaryCtor != NoSymbol &&
primaryCtorParamsOpt.nonEmpty &&
(primaryCtorParamsOpt.get.forall { preSym =>
// println(s"!!! tpe ${tpe.toString}, ctor param $preSym:")
val notTransient = !transientAccessors.exists(_.name == preSym.name)
// println(s"$notTransient")
if (notTransient) {
val symOpt = //tpe.declaration(preSym.name)
filteredAccessors.find(_.name == preSym.name)
symOpt match {
case None => false
case Some(sym) =>
val isVal = sym.asTerm.isVal
val getterExists = sym.asTerm.getter != NoSymbol
// println(s"$isVal (public: ${sym.asTerm.isPublic}, isParamAcc: ${sym.asTerm.isParamAccessor}), $getterExists (${sym.asTerm.getter}, public: ${sym.asTerm.getter.isPublic})")
(isVal && sym.asTerm.isPublic) || (getterExists && sym.asTerm.getter.isPublic)
}
} else false
// println(s"$notTransient, $isMethod, $getterExists, $getterIsMetod")
// notTransient && isMethod && getterExists && getterIsMetod
})
val (quantified, rawTpe) = tpe match { case ExistentialType(quantified, rtpe) => (quantified, rtpe); case rtpe => (Nil, rtpe) }
val baseClasses = tpe.typeSymbol.asClass.baseClasses
// println(s"base classes: ${baseClasses.mkString(",")}")
def fieldIRsUsingCtor(): List[FieldIR] = {
// collect:
// (a) all ctor params
val ctorFieldIRs = primaryCtorParamsOpt.get.map { s =>
val sym = s.asTerm // already tested
val baseSym = if (sym.isVal) sym else sym.getter.asMethod
val baseSymTpe = baseSym.typeSignature.asSeenFrom(rawTpe, rawTpe.typeSymbol.asClass)
// println(s"baseSymTpe: ${baseSymTpe.toString}")
val rawSymTpe = baseSymTpe match { case NullaryMethodType(ntpe) => ntpe; case ntpe => ntpe }
val symTpe = existentialAbstraction(quantified, rawSymTpe)
FieldIR(sym.name.toString, symTpe, if (sym.isVal) Some(sym) else None, if (sym.isVal) None else Some(sym.getter.asMethod))
}
// (b) non-abstract vars (also private ones)
val allNonAbstractVars = baseClasses.flatMap { baseClass =>
nonAbstractVars(tpe.baseType(baseClass), quantified, rawTpe, baseClass.isJava)
}
ctorFieldIRs ++ allNonAbstractVars
}
def fieldIRsUsingAllocateInstance(): List[FieldIR] = {
// collect:
// (a) all vals or vars (even if abstract!!)
val fieldIRs1 = baseClasses.flatMap { baseClass =>
val stpe = tpe.baseType(baseClass)
val allGetters = stpe.declarations.collect {
case sym: MethodSymbol if sym.isGetter && notMarkedTransient(sym) => sym
}
// println(s"allGetters of $baseClass: ${allGetters.mkString(",")}")
allGetters.map { getter =>
val rawSymTpe = getter.typeSignatureIn(rawTpe) match { case NullaryMethodType(ntpe) => ntpe; case ntpe => ntpe }
val symTpe = existentialAbstraction(quantified, rawSymTpe)
FieldIR(getter.name.toString, symTpe, None, Some(getter))
}
}
val fieldIRs2 = {
// also add ctor params that are not accessors (need Java reflection for those!)
val reflectionGetters = {
if (primaryCtor.isMethod) {
primaryCtor.asMethod.paramss.flatten.filter { s =>
val acc = allAccessors.find(_.name == s.name)
acc.isEmpty
}
}
else List()
}
reflectionGetters.map { sym =>
val rawSymTpe = sym.typeSignatureIn(rawTpe) match { case NullaryMethodType(ntpe) => ntpe; case ntpe => ntpe }
val symTpe = existentialAbstraction(quantified, rawSymTpe)
FieldIR(sym.name.toString, symTpe, None, None)
}
}
fieldIRs1 ++ fieldIRs2
}
val fieldIRs = if (canCallCtor) {
val fields = fieldIRsUsingCtor()
// println(s"fieldIRsUsingCtor of ${tpe.toString}: $fields")
fields
} else {
val fields = fieldIRsUsingAllocateInstance()
// println(s"fieldIRsUsingAllocateInstance of ${tpe.toString}: $fields")
fields
}
val useGetInstance = if (!(tpe =:= AnyRefTpe) && tpe.typeSymbol.isJava && fieldIRs.isEmpty) {
val methodOpt =
try Some(Class.forName(tpe.toString).getDeclaredMethod("getInstance"))
catch {
case _: NoSuchMethodException => None
case _: ClassNotFoundException => None
case _: LinkageError => None
case _: ExceptionInInitializerError => None
case _: SecurityException => None
}
methodOpt.nonEmpty && {
val mods = methodOpt.get.getModifiers
Modifier.isStatic(mods) && Modifier.isPublic(mods)
}
} else false
val cir = ClassIR(tpe, null, fieldIRs, useGetInstance)
cir.canCallCtor = canCallCtor
cir
}
private type Q = List[FieldIR]
private type C = ClassIR
// TODO: minimal versus verbose PickleFormat. i.e. someone might want all concrete inherited fields in their pickle
def notMarkedTransient(sym: TermSymbol): Boolean = {
val tr = scala.util.Try {
(sym.accessed == NoSymbol) || // if there is no backing field, then it cannot be marked transient
!sym.accessed.annotations.exists(_.tpe =:= typeOf[scala.transient])
}
tr.isFailure || tr.get
}
/** Creates FieldIRs for the given type, tp.
*/
private def fields(tp: Type): Q = {
val ctor = tp.declaration(nme.CONSTRUCTOR) match {
case overloaded: TermSymbol => overloaded.alternatives.head.asMethod // NOTE: primary ctor is always the first in the list
case primaryCtor: MethodSymbol => primaryCtor
case NoSymbol => NoSymbol
}
val allAccessors = tp.declarations.collect { case meth: MethodSymbol if meth.isAccessor || meth.isParamAccessor => meth }
val (filteredAccessors, transientAccessors) = allAccessors.partition(notMarkedTransient)
val ctorParams = if (ctor != NoSymbol) ctor.asMethod.paramss.flatten.flatMap { sym =>
if (transientAccessors.exists(acc => acc.name.toString == sym.name.toString)) List()
else List(sym.asTerm)
} else Nil
val (paramAccessors, otherAccessors) = allAccessors.partition(_.isParamAccessor)
def mkFieldIR(sym: TermSymbol, param: Option[TermSymbol], accessor: Option[MethodSymbol]) = {
val (quantified, rawTp) = tp match { case ExistentialType(quantified, tpe) => (quantified, tpe); case tpe => (Nil, tpe) }
val rawSymTp = accessor.getOrElse(sym).typeSignatureIn(rawTp) match { case NullaryMethodType(tpe) => tpe; case tpe => tpe }
val symTp = existentialAbstraction(quantified, rawSymTp)
FieldIR(sym.name.toString.trim, symTp, param, accessor)
}
val paramFields = ctorParams.map(sym => mkFieldIR(sym, Some(sym), paramAccessors.find(_.name == sym.name)))
val varGetters = otherAccessors.collect{ case meth if meth.isGetter && meth.accessed != NoSymbol && meth.accessed.asTerm.isVar => meth }
val varFields = varGetters.map(sym => mkFieldIR(sym, None, Some(sym)))
paramFields ++ varFields
}
private def composition(f1: (Q, Q) => Q, f2: (C, C) => C, f3: C => List[C]) =
(c: C) => f3(c).reverse.reduce[C](f2)
private val f1 = (q1: Q, q2: Q) => q1 ++ q2
private val f2 = (c1: C, c2: C) => ClassIR(c2.tpe, c1, c2.fields)
private val f3 = (c: C) =>
c.tpe.baseClasses
.map(superSym => c.tpe.baseType(superSym))
.map(tp => ClassIR(tp, null, fields(tp)))
private val compose =
composition(f1, f2, f3)
private val flatten: C => C = (c: C) =>
if (c.parent != null) ClassIR(c.tpe, c.parent, f1(c.fields, flatten(c.parent).fields))
else c
def flattenedClassIR(tpe: Type) = flatten(compose(ClassIR(tpe, null, Nil)))
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy