Skip to content

Commit 6a14322

Browse files
committed
address review and add more tests
1 parent d0b6b20 commit 6a14322

File tree

5 files changed

+125
-24
lines changed

5 files changed

+125
-24
lines changed

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

Lines changed: 78 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@ package transform
44
import util.Positions._
55
import MegaPhase.MiniPhase
66
import core._
7-
import Contexts.Context, Types._, Decorators._, Symbols._, typer._, ast._
7+
import Contexts.Context, Types._, Decorators._, Symbols._, typer._, ast._, NameKinds._, StdNames._
88
import TypeUtils._, Flags._
99
import config.Printers.{ transforms => debug }
10+
import collection.mutable.Map
1011

1112
/** Check runtime realizability of type test, see the documentation for `Checkable`.
1213
*/
@@ -16,9 +17,24 @@ class IsInstanceOfChecker extends MiniPhase {
1617

1718
val phaseName = "isInstanceOfChecker"
1819

19-
override def transformTypeApply(tree: TypeApply)(implicit ctx: Context): Tree = {
20+
override def transformTemplate(tree: Template)(implicit ctx: Context): Tree = {
21+
// remap to cancel effect of GADT narrowing
22+
val binders = Map.empty[Symbol, Symbol]
23+
24+
new TreeTraverser() {
25+
override def traverse(tree: Tree)(implicit ctx: Context): Unit =
26+
tree match {
27+
case t: TypeApply => checkInstanceOf(t, binders)
28+
case _ => traverseChildren(tree)
29+
}
30+
}.traverse(tree)
31+
32+
tree
33+
}
34+
35+
def checkInstanceOf(tree: TypeApply, binders: Map[Symbol, Symbol])(implicit ctx: Context): Tree = {
2036
def ensureCheckable(qual: Tree, pt: Tree): Tree = {
21-
if (!Checkable.checkable(qual.tpe, pt.tpe))
37+
if (!Checkable.checkable(qual.tpe, pt.tpe, binders, tree.pos))
2238
ctx.warning(
2339
s"the type test for ${pt.show} cannot be checked at runtime",
2440
tree.pos
@@ -43,12 +59,7 @@ object Checkable {
4359

4460
/** Whether `(x:X).isInstanceOf[P]` can be checked at runtime?
4561
*
46-
* First do the following substitution:
47-
* (a) replace `T @unchecked` and pattern binder types (e.g., `_$1`) in P with WildcardType
48-
* (b) replace pattern binder types (e.g., `_$1`) in X:
49-
* - variance = 1 : hiBound
50-
* - variance = -1 : loBound
51-
* - variance = 0 : OrType(Any, Nothing) // TODO: use original type param bounds
62+
* First cancel the effect of GADT bound narrowing with the map `binders`.
5263
*
5364
* Then check:
5465
*
@@ -58,13 +69,14 @@ object Checkable {
5869
* 4. if `P = Array[T]`, checkable(E, T) where `E` is the element type of `X`, defaults to `Any`.
5970
* 5. if `P` is `pre.F[Ts]` and `pre.F` refers to a class which is not `Array`:
6071
* (a) replace `Ts` with fresh type variables `Xs`
61-
* (b) constrain `Xs` with `pre.F[Xs] <:< X` (may fail)
72+
* (b) constrain `Xs` with `pre.F[Xs] <:< X`,
73+
* if `X` cannot be uniquely determined, instantiate `X` with fresh type symbol.
6274
* (c) instantiate Xs and check `pre.F[Xs] <:< P`
6375
* 6. if `P = T1 | T2` or `P = T1 & T2`, checkable(X, T1) && checkable(X, T2).
6476
* 7. if `P` is a refinement type, FALSE
6577
* 8. otherwise, TRUE
6678
*/
67-
def checkable(X: Type, P: Type)(implicit ctx: Context): Boolean = {
79+
def checkable(X: Type, P: Type, binders: Map[Symbol, Symbol], pos: Position)(implicit ctx: Context): Boolean = {
6880
def isAbstract(P: Type) = !P.dealias.typeSymbol.isClass
6981

7082
def replaceP(implicit ctx: Context) = new TypeMap {
@@ -88,6 +100,40 @@ object Checkable {
88100
}
89101
}
90102

103+
def replace(implicit ctx: Context) = new TypeMap {
104+
def mapTypeBoundSymbol(tp: Type, paramInfo: Type): Type = tp match {
105+
case tref: TypeRef
106+
if !tref.typeSymbol.isClass && tref.symbol.is(Case) =>
107+
if (binders.contains(tref.typeSymbol))
108+
binders(tref.typeSymbol).typeRef
109+
else if (tref.typeSymbol.name == tpnme.WILDCARD)
110+
WildcardType
111+
else {
112+
val fresh = ctx.newSymbol(ctx.owner, WildcardParamName.fresh().toTypeName, Case, paramInfo.bounds, coord = pos)
113+
binders(tref.typeSymbol) = fresh
114+
WildcardType(paramInfo.bounds)
115+
}
116+
case _ =>
117+
apply(tp)
118+
}
119+
120+
def apply(tp: Type) = tp match {
121+
case tapp @ AppliedType(tycon, args) =>
122+
val args2 = args.zip(tycon.typeParams).map { case (arg, paramInfo) => mapTypeBoundSymbol(arg, paramInfo.paramInfo) }
123+
tycon.appliedTo(args2)
124+
case tref: TypeRef if !tref.typeSymbol.isClass && tref.symbol.is(Case) =>
125+
if (binders.contains(tref.typeSymbol))
126+
binders(tref.typeSymbol).typeRef
127+
else
128+
// annotation for Array[_] could reach here
129+
tref
130+
case AnnotatedType(_, annot) if annot.symbol == defn.UncheckedAnnot =>
131+
WildcardType
132+
case _ =>
133+
mapOver(tp)
134+
}
135+
}
136+
91137
def isClassDetermined(X: Type, P: AppliedType)(implicit ctx: Context) = {
92138
val AppliedType(tycon, _) = P
93139
val typeLambda = tycon.ensureHK.asInstanceOf[TypeLambda]
@@ -100,7 +146,17 @@ object Checkable {
100146

101147
P1 <:< X // may fail, ignore
102148

103-
val res = isFullyDefined(P1, ForceDegree.noBottom) && P1 <:< P
149+
tvars.foreach { case tvar: TypeVar =>
150+
val bounds = ctx.typerState.constraint.entry(tvar.origin)
151+
if (bounds.loBound =:= bounds.hiBound)
152+
tvar.instantiateWith(bounds.loBound)
153+
else {
154+
val wildCard = ctx.newSymbol(ctx.owner, WildcardParamName.fresh().toTypeName, Case, tvar.origin.underlying, coord = pos)
155+
tvar.instantiateWith(wildCard.typeRef)
156+
}
157+
}
158+
159+
val res = P1 <:< P
104160
debug.println("P1 : " + P1)
105161
debug.println("P1 <:< P = " + res)
106162

@@ -119,12 +175,20 @@ object Checkable {
119175
case tpe: AppliedType => isClassDetermined(X, tpe)(ctx.fresh.setNewTyperState())
120176
case AndType(tp1, tp2) => recur(X, tp1) && recur(X, tp2)
121177
case OrType(tp1, tp2) => recur(X, tp1) && recur(X, tp2)
122-
case AnnotatedType(t, an) => recur(X, t)
178+
case AnnotatedType(t, _) => recur(X, t)
123179
case _: RefinedType => false
124180
case _ => true
125181
})
126182

127-
val res = recur(replaceX.apply(X.widen), replaceP.apply(P))
183+
debug.println(s"$X <: $P")
184+
185+
val X1 = replace.apply(X.widen)
186+
val P1 = replace.apply(P)
187+
188+
debug.println(s"$X ~~> $X1")
189+
debug.println(s"$P ~~> $P1")
190+
191+
val res = recur(X1, P1)
128192

129193
debug.println(i"checking ${X.show} isInstanceOf ${P} = $res")
130194

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,9 @@ class SyntheticMethods(thisPhase: DenotTransformer) {
159159
* ```
160160
*
161161
* If `C` is a value class the initial `eq` test is omitted.
162+
*
163+
* `@unchecked` is needed for parametric case classes.
164+
*
162165
*/
163166
def equalsBody(that: Tree)(implicit ctx: Context): Tree = {
164167
val thatAsClazz = ctx.newSymbol(ctx.owner, nme.x_0, Synthetic, clazzType, coord = ctx.owner.pos) // x$0
@@ -253,6 +256,8 @@ class SyntheticMethods(thisPhase: DenotTransformer) {
253256
* ```
254257
* def canEqual(that: Any) = that.isInstanceOf[C @unchecked]
255258
* ```
259+
*
260+
* `@unchecked` is needed for parametric case classes.
256261
*/
257262
def canEqualBody(that: Tree): Tree = that.isInstance(AnnotatedType(clazzType, Annotation(defn.UncheckedAnnot)))
258263

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
class Test {
2+
trait A[+T]
3+
class B[T] extends A[T]
4+
class C[T] extends B[Any] with A[T]
5+
6+
def foo[T](c: C[T]): Unit = c match {
7+
case _: B[T] => // error
8+
}
9+
10+
def bar[T](b: B[T]): Unit = b match {
11+
case _: A[T] =>
12+
}
13+
14+
def quux[T](a: A[T]): Unit = a match {
15+
case _: B[T] => // error
16+
}
17+
18+
quux(new C[Int])
19+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
object Test {
2+
trait Foo
3+
case class One[+T](fst: T)
4+
5+
def bad[T <: Foo](e: One[T])(x: T) = e match {
6+
case foo: One[a] =>
7+
x.isInstanceOf[a] // error
8+
val y: Any = ???
9+
y.isInstanceOf[a] // error
10+
}
11+
}
12+
13+
object Test2 {
14+
case class One[T](fst: T)
15+
16+
def bad[T](e: One[T])(x: T) = e match {
17+
case foo: One[a] =>
18+
x.isInstanceOf[a] // error
19+
val y: Any = ???
20+
y.isInstanceOf[a] // error
21+
}
22+
}
Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,11 @@
1-
import scala.util.control.NonFatal
21
object p {
32

3+
// test parametric case classes, which synthesis `canEqual` and `equals`
44
enum Result[+T, +E] {
55
case OK [T](x: T) extends Result[T, Nothing]
66
case Err[E](e: E) extends Result[Nothing, E]
77
}
88

9-
type Try[T] = Result[T, Throwable]
10-
object Try {
11-
def apply[T](x: => T): Try[T] =
12-
try Result.OK(x)
13-
catch {
14-
case NonFatal(ex) => Result.Err(ex)
15-
}
16-
}
17-
189
def foo(x: Any): Boolean =
1910
x.isInstanceOf[List[String]] // error
2011
}

0 commit comments

Comments
 (0)