Skip to content

Commit 796c91f

Browse files
committed
Fix scala#16899: Better handle X instanceOf P where X is T1 | T2
1 parent a89fbf5 commit 796c91f

File tree

3 files changed

+44
-6
lines changed

3 files changed

+44
-6
lines changed

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

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import util.Spans._
1616
import reporting._
1717
import config.Printers.{ transforms => debug }
1818

19+
import patmat.Typ
20+
1921
/** This transform normalizes type tests and type casts,
2022
* also replacing type tests with singleton argument type with reference equality check
2123
* Any remaining type tests
@@ -51,7 +53,8 @@ object TypeTestsCasts {
5153
* 6. if `P = T1 | T2` or `P = T1 & T2`, checkable(X, T1) && checkable(X, T2).
5254
* 7. if `P` is a refinement type, "it's a refinement type"
5355
* 8. if `P` is a local class which is not statically reachable from the scope where `X` is defined, "it's a local class"
54-
* 9. otherwise, ""
56+
* 9. if `X` is `T1 | T2`, (isCheckDefinitelyFalse(T1, P) && checkable(T2, P)) or (checkable(T1, P) && isCheckDefinitelyFalse(T2, P)).
57+
* 10. otherwise, ""
5558
*/
5659
def whyUncheckable(X: Type, P: Type, span: Span)(using Context): String = atPhase(Phases.refchecksPhase.next) {
5760
extension (inline s1: String) inline def &&(inline s2: String): String = if s1 == "" then s2 else s1
@@ -129,6 +132,34 @@ object TypeTestsCasts {
129132

130133
}
131134

135+
/** Whether the check X.isInstanceOf[P] is definitely false? */
136+
def isCheckDefinitelyFalse(X: Type, P: Type)(using Context): Boolean = trace(s"isCheckDefinitelyFalse(${X.show}, ${P.show})") {
137+
X.dealias match
138+
case AndType(x1, x2) =>
139+
isCheckDefinitelyFalse(x1, P) || isCheckDefinitelyFalse(x2, P)
140+
141+
case x =>
142+
P.dealias match
143+
case AndType(p1, p2) =>
144+
isCheckDefinitelyFalse(x, p1) || isCheckDefinitelyFalse(x, p2)
145+
146+
case p =>
147+
val pSpace = Typ(p)
148+
val xSpace = Typ(x)
149+
if pSpace.canDecompose then
150+
val ps = pSpace.decompose.map(_.tp)
151+
ps.forall(p => isCheckDefinitelyFalse(x, p))
152+
else if xSpace.canDecompose then
153+
val xs = xSpace.decompose.map(_.tp)
154+
xs.exists(x => isCheckDefinitelyFalse(x, p))
155+
else
156+
val xClass = effectiveClass(x.widen)
157+
val pClass = effectiveClass(p.widen)
158+
159+
!xClass.derivesFrom(pClass)
160+
&& (xClass.is(Final) || pClass.is(Final) || !xClass.is(Trait) && !pClass.is(Trait))
161+
}
162+
132163
def recur(X: Type, P: Type): String = (X <:< P) ||| (P.dealias match {
133164
case _: SingletonType => ""
134165
case _: TypeProxy
@@ -146,7 +177,14 @@ object TypeTestsCasts {
146177
// - T1 <:< T2 | T3
147178
// - T1 & T2 <:< T3
148179
// See TypeComparer#either
149-
recur(tp1, P) && recur(tp2, P)
180+
val res1 = recur(tp1, P)
181+
val res2 = recur(tp2, P)
182+
183+
if res1.isEmpty && res2.isEmpty then res1
184+
else if isCheckDefinitelyFalse(tp1, P) && res2.isEmpty then res2
185+
else if res1.isEmpty && isCheckDefinitelyFalse(tp2, P) then res1
186+
else res1
187+
150188
case _ =>
151189
// always false test warnings are emitted elsewhere
152190
X.classSymbol.exists && P.classSymbol.exists &&
@@ -302,8 +340,8 @@ object TypeTestsCasts {
302340

303341
/** Transform isInstanceOf
304342
*
305-
* expr.isInstanceOf[A | B] ~~> expr.isInstanceOf[A] | expr.isInstanceOf[B]
306-
* expr.isInstanceOf[A & B] ~~> expr.isInstanceOf[A] & expr.isInstanceOf[B]
343+
* expr.isInstanceOf[A | B] ~~> expr.isInstanceOf[A] | expr.isInstanceOf[B]
344+
* expr.isInstanceOf[A & B] ~~> expr.isInstanceOf[A] & expr.isInstanceOf[B]
307345
* expr.isInstanceOf[Tuple] ~~> scala.runtime.Tuples.isInstanceOfTuple(expr)
308346
* expr.isInstanceOf[EmptyTuple] ~~> scala.runtime.Tuples.isInstanceOfEmptyTuple(expr)
309347
* expr.isInstanceOf[NonEmptyTuple] ~~> scala.runtime.Tuples.isInstanceOfNonEmptyTuple(expr)

0 commit comments

Comments
 (0)