diff --git a/compiler/src/dotty/tools/dotc/reporting/diagnostic/ErrorMessageID.java b/compiler/src/dotty/tools/dotc/reporting/diagnostic/ErrorMessageID.java index 2d653d581e3c..e98659c1663c 100644 --- a/compiler/src/dotty/tools/dotc/reporting/diagnostic/ErrorMessageID.java +++ b/compiler/src/dotty/tools/dotc/reporting/diagnostic/ErrorMessageID.java @@ -99,6 +99,7 @@ public enum ErrorMessageID { MissingReturnTypeWithReturnStatementID, NoReturnFromInlineID, ReturnOutsideMethodDefinitionID, + UncheckedTypePatternID, ; public int errorNumber() { diff --git a/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala b/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala index e45c581cea1a..6da65f77d534 100644 --- a/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala @@ -880,6 +880,18 @@ object messages { |""" } + case class UncheckedTypePattern(msg: String)(implicit ctx: Context) + extends Message(UncheckedTypePatternID) { + val kind = "Pattern Match Exhaustivity" + + val explanation = + hl"""|Type arguments and type refinements are erased during compile time, thus it's + |impossible to check them at run-time. + | + |You can either replace the type arguments by `_` or use `@unchecked`. + |""" + } + case class MatchCaseUnreachable()(implicit ctx: Context) extends Message(MatchCaseUnreachableID) { val kind = s"""Match ${hl"case"} Unreachable""" diff --git a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala index da53a106edad..1e6c4d29798d 100644 --- a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala +++ b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala @@ -7,7 +7,7 @@ import Types._ import Contexts._ import Flags._ import ast.Trees._ -import ast.{tpd, untpd} +import ast.tpd import Decorators._ import Symbols._ import StdNames._ @@ -403,21 +403,37 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic { else Prod(pat.tpe.stripAnnots, fun.tpe.widen, fun.symbol, pats.map(project), irrefutable(fun)) case Typed(pat @ UnApply(_, _, _), _) => project(pat) - case Typed(expr, _) => Typ(erase(expr.tpe.stripAnnots), true) + case Typed(expr, tpt) => + val unchecked = expr.tpe.hasAnnotation(ctx.definitions.UncheckedAnnot) + def warn(msg: String): Unit = if (!unchecked) ctx.warning(UncheckedTypePattern(msg), tpt.pos) + Typ(erase(expr.tpe.stripAnnots)(warn), true) case _ => debug.println(s"unknown pattern: $pat") Empty } /* Erase a type binding according to erasure semantics in pattern matching */ - def erase(tp: Type): Type = tp match { + def erase(tp: Type)(implicit warn: String => Unit): Type = tp match { case tp @ AppliedType(tycon, args) => if (tycon.isRef(defn.ArrayClass)) tp.derivedAppliedType(tycon, args.map(erase)) - else tp.derivedAppliedType(tycon, args.map(t => WildcardType)) + else { + val ignoreWarning = args.forall { p => + p.typeSymbol.is(BindDefinedType) || + p.hasAnnotation(defn.UncheckedAnnot) || + p.isInstanceOf[TypeBounds] + } + if (!ignoreWarning) + warn("type arguments are not checked since they are eliminated by erasure") + + tp.derivedAppliedType(tycon, args.map(t => WildcardType)) + } case OrType(tp1, tp2) => OrType(erase(tp1), erase(tp2)) case AndType(tp1, tp2) => AndType(erase(tp1), erase(tp2)) + case tp: RefinedType => + warn("type refinement is not checked since it is eliminated by erasure") + tp.derivedRefinedType(erase(tp.parent), tp.refinedName, WildcardType) case _ => tp } @@ -721,12 +737,10 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic { } def checkable(tree: Match): Boolean = { - def isCheckable(tp: Type): Boolean = tp match { - case AnnotatedType(tp, annot) => - (ctx.definitions.UncheckedAnnot != annot.symbol) && isCheckable(tp) - case _ => - // Possible to check everything, but be compatible with scalac by default - ctx.settings.YcheckAllPatmat.value || + // Possible to check everything, but be compatible with scalac by default + def isCheckable(tp: Type): Boolean = + !tp.hasAnnotation(defn.UncheckedAnnot) && ( + ctx.settings.YcheckAllPatmat.value || tp.typeSymbol.is(Sealed) || tp.isInstanceOf[OrType] || (tp.isInstanceOf[AndType] && { @@ -737,7 +751,7 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic { tp.typeSymbol.is(Enum) || canDecompose(tp) || (defn.isTupleType(tp) && tp.dealias.argInfos.exists(isCheckable(_))) - } + ) val Match(sel, cases) = tree val res = isCheckable(sel.tpe.widen.dealiasKeepAnnots) diff --git a/tests/patmat/3144.check b/tests/patmat/3144.check new file mode 100644 index 000000000000..207dd36e1d9a --- /dev/null +++ b/tests/patmat/3144.check @@ -0,0 +1,2 @@ +2: Pattern Match Exhaustivity +7: Pattern Match Exhaustivity diff --git a/tests/patmat/3144.scala b/tests/patmat/3144.scala new file mode 100644 index 000000000000..2aea42bfaaeb --- /dev/null +++ b/tests/patmat/3144.scala @@ -0,0 +1,9 @@ +sealed trait Foo[T] +case class Bar[T](s: String) + +object Test { + def shouldError[T](foo: Foo[T]): String = + foo match { + case bar: Bar[T] => bar.s + } +} \ No newline at end of file diff --git a/tests/patmat/3144b.check b/tests/patmat/3144b.check new file mode 100644 index 000000000000..d4bd280466da --- /dev/null +++ b/tests/patmat/3144b.check @@ -0,0 +1,2 @@ +4: Pattern Match Exhaustivity +10: Pattern Match Exhaustivity diff --git a/tests/patmat/3144b.scala b/tests/patmat/3144b.scala new file mode 100644 index 000000000000..63445339461a --- /dev/null +++ b/tests/patmat/3144b.scala @@ -0,0 +1,13 @@ +class Test { + def f(x: Any): Int = x match { + case xs: List[Int] @unchecked => xs.head + case xs: Array[List[Int]] => 3 + case _ => 0 + } + + def g(x: Any): Int = x match { + case xs: List[Int @unchecked] => xs.head + case xs: Array[List[Int]] => 3 + case _ => 0 + } +} diff --git a/tests/patmat/enum-HList.check b/tests/patmat/enum-HList.check new file mode 100644 index 000000000000..414335be5ea7 --- /dev/null +++ b/tests/patmat/enum-HList.check @@ -0,0 +1 @@ +2: Pattern Match Exhaustivity diff --git a/tests/patmat/enum-Tree.check b/tests/patmat/enum-Tree.check new file mode 100644 index 000000000000..84053e20646d --- /dev/null +++ b/tests/patmat/enum-Tree.check @@ -0,0 +1 @@ +8: Pattern Match Exhaustivity \ No newline at end of file diff --git a/tests/patmat/t10019.check b/tests/patmat/t10019.check new file mode 100644 index 000000000000..49f5b152d1fb --- /dev/null +++ b/tests/patmat/t10019.check @@ -0,0 +1,2 @@ +2: Pattern Match Exhaustivity: (List(_, _: _*), Nil), (List(_, _: _*), List(_, _, _: _*)), (Nil, List(_, _: _*)), (List(_, _, _: _*), List(_, _: _*)) +11: Pattern Match Exhaustivity: (Foo(None), Foo(_)) diff --git a/tests/patmat/t10019.scala b/tests/patmat/t10019.scala new file mode 100644 index 000000000000..b34e453b8bab --- /dev/null +++ b/tests/patmat/t10019.scala @@ -0,0 +1,14 @@ +object Bug { + def foo[T](t1: List[T], t2: List[T]) = (t1, t2) match { + case (Nil, Nil) => () + case (List(_), List(_)) => () + } +} + +object Bug2 { + sealed case class Foo(e: Option[Int]) + + def loop(s: Foo, t: Foo): Nothing = (s,t) match { + case (Foo(Some(_)), _) => ??? + } +} diff --git a/tests/patmat/t3683.check b/tests/patmat/t3683.check new file mode 100644 index 000000000000..2d090c590d8c --- /dev/null +++ b/tests/patmat/t3683.check @@ -0,0 +1 @@ +7: Pattern Match Exhaustivity \ No newline at end of file diff --git a/tests/patmat/t3683a.check b/tests/patmat/t3683a.check index fdcafc5961b9..c39820a1a0be 100644 --- a/tests/patmat/t3683a.check +++ b/tests/patmat/t3683a.check @@ -1 +1,2 @@ +8: Pattern Match Exhaustivity 14: Pattern Match Exhaustivity: XX()