aboutsummaryrefslogtreecommitdiff
path: root/src/dotty/tools/dotc/transform/Memoize.scala
blob: 01c240e3ad180f6d98d44d8dadc10d3a9213e371 (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
package dotty.tools.dotc
package transform

import core._
import DenotTransformers._
import Phases.Phase
import Contexts.Context
import SymDenotations.SymDenotation
import Types._
import Symbols._
import SymUtils._
import Constants._
import ast.Trees._
import TreeTransforms._
import NameOps._
import Flags._
import Decorators._

/** Provides the implementations of all getters and setters, introducing
 *  fields to hold the value accessed by them.
 *  TODO: Make LazyVals a part of this phase?
 *
 *    <accessor> <stable> <mods> def x(): T = e
 *      -->  private val x: T = e
 *           <accessor> <stable> <mods> def x(): T = x
 *
 *    <accessor> <mods> def x(): T = e
 *      -->  private var x: T = e
 *           <accessor> <mods> def x(): T = x
 *
 *    <accessor> <mods> def x_=(y: T): Unit = ()
 *      --> <accessor> <mods> def x_=(y: T): Unit = x = y
 */
 class Memoize extends MiniPhaseTransform with IdentityDenotTransformer { thisTransform =>
  import ast.tpd._

  override def phaseName = "memoize"

  /* Makes sure that, after getters and constructors gen, there doesn't
   * exist non-deferred definitions that are not implemented. */
  override def checkPostCondition(tree: Tree)(implicit ctx: Context): Unit = {
    def errorLackImplementation(t: Tree) = {
      val firstPhaseId = t.symbol.initial.validFor.firstPhaseId
      val definingPhase = ctx.withPhase(firstPhaseId).phase.prev
      throw new AssertionError(
        i"Non-deferred definition introduced by $definingPhase lacks implementation: $t")
    }
    tree match {
      case ddef: DefDef
        if !ddef.symbol.is(Deferred) && ddef.rhs == EmptyTree =>
          errorLackImplementation(ddef)
      case tdef: TypeDef
        if tdef.symbol.isClass && !tdef.symbol.is(Deferred) && tdef.rhs == EmptyTree =>
        errorLackImplementation(tdef)
      case _ =>
    }
    super.checkPostCondition(tree)
  }

  /** Should run after mixin so that fields get generated in the
   *  class that contains the concrete getter rather than the trait
   *  that defines it.
   */
  override def runsAfter: Set[Class[_ <: Phase]] = Set(classOf[Mixin])

  override def transformDefDef(tree: DefDef)(implicit ctx: Context, info: TransformerInfo): Tree = {
    val sym = tree.symbol

    def newField = {
      val fieldType =
        if (sym.isGetter) sym.info.resultType
        else /*sym.isSetter*/ sym.info.firstParamTypes.head

      ctx.newSymbol(
        owner = ctx.owner,
        name = sym.name.asTermName.fieldName,
        flags = Private | (if (sym is Stable) EmptyFlags else Mutable),
        info = fieldType,
        coord = tree.pos)
        .withAnnotationsCarrying(sym, defn.FieldMetaAnnot)
        .enteredAfter(thisTransform)
    }

    /** Can be used to filter annotations on getters and setters; not used yet */
    def keepAnnotations(denot: SymDenotation, meta: ClassSymbol) = {
      val cpy = sym.copySymDenotation()
      cpy.filterAnnotations(_.symbol.derivesFrom(meta))
      if (cpy.annotations ne denot.annotations) cpy.installAfter(thisTransform)
    }

    lazy val field = sym.field.orElse(newField).asTerm
    
    def adaptToField(tree: Tree) =
      if (tree.isEmpty) tree else tree.ensureConforms(field.info.widen)
      
    if (sym.is(Accessor, butNot = NoFieldNeeded))
      if (sym.isGetter) {
        def skipBlocks(t: Tree): Tree = t match {
          case Block(_, t1) => skipBlocks(t1)
          case _ => t
        }
        skipBlocks(tree.rhs) match {
          case lit: Literal if sym.is(Final) && isIdempotentExpr(tree.rhs) =>
            // duplicating scalac behavior: for final vals that have rhs as constant, we do not create a field
            // and instead return the value. This seemingly minor optimization has huge effect on initialization
            // order and the values that can be observed during superconstructor call

            // see remark about idempotency in PostTyper#normalizeTree
            cpy.DefDef(tree)(rhs = lit)
          case _ =>
            var rhs = tree.rhs.changeOwnerAfter(sym, field, thisTransform)
            if (isWildcardArg(rhs)) rhs = EmptyTree

            val fieldDef = transformFollowing(ValDef(field, adaptToField(rhs)))
            val getterDef = cpy.DefDef(tree)(rhs = transformFollowingDeep(ref(field))(ctx.withOwner(sym), info))
            Thicket(fieldDef, getterDef)
        }
      } else if (sym.isSetter) {
        if (!sym.is(ParamAccessor)) { val Literal(Constant(())) = tree.rhs } // this is intended as an assertion
        field.setFlag(Mutable) // necessary for vals mixed in from Scala2 traits
        val initializer = Assign(ref(field), adaptToField(ref(tree.vparamss.head.head.symbol)))
        cpy.DefDef(tree)(rhs = transformFollowingDeep(initializer)(ctx.withOwner(sym), info))
      }
      else tree // curiously, some accessors from Scala2 have ' ' suffixes. They count as
                // neither getters nor setters
    else tree
  }
  private val NoFieldNeeded  = Lazy | Deferred | JavaDefined
}