Skip to content

Fix TypeTest exhaustivity check #12036

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the issues are really urgent to fix, I'd suggest extracting the code in #11186 (without introducing any annotations) and specialize the method covers instead. This way, we keep the logic in the base class general, which is a design invariant supposed to improve maintainability of the code.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@liufengyun could you help me with that?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No problem, I'll create a new PR to do that today.

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
17 changes: 17 additions & 0 deletions tests/pos-special/fatal-warnings/i12020.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import scala.quoted.*

def qwe(using Quotes) = {
import quotes.reflect.*

def ko_1(param: ValDef | TypeDef) =
param match {
case _: ValDef =>
case _: TypeDef =>
}

def ko_2(params: List[ValDef] | List[TypeDef]) =
params.map {
case x: ValDef =>
case y: TypeDef =>
}
}
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 =>
}
5 changes: 5 additions & 0 deletions tests/pos-special/fatal-warnings/i12026b.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
def test[A, B](a: A|B)(tta: reflect.TypeTest[Any, A], ttb: reflect.TypeTest[Any, B]) =
a match {
case tta(a: A) =>
case ttb(b: B) =>
}