aboutsummaryrefslogblamecommitdiff
path: root/src/dotty/tools/dotc/transform/ExplicitOuter.scala
blob: 179f8d7124e67e8e8e648d952b42f68668142c4a (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12











                               
                     

                  
                                         


                         

                                                                         

                                                                   






                                                                    

                                                                                               
   
                                                                                        
                        



                                      
                                                  
 



                                                                                                 





                                                                                       
                                                    




                                           

                                                                                                   
                                                                  










                                                                                                        

                                      
                                                    
                               
                        

                                                
                                                               
            
                                                   
                                                   
                                                                        





















                                                                                               
                      



                                                       

                                                                             
                                                        
                                  
                                                                                                         
                                                                                                             
                         
                                                                                              























                                                                                                                          


                                                                               
                                                                                            
                                                                                   









                                                                                       
                                                                                  





                                                                                                               
                                                                                       

                                                      
                                                   


                                                                                       








                                                                                          
                                                                      

                                                                                 
 
                                                                                   

                                                                          
 
                                                                            
     

                                                                                  
                                                          
                
                                










                                                                                              
               
            
     
   

                                               
                                                                               










                                                                       
 




































                                                                                         





                                                                                                                            


                               
                                                                                  


                
     
 








                                                                                                       
       




                                                                                                  
     
   
 
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 =
    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

  /** 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 (hasOuter(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(tpt) =>
            singleton(outerPrefix(tpt.tpe))
          case This(_) =>
            ref(outerParamAccessor(cls)) // will be rewried to outer argument of secondary constructor in phase Constructors
          case TypeApply(Select(r, nme.asInstanceOf_), args) =>
            outerArg(r) // cast was inserted, skip
        }
        if (hasOuter(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")
    }
  }
}