summaryrefslogtreecommitdiff
path: root/src/compiler/scala/reflect/reify/utils/SymbolTables.scala
blob: 5f8de9894fb3cb791fd6ee7bb7eb2fa23d4dab4d (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
198
199
200
201
202
203
204
205
206
207
package scala.reflect.reify
package utils

import scala.collection._
import scala.compat.Platform.EOL

trait SymbolTables {
  self: Utils =>

  import global._

  class SymbolTable private[SymbolTable] (
    private[SymbolTable] val symtab: immutable.ListMap[Symbol, Tree] = immutable.ListMap[Symbol, Tree](),
    private[SymbolTable] val aliases: List[(Symbol, TermName)] = List[(Symbol, TermName)](),
    private[SymbolTable] val original: Option[List[Tree]] = None) {

    def syms: List[Symbol] = symtab.keys.toList

    def symDef(sym: Symbol): Tree =
      symtab.getOrElse(sym, EmptyTree)

    def symName(sym: Symbol): TermName =
      symtab.get(sym) match {
        case Some(FreeDef(_, name, _, _, _)) => name
        case Some(SymDef(_, name, _, _)) => name
        case None => nme.EMPTY
      }

    def symAliases(sym: Symbol): List[TermName] =
      symName(sym) match {
        case name if name.isEmpty => Nil
        case _ => (aliases.distinct groupBy (_._1) mapValues (_ map (_._2)))(sym)
      }

    def symBinding(sym: Symbol): Tree =
      symtab.get(sym) match {
        case Some(FreeDef(_, _, binding, _, _)) => binding
        case Some(SymDef(_, _, _, _)) => throw new UnsupportedOperationException(s"${symtab(sym)} is a symdef, hence it doesn't have a binding")
        case None => EmptyTree
      }

    def symRef(sym: Symbol): Tree =
      symtab.get(sym) match {
        case Some(FreeDef(_, name, binding, _, _)) => Ident(name) updateAttachment binding
        case Some(SymDef(_, name, _, _)) => Ident(name) updateAttachment ReifyBindingAttachment(Ident(sym))
        case None => EmptyTree
      }

    def +(sym: Symbol, name: TermName, reification: Tree): SymbolTable = add(sym, name, reification)
    def +(symDef: Tree): SymbolTable = add(symDef)
    def ++(symDefs: TraversableOnce[Tree]): SymbolTable = (this /: symDefs)((symtab, symDef) => symtab.add(symDef))
    def ++(symtab: SymbolTable): SymbolTable = { val updated = this ++ symtab.symtab.values; new SymbolTable(updated.symtab, updated.aliases ++ symtab.aliases) }
    def -(sym: Symbol): SymbolTable = remove(sym)
    def -(name: TermName): SymbolTable = remove(name)
    def -(symDef: Tree): SymbolTable = remove(reifyBinding(symDef).symbol)
    def --(syms: GenTraversableOnce[Symbol]): SymbolTable = (this /: syms)((symtab, sym) => symtab.remove(sym))
    def --(names: Iterable[TermName]): SymbolTable = (this /: names)((symtab, name) => symtab.remove(name))
    def --(symDefs: TraversableOnce[Tree]): SymbolTable = this -- (symDefs map (reifyBinding(_)))
    def --(symtab: SymbolTable): SymbolTable = { val updated = this -- symtab.symtab.values; new SymbolTable(updated.symtab, updated.aliases diff symtab.aliases) }
    def filterSyms(p: Symbol => Boolean): SymbolTable = this -- (syms filterNot p)
    def filterAliases(p: (Symbol, TermName) => Boolean): SymbolTable = this -- (aliases filterNot (tuple => p(tuple._1, tuple._2)) map (_._2))

    private def add(symDef: Tree): SymbolTable = {
      val sym = reifyBinding(symDef).symbol
      assert(sym != NoSymbol, showRaw(symDef))
      val name = symDef match {
        case FreeDef(_, name, _, _, _) => name
        case SymDef(_, name, _, _) => name
      }
      val newSymtab = if (!(symtab contains sym)) symtab + (sym -> symDef) else symtab
      val newAliases = aliases :+ (sym -> name)
      new SymbolTable(newSymtab, newAliases)
    }

    private def add(sym: Symbol, name0: TermName, reification: Tree): SymbolTable = {
      def freshName(name0: TermName): TermName = {
        var name = name0.toString
        name = name.replace(".type", "$type")
        name = name.replace(" ", "$")
        val fresh = typer.context.unit.fresh
        newTermName(fresh.newName(name))
      }
      val bindingAttachment = reification.attachments.get[ReifyBindingAttachment].get
      add(ValDef(NoMods, freshName(name0), TypeTree(), reification) updateAttachment bindingAttachment)
    }

    private def remove(sym: Symbol): SymbolTable = {
      val newSymtab = symtab - sym
      val newAliases = aliases filter (_._1 != sym)
      new SymbolTable(newSymtab, newAliases)
    }

    private def remove(name: TermName): SymbolTable = {
      var newSymtab = symtab
      val newAliases = aliases filter (_._2 != name)
      newSymtab = newSymtab filter { case ((sym, _)) => newAliases exists (_._1 == sym) }
      newSymtab = newSymtab map { case ((sym, tree)) =>
        val ValDef(mods, primaryName, tpt, rhs) = tree
        val tree1 =
          if (!(newAliases contains ((sym, primaryName)))) {
            val primaryName1 = newAliases.find(_._1 == sym).get._2
            ValDef(mods, primaryName1, tpt, rhs).copyAttrs(tree)
          } else tree
        (sym, tree1)
      }
      new SymbolTable(newSymtab, newAliases)
    }

    private val cache = mutable.Map[SymbolTable, List[Tree]]()
    def encode: List[Tree] = cache.getOrElseUpdate(this, SymbolTable.encode(this)) map (_.duplicate)

    override def toString = {
      val symtabString = symtab.keys.map(symName(_)).mkString(", ")
      val trueAliases = aliases.distinct.filter(entry => symName(entry._1) != entry._2)
      val aliasesString = trueAliases.map(entry => s"${symName(entry._1)} -> ${entry._2}").mkString(", ")
      s"""symtab = [$symtabString], aliases = [$aliasesString]${if (original.isDefined) ", has original" else ""}"""
    }

    def debugString: String = {
      val buf = new StringBuilder
      buf.append("symbol table = " + (if (syms.length == 0) "<empty>" else "")).append(EOL)
      syms foreach (sym => buf.append(symDef(sym)).append(EOL))
      buf.delete(buf.length - EOL.length, buf.length)
      buf.toString
    }
  }

  object SymbolTable {
    def apply(): SymbolTable =
      new SymbolTable()

    def apply(encoded: List[Tree]): SymbolTable = {
      var result = new SymbolTable(original = Some(encoded))
      encoded foreach (entry => (entry.attachments.get[ReifyBindingAttachment], entry.attachments.get[ReifyAliasAttachment]) match {
        case (Some(ReifyBindingAttachment(_)), _) => result += entry
        case (_, Some(ReifyAliasAttachment(sym, alias))) => result = new SymbolTable(result.symtab, result.aliases :+ ((sym, alias)))
        case _ => // do nothing, this is boilerplate that can easily be recreated by subsequent `result.encode`
      })
      result
    }

    private[SymbolTable] def encode(symtab0: SymbolTable): List[Tree] = {
      if (symtab0.original.isDefined) return symtab0.original.get.map(_.duplicate)
      else assert(hasReifier, "encoding a symbol table requires a reifier")
      // during `encode` we might need to do some reifications
      // these reifications might lead to changes in `reifier.symtab`
      // reifier is mutable, symtab is immutable. this is a tough friendship
      val backup = reifier.state.backup
      reifier.state.symtab = symtab0.asInstanceOf[reifier.SymbolTable]
      def currtab = reifier.symtab.asInstanceOf[SymbolTable]
      try {
        val cumulativeSymtab = mutable.ArrayBuffer[Tree](symtab0.symtab.values.toList: _*)
        val cumulativeAliases = mutable.ArrayBuffer[(Symbol, TermName)](symtab0.aliases: _*)

        def fillInSymbol(sym: Symbol): Tree = {
          if (reifyDebug) println("Filling in: %s (%s)".format(sym, sym.accurateKindString))
          val isFreeTerm = FreeTermDef.unapply(currtab.symDef(sym)).isDefined
          // SI-6204 don't reify signatures for incomplete symbols, because this might lead to cyclic reference errors
          val signature =
            if (sym.isInitialized) {
              if (sym.isCapturedVariable) capturedVariableType(sym)
              else if (isFreeTerm) sym.tpe
              else sym.info
            } else NoType
          val rset = reifier.mirrorBuildCall(nme.setTypeSignature, currtab.symRef(sym), reifier.reify(signature))
          // `Symbol.annotations` doesn't initialize the symbol, so we don't need to do anything special here
          // also since we call `sym.info` a few lines above, by now the symbol will be initialized (if possible)
          // so the annotations will be filled in and will be waiting to be reified (unless symbol initialization is prohibited as described above)
          if (sym.annotations.isEmpty) rset
          else reifier.mirrorBuildCall(nme.setAnnotations, rset, reifier.mkList(sym.annotations map reifier.reifyAnnotationInfo))
        }

        // `fillInSymbol` might add symbols to `symtab`, that's why this is done iteratively
        var progress = 0
        while (progress < cumulativeSymtab.length) {
          val sym = reifyBinding(cumulativeSymtab(progress)).symbol
          if (sym != NoSymbol) {
            val symtabProgress = currtab.symtab.size
            val aliasesProgress = currtab.aliases.length
            val fillIn = fillInSymbol(sym)
            cumulativeSymtab ++= currtab.symtab.values drop symtabProgress
            cumulativeAliases ++= currtab.aliases drop aliasesProgress
            cumulativeSymtab += fillIn
          }
          progress += 1
        }

        val withAliases = cumulativeSymtab flatMap (entry => {
          val result = mutable.ListBuffer[Tree]()
          result += entry
          val sym = reifyBinding(entry).symbol
          if (sym != NoSymbol)
            result ++= cumulativeAliases.distinct filter (alias => alias._1 == sym && alias._2 != currtab.symName(sym)) map (alias => {
              val canonicalName = currtab.symName(sym)
              val aliasName = alias._2
              ValDef(NoMods, aliasName, TypeTree(), Ident(canonicalName)) updateAttachment ReifyAliasAttachment(sym, aliasName)
            })
          result.toList
        })

        withAliases.toList
      } finally {
        reifier.state.restore(backup)
      }
    }
  }
}