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

import TreeTransforms._
import ast.Trees._
import core._
import Contexts._, Types._, Decorators._, Denotations._, Symbols._, SymDenotations._, Names._

/** This transform makes sure every identifier and select node
 *  carries a symbol. To do this, certain qualifiers with a union type
 *  have to be "splitted" with a type test.
 *
 *  For now, only self references are treated.
 */
class Splitter extends MiniPhaseTransform { thisTransform =>
  import ast.tpd._

  override def phaseName: String = "splitter"

  /** Replace self referencing idents with ThisTypes. */
  override def transformIdent(tree: Ident)(implicit ctx: Context, info: TransformerInfo) = tree.tpe match {
    case tp: ThisType =>
      ctx.debuglog(s"owner = ${ctx.owner}, context = ${ctx}")
      This(tp.cls) withPos tree.pos
    case _ => tree
  }

  /** If we select a name, make sure the node has a symbol.
   *  If necessary, split the qualifier with type tests.
   *  Example: Assume:
   *
   *      class A { def f(x: S): T }
   *      class B { def f(x: S): T }
   *      def p(): A | B
   *
   *  Then   p().f(a)   translates to
   *
   *      val ev$1 = p()
   *      if (ev$1.isInstanceOf[A]) ev$1.asInstanceOf[A].f(a)
   *      else ev$1.asInstanceOf[B].f(a)
   */
  override def transformSelect(tree: Select)(implicit ctx: Context, info: TransformerInfo) = {
    val Select(qual, name) = tree

    def memberDenot(tp: Type): SingleDenotation = {
      val mbr = tp.member(name)
      if (!mbr.isOverloaded) mbr.asSingleDenotation
      else tree.tpe match {
        case tref: TermRefWithSignature => mbr.atSignature(tref.sig).checkUnique
        case _ =>
          def alts = mbr.alternatives.map(alt => i"$alt: ${alt.info}").mkString(", ")
          ctx.error(s"cannot disambiguate overloaded members $alts", tree.pos)
          NoDenotation
      }
    }

    def candidates(tp: Type): List[Symbol] = {
      val mbr = memberDenot(tp)
      if (mbr.symbol.exists) mbr.symbol :: Nil
      else tp.widen match {
        case tref: TypeRef =>
          tref.info match {
            case TypeBounds(_, hi) => candidates(hi)
            case _ => Nil
          }
        case OrType(tp1, tp2) =>
          candidates(tp1) | candidates(tp2)
        case AndType(tp1, tp2) =>
          candidates(tp1) & candidates(tp2)
        case tpw =>
          Nil
      }
    }

    def isStructuralSelect(tp: Type): Boolean = tp.stripTypeVar match {
      case tp: RefinedType => tp.refinedName == name || isStructuralSelect(tp.parent)
      case tp: TypeProxy => isStructuralSelect(tp.underlying)
      case AndType(tp1, tp2) => isStructuralSelect(tp1) || isStructuralSelect(tp2)
      case _ => false
    }

    if (tree.symbol.exists) tree
    else {
      def choose(qual: Tree, syms: List[Symbol]): Tree = {
        def testOrCast(which: Symbol, mbr: Symbol) =
          qual.select(which).appliedToType(mbr.owner.typeRef)
        def select(sym: Symbol) = {
          val qual1 =
            if (qual.tpe derivesFrom sym.owner) qual
            else testOrCast(defn.Any_asInstanceOf, sym)
          qual1.select(sym).withPos(tree.pos)
        }
        syms match {
          case Nil =>
            def msg =
              if (isStructuralSelect(qual.tpe))
                s"cannot access member '$name' from structural type ${qual.tpe.widen.show}; use Dynamic instead"
              else
                s"no candidate symbols for ${tree.tpe.show} found in ${qual.tpe.show}"
            ctx.error(msg, tree.pos)
            tree
          case sym :: Nil =>
            select(sym)
          case sym :: syms1 =>
            If(testOrCast(defn.Any_isInstanceOf, sym), select(sym), choose(qual, syms1))
        }
      }
      evalOnce(qual)(qual => choose(qual, candidates(qual.tpe)))
    }
  }

  /** Distribute arguments among splitted branches */
  def distribute(tree: GenericApply[Type], rebuild: (Tree, List[Tree]) => Context => Tree)(implicit ctx: Context) = {
    def recur(fn: Tree): Tree = fn match {
      case Block(stats, expr) => Block(stats, recur(expr))
      case If(cond, thenp, elsep) => If(cond, recur(thenp), recur(elsep))
      case _ => rebuild(fn, tree.args)(ctx) withPos tree.pos
    }
    recur(tree.fun)
  }

  override def transformTypeApply(tree: TypeApply)(implicit ctx: Context, info: TransformerInfo) =
    distribute(tree, typeApply)

  override def transformApply(tree: Apply)(implicit ctx: Context, info: TransformerInfo) =
    distribute(tree, apply)

  private val typeApply = (fn: Tree, args: List[Tree]) => (ctx: Context) => TypeApply(fn, args)(ctx)
  private val apply     = (fn: Tree, args: List[Tree]) => (ctx: Context) => Apply(fn, args)(ctx)
}