@@ -4,6 +4,7 @@ package transform
44import core ._
55import Contexts ._ , Symbols ._ , Types ._ , Constants ._ , StdNames ._ , Decorators ._
66import ast .Trees ._
7+ import ast .untpd
78import Erasure .Boxing ._
89import TypeErasure ._
910import ValueClasses ._
@@ -12,7 +13,7 @@ import core.Flags._
1213import util .Positions ._
1314import reporting .diagnostic .messages .TypeTestAlwaysSucceeds
1415import reporting .trace
15-
16+ import config . Printers .{ transforms => debug }
1617
1718/** This transform normalizes type tests and type casts,
1819 * also replacing type tests with singleton argument type with reference equality check
@@ -21,12 +22,122 @@ import reporting.trace
2122 * - have a reference type as receiver
2223 * - can be translated directly to machine instructions
2324 *
24- *
2525 * Unfortunately this phase ended up being not Y-checkable unless types are erased. A cast to an ConstantType(3) or x.type
26- * cannot be rewritten before erasure.
26+ * cannot be rewritten before erasure. That's why TypeTestsCasts is called from Erasure.
2727 */
2828object TypeTestsCasts {
2929 import ast .tpd ._
30+ import typer .Inferencing .maximizeType
31+ import typer .ProtoTypes .constrained
32+
33+ /** Whether `(x:X).isInstanceOf[P]` can be checked at runtime?
34+ *
35+ * First do the following substitution:
36+ * (a) replace `T @unchecked` and pattern binder types (e.g., `_$1`) in P with WildcardType
37+ * (b) replace pattern binder types (e.g., `_$1`) in X:
38+ * - variance = 1 : hiBound
39+ * - variance = -1 : loBound
40+ * - variance = 0 : OrType(Any, Nothing) // TODO: use original type param bounds
41+ *
42+ * Then check:
43+ *
44+ * 1. if `X <:< P`, TRUE
45+ * 2. if `P` is a singleton type, TRUE
46+ * 3. if `P` refers to an abstract type member or type parameter, FALSE
47+ * 4. if `P = Array[T]`, checkable(E, T) where `E` is the element type of `X`, defaults to `Any`.
48+ * 5. if `P` is `pre.F[Ts]` and `pre.F` refers to a class which is not `Array`:
49+ * (a) replace `Ts` with fresh type variables `Xs`
50+ * (b) constrain `Xs` with `pre.F[Xs] <:< X`
51+ * (c) instantiate Xs and check `pre.F[Xs] <:< P`
52+ * 6. if `P = T1 | T2` or `P = T1 & T2`, checkable(X, T1) && checkable(X, T2).
53+ * 7. if `P` is a refinement type, FALSE
54+ * 8. otherwise, TRUE
55+ */
56+ def checkable (X : Type , P : Type , pos : Position )(implicit ctx : Context ): Boolean = {
57+ def isAbstract (P : Type ) = ! P .dealias.typeSymbol.isClass
58+ def isPatternTypeSymbol (sym : Symbol ) = ! sym.isClass && sym.is(Case )
59+
60+ def replaceP (tp : Type )(implicit ctx : Context ) = new TypeMap {
61+ def apply (tp : Type ) = tp match {
62+ case tref : TypeRef
63+ if isPatternTypeSymbol(tref.typeSymbol) => WildcardType
64+ case AnnotatedType (_, annot)
65+ if annot.symbol == defn.UncheckedAnnot => WildcardType
66+ case _ => mapOver(tp)
67+ }
68+ }.apply(tp)
69+
70+ def replaceX (tp : Type )(implicit ctx : Context ) = new TypeMap {
71+ def apply (tp : Type ) = tp match {
72+ case tref : TypeRef
73+ if isPatternTypeSymbol(tref.typeSymbol) =>
74+ if (variance == 1 ) tref.info.hiBound
75+ else if (variance == - 1 ) tref.info.loBound
76+ else OrType (defn.AnyType , defn.NothingType )
77+ case _ => mapOver(tp)
78+ }
79+ }.apply(tp)
80+
81+ /** Approximate type parameters depending on variance */
82+ def stripTypeParam (tp : Type )(implicit ctx : Context ) = new ApproximatingTypeMap {
83+ def apply (tp : Type ): Type = tp match {
84+ case tp : TypeRef if tp.underlying.isInstanceOf [TypeBounds ] =>
85+ val lo = apply(tp.info.loBound)
86+ val hi = apply(tp.info.hiBound)
87+ range(lo, hi)
88+ case _ =>
89+ mapOver(tp)
90+ }
91+ }.apply(tp)
92+
93+ def isClassDetermined (X : Type , P : AppliedType )(implicit ctx : Context ) = {
94+ val AppliedType (tycon, _) = P
95+ val typeLambda = tycon.ensureLambdaSub.asInstanceOf [TypeLambda ]
96+ val tvars = constrained(typeLambda, untpd.EmptyTree , alwaysAddTypeVars = true )._2.map(_.tpe)
97+ val P1 = tycon.appliedTo(tvars)
98+
99+ debug.println(" P : " + P )
100+ debug.println(" P1 : " + P1 )
101+ debug.println(" X : " + X )
102+
103+ P1 <:< X // constraint P1
104+
105+ // use fromScala2x to avoid generating pattern bound symbols
106+ maximizeType(P1 , pos, fromScala2x = true )
107+
108+ val res = P1 <:< P
109+ debug.println(" P1 : " + P1 )
110+ debug.println(" P1 <:< P = " + res)
111+
112+ res
113+ }
114+
115+ def recur (X : Type , P : Type ): Boolean = (X <:< P ) || (P match {
116+ case _ : SingletonType => true
117+ case _ : TypeProxy
118+ if isAbstract(P ) => false
119+ case defn.ArrayOf (tpT) =>
120+ X match {
121+ case defn.ArrayOf (tpE) => recur(tpE, tpT)
122+ case _ => recur(defn.AnyType , tpT)
123+ }
124+ case tpe : AppliedType =>
125+ // first try withou striping type parameters for performance
126+ isClassDetermined(X , tpe)(ctx.fresh.setNewTyperState()) ||
127+ isClassDetermined(stripTypeParam(X ), tpe)(ctx.fresh.setNewTyperState())
128+ case AndType (tp1, tp2) => recur(X , tp1) && recur(X , tp2)
129+ case OrType (tp1, tp2) => recur(X , tp1) && recur(X , tp2)
130+ case AnnotatedType (t, _) => recur(X , t)
131+ case _ : RefinedType => false
132+ case _ => true
133+ })
134+
135+ val res = recur(replaceX(X .widen), replaceP(P ))
136+
137+ debug.println(i " checking ${X .show} isInstanceOf ${P } = $res" )
138+
139+ res
140+ }
30141
31142 def interceptTypeApply (tree : TypeApply )(implicit ctx : Context ): Tree = trace(s " transforming ${tree.show}" , show = true ) {
32143 tree.fun match {
@@ -156,8 +267,12 @@ object TypeTestsCasts {
156267 transformIsInstanceOf(expr, erasure(testType), flagUnrelated)
157268 }
158269
159- if (sym.isTypeTest)
270+ if (sym.isTypeTest) {
271+ val argType = tree.args.head.tpe
272+ if (! checkable(expr.tpe, argType, tree.pos))
273+ ctx.warning(s " the type test for $argType cannot be checked at runtime " , tree.pos)
160274 transformTypeTest(expr, tree.args.head.tpe, flagUnrelated = true )
275+ }
161276 else if (sym eq defn.Any_asInstanceOf )
162277 transformAsInstanceOf(erasure(tree.args.head.tpe))
163278 else tree
0 commit comments