@@ -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)
0 commit comments