Skip to content

Commit

Permalink
Fix TypeTest exhaustivity check
Browse files Browse the repository at this point in the history
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 cover all `T`.

* Update the documentation to make this feature clearer
* Fixes scala#12026
* Fixes scala#12020
* Improves scala#11541
  • Loading branch information
nicolasstucki committed Apr 9, 2021
1 parent 8b35b67 commit 54be90e
Show file tree
Hide file tree
Showing 4 changed files with 15 additions and 4 deletions.
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -768,6 +768,7 @@ class Definitions {
@tu lazy val ClassTagModule_apply: Symbol = ClassTagModule.requiredMethod(nme.apply)

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

@tu lazy val QuotedExprClass: ClassSymbol = requiredClass("scala.quoted.Expr")
Expand Down
9 changes: 7 additions & 2 deletions compiler/src/dotty/tools/dotc/transform/patmat/Space.scala
Original file line number Diff line number Diff line change
Expand Up @@ -265,8 +265,13 @@ trait SpaceLogic {
case (Prod(tp1, _, _, false), Typ(tp2, _)) =>
if (isSubType(tp1, tp2)) Empty
else a
case (Typ(tp1, _), Prod(tp2, _, _, false)) =>
a // approximation
case (Typ(tp1, _), Prod(tp2, unappTp, params, false)) =>
if unappTp.symbol == defn.TypeTest_unapply then
// The `TypeTest[S, T].unapply` covers all `T`s if the scrutinee is of type `S`.
// If the scrutinee is not of type `S`, then we would already have an unchecked warning.
minus(a, params.head)
else
a // approximation
case (Prod(tp1, fun1, ss1, full), Prod(tp2, fun2, ss2, _)) =>
if (!isSameUnapply(fun1, fun2)) return a

Expand Down
4 changes: 2 additions & 2 deletions library/src/scala/reflect/TypeTest.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package scala.reflect

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

/** A TypeTest[S, T] can serve as an extractor that matches only S of type T.
/** A TypeTest[S, T] can serve as an extractor that matches if and only if S of type T.
*
* The compiler tries to turn unchecked type tests in pattern matches into checked ones
* by wrapping a `(_: T)` type pattern as `tt(_: T)`, where `tt` is the `TypeTest[S, T]` instance.
Expand Down
5 changes: 5 additions & 0 deletions tests/pos-special/fatal-warnings/i12026.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
def test[A, B](a: A|B)(using reflect.TypeTest[Any, A], reflect.TypeTest[Any, B]) =
a match {
case a: A =>
case b: B =>
}

0 comments on commit 54be90e

Please sign in to comment.