Skip to content

Commit 85c0589

Browse files
committed
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
1 parent 5587767 commit 85c0589

File tree

6 files changed

+50
-6
lines changed

6 files changed

+50
-6
lines changed

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

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,23 @@ object TypeTestsCasts {
243243
else foundClasses.exists(check)
244244
end checkSensical
245245

246-
if (expr.tpe <:< testType)
246+
def hasUncheckedPrefix(tpe: Type): Boolean = tpe.stripped match
247+
case tpe: TermRef =>
248+
tpe.symbol.info.hasAnnotation(defn.UncheckedAnnot)
249+
|| hasUncheckedPrefix(tpe.prefix)
250+
case tpe: TypeRef =>
251+
tpe.symbol.hasAnnotation(defn.UncheckedAnnot)
252+
|| hasUncheckedPrefix(tpe.prefix)
253+
case tpe: AndOrType =>
254+
hasUncheckedPrefix(tpe.tp1) || hasUncheckedPrefix(tpe.tp2)
255+
case tpe: AppliedType =>
256+
hasUncheckedPrefix(tpe.tycon) || tpe.args.exists(hasUncheckedPrefix)
257+
case tpe: TypeProxy =>
258+
hasUncheckedPrefix(tpe.underlying)
259+
case _ =>
260+
false
261+
262+
if expr.tpe <:< testType && !hasUncheckedPrefix(expr.tpe) then
247263
if (expr.tpe.isNotNull) {
248264
if (!inMatch) report.warning(TypeTestAlwaysSucceeds(expr.tpe, testType), tree.srcPos)
249265
constant(expr, Literal(Constant(true)))

tests/neg/tuplePatDef.scala

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
2+
object Test {
3+
val (x,y): (String, Int) = null // error: unreachable
4+
}

tests/pos/t2613.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,5 @@ object Test {
77

88
type M = MyRelation[_ <: Row, _ <: MyRelation[_, _]]
99

10-
val (x,y): (String, M) = null
10+
val (x,y): (String, M) = null // error
1111
}

tests/pos/tuplePatDef.scala

Lines changed: 0 additions & 4 deletions
This file was deleted.

tests/run/i14693.scala

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
object Test {
2+
val a: Array[Long] = Array(1L)
3+
4+
def test(x: Any) = x match {
5+
case Array(i: Long) => println("Success!")
6+
case _ => println("Failure!") }
7+
8+
def main(args: Array[String]): Unit = test(a)
9+
}

tests/run/i14705.scala

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
trait Fruit
2+
case class Apple() extends Fruit
3+
case class Orange() extends Fruit
4+
5+
case class Box[C](fruit: C) extends Fruit
6+
7+
val apple = Box(fruit = Apple())
8+
val orange = Box(fruit = Orange())
9+
10+
11+
val result = List(apple, orange).map {
12+
case appleBox: Box[Apple] @unchecked if appleBox.fruit.isInstanceOf[Apple] => //contains apple
13+
"apple"
14+
case _ =>
15+
"orange"
16+
}
17+
18+
@main def Test =
19+
assert(result == List("apple", "orange"))

0 commit comments

Comments
 (0)