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)
}
}