aboutsummaryrefslogblamecommitdiff
path: root/src/dotty/tools/dotc/transform/OuterAccessors.scala
blob: 5e6257e7f3dc5d1fd116dcc2f431c546f3f26563 (plain) (tree)



































































































































































                                                                                                                          
package dotty.tools.dotc
package transform

import TreeTransforms._
import core.DenotTransformers._
import core.Symbols._
import core.Contexts._
import core.Types._
import core.Flags._
import core.Decorators._
import core.StdNames.nme
import core.Names._
import ast.Trees._
import SymUtils._
import util.Attachment
import collection.mutable

/** This phase decorates News and parent constructors of non-static inner classes
 *  with an attachment indicating the outer reference as a tree. This is necessary because
 *  outer prefixes are erased, and explicit outer runs only after erasure.
 */
class OuterAccessors extends MiniPhaseTransform with InfoTransformer { thisTransformer =>
  import OuterAccessors._
  import ast.tpd._

  val Outer = new Attachment.Key[Tree]

  override def phaseName: String = "outerAccessors"

  override def treeTransformPhase = thisTransformer.next

  /** Add outer accessors if a class always needs an outer pointer */
  override def transformInfo(tp: Type, sym: Symbol)(implicit ctx: Context) = tp match {
    case tp @ ClassInfo(_, cls, _, decls, _) if needsOuterAlways(cls) =>
      val newDecls = decls.cloneScope
      newOuterAccessors(cls).foreach(newDecls.enter)
      tp.derivedClassInfo(decls = newDecls)
    case _ =>
      tp
  }

  /** A new outer accessor or param accessor */
  private def newOuterSym(owner: ClassSymbol, cls: ClassSymbol, name: TermName, flags: FlagSet)(implicit ctx: Context) = {
    ctx.newSymbol(owner, name, Synthetic | flags, cls.owner.enclosingClass.typeRef, coord = cls.coord)
  }

  /** A new outer accessor for class `cls` which is a member of `owner` */
  private def newOuterAccessor(owner: ClassSymbol, cls: ClassSymbol)(implicit ctx: Context) = {
    val deferredIfTrait = if (cls.is(Trait)) Deferred else EmptyFlags
    newOuterSym(owner, cls, cls.outerAccName, Final | Stable | deferredIfTrait)
  }

  /** A new param accessor for the outer field in class `cls` */
  private def newOuterParamAccessor(cls: ClassSymbol)(implicit ctx: Context) =
    newOuterSym(cls, cls, nme.OUTER, Private | ParamAccessor)

  /** The outer accessor and potentially outer param accessor needed for class `cls` */
  private def newOuterAccessors(cls: ClassSymbol)(implicit ctx: Context) =
    newOuterAccessor(cls, cls) :: (if (cls is Trait) Nil else newOuterParamAccessor(cls) :: Nil)

  /** First, add outer accessors if a class does not have them yet and it references an outer this.
   *  If the class has outer accessors, implement them.
   *  Furthermore, if a parent trait might have outer accessors (decided by needsOuterIfReferenced),
   *  provide an implementation for the outer accessor by computing the parent's
   *  outer from the parent type prefix. If the trait ends up not having an outer accessor
   *  after all, the implementation is redundant, but does not harm.
   *  The same logic is not done for non-trait parent classes because for them the outer
   *  pointer is passed in the super constructor, which will be implemented later in
   *  a separate phase which needs to run after erasure. However, we make sure here
   *  that the super class constructor is indeed a New, and not just a type.
   */
  override def transformTemplate(impl: Template)(implicit ctx: Context, info: TransformerInfo): Tree = {
    val cls = ctx.owner.asClass
    val isTrait = cls.is(Trait)
    if (needsOuterIfReferenced(cls) && !needsOuterAlways(cls) && referencesOuter(cls, impl))
      newOuterAccessors(cls).foreach(_.enteredAfter(thisTransformer))
    if (hasOuter(cls)) {
      val outerAcc = cls.info.member(cls.outerAccName).symbol.asTerm
      val newDefs = new mutable.ListBuffer[Tree]
      if (isTrait)
        newDefs += DefDef(outerAcc, EmptyTree)
      else {
        val outerParamAcc = cls.info.decl(nme.OUTER).symbol.asTerm
        newDefs += ValDef(outerParamAcc, EmptyTree)
        newDefs += DefDef(outerAcc, ref(outerParamAcc))
      }
      val parents1 =
        for (parent <- impl.parents) yield {
          val parentCls = parent.tpe.classSymbol.asClass
          if (parentCls.is(Trait)) {
            if (needsOuterIfReferenced(parentCls)) {
              val outerAccImpl = newOuterAccessor(cls, parentCls).enteredAfter(thisTransformer)
              newDefs += DefDef(outerAccImpl, singleton(outerPrefix(parent.tpe)))
            }
            parent
          }
          else parent match { // ensure class parent is a constructor
            case parent: TypeTree => New(parent.tpe, Nil).withPos(impl.pos)
            case _ => parent
          }
        }
      cpy.Template(impl)(parents = parents1, body = impl.body ++ newDefs)
    }
    else impl
  }
}

object OuterAccessors {
  import ast.tpd._

  private val LocalInstantiationSite = Module | Private

  /** Class needs an outer pointer, provided there is a reference to an outer this in it. */
  def needsOuterIfReferenced(cls: ClassSymbol)(implicit ctx: Context): Boolean = !(
    cls.isStatic ||
    cls.owner.enclosingClass.isStaticOwner ||
    cls.is(Interface)
  )

  /** Class unconditionally needs an outer pointer. This is the case if
   *  the class needs an outer pointer if referenced and one of the following holds:
   *  - we might not know at all instantiation sites whether outer is referenced or not
   *  - we need to potentially pass along outer to a parent class or trait
   */
  def needsOuterAlways(cls: ClassSymbol)(implicit ctx: Context): Boolean =
    needsOuterIfReferenced(cls) &&
    (!hasLocalInstantiation(cls) || // needs outer because we might not know whether outer is referenced or not
     cls.classInfo.parents.exists(parent => // needs outer to potentially pass along to parent
       needsOuterIfReferenced(parent.classSymbol.asClass)))

  /** Class is always instantiated in the compilation unit where it is defined */
  def hasLocalInstantiation(cls: ClassSymbol)(implicit ctx: Context): Boolean =
    cls.owner.isTerm || cls.is(LocalInstantiationSite)

  /** Class has outer accessor. Can be called only after phase OuterAccessors. */
  def hasOuter(cls: ClassSymbol)(implicit ctx: Context): Boolean =
    cls.info.decl(cls.outerAccName).exists

  /** Template `impl` of class `cls` references an outer this which goes to
   *  a class that is not a static owner.
   */
  def referencesOuter(cls: ClassSymbol, impl: Template)(implicit ctx: Context): Boolean =
    existsSubTreeOf(impl) {
      case thisTree @ This(_) =>
        val thisCls = thisTree.symbol
        thisCls != cls && !thisCls.isStaticOwner && cls.isContainedIn(thisCls)
      case _ =>
        false
    }

  /** The outer prefix implied by type `tpe` */
  def outerPrefix(tpe: Type)(implicit ctx: Context): Type = tpe match {
    case tpe: TypeRef =>
      tpe.symbol match {
        case cls: ClassSymbol =>
          if (tpe.prefix eq NoPrefix) cls.owner.enclosingClass.thisType
          else tpe.prefix
        case _ =>
          outerPrefix(tpe.underlying)
      }
    case tpe: TypeProxy =>
      outerPrefix(tpe.underlying)
  }
}