All Downloads are FREE. Search and download functionalities are using the official Maven repository.

dotty.tools.dotc.transform.IsInstanceOfChecker.scala Maven / Gradle / Ivy

package dotty.tools.dotc
package transform

import util.Positions._
import MegaPhase.MiniPhase
import core._
import Contexts.Context, Types._, Decorators._, Symbols._, typer._, ast._, NameKinds._
import TypeUtils._, Flags._
import config.Printers.{ transforms => debug }

/** Check runtime realizability of type test, see the documentation for `Checkable`.
 */
class IsInstanceOfChecker extends MiniPhase {

  import ast.tpd._

  val phaseName = "isInstanceOfChecker"

  override def transformTypeApply(tree: TypeApply)(implicit ctx: Context): Tree = {
    def ensureCheckable(qual: Tree, pt: Tree): Tree = {
      if (!Checkable.checkable(qual.tpe, pt.tpe, tree.pos))
         ctx.warning(
           s"the type test for ${pt.show} cannot be checked at runtime",
           tree.pos
         )

      tree
    }

    tree.fun match {
      case fn: Select
      if fn.symbol == defn.Any_typeTest || fn.symbol == defn.Any_isInstanceOf =>
        ensureCheckable(fn.qualifier, tree.args.head)
      case _ => tree
    }
  }
}

object Checkable {
  import Inferencing._
  import ProtoTypes._

  /** Whether `(x:X).isInstanceOf[P]` can be checked at runtime?
   *
   *  First do the following substitution:
   *  (a) replace `T @unchecked` and pattern binder types (e.g., `_$1`) in P with WildcardType
   *  (b) replace pattern binder types (e.g., `_$1`) in X:
   *      - variance = 1  : hiBound
   *      - variance = -1 : loBound
   *      - variance = 0  : OrType(Any, Nothing) // TODO: use original type param bounds
   *
   *  Then check:
   *
   *  1. if `X <:< P`, TRUE
   *  2. if `P` is a singleton type, TRUE
   *  3. if `P` refers to an abstract type member or type parameter, FALSE
   *  4. if `P = Array[T]`, checkable(E, T) where `E` is the element type of `X`, defaults to `Any`.
   *  5. if `P` is `pre.F[Ts]` and `pre.F` refers to a class which is not `Array`:
   *     (a) replace `Ts` with fresh type variables `Xs`
   *     (b) constrain `Xs` with `pre.F[Xs] <:< X`
   *     (c) instantiate Xs and check `pre.F[Xs] <:< P`
   *  6. if `P = T1 | T2` or `P = T1 & T2`, checkable(X, T1) && checkable(X, T2).
   *  7. if `P` is a refinement type, FALSE
   *  8. otherwise, TRUE
   */
  def checkable(X: Type, P: Type, pos: Position)(implicit ctx: Context): Boolean = {
    def isAbstract(P: Type) = !P.dealias.typeSymbol.isClass
    def isPatternTypeSymbol(sym: Symbol) = !sym.isClass && sym.is(Case)

    def replaceP(tp: Type)(implicit ctx: Context) = new TypeMap {
      def apply(tp: Type) = tp match {
        case tref: TypeRef
        if isPatternTypeSymbol(tref.typeSymbol) => WildcardType
        case AnnotatedType(_, annot)
        if annot.symbol == defn.UncheckedAnnot => WildcardType
        case _ => mapOver(tp)
      }
    }.apply(tp)

    def replaceX(tp: Type)(implicit ctx: Context) = new TypeMap {
      def apply(tp: Type) = tp match {
        case tref: TypeRef
        if isPatternTypeSymbol(tref.typeSymbol) =>
          if (variance == 1) tref.info.hiBound
          else if (variance == -1) tref.info.loBound
          else OrType(defn.AnyType, defn.NothingType)
        case _ => mapOver(tp)
      }
    }.apply(tp)

    /** Approximate type parameters depending on variance */
    def stripTypeParam(tp: Type)(implicit ctx: Context) = new ApproximatingTypeMap {
      def apply(tp: Type): Type = tp match {
        case tp: TypeRef if tp.underlying.isInstanceOf[TypeBounds] =>
          val lo = apply(tp.info.loBound)
          val hi = apply(tp.info.hiBound)
          range(lo, hi)
        case _ =>
          mapOver(tp)
      }
    }.apply(tp)

    def isClassDetermined(X: Type, P: AppliedType)(implicit ctx: Context) = {
      val AppliedType(tycon, _) = P
      val typeLambda = tycon.ensureLambdaSub.asInstanceOf[TypeLambda]
      val tvars = constrained(typeLambda, untpd.EmptyTree, alwaysAddTypeVars = true)._2.map(_.tpe)
      val P1 = tycon.appliedTo(tvars)

      debug.println("P : " + P)
      debug.println("P1 : " + P1)
      debug.println("X : " + X)

      P1 <:< X       // constraint P1

      // use fromScala2x to avoid generating pattern bound symbols
      maximizeType(P1, pos, fromScala2x = true)

      val res = P1 <:< P
      debug.println("P1 : " + P1)
      debug.println("P1 <:< P = " + res)

      res
    }

    def recur(X: Type, P: Type): Boolean = (X <:< P) || (P match {
      case _: SingletonType     => true
      case _: TypeProxy
      if isAbstract(P)          => false
      case defn.ArrayOf(tpT)    =>
        X match {
          case defn.ArrayOf(tpE)   => recur(tpE, tpT)
          case _                   => recur(defn.AnyType, tpT)
        }
      case tpe: AppliedType     =>
        // first try withou striping type parameters for performance
        isClassDetermined(X, tpe)(ctx.fresh.setNewTyperState()) ||
        isClassDetermined(stripTypeParam(X), tpe)(ctx.fresh.setNewTyperState())
      case AndType(tp1, tp2)    => recur(X, tp1) && recur(X, tp2)
      case OrType(tp1, tp2)     => recur(X, tp1) && recur(X, tp2)
      case AnnotatedType(t, _)  => recur(X, t)
      case _: RefinedType       => false
      case _                    => true
    })

    val res = recur(replaceX(X.widen), replaceP(P))

    debug.println(i"checking  ${X.show} isInstanceOf ${P} = $res")

    res
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy