package dotty.tools.dotc
package transform
import core._
import TreeTransforms._
import dotty.tools.dotc.ast.tpd._
import dotty.tools.dotc.core.Contexts.Context
import dotty.tools.dotc.core.StdNames._
import Phases._
import ast._
import Trees._
import Flags._
import SymUtils._
import Symbols._
import SymDenotations._
import Types._
import Decorators._
import DenotTransformers._
import util.Positions._
import Constants.Constant
import collection.mutable
/** This transform
* - moves initializers from body to constructor.
* - makes all supercalls explicit
* - also moves private fields that are accessed only from constructor
* into the constructor if possible.
*/
class Constructors extends MiniPhaseTransform with IdentityDenotTransformer { thisTransform =>
import tpd._
override def phaseName: String = "constructors"
override def runsAfter: Set[Class[_ <: Phase]] = Set(classOf[Memoize])
// Collect all private parameter accessors and value definitions that need
// to be retained. There are several reasons why a parameter accessor or
// definition might need to be retained:
// 1. It is accessed after the constructor has finished
// 2. It is accessed before it is defined
// 3. It is accessed on an object other than `this`
// 4. It is a mutable parameter accessor
// 5. It is has a wildcard initializer `_`
private val retainedPrivateVals = mutable.Set[Symbol]()
private val seenPrivateVals = mutable.Set[Symbol]()
private def markUsedPrivateSymbols(tree: RefTree)(implicit ctx: Context): Unit = {
val sym = tree.symbol
def retain() =
retainedPrivateVals.add(sym)
if (sym.exists && sym.owner.isClass && mightBeDropped(sym)) {
val owner = sym.owner.asClass
tree match {
case Ident(_) | Select(This(_), _) =>
def inConstructor = {
val method = ctx.owner.enclosingMethod
method.isPrimaryConstructor && ctx.owner.enclosingClass == owner
}
if (inConstructor && (sym.is(ParamAccessor) || seenPrivateVals.contains(sym))) {
// used inside constructor, accessed on this,
// could use constructor argument instead, no need to retain field
}
else retain()
case _ => retain()
}
}
}
override def transformIdent(tree: tpd.Ident)(implicit ctx: Context, info: TransformerInfo): tpd.Tree = {
markUsedPrivateSymbols(tree)
tree
}
override def transformSelect(tree: tpd.Select)(implicit ctx: Context, info: TransformerInfo): tpd.Tree = {
markUsedPrivateSymbols(tree)
tree
}
override def transformValDef(tree: tpd.ValDef)(implicit ctx: Context, info: TransformerInfo): tpd.Tree = {
if (mightBeDropped(tree.symbol))
(if (isWildcardStarArg(tree.rhs)) retainedPrivateVals else seenPrivateVals) += tree.symbol
tree
}
/** All initializers for non-lazy fields should be moved into constructor.
* All non-abstract methods should be implemented (this is assured for constructors
* in this phase and for other methods in memoize).
*/
override def checkPostCondition(tree: tpd.Tree)(implicit ctx: Context): Unit = {
tree match {
case tree: ValDef if tree.symbol.exists && tree.symbol.owner.isClass && !tree.symbol.is(Lazy) && !tree.symbol.hasAnnotation(defn.ScalaStaticAnnot) =>
assert(tree.rhs.isEmpty, i"$tree: initializer should be moved to constructors")
case tree: DefDef if !tree.symbol.is(LazyOrDeferred) =>
assert(!tree.rhs.isEmpty, i"unimplemented: $tree")
case _ =>
}
}
/** @return true if after ExplicitOuter, all references from this tree go via an
* outer link, so no parameter accessors need to be rewired to parameters
*/
private def noDirectRefsFrom(tree: Tree)(implicit ctx: Context) =
tree.isDef && tree.symbol.isClass && !tree.symbol.is(InSuperCall)
/** Class members that can be eliminated if referenced only from their own
* constructor.
*/
private def mightBeDropped(sym: Symbol)(implicit ctx: Context) =
sym.is(Private, butNot = MethodOrLazy) && !sym.is(MutableParamAccessor)
private final val MutableParamAccessor = allOf(Mutable, ParamAccessor)
override def transformTemplate(tree: Template)(implicit ctx: Context, info: TransformerInfo): Tree = {
val cls = ctx.owner.asClass
val constr @ DefDef(nme.CONSTRUCTOR, Nil, vparams :: Nil, _, EmptyTree) = tree.constr
// Produce aligned accessors and constructor parameters. We have to adjust
// for any outer parameters, which are last in the sequence of original
// parameter accessors but come first in the constructor parameter list.
val accessors = cls.paramAccessors.filterNot(_.isSetter)
val vparamsWithOuterLast = vparams match {
case vparam :: rest if vparam.name == nme.OUTER => rest ::: vparam :: Nil
case _ => vparams
}
val paramSyms = vparamsWithOuterLast map (_.symbol)
// Adjustments performed when moving code into the constructor:
// (1) Replace references to param accessors by constructor parameters
// except possibly references to mutable variables, if `excluded = Mutable`.
// (Mutable parameters should be replaced only during the super call)
// (2) If the parameter accessor reference was to an alias getter,
// drop the () when replacing by the parameter.
object intoConstr extends TreeMap {
override def transform(tree: Tree)(implicit ctx: Context): Tree = tree match {
case Ident(_) | Select(This(_), _) =>
var sym = tree.symbol
if (sym is (ParamAccessor, butNot = Mutable)) sym = sym.subst(accessors, paramSyms)
if (sym.owner.isConstructor) ref(sym).withPos(tree.pos) else tree
case Apply(fn, Nil) =>
val fn1 = transform(fn)
if ((fn1 ne fn) && fn1.symbol.is(Param) && fn1.symbol.owner.isPrimaryConstructor)
fn1 // in this case, fn1.symbol was an alias for a parameter in a superclass
else cpy.Apply(tree)(fn1, Nil)
case _ =>
if (noDirectRefsFrom(tree)) tree else super.transform(tree)
}
def apply(tree: Tree, prevOwner: Symbol)(implicit ctx: Context): Tree = {
transform(tree).changeOwnerAfter(prevOwner, constr.symbol, thisTransform)
}
}
def isRetained(acc: Symbol) = {
!mightBeDropped(acc) || retainedPrivateVals(acc)
}
val constrStats, clsStats = new mutable.ListBuffer[Tree]
/** Map outer getters $outer and outer accessors $A$B$$$outer to the given outer parameter. */
def mapOuter(outerParam: Symbol) = new TreeMap {
override def transform(tree: Tree)(implicit ctx: Context) = tree match {
case Apply(fn, Nil)
if (fn.symbol.is(OuterAccessor)
|| fn.symbol.isGetter && fn.symbol.name == nme.OUTER
) &&
fn.symbol.info.resultType.classSymbol == outerParam.info.classSymbol =>
ref(outerParam)
case _ =>
super.transform(tree)
}
}
val dropped = mutable.Set[Symbol]()
// Split class body into statements that go into constructor and
// definitions that are kept as members of the class.
def splitStats(stats: List[Tree]): Unit = stats match {
case stat :: stats1 =>
stat match {
case stat @ ValDef(name, tpt, _) if !stat.symbol.is(Lazy) && !stat.symbol.hasAnnotation(defn.ScalaStaticAnnot) =>
val sym = stat.symbol
if (isRetained(sym)) {
if (!stat.rhs.isEmpty && !isWildcardArg(stat.rhs))
constrStats += Assign(ref(sym), intoConstr(stat.rhs, sym)).withPos(stat.pos)
clsStats += cpy.ValDef(stat)(rhs = EmptyTree)
}
else if (!stat.rhs.isEmpty) {
dropped += sym
sym.copySymDenotation(
initFlags = sym.flags &~ Private,
owner = constr.symbol).installAfter(thisTransform)
constrStats += intoConstr(stat, sym)
}
case DefDef(nme.CONSTRUCTOR, _, ((outerParam @ ValDef(nme.OUTER, _, _)) :: _) :: Nil, _, _) =>
clsStats += mapOuter(outerParam.symbol).transform(stat)
case _: DefTree =>
clsStats += stat
case _ =>
constrStats += intoConstr(stat, tree.symbol)
}
splitStats(stats1)
case Nil =>
(Nil, Nil)
}
splitStats(tree.body)
// The initializers for the retained accessors */
val copyParams = accessors flatMap { acc =>
if (!isRetained(acc)) {
dropped += acc
Nil
} else {
val target = if (acc.is(Method)) acc.field else acc
if (!target.exists) Nil // this case arises when the parameter accessor is an alias
else {
val param = acc.subst(accessors, paramSyms)
val assigns = Assign(ref(target), ref(param)).withPos(tree.pos) :: Nil
if (acc.name != nme.OUTER) assigns
else {
// insert test: if ($outer eq null) throw new NullPointerException
val nullTest =
If(ref(param).select(defn.Object_eq).appliedTo(Literal(Constant(null))),
Throw(New(defn.NullPointerExceptionClass.typeRef, Nil)),
unitLiteral)
nullTest :: assigns
}
}
}
}
// Drop accessors that are not retained from class scope
if (dropped.nonEmpty) {
val clsInfo = cls.classInfo
cls.copy(
info = clsInfo.derivedClassInfo(
decls = clsInfo.decls.filteredScope(!dropped.contains(_))))
// TODO: this happens to work only because Constructors is the last phase in group
}
val (superCalls, followConstrStats) = constrStats.toList match {
case (sc: Apply) :: rest if sc.symbol.isConstructor => (sc :: Nil, rest)
case stats => (Nil, stats)
}
val mappedSuperCalls = vparams match {
case (outerParam @ ValDef(nme.OUTER, _, _)) :: _ =>
superCalls.map(mapOuter(outerParam.symbol).transform)
case _ => superCalls
}
cpy.Template(tree)(
constr = cpy.DefDef(constr)(
rhs = Block(copyParams ::: mappedSuperCalls ::: followConstrStats, unitLiteral)),
body = clsStats.toList)
}
}