summaryrefslogtreecommitdiff
path: root/src/compiler/scala/reflect/reify/phases/Reshape.scala
blob: 6c073c0b4c66804687e8adccc2eac0dc3120322b (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
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
package scala.reflect.reify
package phases

import scala.tools.nsc.symtab.Flags._

trait Reshape {
  self: Reifier =>

  import global._
  import definitions._
  import treeInfo.Unapplied
  private val runDefinitions = currentRun.runDefinitions
  import runDefinitions._

  /**
   *  Rolls back certain changes that were introduced during typechecking of the reifee.
   *
   *  These include:
   *    * Undoing macro expansions
   *    * Replacing type trees with TypeTree(tpe)
   *    * Reassembling CompoundTypeTrees into reifiable form
   *    * Transforming Modifiers.annotations into Symbol.annotations
   *    * Transforming Annotated annotations into AnnotatedType annotations
   *    * Transforming Annotated(annot, expr) into Typed(expr, TypeTree(Annotated(annot, _))
   *    * Non-idempotencies of the typechecker: https://issues.scala-lang.org/browse/SI-5464
   */
  val reshape = new Transformer {
    var currentSymbol: Symbol = NoSymbol

    override def transform(tree0: Tree) = {
      val tree = undoMacroExpansion(tree0)
      currentSymbol = tree.symbol

      val preTyper = tree match {
        case tree if tree.isErroneous =>
          tree
        case tt @ TypeTree() =>
          toPreTyperTypeTree(tt)
        case ctt @ CompoundTypeTree(_) =>
          toPreTyperCompoundTypeTree(ctt)
        case toa @ TypedOrAnnotated(_) =>
          toPreTyperTypedOrAnnotated(toa)
        case ta @ TypeApply(_, _) if isCrossStageTypeBearer(ta) =>
          if (reifyDebug) println("cross-stage type bearer, retaining: " + tree)
          ta
        case ta @ TypeApply(hk, ts) =>
          val discard = ts collect { case tt: TypeTree => tt } exists isDiscarded
          if (reifyDebug && discard) println("discarding TypeApply: " + tree)
          if (discard) hk else ta
        case classDef @ ClassDef(mods, name, params, impl) =>
          val Template(parents, self, body) = impl
          var body1 = trimAccessors(classDef, reshapeLazyVals(body))
          body1 = trimSyntheticCaseClassMembers(classDef, body1)
          val impl1 = Template(parents, self, body1).copyAttrs(impl)
          ClassDef(mods, name, params, impl1).copyAttrs(classDef)
        case moduledef @ ModuleDef(mods, name, impl) =>
          val Template(parents, self, body) = impl
          var body1 = trimAccessors(moduledef, reshapeLazyVals(body))
          body1 = trimSyntheticCaseClassMembers(moduledef, body1)
          val impl1 = Template(parents, self, body1).copyAttrs(impl)
          ModuleDef(mods, name, impl1).copyAttrs(moduledef)
        case template @ Template(parents, self, body) =>
          val discardedParents = parents collect { case tt: TypeTree => tt } filter isDiscarded
          if (reifyDebug && discardedParents.length > 0) println("discarding parents in Template: " + discardedParents.mkString(", "))
          val parents1 = parents diff discardedParents
          val body1 = reshapeLazyVals(trimSyntheticCaseClassCompanions(body))
          Template(parents1, self, body1).copyAttrs(template)
        case block @ Block(stats, expr) =>
          val stats1 = reshapeLazyVals(trimSyntheticCaseClassCompanions(stats))
          Block(stats1, expr).copyAttrs(block)
        case unapply @ UnApply(Unapplied(Select(fun, nme.unapply | nme.unapplySeq)), args) =>
          if (reifyDebug) println("unapplying unapply: " + tree)
          Apply(fun, args).copyAttrs(unapply)
        case _ =>
          tree
      }

      super.transform(preTyper)
    }

    private def undoMacroExpansion(tree: Tree): Tree =
      tree.attachments.get[analyzer.MacroExpansionAttachment] match {
        case Some(analyzer.MacroExpansionAttachment(original, _)) =>
          def mkImplicitly(tp: Type) = atPos(tree.pos)(
            gen.mkNullaryCall(Predef_implicitly, List(tp))
          )
          val sym = original.symbol
          original match {
            // this hack is necessary until I fix implicit macros
            // so far tag materialization is implemented by sneaky macros hidden in scala-compiler.jar
            // hence we cannot reify references to them, because noone will be able to see them later
            // when implicit macros are fixed, these sneaky macros will move to corresponding companion objects
            // of, say, ClassTag or TypeTag
            case Apply(TypeApply(_, List(tt)), _) if sym == materializeClassTag            => mkImplicitly(appliedType(ClassTagClass, tt.tpe))
            case Apply(TypeApply(_, List(tt)), List(pre)) if sym == materializeWeakTypeTag => mkImplicitly(typeRef(pre.tpe, WeakTypeTagClass, List(tt.tpe)))
            case Apply(TypeApply(_, List(tt)), List(pre)) if sym == materializeTypeTag     => mkImplicitly(typeRef(pre.tpe, TypeTagClass, List(tt.tpe)))
            case _                                                                         => original
          }
        case _ => tree
      }

    override def transformModifiers(mods: Modifiers) = {
      val mods1 = toPreTyperModifiers(mods, currentSymbol)
      super.transformModifiers(mods1)
    }

    private def toPreTyperModifiers(mods: Modifiers, sym: Symbol) = {
      if (!sym.annotations.isEmpty) {
        val postTyper = sym.annotations filter (_.original != EmptyTree)
        if (reifyDebug && !postTyper.isEmpty) println("reify symbol annotations for: " + sym)
        if (reifyDebug && !postTyper.isEmpty) println("originals are: " + sym.annotations)
        val preTyper = postTyper map toPreTyperAnnotation
        mods.withAnnotations(preTyper)
      } else {
        mods
      }
    }

    /** Restore pre-typer representation of a type.
     *
     *  NB: This is the trickiest part of reification!
     *
     *  In most cases, we're perfectly fine to reify a Type itself (see `reifyType`).
     *  However if the type involves a symbol declared inside the quasiquote (i.e. registered in `boundSyms`),
     *  then we cannot reify it, or otherwise subsequent reflective compilation will fail.
     *
     *  Why will it fail? Because reified deftrees (e.g. ClassDef(...)) will generate fresh symbols during that compilation,
     *  so naively reified symbols will become out of sync, which brings really funny compilation errors and/or crashes, e.g.:
     *  https://issues.scala-lang.org/browse/SI-5230
     *
     *  To deal with this unpleasant fact, we need to fall back from types to equivalent trees (after all, parser trees don't contain any types, just trees, so it should be possible).
     *  Luckily, these original trees get preserved for us in the `original` field when Trees get transformed into TypeTrees.
     *  And if an original of a type tree is empty, we can safely assume that this type is non-essential (e.g. was inferred/generated by the compiler).
     *  In that case the type can be omitted (e.g. reified as an empty TypeTree), since it will be inferred again later on.
     *
     *  An important property of the original is that it isn't just a pre-typer tree.
     *  It's actually kind of a post-typer tree with symbols assigned to its Idents (e.g. Ident("List") will contain a symbol that points to immutable.this.List).
     *  This is very important, since subsequent reflective compilation won't have to resolve these symbols.
     *  In general case, such resolution cannot be performed, since reification doesn't preserve lexical context,
     *  which means that reflective compilation won't be aware of, say, imports that were provided when the reifee has been compiled.
     *
     *  This workaround worked surprisingly well and allowed me to fix several important reification bugs, until the abstraction has leaked.
     *  Suddenly I found out that in certain contexts original trees do not contain symbols, but are just parser trees.
     *  To the moment I know only one such situation: typedAnnotations does not typecheck the annotation in-place, but rather creates new trees and typechecks them, so the original remains symless.
     *  Thus we apply a workaround for that in typedAnnotated. I hope this will be the only workaround in this department.
     *  upd. There are also problems with CompoundTypeTrees. I had to use attachments to retain necessary information.
     *
     *  upd. Recently I went ahead and started using original for all TypeTrees, regardless of whether they refer to local symbols or not.
     *  As a result, `reifyType` is never called directly by tree reification (and, wow, it seems to work great!).
     *  The only usage of `reifyType` now is for servicing typetags, however, I have some ideas how to get rid of that as well.
     */
    private def isDiscarded(tt: TypeTree) = tt.original == null
    private def toPreTyperTypeTree(tt: TypeTree): Tree = {
      if (!isDiscarded(tt)) {
        // here we rely on the fact that the originals that reach this point
        // have all necessary symbols attached to them (i.e. that they can be recompiled in any lexical context)
        // if this assumption fails, please, don't be quick to add postprocessing here (like I did before)
        // but rather try to fix this in Typer, so that it produces quality originals (like it's done for typedAnnotated)
        if (reifyDebug) println("TypeTree, essential: %s (%s)".format(tt.tpe, tt.tpe.kind))
        if (reifyDebug) println("verdict: rolled back to original %s".format(tt.original.toString.replaceAll("\\s+", " ")))
        transform(tt.original)
      } else {
        // type is deemed to be non-essential
        // erase it and hope that subsequent reflective compilation will be able to recreate it again
        if (reifyDebug) println("TypeTree, non-essential: %s (%s)".format(tt.tpe, tt.tpe.kind))
        if (reifyDebug) println("verdict: discarded")
        TypeTree()
      }
    }

    private def toPreTyperCompoundTypeTree(ctt: CompoundTypeTree): Tree = {
      val CompoundTypeTree(tmpl @ Template(parents, self, stats)) = ctt
      if (stats.nonEmpty) CannotReifyCompoundTypeTreeWithNonEmptyBody(ctt)
      assert(self eq noSelfType, self)
      val att = tmpl.attachments.get[CompoundTypeTreeOriginalAttachment]
      val CompoundTypeTreeOriginalAttachment(parents1, stats1) = att.getOrElse(CompoundTypeTreeOriginalAttachment(parents, stats))
      CompoundTypeTree(Template(parents1, self, stats1))
    }

    private def toPreTyperTypedOrAnnotated(tree: Tree): Tree = tree match {
      case ty @ Typed(expr1, tpt) =>
        if (reifyDebug) println("reify typed: " + tree)
        val original = tpt match {
          case tt @ TypeTree() => tt.original
          case tpt => tpt
        }
        val annotatedArg = {
          def loop(tree: Tree): Tree = tree match {
            case annotated1 @ Annotated(ann, annotated2 @ Annotated(_, _)) => loop(annotated2)
            case annotated1 @ Annotated(ann, arg) => arg
            case _ => EmptyTree
          }

          loop(original)
        }
        if (annotatedArg != EmptyTree) {
          if (annotatedArg.isType) {
            if (reifyDebug) println("verdict: was an annotated type, reify as usual")
            ty
          } else {
            if (reifyDebug) println("verdict: was an annotated value, equivalent is " + original)
            toPreTyperTypedOrAnnotated(original)
          }
        } else {
          if (reifyDebug) println("verdict: wasn't annotated, reify as usual")
          ty
        }
      case at @ Annotated(annot, arg) =>
        if (reifyDebug) println("reify type annotations for: " + tree)
        assert(at.tpe.isInstanceOf[AnnotatedType], "%s (%s)".format(at.tpe, at.tpe.kind))
        val annot1 = toPreTyperAnnotation(at.tpe.asInstanceOf[AnnotatedType].annotations(0))
        if (reifyDebug) println("originals are: " + annot1)
        Annotated(annot1, arg).copyAttrs(at)
    }

    /** Restore pre-typer representation of an annotation.
     *  The trick here is to retain the symbols that have been populated during typechecking of the annotation.
     *  If we do not do that, subsequent reflective compilation will fail.
     */
    private def toPreTyperAnnotation(ann: AnnotationInfo): Tree = {
      val args = if (ann.assocs.isEmpty) {
        ann.args
      } else {
        def toScalaAnnotation(jann: ClassfileAnnotArg): Tree = (jann: @unchecked) match {
          case LiteralAnnotArg(const) => Literal(const)
          case ArrayAnnotArg(arr)     => Apply(Ident(definitions.ArrayModule), arr.toList map toScalaAnnotation)
          case NestedAnnotArg(ann)    => toPreTyperAnnotation(ann)
        }

        ann.assocs map { case (nme, arg) => AssignOrNamedArg(Ident(nme), toScalaAnnotation(arg)) }
      }

      def extractOriginal: PartialFunction[Tree, Tree] = { case Apply(Select(New(tpt), _), _) => tpt }
      assert(extractOriginal.isDefinedAt(ann.original), showRaw(ann.original))
      New(TypeTree(ann.atp) setOriginal extractOriginal(ann.original), List(args))
    }

    private def toPreTyperLazyVal(ddef: DefDef): ValDef = {
      def extractRhs(rhs: Tree) = rhs match {
        case Block(Assign(lhs, rhs)::Nil, _) if lhs.symbol.isLazy => rhs
        case _ => rhs // unit or trait case
      }
      val DefDef(mods0, name0, _, _, tpt0, rhs0) = ddef
      val name1 = name0.dropLocal
      val Modifiers(flags0, privateWithin0, annotations0) = mods0
      val flags1 = (flags0 & GetterFlags) & ~(STABLE | ACCESSOR | METHOD)
      val mods1 = Modifiers(flags1, privateWithin0, annotations0) setPositions mods0.positions
      val mods2 = toPreTyperModifiers(mods1, ddef.symbol)
      ValDef(mods2, name1, tpt0, extractRhs(rhs0))
    }

    private def trimAccessors(deff: Tree, stats: List[Tree]): List[Tree] = {
      val symdefs = (stats collect { case vodef: ValOrDefDef => vodef } map (vodeff => vodeff.symbol -> vodeff)).toMap
      val accessors = scala.collection.mutable.Map[ValDef, List[DefDef]]()
      stats collect { case ddef: DefDef => ddef } foreach (defdef => {
        val valdef = symdefs get defdef.symbol.accessedOrSelf collect { case vdef: ValDef => vdef } getOrElse null
        if (valdef != null) accessors(valdef) = accessors.getOrElse(valdef, Nil) :+ defdef

        def detectBeanAccessors(prefix: String): Unit = {
          if (defdef.name.startsWith(prefix)) {
            val name = defdef.name.toString.substring(prefix.length)
            def uncapitalize(s: String) = if (s.length == 0) "" else { val chars = s.toCharArray; chars(0) = chars(0).toLower; new String(chars) }
            def findValDef(name: String) = symdefs.values collectFirst {
              case vdef: ValDef if vdef.name.dropLocal string_== name => vdef
            }
            val valdef = findValDef(name).orElse(findValDef(uncapitalize(name))).orNull
            if (valdef != null) accessors(valdef) = accessors.getOrElse(valdef, Nil) :+ defdef
          }
        }
        detectBeanAccessors("get")
        detectBeanAccessors("set")
        detectBeanAccessors("is")
      })

      val stats1 = stats flatMap {
        case vdef @ ValDef(mods, name, tpt, rhs) if !mods.isLazy =>
          val mods1 = if (accessors.contains(vdef)) {
            val ddef = accessors(vdef)(0) // any accessor will do
            val Modifiers(flags, _, annotations) = mods
            var flags1 = flags & ~LOCAL
            if (!ddef.symbol.isPrivate) flags1 = flags1 & ~PRIVATE
            val privateWithin1 = ddef.mods.privateWithin
            val annotations1 = accessors(vdef).foldLeft(annotations)((curr, acc) => curr ++ (acc.symbol.annotations map toPreTyperAnnotation))
            Modifiers(flags1, privateWithin1, annotations1) setPositions mods.positions
          } else {
            mods
          }
          val mods2 = toPreTyperModifiers(mods1, vdef.symbol)
          val name1 = name.dropLocal
          val vdef1 = ValDef(mods2, name1.toTermName, tpt, rhs)
          if (reifyDebug) println("resetting visibility of field: %s => %s".format(vdef, vdef1))
          Some(vdef1) // no copyAttrs here, because new ValDef and old symbols are now out of sync
        case ddef: DefDef if !ddef.mods.isLazy =>
          // lazy val accessors are removed in reshapeLazyVals
          // as they are needed to recreate lazy vals
          if (accessors.values.exists(_.contains(ddef))) {
            if (reifyDebug) println("discarding accessor method: " + ddef)
            None
          } else {
            Some(ddef)
          }
        case tree =>
          Some(tree)
      }

      stats1
    }

    private def reshapeLazyVals(stats: List[Tree]): List[Tree] = {
      val lazyvaldefs:Map[Symbol, DefDef] = stats.collect({ case ddef: DefDef if ddef.mods.isLazy => ddef }).
        map((ddef: DefDef) => ddef.symbol -> ddef).toMap
      // lazy valdef and defdef are in the same block.
      // only that valdef needs to have its rhs rebuilt from defdef
      stats flatMap (stat => stat match {
        case vdef: ValDef if vdef.symbol.isLazy =>
          if (reifyDebug) println(s"reconstructing original lazy value for $vdef")
          val ddefSym = vdef.symbol.lazyAccessor
          val vdef1 = lazyvaldefs.get(ddefSym) match {
            case Some(ddef) =>
              toPreTyperLazyVal(ddef)
            case None       =>
              if (reifyDebug) println("couldn't find corresponding lazy val accessor")
              vdef
          }
          if (reifyDebug) println(s"reconstructed lazy val is $vdef1")
          vdef1::Nil
        case ddef: DefDef if ddef.symbol.isLazy =>
          def hasUnitType(sym: Symbol) = (sym.tpe.typeSymbol == UnitClass) && sym.tpe.annotations.isEmpty
          if (hasUnitType(ddef.symbol)) {
            // since lazy values of type Unit don't have val's
            // we need to create them from scratch
            toPreTyperLazyVal(ddef) :: Nil
          } else Nil
        case _ => stat::Nil
      })
    }

    private def trimSyntheticCaseClassMembers(deff: Tree, stats: List[Tree]): List[Tree] =
      stats filterNot (memberDef => memberDef.isDef && {
        val isSynthetic = memberDef.symbol.isSynthetic
        // this doesn't work for local classes, e.g. for ones that are top-level to a quasiquote (see comments to companionClass)
        // that's why I replace the check with an assumption that all synthetic members are, in fact, generated of case classes
        // val isCaseMember = deff.symbol.isCaseClass || deff.symbol.companionClass.isCaseClass
        val isCaseMember = true
        if (isSynthetic && isCaseMember && reifyDebug) println("discarding case class synthetic def: " + memberDef)
        isSynthetic && isCaseMember
      })

    private def trimSyntheticCaseClassCompanions(stats: List[Tree]): List[Tree] =
      stats diff (stats collect { case moddef: ModuleDef => moddef } filter (moddef => {
        val isSynthetic = moddef.symbol.isSynthetic
        // this doesn't work for local classes, e.g. for ones that are top-level to a quasiquote (see comments to companionClass)
        // that's why I replace the check with an assumption that all synthetic modules are, in fact, companions of case classes
        // val isCaseCompanion = moddef.symbol.companionClass.isCaseClass
        val isCaseCompanion = true
        if (isSynthetic && isCaseCompanion && reifyDebug) println("discarding synthetic case class companion: " + moddef)
        isSynthetic && isCaseCompanion
      }))
  }
}