From 6f7f7ae136b41710c0859fda143bc5c2a40322f3 Mon Sep 17 00:00:00 2001 From: odersky Date: Fri, 18 Mar 2022 12:46:58 +0100 Subject: [PATCH 1/2] Don't trust @unchecked types when optimizing type tests Some type tests are elided or replaced with non null-checks if we can show that the expression's type is a subtype of the test type. But that can be done safely only if the expression's type does not come from an `@unchecked` cast, inserted by pattern matching or the user. Fixes #14705 --- .../tools/dotc/transform/TypeTestsCasts.scala | 18 +++++++++++++++++- tests/neg/tuplePatDef.scala | 4 ++++ tests/pos/t2613.scala | 2 +- tests/pos/tuplePatDef.scala | 4 ---- tests/run/i14693.scala | 9 +++++++++ tests/run/i14705.scala | 19 +++++++++++++++++++ 6 files changed, 50 insertions(+), 6 deletions(-) create mode 100644 tests/neg/tuplePatDef.scala delete mode 100644 tests/pos/tuplePatDef.scala create mode 100644 tests/run/i14693.scala create mode 100644 tests/run/i14705.scala diff --git a/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala b/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala index 0b880afdd995..7d1740b2b581 100644 --- a/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala +++ b/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala @@ -243,7 +243,23 @@ object TypeTestsCasts { else foundClasses.exists(check) end checkSensical - if (expr.tpe <:< testType) + def hasUncheckedPrefix(tpe: Type): Boolean = tpe.stripped match + case tpe: TermRef => + tpe.symbol.info.hasAnnotation(defn.UncheckedAnnot) + || hasUncheckedPrefix(tpe.prefix) + case tpe: TypeRef => + tpe.symbol.hasAnnotation(defn.UncheckedAnnot) + || hasUncheckedPrefix(tpe.prefix) + case tpe: AndOrType => + hasUncheckedPrefix(tpe.tp1) || hasUncheckedPrefix(tpe.tp2) + case tpe: AppliedType => + hasUncheckedPrefix(tpe.tycon) || tpe.args.exists(hasUncheckedPrefix) + case tpe: TypeProxy => + hasUncheckedPrefix(tpe.underlying) + case _ => + false + + if expr.tpe <:< testType && !hasUncheckedPrefix(expr.tpe) then if (expr.tpe.isNotNull) { if (!inMatch) report.warning(TypeTestAlwaysSucceeds(expr.tpe, testType), tree.srcPos) constant(expr, Literal(Constant(true))) diff --git a/tests/neg/tuplePatDef.scala b/tests/neg/tuplePatDef.scala new file mode 100644 index 000000000000..03aa4bcd15e7 --- /dev/null +++ b/tests/neg/tuplePatDef.scala @@ -0,0 +1,4 @@ + +object Test { + val (x,y): (String, Int) = null // error: unreachable +} diff --git a/tests/pos/t2613.scala b/tests/pos/t2613.scala index 17ebe2d7e9bf..2540ef12df7b 100644 --- a/tests/pos/t2613.scala +++ b/tests/pos/t2613.scala @@ -7,5 +7,5 @@ object Test { type M = MyRelation[_ <: Row, _ <: MyRelation[_, _]] - val (x,y): (String, M) = null + val (x,y): (String, M) = null : Any } diff --git a/tests/pos/tuplePatDef.scala b/tests/pos/tuplePatDef.scala deleted file mode 100644 index 22f8f8e7d6ed..000000000000 --- a/tests/pos/tuplePatDef.scala +++ /dev/null @@ -1,4 +0,0 @@ - -object Test { - val (x,y): (String, Int) = null -} diff --git a/tests/run/i14693.scala b/tests/run/i14693.scala new file mode 100644 index 000000000000..52d7ca6de4a6 --- /dev/null +++ b/tests/run/i14693.scala @@ -0,0 +1,9 @@ +object Test { + val a: Array[Long] = Array(1L) + + def test(x: Any) = x match { + case Array(i: Long) => println("Success!") + case _ => println("Failure!") } + + def main(args: Array[String]): Unit = test(a) +} \ No newline at end of file diff --git a/tests/run/i14705.scala b/tests/run/i14705.scala new file mode 100644 index 000000000000..1ee4f6c7752b --- /dev/null +++ b/tests/run/i14705.scala @@ -0,0 +1,19 @@ +trait Fruit +case class Apple() extends Fruit +case class Orange() extends Fruit + +case class Box[C](fruit: C) extends Fruit + +val apple = Box(fruit = Apple()) +val orange = Box(fruit = Orange()) + + +val result = List(apple, orange).map { + case appleBox: Box[Apple] @unchecked if appleBox.fruit.isInstanceOf[Apple] => //contains apple + "apple" + case _ => + "orange" +} + +@main def Test = + assert(result == List("apple", "orange")) From dafc69a2a3c86a6151268897f1c7fe9db8d07aaf Mon Sep 17 00:00:00 2001 From: odersky Date: Fri, 18 Mar 2022 20:10:06 +0100 Subject: [PATCH 2/2] Drop unrelated test --- tests/run/i14693.scala | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 tests/run/i14693.scala diff --git a/tests/run/i14693.scala b/tests/run/i14693.scala deleted file mode 100644 index 52d7ca6de4a6..000000000000 --- a/tests/run/i14693.scala +++ /dev/null @@ -1,9 +0,0 @@ -object Test { - val a: Array[Long] = Array(1L) - - def test(x: Any) = x match { - case Array(i: Long) => println("Success!") - case _ => println("Failure!") } - - def main(args: Array[String]): Unit = test(a) -} \ No newline at end of file