aboutsummaryrefslogtreecommitdiff
path: root/src/dotty/tools/dotc/transform/OuterAccessors.scala
blob: 2cad74cf30dcd7456085e84c9c2972ab2bb06139 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
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 ast.Trees._
import SymUtils._
import util.Attachment
import collection.mutable

/** This phase decorates News and parent constructors of non-static inner classes
 *  with an attachment indicating the outer reference as a tree. This is necessary because
 *  outer prefixes are erased, and explicit outer runs only after erasure.
 */
class OuterAccessors extends MiniPhaseTransform with InfoTransformer { thisTransformer =>
  import OuterAccessors._
  import ast.tpd._

  val Outer = new Attachment.Key[Tree]

  override def phaseName: String = "outerAccessors"

  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
  }

  /** 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
    newOuterSym(owner, cls, cls.outerAccName, Final | Stable | 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 newOuterAccessors(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))
      newOuterAccessors(cls).foreach(_.enteredAfter(thisTransformer))
    if (hasOuter(cls)) {
      val outerAcc = cls.info.member(cls.outerAccName).symbol.asTerm
      val newDefs = new mutable.ListBuffer[Tree]
      if (isTrait)
        newDefs += DefDef(outerAcc, EmptyTree)
      else {
        val outerParamAcc = outerParamAccessor(cls).asTerm
        newDefs += ValDef(outerParamAcc, EmptyTree)
        newDefs += DefDef(outerAcc, 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 OuterAccessors {
  import ast.tpd._

  private val LocalInstantiationSite = Module | Private

  /** 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
   */
  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 */
  def hasLocalInstantiation(cls: ClassSymbol)(implicit ctx: Context): Boolean =
    cls.owner.isTerm || cls.is(LocalInstantiationSite)

  /** The outer parameter accessor of cass `cls` */
  def outerParamAccessor(cls: ClassSymbol)(implicit ctx: Context) =
    cls.info.decl(nme.OUTER).symbol

  /** Class has outer accessor. Can be called only after phase OuterAccessors. */
  def hasOuter(cls: ClassSymbol)(implicit ctx: Context): Boolean =
    cls.info.decl(cls.outerAccName).exists

  /** Template `impl` of class `cls` references an outer this which goes to
   *  a class that is not a static owner.
   */
  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` */
  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)
  }

  /** If `cls` has an outer parameter add one to the method type `tp`. */
  def addOuterParam(cls: ClassSymbol, tp: Type)(implicit ctx: Context): 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 outerArgs(fun: Tree)(implicit ctx: Context): 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
  }
}