@@ -31,27 +31,32 @@ object TypeTestsCasts {
31
31
import typer .Inferencing .maximizeType
32
32
import typer .ProtoTypes .constrained
33
33
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.
35
36
*
36
37
* First do the following substitution:
37
38
* (a) replace `T @unchecked` and pattern binder types (e.g., `_$1`) in P with WildcardType
38
39
*
39
40
* Then check:
40
41
*
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"
44
45
* 4. if `P = Array[T]`, checkable(E, T) where `E` is the element type of `X`, defaults to `Any`.
45
46
* 5. if `P` is `pre.F[Ts]` and `pre.F` refers to a class which is not `Array`:
46
47
* (a) replace `Ts` with fresh type variables `Xs`
47
48
* (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"
49
51
* 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, ""
53
55
*/
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
+
55
60
// Run just before ElimOpaque transform (which follows RefChecks)
56
61
def isAbstract (P : Type ) = ! P .dealias.typeSymbol.isClass
57
62
@@ -124,10 +129,10 @@ object TypeTestsCasts {
124
129
125
130
}
126
131
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 => " "
129
134
case _ : TypeProxy
130
- if isAbstract(P ) => false
135
+ if isAbstract(P ) => i " it refers to an abstract type member or type parameter "
131
136
case defn.ArrayOf (tpT) =>
132
137
X match {
133
138
case defn.ArrayOf (tpE) => recur(tpE, tpT)
@@ -147,21 +152,23 @@ object TypeTestsCasts {
147
152
X .classSymbol.exists && P .classSymbol.exists &&
148
153
! X .classSymbol.asClass.mayHaveCommonChild(P .classSymbol.asClass)
149
154
|| typeArgsTrivial(X , tpe)
155
+ ||| i " its type arguments can't be determined from $X"
150
156
}
151
157
case AndType (tp1, tp2) => recur(X , tp1) && recur(X , tp2)
152
158
case OrType (tp1, tp2) => recur(X , tp1) && recur(X , tp2)
153
159
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 " )
155
162
case tp2 : RecType => recur(X , tp2.parent)
156
163
case _
157
164
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 _ => " "
160
167
})
161
168
162
- val res = X .widenTermRefExpr.hasAnnotation(defn. UncheckedAnnot ) || recur(X .widen, replaceP(P ))
169
+ val res = recur(X .widen, replaceP(P ))
163
170
164
- debug.println(i " checking ${ X .show} isInstanceOf ${ P } = $res" )
171
+ debug.println(i " checking $X isInstanceOf $P = $res" )
165
172
166
173
res
167
174
}
@@ -348,9 +355,12 @@ object TypeTestsCasts {
348
355
if (sym.isTypeTest) {
349
356
val argType = tree.args.head.tpe
350
357
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,
354
364
flagUnrelated = enclosingInlineds.isEmpty) // if test comes from inlined code, dont't flag it even if it always false
355
365
}
356
366
else if (sym.isTypeCast)
0 commit comments