aboutsummaryrefslogtreecommitdiff
path: root/compiler/src/dotty/tools/dotc/transform/ParamForwarding.scala
blob: 2836ebcfe44ca39ff9c8214ce63a768541f7beca (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
package dotty.tools.dotc
package transform

import core._
import ast.Trees._
import Contexts._, Types._, Symbols._, Flags._, TypeUtils._, DenotTransformers._, StdNames._
import Decorators._
import config.Printers.typr

/** For all parameter accessors
 *
 *      val x: T = ...
 *
 *  if
 *  (1) x is forwarded in the supercall to a parameter that's also named `x`
 *  (2) the superclass parameter accessor for `x` is accessible from the current class
 *  change the accessor to
 *
 *      def x: T = super.x.asInstanceOf[T]
 *
 *  Do the same also if there are intermediate inaccessible parameter accessor forwarders.
 *  The aim of this transformation is to avoid redundant parameter accessor fields.
 */
class ParamForwarding(thisTransformer: DenotTransformer) {
  import ast.tpd._

  def forwardParamAccessors(impl: Template)(implicit ctx: Context): Template = {
    def fwd(stats: List[Tree])(implicit ctx: Context): List[Tree] = {
      val (superArgs, superParamNames) = impl.parents match {
        case superCall @ Apply(fn, args) :: _ =>
          fn.tpe.widen match {
            case MethodType(paramNames) => (args, paramNames)
            case _ => (Nil, Nil)
          }
        case _ => (Nil, Nil)
      }
      def inheritedAccessor(sym: Symbol): Symbol = {
        /**
         * Dmitry: having it have the same name is needed to maintain correctness in presence of subclassing
         * if you would use parent param-name `a` to implement param-field `b`
         * overriding field `b` will actually override field `a`, that is wrong!
         *
         * class A(val s: Int);
         * class B(val b: Int) extends A(b)
         * class C extends A(2) {
         *   def s = 3
         *   assert(this.b == 2)
         * }
         */
        val candidate = sym.owner.asClass.superClass
          .info.decl(sym.name).suchThat(_ is (ParamAccessor, butNot = Mutable)).symbol
        if (candidate.isAccessibleFrom(currentClass.thisType, superAccess = true)) candidate
        else if (candidate.exists) inheritedAccessor(candidate)
        else NoSymbol
      }
      def forwardParamAccessor(stat: Tree): Tree = {
        stat match {
          case stat: ValDef =>
            val sym = stat.symbol.asTerm
            if (sym.is(ParamAccessor, butNot = Mutable) && !sym.info.isInstanceOf[ExprType]) {
              // ElimByName gets confused with methods returning an ExprType,
              // so avoid param forwarding if parameter is by name. See i1766.scala
              val idx = superArgs.indexWhere(_.symbol == sym)
              if (idx >= 0 && superParamNames(idx) == stat.name) { // supercall to like-named parameter
                val alias = inheritedAccessor(sym)
                if (alias.exists) {
                  def forwarder(implicit ctx: Context) = {
                    sym.copySymDenotation(initFlags = sym.flags | Method | Stable, info = sym.info.ensureMethodic)
                      .installAfter(thisTransformer)
                    var superAcc =
                      Super(This(currentClass), tpnme.EMPTY, inConstrCall = false).select(alias)
                    if (alias.owner != currentClass.superClass)
                      // need to use shadowed in order not to accidentally address an
                      // intervening private forwarder in the superclass
                      superAcc = superAcc.withType(superAcc.tpe.asInstanceOf[TermRef].shadowed)
                    typr.println(i"adding param forwarder $superAcc")
                    DefDef(sym, superAcc.ensureConforms(sym.info.widen))
                  }
                  return forwarder(ctx.withPhase(thisTransformer.next))
                }
              }
            }
          case _ =>
        }
        stat
      }
      stats map forwardParamAccessor
    }

    cpy.Template(impl)(body = fwd(impl.body)(ctx.withPhase(thisTransformer)))
  }

  def adaptRef[T <: RefTree](tree: T)(implicit ctx: Context): T = tree.tpe match {
    case tpe: TermRefWithSignature
    if tpe.sig == Signature.NotAMethod && tpe.symbol.is(Method) =>
      // It's a param forwarder; adapt the signature
      tree.withType(
        TermRef.withSig(tpe.prefix, tpe.name, tpe.prefix.memberInfo(tpe.symbol).signature))
        .asInstanceOf[T]
    case _ =>
      tree
  }
}