Skip to content

Commit 0cd96ae

Browse files
committed
Rewrite TypeApply to null-check on rewrite to true, add docstrings
1 parent ced732f commit 0cd96ae

File tree

1 file changed

+82
-23
lines changed

1 file changed

+82
-23
lines changed

src/dotty/tools/dotc/transform/IsInstanceOfEvaluator.scala

Lines changed: 82 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,95 @@
11
package dotty.tools.dotc
22
package transform
33

4-
import dotty.tools.dotc.core.Contexts.Context
5-
import dotty.tools.dotc.core.Types._
6-
import dotty.tools.dotc.core.Decorators._
7-
import dotty.tools.dotc.core.Flags
8-
import dotty.tools.dotc.core.Symbols._
94
import dotty.tools.dotc.util.Positions._
10-
import dotty.tools.dotc.core.TypeErasure._
115
import TreeTransforms.{MiniPhaseTransform, TransformerInfo}
12-
6+
import core._
7+
import Contexts.Context, Types._, Constants._, Decorators._, Symbols._
8+
import TypeUtils._, TypeErasure._
9+
10+
11+
/** Implements partial `isInstanceOf` evaluation according to the matrix on:
12+
* https://github.com/lampepfl/dotty/issues/1255
13+
*
14+
* This is a generalized solution to raising an error on unreachable match
15+
* cases and warnings on other statically known results of `isInstanceOf`.
16+
*
17+
* Steps taken:
18+
*
19+
* 1. evalTypeApply will establish the matrix and choose the appropriate
20+
* handling for the case:
21+
* 2. a) handleStaticallyKnown
22+
* b) falseIfUnrelated with `scrutinee <:< selector`
23+
* c) handleFalseUnrelated
24+
* d) leave as is (aka `happens`)
25+
* 3. Rewrite according to step taken in `2`
26+
*/
1327
class IsInstanceOfEvaluator extends MiniPhaseTransform { thisTransformer =>
1428

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

17-
def phaseName = "reachabilityChecker"
18-
31+
def phaseName = "isInstanceOfEvaluator"
1932
override def transformTypeApply(tree: TypeApply)(implicit ctx: Context, info: TransformerInfo): Tree = {
2033
val defn = ctx.definitions
2134

22-
def handleStaticallyKnown(tree: TypeApply, scrutinee: Type, selector: Type, inMatch: Boolean, pos: Position): Tree =
35+
/** Handles the four cases of statically known `isInstanceOf`s and gives
36+
* the correct warnings, or an error if statically known to be false in
37+
* match
38+
*/
39+
def handleStaticallyKnown(tree: Select, scrutinee: Type, selector: Type, inMatch: Boolean, pos: Position): Tree =
2340
if (!(scrutinee <:< selector) && inMatch) {
24-
ctx.error(s"this case is unreachable due to `${selector.show}` not being a subclass of `${scrutinee.show}`", pos)
25-
tree
41+
ctx.error(
42+
s"this case is unreachable due to `${selector.show}` not being a subclass of `${scrutinee.show}`",
43+
Position(pos.start - 5, pos.end - 5)
44+
)
45+
rewrite(tree, to = false)
2646
} else if (!(scrutinee <:< selector) && !inMatch) {
27-
ctx.warning(s"this will always yield false since `${scrutinee.show}` is not a subclass of `${selector.show}` (will be optimized away)", pos)
47+
ctx.warning(
48+
s"this will always yield false since `${scrutinee.show}` is not a subclass of `${selector.show}` (will be optimized away)",
49+
pos
50+
)
2851
rewrite(tree, to = false)
2952
} else if (scrutinee <:< selector && !inMatch) {
30-
ctx.warning(s"this will always yield true since `${scrutinee.show}` is a subclass of `${selector.show}` (will be optimized away)", pos)
53+
ctx.warning(
54+
s"this will always yield true since `${scrutinee.show}` is a subclass of `${selector.show}` (will be optimized away)",
55+
pos
56+
)
3157
rewrite(tree, to = true)
3258
} else /* if (scrutinee <:< selector && inMatch) */ rewrite(tree, to = true)
3359

34-
def rewrite(tree: TypeApply, to: Boolean): Tree = tree
60+
/** Rewrites cases with unrelated types */
61+
def handleFalseUnrelated(tree: Select, scrutinee: Type, selector: Type, inMatch: Boolean) =
62+
if (inMatch) {
63+
ctx.error(
64+
s"will never match since `${selector.show}` is not a subclass of `${scrutinee.show}`",
65+
Position(tree.pos.start - 5, tree.pos.end - 5)
66+
)
67+
rewrite(tree, to = false)
68+
} else {
69+
ctx.warning(
70+
s"will always yield false since `${scrutinee.show}` is not a subclass of `${selector.show}`",
71+
tree.pos
72+
)
73+
rewrite(tree, to = false)
74+
}
3575

76+
/** Rewrites the select to a boolean if `to` is false or if the qualifier
77+
* is a primitive.
78+
*
79+
* If `to` is set to true and the qualifier is not a primitive, the
80+
* instanceOf is replaced by a null check, since:
81+
*
82+
* `srutinee.isInstanceOf[Selector]` if `scrutinee eq null`
83+
*/
84+
def rewrite(tree: Select, to: Boolean): Tree =
85+
if (!to || tree.qualifier.tpe.widen.isPrimitiveValueType)
86+
Literal(Constant(to))
87+
else
88+
Apply(tree.qualifier.select(defn.Object_ne), List(Literal(Constant(null))))
89+
90+
/** Attempts to rewrite TypeApply to either `scrutinee ne null` or a
91+
* constant
92+
*/
3693
def evalTypeApply(tree: TypeApply): Tree =
3794
if (tree.symbol != defn.Any_isInstanceOf) tree
3895
else tree.fun match {
@@ -66,19 +123,21 @@ class IsInstanceOfEvaluator extends MiniPhaseTransform { thisTransformer =>
66123
(scClassNonFinal && selFinalClass) ||
67124
(scTrait && selFinalClass)
68125

69-
// Doesn't need to be calculated (since true if others are false)
70-
//val happens =
71-
// (scClassNonFinal && selClassNonFinal) ||
72-
// (scTrait && selClassNonFinal) ||
73-
// (scTrait && selTrait)
126+
val happens =
127+
(scClassNonFinal && selClassNonFinal) ||
128+
(scTrait && selClassNonFinal) ||
129+
(scTrait && selTrait)
74130

75131
val inMatch = s.qualifier.symbol is Flags.Case
76132

77133
if (knownStatically)
78-
handleStaticallyKnown(tree, scrutinee, selector, inMatch, tree.pos)
134+
handleStaticallyKnown(s, scrutinee, selector, inMatch, tree.pos)
135+
else if (falseIfUnrelated && scrutinee <:< selector)
136+
rewrite(s, to = true)
79137
else if (falseIfUnrelated && !(selector <:< scrutinee))
80-
rewrite(tree, to = false)
81-
else /*if (happens)*/ tree
138+
handleFalseUnrelated(s, scrutinee, selector, inMatch)
139+
else if (happens) tree
140+
else tree
82141
}
83142

84143
case _ => tree

0 commit comments

Comments
 (0)