Skip to content

Commit 4b35f6f

Browse files
committed
Merge pull request #1275 from felixmulder/topic/partial-eval-isInstanceOf
Add partial evaluation of `isInstanceOf` mini-phase
2 parents 1f4c9f4 + 73f099b commit 4b35f6f

File tree

10 files changed

+196
-5
lines changed

10 files changed

+196
-5
lines changed

src/dotty/tools/dotc/Compiler.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ class Compiler {
6161
new CrossCastAnd, // Normalize selections involving intersection types.
6262
new Splitter), // Expand selections involving union types into conditionals
6363
List(new VCInlineMethods, // Inlines calls to value class methods
64+
new IsInstanceOfEvaluator, // Issues warnings when unreachable statements are present in match/if expressions
6465
new SeqLiterals, // Express vararg arguments as arrays
6566
new InterceptedMethods, // Special handling of `==`, `|=`, `getClass` methods
6667
new Getters, // Replace non-private vals and vars with getter defs (fields are added later)
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
package dotty.tools.dotc
2+
package transform
3+
4+
import dotty.tools.dotc.util.Positions._
5+
import TreeTransforms.{MiniPhaseTransform, TransformerInfo}
6+
import core._
7+
import Contexts.Context, Types._, Constants._, Decorators._, Symbols._
8+
import TypeUtils._, TypeErasure._, Flags._
9+
10+
11+
/** Implements partial evaluation of `sc.isInstanceOf[Sel]` according to:
12+
*
13+
* +-------------+----------------------------+----------------------------+------------------+
14+
* | Sel\sc | trait | class | final class |
15+
* +-------------+----------------------------+----------------------------+------------------+
16+
* | trait | ? | ? | statically known |
17+
* | class | ? | false if classes unrelated | statically known |
18+
* | final class | false if classes unrelated | false if classes unrelated | statically known |
19+
* +-------------+----------------------------+----------------------------+------------------+
20+
*
21+
* This is a generalized solution to raising an error on unreachable match
22+
* cases and warnings on other statically known results of `isInstanceOf`.
23+
*
24+
* Steps taken:
25+
*
26+
* 1. evalTypeApply will establish the matrix and choose the appropriate
27+
* handling for the case:
28+
* 2. a) Sel/sc is a value class or scrutinee is `Any`
29+
* b) handleStaticallyKnown
30+
* c) falseIfUnrelated with `scrutinee <:< selector`
31+
* d) handleFalseUnrelated
32+
* e) leave as is (aka `happens`)
33+
* 3. Rewrite according to step taken in `2`
34+
*/
35+
class IsInstanceOfEvaluator extends MiniPhaseTransform { thisTransformer =>
36+
37+
import dotty.tools.dotc.ast.tpd._
38+
39+
def phaseName = "isInstanceOfEvaluator"
40+
override def transformTypeApply(tree: TypeApply)(implicit ctx: Context, info: TransformerInfo): Tree = {
41+
val defn = ctx.definitions
42+
43+
/** Handles the four cases of statically known `isInstanceOf`s and gives
44+
* the correct warnings, or an error if statically known to be false in
45+
* match
46+
*/
47+
def handleStaticallyKnown(select: Select, scrutinee: Type, selector: Type, inMatch: Boolean, pos: Position): Tree = {
48+
val scrutineeSubSelector = scrutinee <:< selector
49+
if (!scrutineeSubSelector && inMatch) {
50+
ctx.error(
51+
s"this case is unreachable due to `${selector.show}` not being a subclass of `${scrutinee.show}`",
52+
Position(pos.start - 5, pos.end - 5)
53+
)
54+
rewrite(select, to = false)
55+
} else if (!scrutineeSubSelector && !inMatch) {
56+
ctx.warning(
57+
s"this will always yield false since `${scrutinee.show}` is not a subclass of `${selector.show}` (will be optimized away)",
58+
pos
59+
)
60+
rewrite(select, to = false)
61+
} else if (scrutineeSubSelector && !inMatch) {
62+
ctx.warning(
63+
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)",
64+
pos
65+
)
66+
rewrite(select, to = true)
67+
} else /* if (scrutineeSubSelector && inMatch) */ rewrite(select, to = true)
68+
}
69+
70+
/** Rewrites cases with unrelated types */
71+
def handleFalseUnrelated(select: Select, scrutinee: Type, selector: Type, inMatch: Boolean) =
72+
if (inMatch) {
73+
ctx.error(
74+
s"will never match since `${selector.show}` is not a subclass of `${scrutinee.show}`",
75+
Position(select.pos.start - 5, select.pos.end - 5)
76+
)
77+
rewrite(select, to = false)
78+
} else {
79+
ctx.warning(
80+
s"will always yield false since `${scrutinee.show}` is not a subclass of `${selector.show}`",
81+
select.pos
82+
)
83+
rewrite(select, to = false)
84+
}
85+
86+
/** Rewrites the select to a boolean if `to` is false or if the qualifier
87+
* is a value class.
88+
*
89+
* If `to` is set to true and the qualifier is not a primitive, the
90+
* instanceOf is replaced by a null check, since:
91+
*
92+
* `scrutinee.isInstanceOf[Selector]` if `scrutinee eq null`
93+
*/
94+
def rewrite(tree: Select, to: Boolean): Tree =
95+
if (!to || !tree.qualifier.tpe.widen.derivesFrom(defn.AnyRefAlias)) {
96+
val literal = Literal(Constant(to))
97+
if (!isPureExpr(tree.qualifier)) Block(List(tree.qualifier), literal)
98+
else literal
99+
} else
100+
Apply(tree.qualifier.select(defn.Object_ne), List(Literal(Constant(null))))
101+
102+
/** Attempts to rewrite TypeApply to either `scrutinee ne null` or a
103+
* constant
104+
*/
105+
def evalTypeApply(tree: TypeApply): Tree =
106+
if (tree.symbol != defn.Any_isInstanceOf) tree
107+
else tree.fun match {
108+
case s: Select => {
109+
val scrutinee = erasure(s.qualifier.tpe.widen)
110+
val selector = erasure(tree.args.head.tpe.widen)
111+
112+
val scTrait = scrutinee.typeSymbol is Trait
113+
val scClass =
114+
scrutinee.typeSymbol.isClass &&
115+
!(scrutinee.typeSymbol is Trait) &&
116+
!(scrutinee.typeSymbol is Module)
117+
118+
val scClassNonFinal = scClass && !(scrutinee.typeSymbol is Final)
119+
val scFinalClass = scClass && (scrutinee.typeSymbol is Final)
120+
121+
val selTrait = selector.typeSymbol is Trait
122+
val selClass =
123+
selector.typeSymbol.isClass &&
124+
!(selector.typeSymbol is Trait) &&
125+
!(selector.typeSymbol is Module)
126+
127+
val selClassNonFinal = scClass && !(selector.typeSymbol is Final)
128+
val selFinalClass = scClass && (selector.typeSymbol is Final)
129+
130+
// Cases ---------------------------------
131+
val valueClassesOrAny =
132+
ValueClasses.isDerivedValueClass(scrutinee.typeSymbol) ||
133+
ValueClasses.isDerivedValueClass(selector.typeSymbol) ||
134+
scrutinee == defn.ObjectType
135+
136+
val knownStatically = scFinalClass
137+
138+
val falseIfUnrelated =
139+
(scClassNonFinal && selClassNonFinal) ||
140+
(scClassNonFinal && selFinalClass) ||
141+
(scTrait && selFinalClass)
142+
143+
val happens =
144+
(scClassNonFinal && selClassNonFinal) ||
145+
(scTrait && selClassNonFinal) ||
146+
(scTrait && selTrait)
147+
148+
val inMatch = s.qualifier.symbol is Case
149+
150+
if (valueClassesOrAny) tree
151+
else if (knownStatically)
152+
handleStaticallyKnown(s, scrutinee, selector, inMatch, tree.pos)
153+
else if (falseIfUnrelated && scrutinee <:< selector)
154+
// scrutinee is a subtype of the selector, safe to rewrite
155+
rewrite(s, to = true)
156+
else if (falseIfUnrelated && !(selector <:< scrutinee))
157+
// selector and scrutinee are unrelated
158+
handleFalseUnrelated(s, scrutinee, selector, inMatch)
159+
else if (happens) tree
160+
else tree
161+
}
162+
163+
case _ => tree
164+
}
165+
166+
evalTypeApply(tree)
167+
}
168+
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans
5656

5757
override def transformTry(tree: tpd.Try)(implicit ctx: Context, info: TransformerInfo): tpd.Tree = {
5858
val selector =
59-
ctx.newSymbol(ctx.owner, ctx.freshName("ex").toTermName, Flags.Synthetic, defn.ThrowableType, coord = tree.pos)
59+
ctx.newSymbol(ctx.owner, ctx.freshName("ex").toTermName, Flags.Synthetic | Flags.Case, defn.ThrowableType, coord = tree.pos)
6060
val sel = Ident(selector.termRef).withPos(tree.pos)
6161
val rethrow = tpd.CaseDef(EmptyTree, EmptyTree, Throw(ref(selector)))
6262
val newCases = tpd.CaseDef(
@@ -80,7 +80,7 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer {thisTrans
8080
// assert(owner ne null); assert(owner ne NoSymbol)
8181
def freshSym(pos: Position, tp: Type = NoType, prefix: String = "x", owner: Symbol = ctx.owner) = {
8282
ctr += 1
83-
ctx.newSymbol(owner, ctx.freshName(prefix + ctr).toTermName, Flags.Synthetic, tp, coord = pos)
83+
ctx.newSymbol(owner, ctx.freshName(prefix + ctr).toTermName, Flags.Synthetic | Flags.Case, tp, coord = pos)
8484
}
8585

8686
def newSynthCaseLabel(name: String, tpe:Type, owner: Symbol = ctx.owner) =
File renamed without changes.
File renamed without changes.

tests/neg/i1255.scala

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
object Test {
2+
def foo(x: Option[Int]) = x match {
3+
case Some(_: Double) => true // error
4+
case None => true
5+
}
6+
}

tests/pos/t1168.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
object Test extends App {
22

3-
trait SpecialException {}
3+
trait SpecialException extends Throwable {}
44

55
try {
66
throw new Exception

tests/run/isInstanceOf-eval.check

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
1
2+
2

tests/run/isInstanceOf-eval.scala

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
object Test extends dotty.runtime.LegacyApp {
2+
lazy val any = {
3+
println(1)
4+
1: Any
5+
}
6+
7+
any.isInstanceOf[Int]
8+
9+
lazy val int = {
10+
println(2)
11+
2
12+
}
13+
14+
int.isInstanceOf[Int]
15+
}

tests/run/t6637.scala

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
21
object Test extends dotty.runtime.LegacyApp {
32
try {
4-
class A ; class B ; List().head.isInstanceOf[A with B]
3+
List().head
54
} catch {
65
case _ :java.util.NoSuchElementException => println("ok")
76
}

0 commit comments

Comments
 (0)