-
Notifications
You must be signed in to change notification settings - Fork 1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #4045 from dotty-staging/fix-3324
Fix #3324: add `isInstanceOf` check
- Loading branch information
Showing
21 changed files
with
384 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
133 changes: 133 additions & 0 deletions
133
compiler/src/dotty/tools/dotc/transform/IsInstanceOfChecker.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
package dotty.tools.dotc | ||
package transform | ||
|
||
import util.Positions._ | ||
import MegaPhase.MiniPhase | ||
import core._ | ||
import Contexts.Context, Types._, Decorators._, Symbols._, typer._, ast._, NameKinds._ | ||
import TypeUtils._, Flags._ | ||
import config.Printers.{ transforms => debug } | ||
|
||
/** Check runtime realizability of type test, see the documentation for `Checkable`. | ||
*/ | ||
class IsInstanceOfChecker extends MiniPhase { | ||
|
||
import ast.tpd._ | ||
|
||
val phaseName = "isInstanceOfChecker" | ||
|
||
override def transformTypeApply(tree: TypeApply)(implicit ctx: Context): Tree = { | ||
def ensureCheckable(qual: Tree, pt: Tree): Tree = { | ||
if (!Checkable.checkable(qual.tpe, pt.tpe, tree.pos)) | ||
ctx.warning( | ||
s"the type test for ${pt.show} cannot be checked at runtime", | ||
tree.pos | ||
) | ||
|
||
tree | ||
} | ||
|
||
tree.fun match { | ||
case fn: Select | ||
if fn.symbol == defn.Any_typeTest || fn.symbol == defn.Any_isInstanceOf => | ||
ensureCheckable(fn.qualifier, tree.args.head) | ||
case _ => tree | ||
} | ||
} | ||
} | ||
|
||
object Checkable { | ||
import Inferencing._ | ||
import ProtoTypes._ | ||
|
||
/** Whether `(x:X).isInstanceOf[P]` can be checked at runtime? | ||
* | ||
* First do the following substitution: | ||
* (a) replace `T @unchecked` and pattern binder types (e.g., `_$1`) in P with WildcardType | ||
* (b) replace pattern binder types (e.g., `_$1`) in X: | ||
* - variance = 1 : hiBound | ||
* - variance = -1 : loBound | ||
* - variance = 0 : OrType(Any, Nothing) // TODO: use original type param bounds | ||
* | ||
* Then check: | ||
* | ||
* 1. if `X <:< P`, TRUE | ||
* 2. if `P` is a singleton type, TRUE | ||
* 3. if `P` refers to an abstract type member or type parameter, FALSE | ||
* 4. if `P = Array[T]`, checkable(E, T) where `E` is the element type of `X`, defaults to `Any`. | ||
* 5. if `P` is `pre.F[Ts]` and `pre.F` refers to a class which is not `Array`: | ||
* (a) replace `Ts` with fresh type variables `Xs` | ||
* (b) constrain `Xs` with `pre.F[Xs] <:< X` | ||
* (c) instantiate Xs and check `pre.F[Xs] <:< P` | ||
* 6. if `P = T1 | T2` or `P = T1 & T2`, checkable(X, T1) && checkable(X, T2). | ||
* 7. if `P` is a refinement type, FALSE | ||
* 8. otherwise, TRUE | ||
*/ | ||
def checkable(X: Type, P: Type, pos: Position)(implicit ctx: Context): Boolean = { | ||
def isAbstract(P: Type) = !P.dealias.typeSymbol.isClass | ||
def isPatternTypeSymbol(sym: Symbol) = !sym.isClass && sym.is(Case) | ||
|
||
def replaceP(implicit ctx: Context) = new TypeMap { | ||
def apply(tp: Type) = tp match { | ||
case tref: TypeRef | ||
if isPatternTypeSymbol(tref.typeSymbol) => WildcardType | ||
case AnnotatedType(_, annot) | ||
if annot.symbol == defn.UncheckedAnnot => WildcardType | ||
case _ => mapOver(tp) | ||
} | ||
} | ||
|
||
def replaceX(implicit ctx: Context) = new TypeMap { | ||
def apply(tp: Type) = tp match { | ||
case tref: TypeRef | ||
if isPatternTypeSymbol(tref.typeSymbol) => | ||
if (variance == 1) tref.info.hiBound | ||
else if (variance == -1) tref.info.loBound | ||
else OrType(defn.AnyType, defn.NothingType) | ||
case _ => mapOver(tp) | ||
} | ||
} | ||
|
||
def isClassDetermined(X: Type, P: AppliedType)(implicit ctx: Context) = { | ||
val AppliedType(tycon, _) = P | ||
val typeLambda = tycon.ensureLambdaSub.asInstanceOf[TypeLambda] | ||
val tvars = constrained(typeLambda, untpd.EmptyTree, alwaysAddTypeVars = true)._2.map(_.tpe) | ||
val P1 = tycon.appliedTo(tvars) | ||
|
||
debug.println("P : " + P.show) | ||
debug.println("P1 : " + P1.show) | ||
debug.println("X : " + X.show) | ||
|
||
P1 <:< X // may fail, ignore | ||
|
||
val res = isFullyDefined(P1, ForceDegree.noBottom) && P1 <:< P | ||
debug.println("P1 : " + P1) | ||
debug.println("P1 <:< P = " + res) | ||
|
||
res | ||
} | ||
|
||
def recur(X: Type, P: Type): Boolean = (X <:< P) || (P match { | ||
case _: SingletonType => true | ||
case _: TypeProxy | ||
if isAbstract(P) => false | ||
case defn.ArrayOf(tpT) => | ||
X match { | ||
case defn.ArrayOf(tpE) => recur(tpE, tpT) | ||
case _ => recur(defn.AnyType, tpT) | ||
} | ||
case tpe: AppliedType => isClassDetermined(X, tpe)(ctx.fresh.setNewTyperState()) | ||
case AndType(tp1, tp2) => recur(X, tp1) && recur(X, tp2) | ||
case OrType(tp1, tp2) => recur(X, tp1) && recur(X, tp2) | ||
case AnnotatedType(t, _) => recur(X, t) | ||
case _: RefinedType => false | ||
case _ => true | ||
}) | ||
|
||
val res = recur(replaceX.apply(X.widen), replaceP.apply(P)) | ||
|
||
debug.println(i"checking ${X.show} isInstanceOf ${P} = $res") | ||
|
||
res | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
class Test { | ||
def remove[S](a: S | Int, f: Int => S):S = a match { | ||
case a: S => a // error | ||
case a: Int => f(a) | ||
} | ||
|
||
val t: Int | String = 5 | ||
val t1 = remove[String](t, _.toString) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
class C[T] { | ||
val x: Any = ??? | ||
if (x.isInstanceOf[List[String]]) // error: unchecked | ||
if (x.isInstanceOf[T]) // error: unchecked | ||
x match { | ||
case x: List[String] => // error: unchecked | ||
case x: T => // error: unchecked | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
trait C[T] | ||
class D[T] | ||
|
||
class Test { | ||
def foo[T](x: C[T]) = x match { | ||
case _: D[T] => // error | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
class Test { | ||
trait A[+T] | ||
class B[T] extends A[T] | ||
class C[T] extends B[Any] with A[T] | ||
|
||
def foo[T](c: C[T]): Unit = c match { | ||
case _: B[T] => // error | ||
} | ||
|
||
def bar[T](b: B[T]): Unit = b match { | ||
case _: A[T] => | ||
} | ||
|
||
def quux[T](a: A[T]): Unit = a match { | ||
case _: B[T] => // should be an error!! | ||
} | ||
|
||
quux(new C[Int]) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
object Test { | ||
trait Foo | ||
case class One[+T](fst: T) | ||
|
||
def bad[T <: Foo](e: One[T])(x: T) = e match { | ||
case foo: One[a] => | ||
x.isInstanceOf[a] // error | ||
val y: Any = ??? | ||
y.isInstanceOf[a] // error | ||
} | ||
} | ||
|
||
object Test2 { | ||
case class One[T](fst: T) | ||
|
||
def bad[T](e: One[T])(x: T) = e match { | ||
case foo: One[a] => | ||
x.isInstanceOf[a] // error | ||
val y: Any = ??? | ||
y.isInstanceOf[a] // error | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
sealed trait Exp[T] | ||
case class Fun[A, B](f: Exp[A => B]) extends Exp[A => B] | ||
|
||
class Test { | ||
def eval[T](e: Exp[T]) = e match { | ||
case Fun(x: Fun[Int, Double]) => ??? // error | ||
case Fun(x: Exp[Int => String]) => ??? // error | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
class Foo { | ||
def foo(x: Any): Boolean = | ||
x.isInstanceOf[List[String]] // error | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
sealed trait A[T] | ||
class B[T] extends A[T] | ||
|
||
class Test { | ||
def f(x: B[Int]) = x match { case _: A[Int] if true => } | ||
|
||
def g(x: A[Int]) = x match { case _: B[Int] => } | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
class Test { | ||
val x: Any = ??? | ||
|
||
x match { | ||
case _: List[Int @unchecked] => 5 | ||
case _: List[Int] @unchecked => 5 | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
class C[T] { | ||
val x: T = ??? | ||
x.isInstanceOf[T] | ||
|
||
val y: Array[T] = ??? | ||
|
||
y match { | ||
case x: Array[T] => | ||
} | ||
|
||
type F[X] | ||
|
||
val z: F[T] = ??? | ||
z match { | ||
case x: F[T] => | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
object Test { | ||
trait Marker | ||
def foo[T](x: T) = x match { | ||
case _: (T & Marker) => // no warning | ||
case _: T with Marker => // scalac emits a warning | ||
case _ => | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
object p { | ||
|
||
// test parametric case classes, which synthesis `canEqual` and `equals` | ||
enum Result[+T, +E] { | ||
case OK [T](x: T) extends Result[T, Nothing] | ||
case Err[E](e: E) extends Result[Nothing, E] | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import scala.reflect.ClassTag | ||
|
||
object IsInstanceOfClassTag { | ||
def safeCast[T: ClassTag](x: Any): Option[T] = { | ||
x match { | ||
case x: T => Some(x) | ||
case _ => None | ||
} | ||
} | ||
} |
Oops, something went wrong.