Skip to content

Commit c7ac8dc

Browse files
authored
Expose reason for unchecked warning (#16086)
2 parents 5437c00 + b3f0429 commit c7ac8dc

File tree

6 files changed

+50
-30
lines changed

6 files changed

+50
-30
lines changed

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

+30-20
Original file line numberDiff line numberDiff line change
@@ -31,27 +31,32 @@ object TypeTestsCasts {
3131
import typer.Inferencing.maximizeType
3232
import typer.ProtoTypes.constrained
3333

34-
/** Whether `(x: X).isInstanceOf[P]` can be checked at runtime?
34+
/** Tests whether `(x: X).isInstanceOf[P]` is uncheckable at runtime, returning the reason,
35+
* or the empty string if it is checkable.
3536
*
3637
* First do the following substitution:
3738
* (a) replace `T @unchecked` and pattern binder types (e.g., `_$1`) in P with WildcardType
3839
*
3940
* Then check:
4041
*
41-
* 1. if `X <:< P`, TRUE
42-
* 2. if `P` is a singleton type, TRUE
43-
* 3. if `P` refers to an abstract type member or type parameter, FALSE
42+
* 1. if `X <:< P`, ""
43+
* 2. if `P` is a singleton type, ""
44+
* 3. if `P` refers to an abstract type member or type parameter, "it refers to an abstract type member or type parameter"
4445
* 4. if `P = Array[T]`, checkable(E, T) where `E` is the element type of `X`, defaults to `Any`.
4546
* 5. if `P` is `pre.F[Ts]` and `pre.F` refers to a class which is not `Array`:
4647
* (a) replace `Ts` with fresh type variables `Xs`
4748
* (b) constrain `Xs` with `pre.F[Xs] <:< X`
48-
* (c) maximize `pre.F[Xs]` and check `pre.F[Xs] <:< P`
49+
* (c) maximize `pre.F[Xs]`
50+
* (d) if !`pre.F[Xs] <:< P`, "its type arguments can't be determined from $X"
4951
* 6. if `P = T1 | T2` or `P = T1 & T2`, checkable(X, T1) && checkable(X, T2).
50-
* 7. if `P` is a refinement type, FALSE
51-
* 8. if `P` is a local class which is not statically reachable from the scope where `X` is defined, FALSE
52-
* 9. otherwise, TRUE
52+
* 7. if `P` is a refinement type, "it's a refinement type"
53+
* 8. if `P` is a local class which is not statically reachable from the scope where `X` is defined, "it's a local class"
54+
* 9. otherwise, ""
5355
*/
54-
def checkable(X: Type, P: Type, span: Span)(using Context): Boolean = atPhase(Phases.refchecksPhase.next) {
56+
def whyUncheckable(X: Type, P: Type, span: Span)(using Context): String = atPhase(Phases.refchecksPhase.next) {
57+
extension (inline s1: String) inline def &&(inline s2: String): String = if s1 == "" then s2 else s1
58+
extension (inline b: Boolean) inline def |||(inline s: String): String = if b then "" else s
59+
5560
// Run just before ElimOpaque transform (which follows RefChecks)
5661
def isAbstract(P: Type) = !P.dealias.typeSymbol.isClass
5762

@@ -124,10 +129,10 @@ object TypeTestsCasts {
124129

125130
}
126131

127-
def recur(X: Type, P: Type): Boolean = (X <:< P) || (P.dealias match {
128-
case _: SingletonType => true
132+
def recur(X: Type, P: Type): String = (X <:< P) ||| (P.dealias match {
133+
case _: SingletonType => ""
129134
case _: TypeProxy
130-
if isAbstract(P) => false
135+
if isAbstract(P) => i"it refers to an abstract type member or type parameter"
131136
case defn.ArrayOf(tpT) =>
132137
X match {
133138
case defn.ArrayOf(tpE) => recur(tpE, tpT)
@@ -147,21 +152,23 @@ object TypeTestsCasts {
147152
X.classSymbol.exists && P.classSymbol.exists &&
148153
!X.classSymbol.asClass.mayHaveCommonChild(P.classSymbol.asClass)
149154
|| typeArgsTrivial(X, tpe)
155+
||| i"its type arguments can't be determined from $X"
150156
}
151157
case AndType(tp1, tp2) => recur(X, tp1) && recur(X, tp2)
152158
case OrType(tp1, tp2) => recur(X, tp1) && recur(X, tp2)
153159
case AnnotatedType(t, _) => recur(X, t)
154-
case tp2: RefinedType => recur(X, tp2.parent) && TypeComparer.hasMatchingMember(tp2.refinedName, X, tp2)
160+
case tp2: RefinedType => recur(X, tp2.parent)
161+
&& (TypeComparer.hasMatchingMember(tp2.refinedName, X, tp2) ||| i"it's a refinement type")
155162
case tp2: RecType => recur(X, tp2.parent)
156163
case _
157164
if P.classSymbol.isLocal && foundClasses(X).exists(P.classSymbol.isInaccessibleChildOf) => // 8
158-
false
159-
case _ => true
165+
i"it's a local class"
166+
case _ => ""
160167
})
161168

162-
val res = X.widenTermRefExpr.hasAnnotation(defn.UncheckedAnnot) || recur(X.widen, replaceP(P))
169+
val res = recur(X.widen, replaceP(P))
163170

164-
debug.println(i"checking ${X.show} isInstanceOf ${P} = $res")
171+
debug.println(i"checking $X isInstanceOf $P = $res")
165172

166173
res
167174
}
@@ -348,9 +355,12 @@ object TypeTestsCasts {
348355
if (sym.isTypeTest) {
349356
val argType = tree.args.head.tpe
350357
val isTrusted = tree.hasAttachment(PatternMatcher.TrustedTypeTestKey)
351-
if (!isTrusted && !checkable(expr.tpe, argType, tree.span))
352-
report.uncheckedWarning(i"the type test for $argType cannot be checked at runtime", expr.srcPos)
353-
transformTypeTest(expr, tree.args.head.tpe,
358+
val isUnchecked = expr.tpe.widenTermRefExpr.hasAnnotation(defn.UncheckedAnnot)
359+
if !isTrusted && !isUnchecked then
360+
val whyNot = whyUncheckable(expr.tpe, argType, tree.span)
361+
if whyNot.nonEmpty then
362+
report.uncheckedWarning(i"the type test for $argType cannot be checked at runtime because $whyNot", expr.srcPos)
363+
transformTypeTest(expr, argType,
354364
flagUnrelated = enclosingInlineds.isEmpty) // if test comes from inlined code, dont't flag it even if it always false
355365
}
356366
else if (sym.isTypeCast)
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
-- Error: tests/neg-custom-args/fatal-warnings/i12253.scala:11:10 ------------------------------------------------------
22
11 | case extractors.InlinedLambda(_, Select(_, name)) => Expr(name) // error // error
33
| ^
4-
| the type test for extractors.q2.reflect.Term cannot be checked at runtime
4+
|the type test for extractors.q2.reflect.Term cannot be checked at runtime because it refers to an abstract type member or type parameter
55
-- Error: tests/neg-custom-args/fatal-warnings/i12253.scala:11:38 ------------------------------------------------------
66
11 | case extractors.InlinedLambda(_, Select(_, name)) => Expr(name) // error // error
77
| ^
8-
| the type test for q1.reflect.Select cannot be checked at runtime
8+
|the type test for q1.reflect.Select cannot be checked at runtime because it refers to an abstract type member or type parameter
99
there was 1 deprecation warning; re-run with -deprecation for details

tests/neg-custom-args/nowarn/nowarn.check

+1-1
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ Matching filters for @nowarn or -Wconf:
6969
-- Unchecked Warning: tests/neg-custom-args/nowarn/nowarn.scala:53:7 ---------------------------------------------------
7070
53 | case _: List[Int] => 0 // warning (patmat, unchecked)
7171
| ^
72-
| the type test for List[Int] cannot be checked at runtime
72+
|the type test for List[Int] cannot be checked at runtime because its type arguments can't be determined from Any
7373
-- Error: tests/neg-custom-args/nowarn/nowarn.scala:31:1 ---------------------------------------------------------------
7474
31 |@nowarn("id=1") def t4d = try 1 // error and warning (unused nowarn, wrong id)
7575
|^^^^^^^^^^^^^^^

tests/neg/15981.check

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
-- Error: tests/neg/15981.scala:4:45 -----------------------------------------------------------------------------------
2+
4 | override def equals(any: Any): Boolean = any.isInstanceOf[PosInt] // error
3+
| ^^^
4+
| the type test for PosInt cannot be checked at runtime because it's a local class

tests/neg/15981.scala

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// scalac: -Werror
2+
val _ = locally{
3+
sealed abstract class PosInt(val value: Int) {
4+
override def equals(any: Any): Boolean = any.isInstanceOf[PosInt] // error
5+
}
6+
}

tests/neg/i4812.check

+7-7
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,28 @@
11
-- Error: tests/neg/i4812.scala:8:11 -----------------------------------------------------------------------------------
22
8 | case prev: A => // error: the type test for A cannot be checked at runtime
33
| ^
4-
| the type test for A cannot be checked at runtime
4+
| the type test for A cannot be checked at runtime because it's a local class
55
-- Error: tests/neg/i4812.scala:18:11 ----------------------------------------------------------------------------------
66
18 | case prev: A => // error: the type test for A cannot be checked at runtime
77
| ^
8-
| the type test for A cannot be checked at runtime
8+
| the type test for A cannot be checked at runtime because it's a local class
99
-- Error: tests/neg/i4812.scala:28:11 ----------------------------------------------------------------------------------
1010
28 | case prev: A => // error: the type test for A cannot be checked at runtime
1111
| ^
12-
| the type test for A cannot be checked at runtime
12+
| the type test for A cannot be checked at runtime because it's a local class
1313
-- Error: tests/neg/i4812.scala:38:11 ----------------------------------------------------------------------------------
1414
38 | case prev: A => // error: the type test for A cannot be checked at runtime
1515
| ^
16-
| the type test for A cannot be checked at runtime
16+
| the type test for A cannot be checked at runtime because it's a local class
1717
-- Error: tests/neg/i4812.scala:50:13 ----------------------------------------------------------------------------------
1818
50 | case prev: A => // error: the type test for A cannot be checked at runtime
1919
| ^
20-
| the type test for A cannot be checked at runtime
20+
| the type test for A cannot be checked at runtime because it's a local class
2121
-- Error: tests/neg/i4812.scala:60:11 ----------------------------------------------------------------------------------
2222
60 | case prev: A => // error: the type test for A cannot be checked at runtime
2323
| ^
24-
| the type test for A cannot be checked at runtime
24+
| the type test for A cannot be checked at runtime because it's a local class
2525
-- Error: tests/neg/i4812.scala:96:11 ----------------------------------------------------------------------------------
2626
96 | case x: B => // error: the type test for B cannot be checked at runtime
2727
| ^
28-
| the type test for B cannot be checked at runtime
28+
| the type test for B cannot be checked at runtime because it's a local class

0 commit comments

Comments
 (0)