Skip to content

Commit 52fd5c9

Browse files
committed
Warn about unchecked type tests in primitive catch cases
1 parent a3854cb commit 52fd5c9

File tree

9 files changed

+70
-6
lines changed

9 files changed

+70
-6
lines changed

compiler/src/dotty/tools/dotc/transform/Erasure.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -823,6 +823,11 @@ object Erasure {
823823
}
824824
}
825825

826+
override def typedBind(tree: untpd.Bind, pt: Type)(using Context): Bind =
827+
atPhase(erasurePhase):
828+
checkBind(promote(tree))
829+
super.typedBind(tree, pt)
830+
826831
/** Besides normal typing, this method does uncurrying and collects parameters
827832
* to anonymous functions of arity > 22.
828833
*/

compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import reporting.*
1616
import config.Printers.{ transforms => debug }
1717

1818
import patmat.Typ
19+
import dotty.tools.dotc.util.SrcPos
1920

2021
/** This transform normalizes type tests and type casts,
2122
* also replacing type tests with singleton argument type with reference equality check
@@ -358,11 +359,8 @@ object TypeTestsCasts {
358359
if (sym.isTypeTest) {
359360
val argType = tree.args.head.tpe
360361
val isTrusted = tree.hasAttachment(PatternMatcher.TrustedTypeTestKey)
361-
val isUnchecked = expr.tpe.widenTermRefExpr.hasAnnotation(defn.UncheckedAnnot)
362-
if !isTrusted && !isUnchecked then
363-
val whyNot = whyUncheckable(expr.tpe, argType, tree.span)
364-
if whyNot.nonEmpty then
365-
report.uncheckedWarning(UncheckedTypePattern(argType, whyNot), expr.srcPos)
362+
if !isTrusted then
363+
checkTypePattern(expr.tpe, argType, expr.srcPos)
366364
transformTypeTest(expr, argType,
367365
flagUnrelated = enclosingInlineds.isEmpty) // if test comes from inlined code, dont't flag it even if it always false
368366
}
@@ -381,6 +379,19 @@ object TypeTestsCasts {
381379
interceptWith(expr)
382380
}
383381

382+
/** After PatternMatcher, the only Bind nodes are present in simple try-catch trees
383+
* See i19013
384+
*/
385+
def checkBind(tree: Bind)(using Context) =
386+
checkTypePattern(defn.ThrowableType, tree.body.tpe, tree.srcPos)
387+
388+
private def checkTypePattern(exprTpe: Type, castTpe: Type, pos: SrcPos)(using Context) =
389+
val isUnchecked = castTpe.widenTermRefExpr.hasAnnotation(defn.UncheckedAnnot)
390+
if !isUnchecked then
391+
val whyNot = whyUncheckable(exprTpe, castTpe, pos.span)
392+
if whyNot.nonEmpty then
393+
report.uncheckedWarning(UncheckedTypePattern(castTpe, whyNot), pos)
394+
384395
private def effectiveClass(tp: Type)(using Context): Symbol =
385396
if tp.isRef(defn.PairClass) then effectiveClass(erasure(tp))
386397
else if tp.isRef(defn.AnyValClass) then defn.AnyClass

library/src/scala/util/control/NonLocalReturns.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ object NonLocalReturns {
4242
val returner = new ReturnThrowable[T]
4343
try op(using returner)
4444
catch {
45-
case ex: ReturnThrowable[T] =>
45+
case ex: ReturnThrowable[T @unchecked] =>
4646
if (ex.eq(returner)) ex.result else throw ex
4747
}
4848
}

tests/pos/i19013-a.scala

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
//> using options -Xfatal-warnings
2+
3+
def handle[E <: Exception](f: => Unit): Option[E] =
4+
try
5+
f
6+
None
7+
catch case e: E @unchecked => Some(e)
8+
9+
val r: RuntimeException = handle[RuntimeException](throw new Exception()).get

tests/pos/i19013-b.scala

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
//> using options -Xfatal-warnings
2+
3+
case class CustomException(x: Any) extends Exception("")
4+
5+
def handle[E](f: => Unit): Option[E] =
6+
try
7+
f
8+
None
9+
catch case CustomException(e: E @unchecked ) => Some(e)
10+
11+
val r: RuntimeException = handle[RuntimeException](throw new Exception()).get

tests/warn/i19013-a.check

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
-- [E092] Pattern Match Unchecked Warning: tests/warn/i19013-a.scala:5:13 ----------------------------------------------
2+
5 | catch case e: E => Some(e) // warn
3+
| ^^^^
4+
| the type test for E cannot be checked at runtime because it refers to an abstract type member or type parameter
5+
|
6+
| longer explanation available when compiling with `-explain`

tests/warn/i19013-a.scala

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
def handle[E <: Exception](f: => Unit): Option[E] =
2+
try
3+
f
4+
None
5+
catch case e: E => Some(e) // warn
6+
7+
val r: RuntimeException = handle[RuntimeException](throw new Exception()).get

tests/warn/i19013-b.check

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
-- [E092] Pattern Match Unchecked Warning: tests/warn/i19013-b.scala:7:29 ----------------------------------------------
2+
7 | catch case CustomException(e: E) => Some(e) // warn
3+
| ^
4+
| the type test for E cannot be checked at runtime because it refers to an abstract type member or type parameter
5+
|
6+
| longer explanation available when compiling with `-explain`

tests/warn/i19013-b.scala

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
case class CustomException(x: Any) extends Exception("")
2+
3+
def handle[E](f: => Unit): Option[E] =
4+
try
5+
f
6+
None
7+
catch case CustomException(e: E) => Some(e) // warn
8+
9+
val r: RuntimeException = handle[RuntimeException](throw new Exception()).get

0 commit comments

Comments
 (0)