From 67024a914784d5a912ce1ac2715cfd332eab7f35 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Tue, 3 Dec 2024 15:28:09 +0100 Subject: [PATCH 1/2] Only trust the type application part for case class unapplies --- .../tools/dotc/transform/TypeTestsCasts.scala | 11 +++++------ tests/neg/i22051.scala | 19 +++++++++++++++++++ 2 files changed, 24 insertions(+), 6 deletions(-) create mode 100644 tests/neg/i22051.scala diff --git a/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala b/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala index c1dd6bc6509e..a8c8ec8ce1d8 100644 --- a/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala +++ b/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala @@ -56,7 +56,7 @@ object TypeTestsCasts { * 9. if `X` is `T1 | T2`, checkable(T1, P) && checkable(T2, P). * 10. otherwise, "" */ - def whyUncheckable(X: Type, P: Type, span: Span)(using Context): String = atPhase(Phases.refchecksPhase.next) { + def whyUncheckable(X: Type, P: Type, span: Span, trustTypeApplication: Boolean)(using Context): String = atPhase(Phases.refchecksPhase.next) { extension (inline s1: String) inline def &&(inline s2: String): String = if s1 == "" then s2 else s1 extension (inline b: Boolean) inline def |||(inline s: String): String = if b then "" else s @@ -143,7 +143,7 @@ object TypeTestsCasts { case defn.ArrayOf(tpE) => recur(tpE, tpT) case _ => recur(defn.AnyType, tpT) } - case tpe @ AppliedType(tycon, targs) => + case tpe @ AppliedType(tycon, targs) if !trustTypeApplication => X.widenDealias match { case OrType(tp1, tp2) => // This case is required to retrofit type inference, @@ -366,8 +366,7 @@ object TypeTestsCasts { if (sym.isTypeTest) { val argType = tree.args.head.tpe val isTrusted = tree.hasAttachment(PatternMatcher.TrustedTypeTestKey) - if !isTrusted then - checkTypePattern(expr.tpe, argType, expr.srcPos) + checkTypePattern(expr.tpe, argType, expr.srcPos, isTrusted) transformTypeTest(expr, argType, flagUnrelated = enclosingInlineds.isEmpty) // if test comes from inlined code, dont't flag it even if it always false } @@ -392,10 +391,10 @@ object TypeTestsCasts { def checkBind(tree: Bind)(using Context) = checkTypePattern(defn.ThrowableType, tree.body.tpe, tree.srcPos) - private def checkTypePattern(exprTpe: Type, castTpe: Type, pos: SrcPos)(using Context) = + private def checkTypePattern(exprTpe: Type, castTpe: Type, pos: SrcPos, trustTypeApplication: Boolean = false)(using Context) = val isUnchecked = exprTpe.widenTermRefExpr.hasAnnotation(defn.UncheckedAnnot) if !isUnchecked then - val whyNot = whyUncheckable(exprTpe, castTpe, pos.span) + val whyNot = whyUncheckable(exprTpe, castTpe, pos.span, trustTypeApplication) if whyNot.nonEmpty then report.uncheckedWarning(UncheckedTypePattern(castTpe, whyNot), pos) diff --git a/tests/neg/i22051.scala b/tests/neg/i22051.scala new file mode 100644 index 000000000000..ba6805a4e166 --- /dev/null +++ b/tests/neg/i22051.scala @@ -0,0 +1,19 @@ +//> using options -Werror + +def boundary[T](body: (T => RuntimeException) => T): T = + case class Break(value: T) extends RuntimeException + try body(Break.apply) + catch case Break(t) => t // error: pattern matching on local classes is unsound currently + +def test = + boundary[Int]: EInt => + val v: String = boundary[String]: EString => + throw EInt(3) + v.length // a runtime error: java.lang.ClassCastException + +def boundaryCorrectBehaviour[T](body: (T => RuntimeException) => T): T = + object local: + // A correct implementation, but is still treated as a local class right now + case class Break(value: T) extends RuntimeException + try body(local.Break.apply) + catch case local.Break(t) => t // error From a7b23d6f1890ce31cf52b72848b559d7873c4213 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Tue, 3 Dec 2024 16:11:36 +0100 Subject: [PATCH 2/2] Move test to warn --- tests/{neg => warn}/i22051.scala | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) rename tests/{neg => warn}/i22051.scala (77%) diff --git a/tests/neg/i22051.scala b/tests/warn/i22051.scala similarity index 77% rename from tests/neg/i22051.scala rename to tests/warn/i22051.scala index ba6805a4e166..e1e902e16379 100644 --- a/tests/neg/i22051.scala +++ b/tests/warn/i22051.scala @@ -1,9 +1,7 @@ -//> using options -Werror - def boundary[T](body: (T => RuntimeException) => T): T = case class Break(value: T) extends RuntimeException try body(Break.apply) - catch case Break(t) => t // error: pattern matching on local classes is unsound currently + catch case Break(t) => t // warn: pattern matching on local classes is unsound currently def test = boundary[Int]: EInt => @@ -16,4 +14,4 @@ def boundaryCorrectBehaviour[T](body: (T => RuntimeException) => T): T = // A correct implementation, but is still treated as a local class right now case class Break(value: T) extends RuntimeException try body(local.Break.apply) - catch case local.Break(t) => t // error + catch case local.Break(t) => t // warn