aboutsummaryrefslogblamecommitdiff
path: root/src/dotty/tools/dotc/transform/ExplicitOuter.scala
blob: 0a1c94dd1ad936399dab09a9ae1ca9e759887710 (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 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 and also tries to minimize the number
 *  of objects referred to by outer accessors. This helps prevent space
 *  leaks.
 *
 *  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.
 */
class ExplicitOuter extends MiniPhaseTransform with InfoTransformer { thisTransformer =>
  import ExplicitOuter._
  import ast.tpd._

  val Outer = new Attachment.Key[Tree]

  override def phaseName: String = "ExplicitOuter"

  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
      newExplicitOuter(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
    val outerAccIfOwn = if (owner == cls) OuterAccessor else EmptyFlags
    newOuterSym(owner, cls, outerAccName(cls),
        Final | Method | Stable | outerAccIfOwn | 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 newExplicitOuter(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))
      newExplicitOuter(cls).foreach(_.enteredAfter(thisTransformer))
    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

  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. */
  private 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 access of class `cls` */
  private 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

  /** Template `impl` of class `cls` references an outer this which goes to
   *  a class that is not a static owner.
   */
  private 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` */
  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) => TypeTree(outerPrefix(tpt.tpe)).withPos(tpt.pos)
          case This(_) => ref(outerParamAccessor(cls))
          case TypeApply(Select(r, nme.asInstanceOf_), args) => outerArg(r) // cast was inserted, skip
        }
        if (hasOuter(cls))
          methPart(fun) match {
            case Select(receiver, _) => outerArg(receiver) :: 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")
      case ex: AssertionError =>
        throw new ClassCastException(i"no path exists from ${ctx.owner.enclosingClass} to $toCls\n because ${ex.getMessage}")
    }
  }
}