summaryrefslogtreecommitdiff
path: root/src/compiler/scala/tools/nsc/transform/Flatten.scala
blob: 29ba21cba7403d076c39b68e07a05512e83383b0 (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
/* NSC -- new Scala compiler
 * Copyright 2005-2013 LAMP/EPFL
 * @author Martin Odersky
 */

package scala.tools.nsc
package transform

import symtab._
import Flags._
import scala.collection.mutable.ListBuffer

abstract class Flatten extends InfoTransform {
  import global._
  import treeInfo.isQualifierSafeToElide

  /** the following two members override abstract members in Transform */
  val phaseName: String = "flatten"

  /** Updates the owning scope with the given symbol, unlinking any others.
   */
  private def replaceSymbolInCurrentScope(sym: Symbol): Unit = exitingFlatten {
    removeSymbolInCurrentScope(sym)
    sym.owner.info.decls enter sym
  }

  private def removeSymbolInCurrentScope(sym: Symbol): Unit = exitingFlatten {
    val scope = sym.owner.info.decls
    val old   = (scope lookupUnshadowedEntries sym.name).toList
    old foreach (scope unlink _)
    def old_s = old map (_.sym) mkString ", "
    if (old.nonEmpty) debuglog(s"In scope of ${sym.owner}, unlinked $old_s")
  }

  private def liftClass(sym: Symbol) {
    if (!sym.isLifted) {
      sym setFlag LIFTED
      debuglog("re-enter " + sym.fullLocationString)
      replaceSymbolInCurrentScope(sym)
    }
  }
  private def liftSymbol(sym: Symbol) {
    liftClass(sym)
  }
  // This is a short-term measure partially working around objects being
  // lifted out of parameterized classes, leaving them referencing
  // invisible type parameters.
  private def isFlattenablePrefix(pre: Type) = {
    val clazz = pre.typeSymbol
    clazz.isClass && !clazz.isPackageClass && {
      // Cannot flatten here: class A[T] { object B }
      // was "at erasurePhase.prev"
      enteringErasure(clazz.typeParams.isEmpty)
    }
  }

  private val flattened = new TypeMap {
    def apply(tp: Type): Type = tp match {
      case TypeRef(pre, sym, args) if isFlattenablePrefix(pre) =>
        assert(args.isEmpty && sym.enclosingTopLevelClass != NoSymbol, sym.ownerChain)
        typeRef(sym.enclosingTopLevelClass.owner.thisType, sym, Nil)
      case ClassInfoType(parents, decls, clazz) =>
        var parents1 = parents
        val decls1 = scopeTransform(clazz) {
          val decls1 = newScope
          if (clazz.isPackageClass) {
            exitingFlatten { decls foreach (decls1 enter _) }
          }
          else {
            val oldowner = clazz.owner
            exitingFlatten { oldowner.info }
            parents1 = parents mapConserve (this)

            for (sym <- decls) {
              if (sym.isTerm && !sym.isStaticModule) {
                decls1 enter sym
                if (sym.isModule) {
                  // In theory, we could assert(sym.isMethod), because nested, non-static modules are
                  // transformed to methods (METHOD flag added in UnCurry). But this requires
                  // forcing sym.info (see comment on isModuleNotMethod), which forces stub symbols
                  // too eagerly (SI-8907).

                  // Note that module classes are not entered into the 'decls' of the ClassInfoType
                  // of the outer class, only the module symbols are. So the current loop does
                  // not visit module classes. Therefore we set the LIFTED flag here for module
                  // classes.
                  // TODO: should we also set the LIFTED flag for static, nested module classes?
                  // currently they don't get the flag, even though they are lifted to the package
                  sym.moduleClass setFlag LIFTED
                }
              } else if (sym.isClass)
                liftSymbol(sym)
            }
          }
          decls1
        }
        ClassInfoType(parents1, decls1, clazz)
      case MethodType(params, restp) =>
        val restp1 = apply(restp)
        if (restp1 eq restp) tp else copyMethodType(tp, params, restp1)
      case PolyType(tparams, restp) =>
        val restp1 = apply(restp)
        if (restp1 eq restp) tp else PolyType(tparams, restp1)
      case _ =>
        mapOver(tp)
    }
  }

  def transformInfo(sym: Symbol, tp: Type): Type = flattened(tp)

  protected def newTransformer(unit: CompilationUnit): Transformer = new Flattener

  class Flattener extends Transformer {
    /** Buffers for lifted out classes */
    private val liftedDefs = perRunCaches.newMap[Symbol, ListBuffer[Tree]]()

    override def transform(tree: Tree): Tree = postTransform {
      tree match {
        case PackageDef(_, _) =>
          liftedDefs(tree.symbol.moduleClass) = new ListBuffer
          super.transform(tree)
        case Template(_, _, _) if tree.symbol.isDefinedInPackage =>
          liftedDefs(tree.symbol.owner) = new ListBuffer
          super.transform(tree)
        case ClassDef(_, _, _, _) if tree.symbol.isNestedClass =>
          // SI-5508 Ordering important. In `object O { trait A { trait B } }`, we want `B` to appear after `A` in
          //         the sequence of lifted trees in the enclosing package. Why does this matter? Currently, mixin
          //         needs to transform `A` first to a chance to create accessors for private[this] trait fields
          //         *before* it transforms inner classes that refer to them. This also fixes SI-6231.
          //
          //         Alternative solutions
          //            - create the private[this] accessors eagerly in Namer (but would this cover private[this] fields
          //              added later phases in compilation?)
          //            - move the accessor creation to the Mixin info transformer
          val liftedBuffer = liftedDefs(tree.symbol.enclosingTopLevelClass.owner)
          val index = liftedBuffer.length
          liftedBuffer.insert(index, super.transform(tree))
          if (tree.symbol.sourceModule.isStaticModule)
            removeSymbolInCurrentScope(tree.symbol.sourceModule)
          EmptyTree
        case _ =>
          super.transform(tree)
      }
    }

    private def postTransform(tree: Tree): Tree = {
      val sym = tree.symbol
      val tree1 = tree match {
        case Select(qual, name) if sym.isStaticModule && !sym.isTopLevel =>
          exitingFlatten {
            atPos(tree.pos) {
              val ref = gen.mkAttributedRef(sym)
              if (isQualifierSafeToElide(qual)) ref
              else Block(List(qual), ref).setType(tree.tpe) // need to execute the qualifier but refer directly to the lifted module.
            }
          }
        case _ =>
          tree
      }
      tree1 setType flattened(tree1.tpe)
    }

    /** Transform statements and add lifted definitions to them. */
    override def transformStats(stats: List[Tree], exprOwner: Symbol): List[Tree] = {
      val stats1 = super.transformStats(stats, exprOwner)
      if (currentOwner.isPackageClass) {
        val lifted = liftedDefs.remove(currentOwner).toList.flatten
        stats1 ::: lifted
      }
      else stats1
    }
  }
}