Skip to content

Commit c468843

Browse files
authored
Merge pull request #3156 from dotty-staging/fix-3144
Fix #3144: emit warnings for unchecked type patterns
2 parents bea77a7 + 608a17c commit c468843

File tree

13 files changed

+84
-11
lines changed

13 files changed

+84
-11
lines changed

compiler/src/dotty/tools/dotc/reporting/diagnostic/ErrorMessageID.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ public enum ErrorMessageID {
9999
MissingReturnTypeWithReturnStatementID,
100100
NoReturnFromInlineID,
101101
ReturnOutsideMethodDefinitionID,
102+
UncheckedTypePatternID,
102103
;
103104

104105
public int errorNumber() {

compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -880,6 +880,18 @@ object messages {
880880
|"""
881881
}
882882

883+
case class UncheckedTypePattern(msg: String)(implicit ctx: Context)
884+
extends Message(UncheckedTypePatternID) {
885+
val kind = "Pattern Match Exhaustivity"
886+
887+
val explanation =
888+
hl"""|Type arguments and type refinements are erased during compile time, thus it's
889+
|impossible to check them at run-time.
890+
|
891+
|You can either replace the type arguments by `_` or use `@unchecked`.
892+
|"""
893+
}
894+
883895
case class MatchCaseUnreachable()(implicit ctx: Context)
884896
extends Message(MatchCaseUnreachableID) {
885897
val kind = s"""Match ${hl"case"} Unreachable"""

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

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import Types._
77
import Contexts._
88
import Flags._
99
import ast.Trees._
10-
import ast.{tpd, untpd}
10+
import ast.tpd
1111
import Decorators._
1212
import Symbols._
1313
import StdNames._
@@ -404,21 +404,37 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic {
404404
else
405405
Prod(pat.tpe.stripAnnots, fun.tpe.widen, fun.symbol, pats.map(project), irrefutable(fun))
406406
case Typed(pat @ UnApply(_, _, _), _) => project(pat)
407-
case Typed(expr, _) => Typ(erase(expr.tpe.stripAnnots), true)
407+
case Typed(expr, tpt) =>
408+
val unchecked = expr.tpe.hasAnnotation(ctx.definitions.UncheckedAnnot)
409+
def warn(msg: String): Unit = if (!unchecked) ctx.warning(UncheckedTypePattern(msg), tpt.pos)
410+
Typ(erase(expr.tpe.stripAnnots)(warn), true)
408411
case _ =>
409412
debug.println(s"unknown pattern: $pat")
410413
Empty
411414
}
412415

413416
/* Erase a type binding according to erasure semantics in pattern matching */
414-
def erase(tp: Type): Type = tp match {
417+
def erase(tp: Type)(implicit warn: String => Unit): Type = tp match {
415418
case tp @ AppliedType(tycon, args) =>
416419
if (tycon.isRef(defn.ArrayClass)) tp.derivedAppliedType(tycon, args.map(erase))
417-
else tp.derivedAppliedType(tycon, args.map(t => WildcardType))
420+
else {
421+
val ignoreWarning = args.forall { p =>
422+
p.typeSymbol.is(BindDefinedType) ||
423+
p.hasAnnotation(defn.UncheckedAnnot) ||
424+
p.isInstanceOf[TypeBounds]
425+
}
426+
if (!ignoreWarning)
427+
warn("type arguments are not checked since they are eliminated by erasure")
428+
429+
tp.derivedAppliedType(tycon, args.map(t => WildcardType))
430+
}
418431
case OrType(tp1, tp2) =>
419432
OrType(erase(tp1), erase(tp2))
420433
case AndType(tp1, tp2) =>
421434
AndType(erase(tp1), erase(tp2))
435+
case tp: RefinedType =>
436+
warn("type refinement is not checked since it is eliminated by erasure")
437+
tp.derivedRefinedType(erase(tp.parent), tp.refinedName, WildcardType)
422438
case _ => tp
423439
}
424440

@@ -740,12 +756,10 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic {
740756
}
741757

742758
def checkable(tree: Match): Boolean = {
743-
def isCheckable(tp: Type): Boolean = tp match {
744-
case AnnotatedType(tp, annot) =>
745-
(ctx.definitions.UncheckedAnnot != annot.symbol) && isCheckable(tp)
746-
case _ =>
747-
// Possible to check everything, but be compatible with scalac by default
748-
ctx.settings.YcheckAllPatmat.value ||
759+
// Possible to check everything, but be compatible with scalac by default
760+
def isCheckable(tp: Type): Boolean =
761+
!tp.hasAnnotation(defn.UncheckedAnnot) && (
762+
ctx.settings.YcheckAllPatmat.value ||
749763
tp.typeSymbol.is(Sealed) ||
750764
tp.isInstanceOf[OrType] ||
751765
(tp.isInstanceOf[AndType] && {
@@ -756,7 +770,7 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic {
756770
tp.typeSymbol.is(Enum) ||
757771
canDecompose(tp) ||
758772
(defn.isTupleType(tp) && tp.dealias.argInfos.exists(isCheckable(_)))
759-
}
773+
)
760774

761775
val Match(sel, cases) = tree
762776
val res = isCheckable(sel.tpe.widen.dealiasKeepAnnots)

tests/patmat/3144.check

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
2: Pattern Match Exhaustivity
2+
7: Pattern Match Exhaustivity

tests/patmat/3144.scala

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
sealed trait Foo[T]
2+
case class Bar[T](s: String)
3+
4+
object Test {
5+
def shouldError[T](foo: Foo[T]): String =
6+
foo match {
7+
case bar: Bar[T] => bar.s
8+
}
9+
}

tests/patmat/3144b.check

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
4: Pattern Match Exhaustivity
2+
10: Pattern Match Exhaustivity

tests/patmat/3144b.scala

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
class Test {
2+
def f(x: Any): Int = x match {
3+
case xs: List[Int] @unchecked => xs.head
4+
case xs: Array[List[Int]] => 3
5+
case _ => 0
6+
}
7+
8+
def g(x: Any): Int = x match {
9+
case xs: List[Int @unchecked] => xs.head
10+
case xs: Array[List[Int]] => 3
11+
case _ => 0
12+
}
13+
}

tests/patmat/enum-HList.check

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
2: Pattern Match Exhaustivity

tests/patmat/enum-Tree.check

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
8: Pattern Match Exhaustivity

tests/patmat/t10019.check

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
2: Pattern Match Exhaustivity: (List(_, _: _*), Nil), (List(_, _: _*), List(_, _, _: _*)), (Nil, List(_, _: _*)), (List(_, _, _: _*), List(_, _: _*))
2+
11: Pattern Match Exhaustivity: (Foo(None), Foo(_))

tests/patmat/t10019.scala

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
object Bug {
2+
def foo[T](t1: List[T], t2: List[T]) = (t1, t2) match {
3+
case (Nil, Nil) => ()
4+
case (List(_), List(_)) => ()
5+
}
6+
}
7+
8+
object Bug2 {
9+
sealed case class Foo(e: Option[Int])
10+
11+
def loop(s: Foo, t: Foo): Nothing = (s,t) match {
12+
case (Foo(Some(_)), _) => ???
13+
}
14+
}

tests/patmat/t3683.check

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
7: Pattern Match Exhaustivity

tests/patmat/t3683a.check

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1+
8: Pattern Match Exhaustivity
12
14: Pattern Match Exhaustivity: XX()

0 commit comments

Comments
 (0)