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 core.NameOps._
import ast.Trees._
import SymUtils._
import dotty.tools.dotc.core.Phases.Phase
import util.Attachment
import collection.mutable
/** This phase adds outer accessors to classes and traits that need them.
* Compared to Scala 2.x, it tries to minimize the set of classes
* that take outer accessors by scanning class implementations for
* outer references.
*
* The following things are delayed until erasure and are performed
* by class OuterOps:
*
* - add outer parameters to constructors
* - pass outer arguments in constructor calls
* - replace outer this by outer paths.
*
* needs to run after pattern matcher as it can add outer checks and force creation of $outer
*/
class ExplicitOuter extends MiniPhaseTransform with InfoTransformer { thisTransformer =>
import ExplicitOuter._
import ast.tpd._
val Outer = new Attachment.Key[Tree]
override def phaseName: String = "explicitOuter"
/** List of names of phases that should have finished their processing of all compilation units
* before this phase starts
*/
override def runsAfter: Set[Class[_ <: Phase]] = Set(classOf[PatternMatcher])
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
}
/** 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 an outer accessor,
* 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) &&
impl.existsSubTree(referencesOuter(cls, _)))
ensureOuterAccessors(cls)
if (hasOuter(cls)) {
val newDefs = new mutable.ListBuffer[Tree]
if (isTrait)
newDefs += DefDef(outerAccessor(cls).asTerm, EmptyTree)
else {
val outerParamAcc = outerParamAccessor(cls)
newDefs += ValDef(outerParamAcc, EmptyTree)
newDefs += DefDef(outerAccessor(cls).asTerm, 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 ExplicitOuter {
import ast.tpd._
private val LocalInstantiationSite = Module | Private
/** Ensure that class `cls` has outer accessors */
def ensureOuterAccessors(cls: ClassSymbol)(implicit ctx: Context): Unit = {
//todo: implementing #165 would simplify this logic
val prevPhase = ctx.phase.prev
assert(prevPhase.id <= ctx.explicitOuterPhase.id, "can add $outer symbols only before ExplicitOuter")
assert(prevPhase.isInstanceOf[DenotTransformer], "adding outerAccessors requires being DenotTransformer")
if (!hasOuter(cls)) {
newOuterAccessors(cls).foreach(_.enteredAfter(prevPhase.asInstanceOf[DenotTransformer]))
}
}
/** 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)
/** 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 param accessor for the outer field in class `cls` */
private def newOuterParamAccessor(cls: ClassSymbol)(implicit ctx: Context) =
newOuterSym(cls, cls, nme.OUTER, Private | ParamAccessor)
/** 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
val outerAccIfOwn = if (owner == cls) OuterAccessor else EmptyFlags
newOuterSym(owner, cls, outerAccName(cls),
Final | Method | Stable | outerAccIfOwn | deferredIfTrait)
}
private def outerAccName(cls: ClassSymbol)(implicit ctx: Context): TermName =
nme.OUTER.expandedName(cls)
/** 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
*/
private 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 */
private def hasLocalInstantiation(cls: ClassSymbol)(implicit ctx: Context): Boolean =
cls.owner.isTerm || cls.is(LocalInstantiationSite)
/** The outer parameter accessor of cass `cls` */
private def outerParamAccessor(cls: ClassSymbol)(implicit ctx: Context): TermSymbol =
cls.info.decl(nme.OUTER).symbol.asTerm
/** The outer accessor of class `cls`. To find it is a bit tricky. The
* class might have been moved with new owners between ExplicitOuter and Erasure,
* where the method is also called. For instance, it might have been part
* of a by-name argument, and therefore be moved under a closure method
* by ElimByName. In that case looking up the method again at Erasure with the
* fully qualified name `outerAccName` will fail, because the `outerAccName`'s
* result is phase dependent. In that case we use a backup strategy where we search all
* definitions in the class to find the one with the OuterAccessor flag.
*/
def outerAccessor(cls: ClassSymbol)(implicit ctx: Context): Symbol =
if (cls.isStatic) NoSymbol // fast return to avoid scanning package decls
else cls.info.member(outerAccName(cls)).suchThat(_ is OuterAccessor).symbol orElse
cls.info.decls.find(_ is OuterAccessor).getOrElse(NoSymbol)
/** Class has an outer accessor. Can be called only after phase ExplicitOuter. */
private def hasOuter(cls: ClassSymbol)(implicit ctx: Context): Boolean =
needsOuterIfReferenced(cls) && outerAccessor(cls).exists
/** Class constructor takes an outer argument. Can be called only after phase ExplicitOuter. */
private def hasOuterParam(cls: ClassSymbol)(implicit ctx: Context): Boolean =
!cls.is(Trait) && needsOuterIfReferenced(cls) && outerAccessor(cls).exists
/** Tree references a an outer class of `cls` which is not a static owner.
*/
def referencesOuter(cls: Symbol, tree: Tree)(implicit ctx: Context): Boolean = {
def isOuter(sym: Symbol) =
!sym.isStaticOwner && cls.isProperlyContainedIn(sym)
tree match {
case thisTree @ This(_) =>
isOuter(thisTree.symbol)
case id: Ident =>
id.tpe match {
case ref @ TermRef(NoPrefix, _) =>
ref.symbol.is(Method) && isOuter(id.symbol.owner.enclosingClass)
// methods will be placed in enclosing class scope by LambdaLift, so they will get
// an outer path then.
case _ => false
}
case nw: New =>
isOuter(nw.tpe.classSymbol.owner.enclosingClass)
case _ =>
false
}
}
/** The outer prefix implied by type `tpe` */
private 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)
}
def outer(implicit ctx: Context): OuterOps = new OuterOps(ctx)
/** The operations in this class
* - add outer parameters
* - pass outer arguments to these parameters
* - replace outer this references by outer paths.
* They are called from erasure. There are two constraints which
* suggest these operations should be done in erasure.
* - Replacing this references with outer paths loses aliasing information,
* so programs will not typecheck with unerased types unless a lot of type
* refinements are added. Therefore, outer paths should be computed no
* earlier than erasure.
* - outer parameters should not show up in signatures, so again
* they cannot be added before erasure.
* - outer arguments need access to outer parameters as well as to the
* original type prefixes of types in New expressions. These prefixes
* get erased during erasure. Therefore, outer argumenrts have to be passed
* no later than erasure.
*/
class OuterOps(val ictx: Context) extends AnyVal {
private implicit def ctx: Context = ictx
/** If `cls` has an outer parameter add one to the method type `tp`. */
def addParam(cls: ClassSymbol, tp: Type): Type =
if (hasOuterParam(cls)) {
val mt @ MethodType(pnames, ptypes) = tp
mt.derivedMethodType(
nme.OUTER :: pnames, cls.owner.enclosingClass.typeRef :: ptypes, mt.resultType)
} else tp
/** If function in an apply node is a constructor that needs to be passed an
* outer argument, the singleton list with the argument, otherwise Nil.
*/
def args(fun: Tree): List[Tree] = {
if (fun.symbol.isConstructor) {
val cls = fun.symbol.owner.asClass
def outerArg(receiver: Tree): Tree = receiver match {
case New(_) | Super(_, _) =>
singleton(outerPrefix(receiver.tpe))
case This(_) =>
ref(outerParamAccessor(cls)) // will be rewired to outer argument of secondary constructor in phase Constructors
case TypeApply(Select(r, nme.asInstanceOf_), args) =>
outerArg(r) // cast was inserted, skip
}
if (hasOuterParam(cls))
methPart(fun) match {
case Select(receiver, _) => outerArg(receiver).withPos(fun.pos) :: Nil
}
else Nil
} else Nil
}
/** The path of outer accessors that references `toCls.this` starting from
* the context owner's this node.
*/
def path(toCls: Symbol): Tree = try {
def loop(tree: Tree): Tree = {
val treeCls = tree.tpe.widen.classSymbol
ctx.log(i"outer to $toCls of $tree: ${tree.tpe}, looking for ${outerAccName(treeCls.asClass)}")
if (treeCls == toCls) tree
else loop(tree select outerAccessor(treeCls.asClass))
}
ctx.log(i"computing outerpath to $toCls from ${ctx.outersIterator.map(_.owner).toList}")
loop(This(ctx.owner.enclosingClass.asClass))
} catch {
case ex: ClassCastException =>
throw new ClassCastException(i"no path exists from ${ctx.owner.enclosingClass} to $toCls")
}
}
}