aboutsummaryrefslogblamecommitdiff
path: root/compiler/src/dotty/tools/dotc/transform/Constructors.scala
blob: db850e944a2567d1c00711ee438911038a1f69a3 (plain) (tree)
1
2
3
4
5
6
7
8

                        
 
             



                                             









                          
                       
                         
                         
 


                                                   

                                                                        
   
                                                                                              
              
 
                                                 
                                                                        
 
 







                                                                            

                                                         



                                                                                    
                  
                                  
 
                                                                 



                                               



                                                                              


                                                                                            
             

                            













                                                                                                            





                                                                                                            



                                                                                      

                                                                                  
                                                                                                                                                           


                                                                                       



               





                                                                                          

                                                                            
     
                                                                  
                                                                           
 
                                                                        


                                                                                                        
 
                                                                                         


                                                                              




                                                                               
     
                                                       
 






                                                                                     


                                                                                    
                                                                                             









                                                                                           
                                                                               
                                                                                 


       
                                   
                                                      
     


                                                            


                                                                                                  




                                                                                    





                               

                                       

                                                                    
                                                           
                            
                    
                                                                                                                           

                                  
                                                                
                                                                                            

                                                           
                                         
                            


                                                                  
                                                  
             

                                                                                                        
                            
                            
                   
                                                        
         
                          


                  
                         
 
                                                     
                                               



                             







                                                                                           
                          







                                                                                      

                                                            
                           
                                 

                                        
                                                                     

                                                                                        

     




                                                                              





                                                             

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