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

import dotty.tools.dotc.util.Positions._
import TreeTransforms.{MiniPhaseTransform, TransformerInfo}
import core._
import Contexts.Context, Types._, Constants._, Decorators._, Symbols._
import TypeUtils._, TypeErasure._, Flags._

/** Implements partial evaluation of `sc.isInstanceOf[Sel]` according to:
 *
 *  | Sel\sc      | trait                      | class                      | final class      |
 *  | ----------: | :------------------------: | :------------------------: | :--------------: |
 *  | trait       |               ?            |               ?            | statically known |
 *  | class       |               ?            | false if classes unrelated | statically known |
 *  | final class | false if classes unrelated | false if classes unrelated | statically known |
 *
 *  This is a generalized solution to raising an error on unreachable match
 *  cases and warnings on other statically known results of `isInstanceOf`.
 *
 *  Steps taken:
 *
 *  1. `evalTypeApply` will establish the matrix and choose the appropriate
 *     handling for the case:
 *      - Sel/sc is a value class or scrutinee is `Any`
 *      - `handleStaticallyKnown`
 *      - `falseIfUnrelated` with `scrutinee <:< selector`
 *      - `handleFalseUnrelated`
 *      - leave as is (`happens`)
 *  2. Rewrite according to steps taken in 1
 */
class IsInstanceOfEvaluator extends MiniPhaseTransform { thisTransformer =>

  import dotty.tools.dotc.ast.tpd._

  val phaseName = "isInstanceOfEvaluator"

  /** Transforms a [TypeApply](dotty.tools.dotc.ast.Trees.TypeApply) in order to
   *  evaluate an `isInstanceOf` check according to the rules defined above.
   */
  override def transformTypeApply(tree: TypeApply)(implicit ctx: Context, info: TransformerInfo): Tree = {
    val defn = ctx.definitions

    /** Handles the four cases of statically known `isInstanceOf`s and gives
     *  the correct warnings, or an error if statically known to be false in
     *  match
     */
    def handleStaticallyKnown(select: Select, scrutinee: Type, selector: Type, inMatch: Boolean, pos: Position): Tree = {
      val scrutineeSubSelector = scrutinee <:< selector
      if (!scrutineeSubSelector && inMatch) {
        ctx.error(
          s"this case is unreachable due to `${selector.show}` not being a subclass of `${scrutinee.show}`",
          Position(pos.start - 5, pos.end - 5)
        )
        rewrite(select, to = false)
      } else if (!scrutineeSubSelector && !inMatch) {
        ctx.warning(
          s"this will always yield false since `${scrutinee.show}` is not a subclass of `${selector.show}` (will be optimized away)",
          pos
        )
        rewrite(select, to = false)
      } else if (scrutineeSubSelector && !inMatch) {
        ctx.warning(
          s"this will always yield true if the scrutinee is non-null, since `${scrutinee.show}` is a subclass of `${selector.show}` (will be optimized away)",
          pos
        )
        rewrite(select, to = true)
      } else /* if (scrutineeSubSelector && inMatch) */ rewrite(select, to = true)
    }

    /** Rewrites cases with unrelated types */
    def handleFalseUnrelated(select: Select, scrutinee: Type, selector: Type, inMatch: Boolean) =
      if (inMatch) {
        ctx.error(
          s"will never match since `${selector.show}` is not a subclass of `${scrutinee.show}`",
          Position(select.pos.start - 5, select.pos.end - 5)
        )
        rewrite(select, to = false)
      } else {
        ctx.warning(
          s"will always yield false since `${scrutinee.show}` is not a subclass of `${selector.show}`",
          select.pos
        )
        rewrite(select, to = false)
      }

    /** Rewrites the select to a boolean if `to` is false or if the qualifier
     *  is a value class.
     *
     *  If `to` is set to true and the qualifier is not a primitive, the
     *  instanceOf is replaced by a null check, since:
     *
     *  `scrutinee.isInstanceOf[Selector]` if `scrutinee eq null`
     */
    def rewrite(tree: Select, to: Boolean): Tree =
      if (!to || !tree.qualifier.tpe.widen.derivesFrom(defn.AnyRefAlias)) {
        val literal = Literal(Constant(to))
        if (!isPureExpr(tree.qualifier)) Block(List(tree.qualifier), literal)
        else literal
      } else
        Apply(tree.qualifier.select(defn.Object_ne), List(Literal(Constant(null))))

    /** Attempts to rewrite TypeApply to either `scrutinee ne null` or a
     *  constant
     */
    def evalTypeApply(tree: TypeApply): Tree =
      if (tree.symbol != defn.Any_isInstanceOf) tree
      else tree.fun match {
        case s: Select => {
          val scrutinee = erasure(s.qualifier.tpe.widen)
          val selector  = erasure(tree.args.head.tpe.widen)

          val scTrait = scrutinee.typeSymbol is Trait
          val scClass =
            scrutinee.typeSymbol.isClass &&
            !(scrutinee.typeSymbol is Trait) &&
            !(scrutinee.typeSymbol is Module)

          val scClassNonFinal = scClass && !(scrutinee.typeSymbol is Final)
          val scFinalClass    = scClass && (scrutinee.typeSymbol is Final)

          val selTrait = selector.typeSymbol is Trait
          val selClass =
            selector.typeSymbol.isClass &&
            !(selector.typeSymbol is Trait) &&
            !(selector.typeSymbol is Module)

          val selClassNonFinal = selClass && !(selector.typeSymbol is Final)
          val selFinalClass    = selClass && (selector.typeSymbol is Final)

          // Cases ---------------------------------
          val valueClassesOrAny =
            ValueClasses.isDerivedValueClass(scrutinee.typeSymbol) ||
            ValueClasses.isDerivedValueClass(selector.typeSymbol)  ||
            scrutinee == defn.ObjectType

          val knownStatically = scFinalClass

          val falseIfUnrelated =
            (scClassNonFinal && selClassNonFinal) ||
            (scClassNonFinal && selFinalClass)    ||
            (scTrait && selFinalClass)

          val happens =
            (scClassNonFinal && selClassNonFinal) ||
            (scTrait && selClassNonFinal)         ||
            (scTrait && selTrait)

          val inMatch = s.qualifier.symbol is Case
            // FIXME: This will misclassify case objects! We need to find another way to characterize
            // isInstanceOfs generated by matches.
            // Probably the most robust way is to use another symbol for the isInstanceOf method.

          if (valueClassesOrAny) tree
          else if (knownStatically)
            handleStaticallyKnown(s, scrutinee, selector, inMatch, tree.pos)
          else if (falseIfUnrelated && scrutinee <:< selector)
            // scrutinee is a subtype of the selector, safe to rewrite
            rewrite(s, to = true)
          else if (falseIfUnrelated && !(selector <:< scrutinee))
            // selector and scrutinee are unrelated
            handleFalseUnrelated(s, scrutinee, selector, inMatch)
          else if (happens) tree
          else tree
        }

        case _ => tree
      }

    evalTypeApply(tree)
  }
}