|
| 1 | +package dotty.tools.dotc |
| 2 | +package transform |
| 3 | + |
| 4 | +import util.Positions._ |
| 5 | +import MegaPhase.MiniPhase |
| 6 | +import core._ |
| 7 | +import Contexts.Context, Types._, Decorators._, Symbols._, typer._, ast._, NameKinds._ |
| 8 | +import TypeUtils._, Flags._ |
| 9 | +import config.Printers.{ transforms => debug } |
| 10 | + |
| 11 | +/** Check runtime realizability of type test, see the documentation for `Checkable`. |
| 12 | + */ |
| 13 | +class IsInstanceOfChecker extends MiniPhase { |
| 14 | + |
| 15 | + import ast.tpd._ |
| 16 | + |
| 17 | + val phaseName = "isInstanceOfChecker" |
| 18 | + |
| 19 | + override def transformTypeApply(tree: TypeApply)(implicit ctx: Context): Tree = { |
| 20 | + def ensureCheckable(qual: Tree, pt: Tree): Tree = { |
| 21 | + if (!Checkable.checkable(qual.tpe, pt.tpe, tree.pos)) |
| 22 | + ctx.warning( |
| 23 | + s"the type test for ${pt.show} cannot be checked at runtime", |
| 24 | + tree.pos |
| 25 | + ) |
| 26 | + |
| 27 | + tree |
| 28 | + } |
| 29 | + |
| 30 | + tree.fun match { |
| 31 | + case fn: Select |
| 32 | + if fn.symbol == defn.Any_typeTest || fn.symbol == defn.Any_isInstanceOf => |
| 33 | + ensureCheckable(fn.qualifier, tree.args.head) |
| 34 | + case _ => tree |
| 35 | + } |
| 36 | + } |
| 37 | +} |
| 38 | + |
| 39 | +object Checkable { |
| 40 | + import Inferencing._ |
| 41 | + import ProtoTypes._ |
| 42 | + |
| 43 | + /** Whether `(x:X).isInstanceOf[P]` can be checked at runtime? |
| 44 | + * |
| 45 | + * First do the following substitution: |
| 46 | + * (a) replace `T @unchecked` and pattern binder types (e.g., `_$1`) in P with WildcardType |
| 47 | + * (b) replace pattern binder types (e.g., `_$1`) in X: |
| 48 | + * - variance = 1 : hiBound |
| 49 | + * - variance = -1 : loBound |
| 50 | + * - variance = 0 : OrType(Any, Nothing) // TODO: use original type param bounds |
| 51 | + * |
| 52 | + * Then check: |
| 53 | + * |
| 54 | + * 1. if `X <:< P`, TRUE |
| 55 | + * 2. if `P` is a singleton type, TRUE |
| 56 | + * 3. if `P` refers to an abstract type member or type parameter, FALSE |
| 57 | + * 4. if `P = Array[T]`, checkable(E, T) where `E` is the element type of `X`, defaults to `Any`. |
| 58 | + * 5. if `P` is `pre.F[Ts]` and `pre.F` refers to a class which is not `Array`: |
| 59 | + * (a) replace `Ts` with fresh type variables `Xs` |
| 60 | + * (b) constrain `Xs` with `pre.F[Xs] <:< X` |
| 61 | + * (c) instantiate Xs and check `pre.F[Xs] <:< P` |
| 62 | + * 6. if `P = T1 | T2` or `P = T1 & T2`, checkable(X, T1) && checkable(X, T2). |
| 63 | + * 7. if `P` is a refinement type, FALSE |
| 64 | + * 8. otherwise, TRUE |
| 65 | + */ |
| 66 | + def checkable(X: Type, P: Type, pos: Position)(implicit ctx: Context): Boolean = { |
| 67 | + def isAbstract(P: Type) = !P.dealias.typeSymbol.isClass |
| 68 | + def isPatternTypeSymbol(sym: Symbol) = !sym.isClass && sym.is(Case) |
| 69 | + |
| 70 | + def replaceP(implicit ctx: Context) = new TypeMap { |
| 71 | + def apply(tp: Type) = tp match { |
| 72 | + case tref: TypeRef |
| 73 | + if isPatternTypeSymbol(tref.typeSymbol) => WildcardType |
| 74 | + case AnnotatedType(_, annot) |
| 75 | + if annot.symbol == defn.UncheckedAnnot => WildcardType |
| 76 | + case _ => mapOver(tp) |
| 77 | + } |
| 78 | + } |
| 79 | + |
| 80 | + def replaceX(implicit ctx: Context) = new TypeMap { |
| 81 | + def apply(tp: Type) = tp match { |
| 82 | + case tref: TypeRef |
| 83 | + if isPatternTypeSymbol(tref.typeSymbol) => |
| 84 | + if (variance == 1) tref.info.hiBound |
| 85 | + else if (variance == -1) tref.info.loBound |
| 86 | + else OrType(defn.AnyType, defn.NothingType) |
| 87 | + case _ => mapOver(tp) |
| 88 | + } |
| 89 | + } |
| 90 | + |
| 91 | + def isClassDetermined(X: Type, P: AppliedType)(implicit ctx: Context) = { |
| 92 | + val AppliedType(tycon, _) = P |
| 93 | + val typeLambda = tycon.ensureLambdaSub.asInstanceOf[TypeLambda] |
| 94 | + val tvars = constrained(typeLambda, untpd.EmptyTree, alwaysAddTypeVars = true)._2.map(_.tpe) |
| 95 | + val P1 = tycon.appliedTo(tvars) |
| 96 | + |
| 97 | + debug.println("P : " + P.show) |
| 98 | + debug.println("P1 : " + P1.show) |
| 99 | + debug.println("X : " + X.show) |
| 100 | + |
| 101 | + P1 <:< X // may fail, ignore |
| 102 | + |
| 103 | + val res = isFullyDefined(P1, ForceDegree.noBottom) && P1 <:< P |
| 104 | + debug.println("P1 : " + P1) |
| 105 | + debug.println("P1 <:< P = " + res) |
| 106 | + |
| 107 | + res |
| 108 | + } |
| 109 | + |
| 110 | + def recur(X: Type, P: Type): Boolean = (X <:< P) || (P match { |
| 111 | + case _: SingletonType => true |
| 112 | + case _: TypeProxy |
| 113 | + if isAbstract(P) => false |
| 114 | + case defn.ArrayOf(tpT) => |
| 115 | + X match { |
| 116 | + case defn.ArrayOf(tpE) => recur(tpE, tpT) |
| 117 | + case _ => recur(defn.AnyType, tpT) |
| 118 | + } |
| 119 | + case tpe: AppliedType => isClassDetermined(X, tpe)(ctx.fresh.setNewTyperState()) |
| 120 | + case AndType(tp1, tp2) => recur(X, tp1) && recur(X, tp2) |
| 121 | + case OrType(tp1, tp2) => recur(X, tp1) && recur(X, tp2) |
| 122 | + case AnnotatedType(t, _) => recur(X, t) |
| 123 | + case _: RefinedType => false |
| 124 | + case _ => true |
| 125 | + }) |
| 126 | + |
| 127 | + val res = recur(replaceX.apply(X.widen), replaceP.apply(P)) |
| 128 | + |
| 129 | + debug.println(i"checking ${X.show} isInstanceOf ${P} = $res") |
| 130 | + |
| 131 | + res |
| 132 | + } |
| 133 | +} |
0 commit comments