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

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

The newest version!
package dotty.tools.dotc
package transform

import dotty.tools.dotc.ast.Trees._
import dotty.tools.dotc.ast.{TreeTypeMap, tpd, untpd}
import dotty.tools.dotc.core.Constants._
import dotty.tools.dotc.core.Contexts._
import dotty.tools.dotc.core.Decorators._
import dotty.tools.dotc.core.Flags._
import dotty.tools.dotc.core.quoted._
import dotty.tools.dotc.core.NameKinds._
import dotty.tools.dotc.core.StagingContext._
import dotty.tools.dotc.core.StdNames._
import dotty.tools.dotc.core.Symbols._
import dotty.tools.dotc.core.tasty.TreePickler.Hole
import dotty.tools.dotc.core.Types._
import dotty.tools.dotc.util.SourcePosition
import dotty.tools.dotc.util.Spans._
import dotty.tools.dotc.transform.SymUtils._
import dotty.tools.dotc.transform.TreeMapWithStages._
import dotty.tools.dotc.typer.Implicits.SearchFailureType
import dotty.tools.dotc.typer.Inliner

import scala.collection.mutable
import dotty.tools.dotc.util.SourcePosition

import scala.annotation.constructorOnly

/** Checks that the Phase Consistency Principle (PCP) holds and heals types.
 *
 *  Type healing consists in transforming a phase inconsistent type `T` into a splice of `implicitly[Type[T]]`.
 */
class PCPCheckAndHeal(@constructorOnly ictx: Context) extends TreeMapWithStages(ictx) {
  import tpd._

  override def transform(tree: Tree)(implicit ctx: Context): Tree = {
    if (tree.source != ctx.source && tree.source.exists)
      transform(tree)(ctx.withSource(tree.source))
    else tree match {
      case tree: DefDef if tree.symbol.is(Inline) && level > 0 => EmptyTree
      case _ => checkLevel(super.transform(tree))
    }
  }

  /** Transform quoted trees while maintaining phase correctness */
  override protected def transformQuotation(body: Tree, quote: Tree)(implicit ctx: Context): Tree = {
    val body1 = transform(body)(quoteContext)
    super.transformQuotation(body1, quote)
  }

  /** Transform splice
   *  - If inside a quote, transform the contents of the splice.
   *  - If inside inlined code, expand the macro code.
   *  - If inside of a macro definition, check the validity of the macro.
   */
  protected def transformSplice(body: Tree, splice: Tree)(implicit ctx: Context): Tree = {
    val body1 = transform(body)(spliceContext)
    splice match {
      case Apply(fun: TypeApply, _) if splice.isTerm =>
        // Type of the splice itsel must also be healed
        // internal.Quoted.expr[F[T]](... T ...)  -->  internal.Quoted.expr[F[$t]](... T ...)
        val tp = checkType(splice.sourcePos).apply(splice.tpe.widenTermRefExpr)
        cpy.Apply(splice)(cpy.TypeApply(fun)(fun.fun, tpd.TypeTree(tp) :: Nil), body1 :: Nil)
      case splice: Select => cpy.Select(splice)(body1, splice.name)
    }
  }

  /** If `tree` refers to a locally defined symbol (either directly, or in a pickled type),
   *  check that its staging level matches the current level. References to types
   *  that are phase-incorrect can still be healed as follows:
   *
   *  If `T` is a reference to a type at the wrong level, try to heal it by replacing it with
   *  `${implicitly[quoted.Type[T]]}`.
   */
  protected def checkLevel(tree: Tree)(implicit ctx: Context): Tree = {
    def checkTp(tp: Type): Type = checkType(tree.sourcePos).apply(tp)
    tree match {
      case Quoted(_) | Spliced(_)  =>
        tree
      case tree: RefTree if tree.symbol.isAllOf(InlineParam) =>
        tree
      case _: This =>
        assert(checkSymLevel(tree.symbol, tree.tpe, tree.sourcePos).isEmpty)
        tree
      case Ident(name) =>
        if (name == nme.WILDCARD) {
          untpd.Ident(name).withType(checkType(tree.sourcePos).apply(tree.tpe)).withSpan(tree.span)
        } else {
          checkSymLevel(tree.symbol, tree.tpe, tree.sourcePos) match {
            case Some(tpRef) => tpRef
            case _ => tree
          }
        }
      case _: TypeTree | _: AppliedTypeTree | _: Apply | _: TypeApply | _: UnApply | Select(_, OuterSelectName(_, _)) =>
        tree.withType(checkTp(tree.tpe))
      case _: ValOrDefDef | _: Bind =>
        tree.symbol.info = checkTp(tree.symbol.info)
        tree
      case _: Template =>
        checkTp(tree.symbol.owner.asClass.givenSelfType)
        tree
      case _ =>
        tree
    }
  }

  /** Check and heal all named types and this-types in a given type for phase consistency. */
  private def checkType(pos: SourcePosition)(implicit ctx: Context): TypeMap = new TypeMap {
    def apply(tp: Type): Type = reporting.trace(i"check type level $tp at $level") {
      tp match {
        case tp: TypeRef if tp.symbol.isSplice =>
          if (tp.isTerm)
            ctx.error(i"splice outside quotes", pos)
          tp
        case tp: NamedType =>
          checkSymLevel(tp.symbol, tp, pos) match {
            case Some(tpRef) => tpRef.tpe
            case _ =>
              if (tp.symbol.is(Param)) tp
              else mapOver(tp)
          }
        case tp: ThisType =>
          assert(checkSymLevel(tp.cls, tp, pos).isEmpty)
          mapOver(tp)
        case _ =>
          mapOver(tp)
      }
    }
  }

  /** Check reference to `sym` for phase consistency, where `tp` is the underlying type
   *  by which we refer to `sym`. If it is an inconsistent type try construct a healed type for it.
   *
   *  @return `None` if the phase is correct or cannot be healed
   *          `Some(tree)` with the `tree` of the healed type tree for `${implicitly[quoted.Type[T]]}`
   */
  private def checkSymLevel(sym: Symbol, tp: Type, pos: SourcePosition)(implicit ctx: Context): Option[Tree] = {
    /** Is a reference to a class but not `this.type` */
    def isClassRef = sym.isClass && !tp.isInstanceOf[ThisType]

    if (sym.exists && !sym.isStaticOwner && !isClassRef && !levelOK(sym))
      tryHeal(sym, tp, pos)
    else
      None
  }

  /** Does the level of `sym` match the current level?
   *  An exception is made for inline vals in macros. These are also OK if their level
   *  is one higher than the current level, because on execution such values
   *  are constant expression trees and we can pull out the constant from the tree.
   */
  private def levelOK(sym: Symbol)(implicit ctx: Context): Boolean = levelOf(sym) match {
    case Some(l) =>
      l == level ||
        level == -1 && (
          sym == defn.QuoteContext_macroContext ||
            // here we assume that Splicer.canBeSpliced was true before going to level -1,
            // this implies that all non-inline arguments are quoted and that the following two cases are checked
            // on inline parameters or type parameters.
            sym.is(Param) ||
            sym.isClass // reference to this in inline methods
          )
    case None =>
      !sym.is(Param) || levelOK(sym.owner)
  }

  /** Try to heal phase-inconsistent reference to type `T` using a local type definition.
   *  @return None      if successful
   *  @return Some(msg) if unsuccessful where `msg` is a potentially empty error message
   *                    to be added to the "inconsistent phase" message.
   */
  protected def tryHeal(sym: Symbol, tp: Type, pos: SourcePosition)(implicit ctx: Context): Option[Tree] = {
    def levelError(errMsg: String) = {
      def symStr =
        if (!tp.isInstanceOf[ThisType]) sym.show
        else if (sym.is(ModuleClass)) sym.sourceModule.show
        else i"${sym.name}.this"
      ctx.error(
        em"""access to $symStr from wrong staging level:
            | - the definition is at level ${levelOf(sym).getOrElse(0)},
            | - but the access is at level $level.$errMsg""", pos)
      None
    }
    tp match {
      case tp: TypeRef =>
        if (level == -1) {
          assert(ctx.inInlineMethod)
          None
        } else {
          val reqType = defn.QuotedTypeType.appliedTo(tp)
          val tag = ctx.typer.inferImplicitArg(reqType, pos.span)
          tag.tpe match {
            case _: TermRef =>
              Some(tag.select(tpnme.splice))
            case _: SearchFailureType =>
              levelError(i"""
                            |
                            | The access would be accepted with the right type tag, but
                            | ${ctx.typer.missingArgMsg(tag, reqType, "")}""")
            case _ =>
              levelError(i"""
                            |
                            | The access would be accepted with an implict $reqType""")
          }
        }
      case _ =>
        levelError("")
    }
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy