Skip to content

Commit 6fd3971

Browse files
committed
Make IsInstanceOfEvaluator more robust
IsInstanceOfEvaluator as prone to mispredict whether the test was generated by a pattern match or not. We fix this by having a special type test symbol that represents tests generated by a pattern match.
1 parent b2aa27e commit 6fd3971

File tree

5 files changed

+101
-102
lines changed

5 files changed

+101
-102
lines changed

compiler/src/dotty/tools/dotc/core/Definitions.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,7 @@ class Definitions {
255255
// generated by pattern matcher, eliminated by erasure
256256

257257
def AnyMethods = List(Any_==, Any_!=, Any_equals, Any_hashCode,
258-
Any_toString, Any_##, Any_getClass, Any_isInstanceOf, Any_asInstanceOf)
258+
Any_toString, Any_##, Any_getClass, Any_isInstanceOf, Any_asInstanceOf, Any_typeTest)
259259

260260
lazy val ObjectClass: ClassSymbol = {
261261
val cls = ctx.requiredClass("java.lang.Object")

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

Lines changed: 82 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import TypeUtils._, TypeErasure._, Flags._
2020
*
2121
* Steps taken:
2222
*
23-
* 1. `evalTypeApply` will establish the matrix and choose the appropriate
23+
* 1. `evalTypeTest` will establish the matrix and choose the appropriate
2424
* handling for the case:
2525
* - Sel/sc is a value class or scrutinee is `Any`
2626
* - `handleStaticallyKnown`
@@ -45,128 +45,128 @@ class IsInstanceOfEvaluator extends MiniPhaseTransform { thisTransformer =>
4545
* the correct warnings, or an error if statically known to be false in
4646
* match
4747
*/
48-
def handleStaticallyKnown(select: Select, scrutinee: Type, selector: Type, inMatch: Boolean, pos: Position): Tree = {
48+
def handleStaticallyKnown(qualifier: Tree, scrutinee: Type, selector: Type, inMatch: Boolean, pos: Position): Tree = {
4949
val scrutineeSubSelector = scrutinee <:< selector
5050
if (!scrutineeSubSelector && inMatch) {
5151
ctx.error(
5252
s"this case is unreachable due to `${selector.show}` not being a subclass of `${scrutinee.show}`",
5353
Position(pos.start - 5, pos.end - 5)
5454
)
55-
rewrite(select, to = false)
55+
rewrite(qualifier, to = false)
5656
} else if (!scrutineeSubSelector && !inMatch) {
5757
ctx.warning(
5858
s"this will always yield false since `${scrutinee.show}` is not a subclass of `${selector.show}` (will be optimized away)",
5959
pos
6060
)
61-
rewrite(select, to = false)
61+
rewrite(qualifier, to = false)
6262
} else if (scrutineeSubSelector && !inMatch) {
6363
ctx.warning(
6464
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)",
6565
pos
6666
)
67-
rewrite(select, to = true)
68-
} else /* if (scrutineeSubSelector && inMatch) */ rewrite(select, to = true)
67+
rewrite(qualifier, to = true)
68+
} else /* if (scrutineeSubSelector && inMatch) */ rewrite(qualifier, to = true)
6969
}
7070

7171
/** Rewrites cases with unrelated types */
72-
def handleFalseUnrelated(select: Select, scrutinee: Type, selector: Type, inMatch: Boolean) =
72+
def handleFalseUnrelated(qualifier: Tree, scrutinee: Type, selector: Type, inMatch: Boolean) =
7373
if (inMatch) {
7474
ctx.error(
7575
s"will never match since `${selector.show}` is not a subclass of `${scrutinee.show}`",
76-
Position(select.pos.start - 5, select.pos.end - 5)
76+
Position(qualifier.pos.start - 5, qualifier.pos.end - 5) // WHY 5?
7777
)
78-
rewrite(select, to = false)
78+
rewrite(qualifier, to = false)
7979
} else {
8080
ctx.warning(
8181
s"will always yield false since `${scrutinee.show}` is not a subclass of `${selector.show}`",
82-
select.pos
82+
tree.pos
8383
)
84-
rewrite(select, to = false)
84+
rewrite(qualifier, to = false)
8585
}
8686

87-
/** Rewrites the select to a boolean if `to` is false or if the qualifier
87+
/** Rewrites the qualifier of a type test to a boolean if `to` is false or if the qualifier
8888
* is a value class.
8989
*
9090
* If `to` is set to true and the qualifier is not a primitive, the
9191
* instanceOf is replaced by a null check, since:
9292
*
9393
* `scrutinee.isInstanceOf[Selector]` if `scrutinee eq null`
9494
*/
95-
def rewrite(tree: Select, to: Boolean): Tree =
96-
if (!to || !tree.qualifier.tpe.widen.derivesFrom(defn.AnyRefAlias)) {
95+
def rewrite(qualifier: Tree, to: Boolean): Tree =
96+
if (!to || !qualifier.tpe.widen.derivesFrom(defn.AnyRefAlias)) {
9797
val literal = Literal(Constant(to))
98-
if (!isPureExpr(tree.qualifier)) Block(List(tree.qualifier), literal)
98+
if (!isPureExpr(qualifier)) Block(List(qualifier), literal)
9999
else literal
100100
} else
101-
Apply(tree.qualifier.select(defn.Object_ne), List(Literal(Constant(null))))
101+
Apply(qualifier.select(defn.Object_ne), List(Literal(Constant(null))))
102102

103-
/** Attempts to rewrite TypeApply to either `scrutinee ne null` or a
104-
* constant
103+
/** Attempts to rewrite type test to either `scrutinee ne null` or a
104+
* constant. Any_typeTest nodes have been rewritten to Any_isInstanceOf at this point.
105+
* @param tree the whole type test <qualifier>.asInstanceOf[T]
106+
* @param qualifier the <qualifier> part
107+
* @param inMatch tree was a type test generated by a pattern match.
105108
*/
106-
def evalTypeApply(tree: TypeApply): Tree =
107-
if (tree.symbol != defn.Any_isInstanceOf) tree
108-
else tree.fun match {
109-
case s: Select => {
110-
val scrutinee = erasure(s.qualifier.tpe.widen)
111-
val selector = erasure(tree.args.head.tpe.widen)
112-
113-
val scTrait = scrutinee.typeSymbol is Trait
114-
val scClass =
115-
scrutinee.typeSymbol.isClass &&
116-
!(scrutinee.typeSymbol is Trait) &&
117-
!(scrutinee.typeSymbol is Module)
118-
119-
val scClassNonFinal = scClass && !(scrutinee.typeSymbol is Final)
120-
val scFinalClass = scClass && (scrutinee.typeSymbol is Final)
121-
122-
val selTrait = selector.typeSymbol is Trait
123-
val selClass =
124-
selector.typeSymbol.isClass &&
125-
!(selector.typeSymbol is Trait) &&
126-
!(selector.typeSymbol is Module)
127-
128-
val selClassNonFinal = selClass && !(selector.typeSymbol is Final)
129-
val selFinalClass = selClass && (selector.typeSymbol is Final)
130-
131-
// Cases ---------------------------------
132-
val valueClassesOrAny =
133-
ValueClasses.isDerivedValueClass(scrutinee.typeSymbol) ||
134-
ValueClasses.isDerivedValueClass(selector.typeSymbol) ||
135-
scrutinee == defn.ObjectType
136-
137-
val knownStatically = scFinalClass
138-
139-
val falseIfUnrelated =
140-
(scClassNonFinal && selClassNonFinal) ||
141-
(scClassNonFinal && selFinalClass) ||
142-
(scTrait && selFinalClass)
143-
144-
val happens =
145-
(scClassNonFinal && selClassNonFinal) ||
146-
(scTrait && selClassNonFinal) ||
147-
(scTrait && selTrait)
148-
149-
val inMatch = s.qualifier.symbol is Case
150-
// FIXME: This will misclassify case objects! We need to find another way to characterize
151-
// isInstanceOfs generated by matches.
152-
// Probably the most robust way is to use another symbol for the isInstanceOf method.
153-
154-
if (valueClassesOrAny) tree
155-
else if (knownStatically)
156-
handleStaticallyKnown(s, scrutinee, selector, inMatch, tree.pos)
157-
else if (falseIfUnrelated && scrutinee <:< selector)
158-
// scrutinee is a subtype of the selector, safe to rewrite
159-
rewrite(s, to = true)
160-
else if (falseIfUnrelated && !(selector <:< scrutinee))
161-
// selector and scrutinee are unrelated
162-
handleFalseUnrelated(s, scrutinee, selector, inMatch)
163-
else if (happens) tree
164-
else tree
165-
}
166-
167-
case _ => tree
168-
}
109+
def evalTypeTest(tree: TypeApply, qualifier: Tree, inMatch: Boolean) = {
110+
val scrutinee = erasure(qualifier.tpe.widen)
111+
val selector = erasure(tree.args.head.tpe.widen)
112+
113+
val scTrait = scrutinee.typeSymbol is Trait
114+
val scClass =
115+
scrutinee.typeSymbol.isClass &&
116+
!(scrutinee.typeSymbol is Trait) &&
117+
!(scrutinee.typeSymbol is Module)
118+
119+
val scClassNonFinal = scClass && !(scrutinee.typeSymbol is Final)
120+
val scFinalClass = scClass && (scrutinee.typeSymbol is Final)
121+
122+
val selTrait = selector.typeSymbol is Trait
123+
val selClass =
124+
selector.typeSymbol.isClass &&
125+
!(selector.typeSymbol is Trait) &&
126+
!(selector.typeSymbol is Module)
127+
128+
val selClassNonFinal = selClass && !(selector.typeSymbol is Final)
129+
val selFinalClass = selClass && (selector.typeSymbol is Final)
130+
131+
// Cases ---------------------------------
132+
val valueClassesOrAny =
133+
ValueClasses.isDerivedValueClass(scrutinee.typeSymbol) ||
134+
ValueClasses.isDerivedValueClass(selector.typeSymbol) ||
135+
scrutinee == defn.ObjectType
136+
137+
val knownStatically = scFinalClass
138+
139+
val falseIfUnrelated =
140+
(scClassNonFinal && selClassNonFinal) ||
141+
(scClassNonFinal && selFinalClass) ||
142+
(scTrait && selFinalClass)
143+
144+
val happens =
145+
(scClassNonFinal && selClassNonFinal) ||
146+
(scTrait && selClassNonFinal) ||
147+
(scTrait && selTrait)
148+
149+
if (valueClassesOrAny) tree
150+
else if (knownStatically)
151+
handleStaticallyKnown(qualifier, scrutinee, selector, inMatch, tree.pos)
152+
else if (falseIfUnrelated && scrutinee <:< selector)
153+
// scrutinee is a subtype of the selector, safe to rewrite
154+
rewrite(qualifier, to = true)
155+
else if (falseIfUnrelated && !(selector <:< scrutinee))
156+
// selector and scrutinee are unrelated
157+
handleFalseUnrelated(qualifier, scrutinee, selector, inMatch)
158+
else if (happens) tree
159+
else tree
160+
}
169161

170-
evalTypeApply(tree)
162+
tree.fun match {
163+
case fn: Select if fn.symbol == defn.Any_typeTest =>
164+
evalTypeTest(
165+
cpy.TypeApply(tree)(fn.qualifier.select(defn.Any_isInstanceOf), tree.args),
166+
fn.qualifier, inMatch = true)
167+
case fn: Select if fn.symbol == defn.Any_isInstanceOf =>
168+
evalTypeTest(tree, fn.qualifier, inMatch = false)
169+
case _ => tree
170+
}
171171
}
172172
}

compiler/src/dotty/tools/dotc/transform/PatMat.scala

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ object PatMat {
100100

101101
class TypeTest(scrut: Symbol, tpt: Tree, ons: Node, onf: Node) extends Test(scrut, ons, onf) {
102102
def pos = tpt.pos
103-
def condition = scrutinee.isInstance(tpt.tpe)
103+
def condition = scrutinee.select(defn.Any_typeTest).appliedToType(tpt.tpe)
104104
override def toString = i"TypeTest($scrutinee: $tpt)"
105105
}
106106

@@ -232,16 +232,16 @@ object PatMat {
232232
letAbstract(get) { getResult =>
233233
if (isUnapplySeq)
234234
translateUnApplySeq(getResult, args)
235-
else {
236-
val selectors =
237-
if (args.tail.isEmpty) ref(getResult) :: Nil
238-
else productSelectors(get.tpe).map(ref(getResult).select(_))
239-
new UnApplyTest(unappResult, translateArgs(selectors, args, onSuccess), onFailure)
240-
}
235+
else {
236+
val selectors =
237+
if (args.tail.isEmpty) ref(getResult) :: Nil
238+
else productSelectors(get.tpe).map(ref(getResult).select(_))
239+
new UnApplyTest(unappResult, translateArgs(selectors, args, onSuccess), onFailure)
241240
}
242241
}
243242
}
244243
}
244+
}
245245
}
246246

