All Downloads are FREE. Search and download functionalities are using the official Maven repository.

scala.pickling.generator.ScalaSymbols.scala Maven / Gradle / Ivy

There is a newer version: 0.11.0-M2
Show newest version
package scala.pickling
package generator


import HasCompat._
import scala.reflect.api.Universe
import scala.reflect.macros.Context


private[pickling] class UnclosedSubclassesException(errors: Seq[String]) extends Exception(errors.mkString("\n")) {
  override def fillInStackTrace(): Throwable = this
}

/** An implementation of a symbol lookup table based on scala reflection.
  *
  *
  * Note:  Scala reflection/compiler have known inaccuracies in some lookups.  For example, the compiler may
  *        remove all knowledge of private symbols from Java classes, because generally it doesn't care about
  *        Java's privates.
  */
private[pickling] class IrScalaSymbols[U <: Universe with Singleton, C <: Context](override val u: U, tools: Tools[C]) extends IrSymbolLoader[U](u) {
  import u._
  import compat._
  import definitions._
  import tools._

  import scala.pickling.internal.RichSymbol

  // HELPER METHODS FOR SUBCLASSES
  def isCaseClass(sym: TypeSymbol): Boolean =
    sym.isClass && sym.asClass.isCaseClass

  def isClosed(sym: tools.u.TypeSymbol): Boolean =
    whyNotClosed(sym).isEmpty

  def whyNotClosed(sym: tools.u.TypeSymbol): Seq[String] = {
    if (sym.isEffectivelyFinal)
      Nil
    else if (isCaseClass(sym.asInstanceOf[u.TypeSymbol]))
      Nil
    else if (sym.isClass) {
      val classSym = sym.asClass
      if (tools.treatAsSealed(classSym)) {
        tools.directSubclasses(classSym).flatMap(cl => whyNotClosed(cl.asType))
      } else {
        List(s"'${sym.fullName}' allows unknown subclasses (it is not sealed or final isCaseClass=${isCaseClass(sym.asInstanceOf[u.TypeSymbol])} isEffectivelyFinal=${sym.isEffectivelyFinal} isSealed=${classSym.isSealed} directSubclasses=${tools.directSubclasses(classSym)})")
      }
    } else {
      List(s"'${sym.fullName}' is not a class or trait")
    }
  }

  def newClass(tpe: Type): IrClass =
    if(tpe.typeSymbol.isClass) {
      val (quantified, rawTpe) = tpe match { case ExistentialType(quantified, rtpe) => (quantified, rtpe); case rtpe => (Nil, rtpe) }
      new ScalaIrClass(tpe, quantified, rawTpe)
    } else sys.error(s"Don't know how to handle $tpe, ${tpe.typeSymbol.owner}")



  // Implementation of IrClass symbol using a scala Type.
  private class ScalaIrClass(private[generator] val tpe: Type) extends IrClass {
    //System.err.println(s"New class: $tpe")
    def this(tpe: Type, quantified: List[Symbol], rawType: Type) = this(tpe)
    private[generator] val (quantified, rawType) = tpe match { case ExistentialType(quantified, rtpe) => (quantified, rtpe); case rtpe => (Nil, rtpe) }
    private def classSymbol = tpe.typeSymbol.asClass
    /** The class name represented by this symbol. */
    override def className: String = classSymbol.fullName
    override def isTrait: Boolean = classSymbol.isTrait
    override def isAbstract: Boolean = {
      classSymbol.isAbstractType
      // NOte: This doesn't exist on scala 2.10
      //classSymbol.isAbstract
      classSymbol.isAbstractClass
    }
    override def primaryConstructor: Option[IrConstructor] = {
      tpe.declaration(nme.CONSTRUCTOR) match {
        // NOTE: primary ctor is always the first in the list
        case overloaded: TermSymbol => Some(new ScalaIrConstructor(overloaded.alternatives.head.asMethod, this))
        case primaryCtor: MethodSymbol => Some(new ScalaIrConstructor(primaryCtor, this))
        case NoSymbol => None
      }
    }
    private val allMethods = {
      val constructorArgs = tpe.members.collect {
        case meth: MethodSymbol => meth
      }.toList.filter { x =>
        //System.err.println(s"$x - param: ${x.isParamAccessor}, var: ${x.isVar}, val: ${x.isVal}, owner: ${x.owner}, owner-constructor: ${x.owner.isConstructor}")
        (x.owner == tpe.typeSymbol) && (x.isParamAccessor)
      }.toList
      //System.err.println(s"$tpe has constructor args:\n - ${constructorArgs.mkString("\n - ")}")
      // NOTE - This will only collect memeber vals/vals.  It's possible some things come from the constructor.
      val declaredVars = (tpe.declarations).collect { case meth: MethodSymbol => meth }.toList
      // NOTE - There can be duplication between 'constructor args' and 'declared vars' we'd like to avoid.
        declaredVars
    }
    // The list of all "accessor" method symbols of the class.  We filter to only these to avoid ahving too many symbols
    //private val fields? = allMethods.collect { case meth: MethodSymbol if meth.isAccessor || meth.isParamAccessor => meth }

    // Here we only return "accessor" methods.
    override val methods: Seq[IrMethod] = {
      (allMethods map { mth =>
        new ScalaIrMethod(mth, this)
      })(collection.breakOut)
    }
    override def fields: Seq[IrField] = {
      // TODO - It's possible some terms come from the constructor.  We don't really know if they are available at runtime
      //        or not, so we may ignore them.
      //        It's actually a really bad scenario because you can't distinguish between something which
      //        is actually annotated as a val and something which is just a constructor argument.
      def isConstructorArg(x: TermSymbol): Boolean = {
        // Note available in scala 2.10
        // x.owner.isConstructor
        x.owner.name == nme.CONSTRUCTOR
      }
      tpe.members.filter(_.isTerm).map(_.asTerm).filter(x => x.isVal || x.isVar).map(x => new ScalaIrField(x, this)).toList
    }
    override def companion: Option[IrClass] = {
      if(tpe.typeSymbol.isType) {
        val tmp = tpe.typeSymbol.asType.companionSymbol
        if(tmp.isType) {
          val cp = tmp.asType.toType
          if (cp != NoType) Some(new ScalaIrClass(cp, quantified, rawType))
          else None
        }
        else None
      } else None
    }

    override def isScala = !tpe.typeSymbol.isJava
    override def isScalaModule: Boolean = classSymbol.isModuleClass

    /** True if this class is a scala case class. */
    override def isCaseClass: Boolean = classSymbol.isCaseClass

    /** True if this class is 'final' (or cannot be extended). */
    override def isFinal: Boolean = classSymbol.isFinal
    override def tpe[U <: Universe with Singleton](u: U): u.Type = tpe.asInstanceOf[u.Type]



    /** The set of known subclasses for this type.  May be empty if we don't know of any.
      * Note:  This method will return a failure if the known subclasses aren't "closed"
      */
    override def closedSubclasses: scala.util.Try[Seq[IrClass]] = {
      val closedError = whyNotClosed(tpe.typeSymbol.asType.asInstanceOf[tools.u.TypeSymbol])
      closedError match {
        case Nil =>
          scala.util.Success({
            val dispatchees = tools.compileTimeDispatchees(tpe.asInstanceOf[tools.c.universe.Type], tools.u.rootMirror, false)
            dispatchees.map(t => new ScalaIrClass(t.asInstanceOf[u.Type], quantified, rawType))(collection.breakOut)
          })
        case errors =>
          scala.util.Failure(new UnclosedSubclassesException(errors))
      }
    }
    // TODO - use tpe.key implicit from helpers to get a consistent tag key here.
    override def toString = s"$tpe"

    /** Returs all the parent classes (traits, interfaces, etc,) for this type. */
    override def parentClasses: Seq[IrClass] = {
      // We always drop the first class, becasue it is ourself.
      classSymbol.baseClasses.drop(1) filter (_.isClass) map { x =>
        //new ScalaIrClass(fillParameters(x), quantified, rawType)
        new ScalaIrClass(tpe.baseType(x), quantified, rawType)
      }
    }

    /** Fill is the concrete types for a given symbol using the concrete types this class knows about. */
    final def fillParameters(baseSym: Symbol): Type = {
      //System.err.println(s"baseSym= ${baseSym.toString}")
      val baseSymTpe = baseSym.typeSignature.asSeenFrom(rawType, rawType.typeSymbol.asClass)
      //System.err.println(s"baseSymTpe: ${baseSymTpe.toString}")

      val rawSymTpe = baseSymTpe match { case NullaryMethodType(ntpe) => ntpe; case ntpe => ntpe }
      val result = existentialAbstraction(quantified, rawSymTpe)
      //System.err.println(s"result = ${result.toString}")
      result

    }
    /** This is part of a workaround for issues discovering transient annotations on fields. */
    private[IrScalaSymbols] val transientArgNames: Set[String] = {
      IrSymbol.allDeclaredMethodIncludingSubclasses(this).filter(x => x.isParamAccessor || x.isVar || x.isVal).filter(_.isMarkedTransient).map(_.methodName).toSet
    }

  }

  private class ScalaIrField(field: TermSymbol, override val owner: ScalaIrClass) extends IrField{

    override def isMarkedTransient: Boolean = {
      val tr = scala.util.Try {
        ((field.accessed != NoSymbol) && field.accessed.annotations.exists(_.tpe =:= typeOf[scala.transient])) ||
        ((field.getter != NoSymbol) && field.getter.annotations.exists(_.tpe =:= typeOf[scala.transient])) ||
          (field.annotations.exists(_.tpe =:= typeOf[scala.transient]))
      }
      // Here we wrokaround a scala symbol issue where the field is never annotated with transient.
      val isSameNameAsTransientVar = owner.transientArgNames(fieldName)
      isSameNameAsTransientVar || tr.getOrElse(false)
    }

    private def removeTrailingSpace(orig: String): String =
      orig match {
        // TODO - Why do we need this random fix, is this a bug?
        case x if x endsWith " " =>
          //System.err.println(s"Caugh funny symbol: $field, name: ${field.name}, fullName: ${field.fullName}")
          x.dropRight(1).toString
        case x => x
      }
    override def fieldName: String = removeTrailingSpace(field.name.toString)
    override def tpe[U <: Universe with Singleton](u: U): u.Type = field.typeSignature.asSeenFrom(owner.tpe, owner.tpe.typeSymbol).asInstanceOf[u.Type]
    override def isPublic: Boolean = field.isPublic
    override def isStatic: Boolean = field.isStatic
    override def isFinal: Boolean = field.isFinal
    override def isScala: Boolean = true // We don't generate fields for java types
    override def isPrivate: Boolean = field.isPrivate
    override def isParameter: Boolean = field.isParameter
    // TODO - isPrivateThis

    // TODO - We want to make sure this name matches what we'll see in reflection.
    override def toString = s"field ${fieldName}: ${field.typeSignature}"

    // TODO - some kind of check to see if the field actually exists in runtime.  Scala sometimes erases
    //        fields completely, and having a field is actually more of a runtime concern for scala.
    //        Unforutnately, this TODO may be impossible.
    override def javaReflectionName: String =
      removeTrailingSpace(field.name.toString)
  }

  private class ScalaIrMethod(mthd: MethodSymbol, override val owner: ScalaIrClass) extends IrMethod {
    import owner.fillParameters
    override def parameterNames: List[List[String]] =
      mthd.paramss.map(_.map(_.name.toString))

    override def parameterTypes[U <: Universe with Singleton](u: U): List[List[u.Type]] = {
      mthd.paramss.map(_.map(x => fillParameters(x).asSeenFrom(owner.tpe, owner.tpe.typeSymbol)).map(_.asInstanceOf[u.Type]))
    }

    override def isMarkedTransient: Boolean = {
      // TODO - is this correct?
      val tr = scala.util.Try {
        ((mthd.accessed != NoSymbol) && mthd.accessed.annotations.exists(_.tpe =:= typeOf[scala.transient])) ||
          ((mthd.getter != NoSymbol) && mthd.getter.annotations.exists(_.tpe =:= typeOf[scala.transient])) ||
          (mthd.annotations.exists(_.tpe =:= typeOf[scala.transient]))
      }
      tr.getOrElse(false)
    }

    override def methodName: String = {
      mthd.name.toString match {
        // TODO - Why do we need this random fix, is this a bug?
        case x if x endsWith " " => x.dropRight(1).toString
        case x => x
      }
    }
    override def javaReflectionName: String = {
      val isPrivateThis = {
        // Note: Scala 2.10 does not support this
        //mthd.isPrivateThis
        false
      }
      if(mthd.isParamAccessor && (mthd.isPrivate || isPrivateThis)) {
        // Here we check to see if we need to encode the funky name that scala gives private fields to avoid conflicts
        // with fields in the parent class.
        def makeEncodedJvmName(names: List[String], buf: StringBuilder, isStart: Boolean = false): String =
           names match {
             case Nil => buf.toString
             case next :: rest if isStart =>
               buf.append(next)
               makeEncodedJvmName(rest, buf, isStart = false)
             case next :: Nil =>
               buf.append("$$").append(next)
               buf.toString
             case next :: rest =>
               buf.append("$").append(next)
               makeEncodedJvmName(rest, buf, isStart = false)
           }
        val split = mthd.fullName.split('.').toList
        val result = makeEncodedJvmName(split, new StringBuilder, true)
        result
      } else mthd match {
          // Note: Not available in Scala 2.10.x
        //case TermName(n) => n
        case x: TermName => x.toString
        case _ => mthd.name.encodedName.toString
      }
    }
    // TODO - Figure out if the method is JVM public or not (different than scala public)
    override def isPublic: Boolean = mthd.isPublic
    override def isStatic: Boolean = mthd.isStatic
    override def isFinal: Boolean = mthd.isFinal
    override def isPrivate: Boolean = mthd.isPrivate
    override def isScala: Boolean = !mthd.isJava
    override def toString = s"def ${methodName}: ${mthd.typeSignature}"
    override def isParamAccessor: Boolean = mthd.isParamAccessor
    override def isVal: Boolean = mthd.isVal
    override def isVar: Boolean =
      (mthd.getter != NoSymbol) && (mthd.setter != NoSymbol) &&
        (mthd.setter != mthd) // THis is  hack so the setter doesn't show up in our list of vars.
    override def returnType[U <: Universe with Singleton](u: Universe): u.Type =
       mthd.returnType.asSeenFrom(owner.tpe, owner.tpe.typeSymbol).asInstanceOf[u.Type]
    override def setter: Option[IrMethod] = {
      mthd.setter match {
        case NoSymbol => None
        case x => Some(new ScalaIrMethod(x.asMethod, owner))
      }
    }
  }

  private class ScalaIrConstructor(mthd: MethodSymbol, owner: ScalaIrClass) extends ScalaIrMethod(mthd, owner) with IrConstructor {

    override def returnType[U <: Universe with Singleton](u: Universe): u.Type = owner.tpe[u.type](u)
    override def toString = s"CONSTRUCTOR ${owner} (${parameterNames.mkString(",")}}): ${mthd.typeSignature}"
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy