Skip to content

Commit b96b5cc

Browse files
committed
Fix scala#3324: introduce IsInstanceOfChecker
add check for runtime realizability of type test
1 parent 2fcb7b0 commit b96b5cc

File tree

6 files changed

+109
-0
lines changed

6 files changed

+109
-0
lines changed

compiler/src/dotty/tools/dotc/Compiler.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ class Compiler {
8181
new CrossCastAnd, // Normalize selections involving intersection types.
8282
new Splitter) :: // Expand selections involving union types into conditionals
8383
List(new ErasedDecls, // Removes all erased defs and vals decls (except for parameters)
84+
new IsInstanceOfChecker, // check runtime realisability for `isInstanceOf`
8485
new VCInlineMethods, // Inlines calls to value class methods
8586
new SeqLiterals, // Express vararg arguments as arrays
8687
new InterceptedMethods, // Special handling of `==`, `|=`, `getClass` methods
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package dotty.tools.dotc
2+
package transform
3+
4+
import util.Positions._
5+
import MegaPhase.MiniPhase
6+
import core._
7+
import Contexts.Context, Types._, Decorators._, Symbols._, typer._
8+
import TypeUtils._, Flags._
9+
import config.Printers.{ transforms => debug }
10+
11+
/** check runtime realizability of type test
12+
*/
13+
class IsInstanceOfChecker extends MiniPhase {
14+
15+
import ast.tpd._
16+
17+
val phaseName = "isInstanceOfChecker"
18+
19+
override def transformTypeApply(tree: TypeApply)(implicit ctx: Context): Tree = {
20+
def ensureCheckable(qual: Tree, pt: Tree): Tree = {
21+
if (!Checkable.checkable(qual.tpe, pt.tpe))
22+
ctx.warning(
23+
s"the type test for ${pt.show} cannot be checked at runtime",
24+
tree.pos
25+
)
26+
27+
tree
28+
}
29+
30+
tree.fun match {
31+
case fn: Select if fn.symbol == defn.Any_typeTest =>
32+
ensureCheckable(fn.qualifier, tree.args.head)
33+
case fn: Select if fn.symbol == defn.Any_isInstanceOf =>
34+
ensureCheckable(fn.qualifier, tree.args.head)
35+
case _ => tree
36+
}
37+
}
38+
}
39+
40+
object Checkable {
41+
import Inferencing._
42+
import ProtoTypes._
43+
44+
/** Whether `(x:X).isInstanceOf[P]` can be checked at runtime?
45+
*
46+
* The following cases are not checkable at runtime:
47+
*
48+
* 1. if `P` refers to an abstract type member
49+
* 2. if `P` is `pre.F[Ts]` and `pre.F` refers to a class:
50+
* (a) replace `Ts` with fresh type variables `Xs`
51+
* (b) instantiate `Xs` with the constraint `pre.F[Xs] <:< X`
52+
* (c) `pre.F[Xs] <:< P` doesn't hold
53+
* 3. if `P = T1 | T2` or `P = T1 & T2`, checkable(X, T1) && checkable(X, T2).
54+
*/
55+
def checkable(X: Type, P: Type)(implicit ctx: Context): Boolean = {
56+
def Psym = P.dealias.typeSymbol
57+
58+
def isAbstract = !Psym.isClass
59+
60+
def isClassDetermined(tpe: AppliedType) = {
61+
val AppliedType(tycon, args) = tpe
62+
val tvars = tycon.typeParams.map { tparam => newTypeVar(tparam.paramInfo.bounds) }
63+
val P2 = tycon.appliedTo(tvars)
64+
65+
debug.println("P2 : " + P2)
66+
debug.println("X : " + X)
67+
68+
!(P2 <:< X.widen) || {
69+
val syms = maximizeType(P2, Psym.pos, fromScala2x = false)
70+
val res = P2 <:< P
71+
debug.println("P2: " + P2.show)
72+
debug.println("P2 <:< P = " + res)
73+
res
74+
}
75+
}
76+
77+
P match {
78+
case tpe: AppliedType => !isAbstract && isClassDetermined(tpe)
79+
case AndType(tp1, tp2) => checkable(X, tp1) && checkable(X, tp2)
80+
case OrType(tp1, tp2) => checkable(X, tp1) && checkable(X, tp2)
81+
case _ => !isAbstract
82+
}
83+
}
84+
}

compiler/test/dotty/tools/dotc/CompilationTests.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ class CompilationTests extends ParallelTesting {
106106
compileFilesInDir("tests/pos-deep-subtype", allowDeepSubtypes) +
107107
compileFilesInDir("tests/pos-kind-polymorphism", defaultOptions and "-Ykind-polymorphism") +
108108
compileDir("tests/pos/i1137-1", defaultOptions and "-Yemit-tasty") +
109+
compileDir("tests/neg-custom-args/isInstanceOf", defaultOptions and "-Xfatal-warnings") +
109110
compileFile(
110111
// succeeds despite -Xfatal-warnings because of -nowarn
111112
"tests/neg-custom-args/fatal-warnings/xfatalWarnings.scala",
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
class C[T] {
2+
val x: Any = ???
3+
if (x.isInstanceOf[List[String]]) // error: unchecked
4+
if (x.isInstanceOf[T]) // error: unchecked
5+
x match {
6+
case x: List[String] => // error: unchecked
7+
case x: T => // error: unchecked
8+
}
9+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
sealed trait A[T]
2+
class B[T] extends A[T]
3+
4+
class Test {
5+
def f(x: B[Int]) = x match { case _: A[Int] if true => }
6+
7+
def g(x: A[Int]) = x match { case _: B[Int] => }
8+
9+
def foo(x: Any) = x.isInstanceOf[List[String]] // error
10+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
class Foo {
2+
def foo(x: Any): Boolean =
3+
x.isInstanceOf[List[String]] // error
4+
}

0 commit comments

Comments
 (0)