247247
swapBind(tree) match {
@@ -371,7 +371,7 @@ object PatMat {
371371
transform(node)
372372
}
373373

374-
val seen = mutable.Set[Int]()
374+
val emitted = mutable.Set[Int]()
375375

376376
def emit(node: Node): Tree = {
377377
if (selfCheck) {

compiler/src/dotty/tools/dotc/transform/SuperAccessors.scala

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import SymDenotations._, Symbols._, StdNames._, Annotations._, Trees._, Scopes._
1212
import util.Positions._
1313
import Decorators._
1414
import NameKinds.{ProtectedAccessorName, ProtectedSetterName, OuterSelectName, SuperAccessorName}
15-
import Symbols._, TypeUtils._
15+
import Symbols._, TypeUtils._, SymUtils._
1616

1717
/** This class performs the following functions:
1818
*
@@ -137,15 +137,11 @@ class SuperAccessors(thisTransformer: DenotTransformer) {
137137
/** Disallow some super.XX calls targeting Any methods which would
138138
* otherwise lead to either a compiler crash or runtime failure.
139139
*/
140-
private def isDisallowed(sym: Symbol)(implicit ctx: Context) = {
141-
val d = defn
142-
import d._
143-
(sym eq Any_isInstanceOf) ||
144-
(sym eq Any_asInstanceOf) ||
145-
(sym eq Any_==) ||
146-
(sym eq Any_!=) ||
147-
(sym eq Any_##)
148-
}
140+
private def isDisallowed(sym: Symbol)(implicit ctx: Context) =
141+
sym.isTypeTestOrCast ||
142+
(sym eq defn.Any_==) ||
143+
(sym eq defn.Any_!=) ||
144+
(sym eq defn.Any_##)
149145

150146
/** Replace `sel` (or `sel[targs]` if `targs` is nonempty) with a protected accessor
151147
* call, if necessary.

compiler/src/dotty/tools/dotc/transform/SymUtils.scala

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,11 @@ class SymUtils(val self: Symbol) extends AnyVal {
4444
else directlyInheritedTraits
4545
}
4646

47+
def isTypeTest(implicit ctx: Context): Boolean =
48+
self == defn.Any_isInstanceOf || self == defn.Any_typeTest
49+
4750
def isTypeTestOrCast(implicit ctx: Context): Boolean =
48-
self == defn.Any_asInstanceOf || self == defn.Any_isInstanceOf
51+
self == defn.Any_asInstanceOf || isTypeTest
4952

5053
def isVolatile(implicit ctx: Context) = self.hasAnnotation(defn.VolatileAnnot)
5154

0 commit comments

Comments
 (0)