Skip to content

Commit 2c5613c

Browse files
committed
Restrict catch patterns under saferExceptions
Restrict catch patterns to `ex: T` with no guard under saferExceptions so that capabilities can be generated safely. Fixes #13849
1 parent abffac2 commit 2c5613c

File tree

6 files changed

+58
-4
lines changed

6 files changed

+58
-4
lines changed

compiler/src/dotty/tools/dotc/typer/Checking.scala

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import NameKinds.DefaultGetterName
3333
import NameOps._
3434
import SymDenotations.{NoCompleter, NoDenotation}
3535
import Applications.unapplyArgs
36+
import Inferencing.isFullyDefined
3637
import transform.patmat.SpaceEngine.isIrrefutable
3738
import config.Feature
3839
import config.Feature.sourceVersion
@@ -1362,6 +1363,21 @@ trait Checking {
13621363
def checkCanThrow(tp: Type, span: Span)(using Context): Unit =
13631364
if Feature.enabled(Feature.saferExceptions) && tp.isCheckedException then
13641365
ctx.typer.implicitArgTree(defn.CanThrowClass.typeRef.appliedTo(tp), span)
1366+
1367+
/** Check that catch can generate a good CanThrow exception */
1368+
def checkCatch(pat: Tree, guard: Tree)(using Context): Unit = pat match
1369+
case Typed(_: Ident, tpt) if isFullyDefined(tpt.tpe, ForceDegree.none) && guard.isEmpty =>
1370+
// OK
1371+
case Bind(_, pat1) =>
1372+
checkCatch(pat1, guard)
1373+
case _ =>
1374+
val req =
1375+
if guard.isEmpty then "for cases of the form `ex: T` where `T` is fully defined"
1376+
else "if no pattern guard is given"
1377+
report.error(
1378+
em"""Implementation restriction: cannot generate CanThrow capability for this kind of catch.
1379+
|CanThrow capabilities can only be generated $req.""",
1380+
pat.srcPos)
13651381
}
13661382

13671383
trait ReChecking extends Checking {
@@ -1375,6 +1391,7 @@ trait ReChecking extends Checking {
13751391
override def checkMatchable(tp: Type, pos: SrcPos, pattern: Boolean)(using Context): Unit = ()
13761392
override def checkNoModuleClash(sym: Symbol)(using Context) = ()
13771393
override def checkCanThrow(tp: Type, span: Span)(using Context): Unit = ()
1394+
override def checkCatch(pat: Tree, guard: Tree)(using Context): Unit = ()
13781395
}
13791396

13801397
trait NoChecking extends ReChecking {

compiler/src/dotty/tools/dotc/typer/Typer.scala

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1745,9 +1745,12 @@ class Typer extends Namer
17451745
.withSpan(expr.span)
17461746
val caps =
17471747
for
1748-
case CaseDef(pat, EmptyTree, _) <- cases
1748+
case CaseDef(pat, guard, _) <- cases
17491749
if Feature.enabled(Feature.saferExceptions) && pat.tpe.widen.isCheckedException
1750-
yield makeCanThrow(pat.tpe.widen)
1750+
yield
1751+
checkCatch(pat, guard)
1752+
makeCanThrow(pat.tpe.widen)
1753+
17511754
caps.foldLeft(expr)((e, g) => untpd.Block(g :: Nil, e))
17521755

17531756
def typedTry(tree: untpd.Try, pt: Type)(using Context): Try = {

tests/neg/i13849.check

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
-- Error: tests/neg/i13849.scala:16:11 ---------------------------------------------------------------------------------
2+
16 | case _: Ex if false => println("Caught") // error
3+
| ^^^^^
4+
| Implementation restriction: cannot generate CanThrow capability for this kind of catch.
5+
| CanThrow capabilities can only be generated if no pattern guard is given.

tests/neg/i13849.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,6 @@ def foo(): Unit throws Ex = throw Ex(1)
1111
object Main:
1212
def main(args: Array[String]): Unit =
1313
try
14-
foo() // error
14+
foo()
1515
catch
16-
case _: Ex if false => println("Caught")
16+
case _: Ex if false => println("Caught") // error

tests/neg/i13864.check

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
-- Error: tests/neg/i13864.scala:11:9 ----------------------------------------------------------------------------------
2+
11 | case Ex(i: Int) => println("Caught an Int") // error
3+
| ^^^^^^^^^^
4+
| Implementation restriction: cannot generate CanThrow capability for this kind of catch.
5+
| CanThrow capabilities can only be generated for cases of the form `ex: T` where `T` is fully defined.
6+
-- Error: tests/neg/i13864.scala:9:10 ----------------------------------------------------------------------------------
7+
9 | foo(1) // error
8+
| ^
9+
| The capability to throw exception Ex[Int] is missing.
10+
| The capability can be provided by one of the following:
11+
| - A using clause `(using CanThrow[Ex[Int]])`
12+
| - A `throws` clause in a result type such as `X throws Ex[Int]`
13+
| - an enclosing `try` that catches Ex[Int]
14+
|
15+
| The following import might fix the problem:
16+
|
17+
| import unsafeExceptions.canThrowAny
18+
|

tests/neg/i13864.scala

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import language.experimental.saferExceptions
2+
3+
case class Ex[A](a: A) extends Exception(s"Ex: $a")
4+
5+
def foo[A](a: A): Unit throws Ex[A] = throw new Ex(a)
6+
7+
def test(): Unit =
8+
try
9+
foo(1) // error
10+
catch
11+
case Ex(i: Int) => println("Caught an Int") // error

0 commit comments

Comments
 (0)