Skip to content

Commit 88de390

Browse files
committed
Fix TypeTest exhaustivity check
Properly capture the semantics of `TypeTest` in the `Space` logic. `TypeTest[S, T].unapply` is equivalent to (and mostly used as) a `_: T` pattern and therefore covers all `T`. * Update the documentation to make this feature clearer * Fixes #12026 * Fixes #12020 * Improves #11541
1 parent 8b35b67 commit 88de390

File tree

6 files changed

+37
-4
lines changed

6 files changed

+37
-4
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -768,6 +768,7 @@ class Definitions {
768768
@tu lazy val ClassTagModule_apply: Symbol = ClassTagModule.requiredMethod(nme.apply)
769769

770770
@tu lazy val TypeTestClass: ClassSymbol = requiredClass("scala.reflect.TypeTest")
771+
@tu lazy val TypeTest_unapply: Symbol = TypeTestClass.requiredMethod(nme.unapply)
771772
@tu lazy val TypeTestModule_identity: Symbol = TypeTestClass.companionModule.requiredMethod(nme.identity)
772773

773774
@tu lazy val QuotedExprClass: ClassSymbol = requiredClass("scala.quoted.Expr")

compiler/src/dotty/tools/dotc/transform/patmat/Space.scala

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -265,8 +265,13 @@ trait SpaceLogic {
265265
case (Prod(tp1, _, _, false), Typ(tp2, _)) =>
266266
if (isSubType(tp1, tp2)) Empty
267267
else a
268-
case (Typ(tp1, _), Prod(tp2, _, _, false)) =>
269-
a // approximation
268+
case (Typ(tp1, _), Prod(tp2, unappTp, params, false)) =>
269+
if unappTp.symbol == defn.TypeTest_unapply then
270+
// The `TypeTest[S, T].unapply` covers all `T`s if the scrutinee is of type `S`.
271+
// If the scrutinee is not of type `S`, then we would already have an unchecked warning.
272+
minus(a, params.head)
273+
else
274+
a // approximation
270275
case (Prod(tp1, fun1, ss1, full), Prod(tp2, fun2, ss2, _)) =>
271276
if (!isSameUnapply(fun1, fun2)) return a
272277

library/src/scala/reflect/TypeTest.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package scala.reflect
22

33
/** A `TypeTest[S, T] contains the logic needed to know at runtime if a value of
4-
* type `S` can be downcasted to `T`.
4+
* type `S` is an instance of `T`.
55
*
66
* If a pattern match is performed on a term of type `s: S` that is uncheckable with `s.isInstanceOf[T]` and
77
* the pattern are of the form:
@@ -12,7 +12,7 @@ package scala.reflect
1212
@scala.annotation.implicitNotFound(msg = "No TypeTest available for [${S}, ${T}]")
1313
trait TypeTest[-S, T] extends Serializable:
1414

15-
/** A TypeTest[S, T] can serve as an extractor that matches only S of type T.
15+
/** A TypeTest[S, T] can serve as an extractor that matches if and only if S of type T.
1616
*
1717
* The compiler tries to turn unchecked type tests in pattern matches into checked ones
1818
* by wrapping a `(_: T)` type pattern as `tt(_: T)`, where `tt` is the `TypeTest[S, T]` instance.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import scala.quoted.*
2+
3+
def qwe(using Quotes) = {
4+
import quotes.reflect.*
5+
6+
def ko_1(param: ValDef | TypeDef) =
7+
param match {
8+
case _: ValDef =>
9+
case _: TypeDef =>
10+
}
11+
12+
def ko_2(params: List[ValDef] | List[TypeDef]) =
13+
params.map {
14+
case x: ValDef =>
15+
case y: TypeDef =>
16+
}
17+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
def test[A, B](a: A|B)(using reflect.TypeTest[Any, A], reflect.TypeTest[Any, B]) =
2+
a match {
3+
case a: A =>
4+
case b: B =>
5+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
def test[A, B](a: A|B)(tta: reflect.TypeTest[Any, A], ttb: reflect.TypeTest[Any, B]) =
2+
a match {
3+
case tta(a: A) =>
4+
case ttb(b: B) =>
5+
}

0 commit comments

Comments
 (0)