<commit_before>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.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, heals types and expand macros.
 *
 *  Type healing consists in transforming a phase inconsistent type `T` into `implicitly[Type[T]].unary_~`.
 *
 *  For macro definitions we assume that we have a single ~ directly as the RHS.
 *  The Splicer is used to check that the RHS will be interpretable (with the `Splicer`) once inlined.
 */
class Staging extends MacroTransform {
  import tpd._
  import Staging._

  /** Classloader used for loading macros */
  private[this] var myMacroClassLoader: java.lang.ClassLoader = _
  private def macroClassLoader(implicit ctx: Context): ClassLoader = {
    if (myMacroClassLoader == null) {
      val urls = ctx.settings.classpath.value.split(java.io.File.pathSeparatorChar).map(cp => java.nio.file.Paths.get(cp).toUri.toURL)
      myMacroClassLoader = new java.net.URLClassLoader(urls, getClass.getClassLoader)
    }
    myMacroClassLoader
  }

  override def phaseName: String = Staging.name

  override def allowsImplicitSearch: Boolean = true

  override def checkPostCondition(tree: Tree)(implicit ctx: Context): Unit = {
    if (ctx.phase <= ctx.reifyQuotesPhase) {
      // Recheck that PCP holds but do not heal any inconsistent types as they should already have been heald
      tree match {
        case PackageDef(pid, _) if tree.symbol.owner == defn.RootClass =>
          val checker = new PCPCheckAndHeal(freshStagingContext) {
            override protected def addSpliceCast(tree: Tree)(implicit ctx: Context): Tree = tree

            override protected def tryHeal(sym: Symbol, tp: Type, pos: SourcePosition)(implicit ctx: Context): Option[tpd.Tree] = {
              def symStr =
                if (!tp.isInstanceOf[ThisType]) sym.show
                else if (sym.is(ModuleClass)) sym.sourceModule.show
                else i"${sym.name}.this"

              val errMsg = s"\nin ${ctx.owner.fullName}"
              assert(false,
                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""")

              None
            }
          }
          checker.transform(tree)
        case _ =>
      }
    }
  }

  override def run(implicit ctx: Context): Unit =
    if (ctx.compilationUnit.needsStaging) super.run(freshStagingContext)

  protected def newTransformer(implicit ctx: Context): Transformer = new Transformer {
    override def transform(tree: tpd.Tree)(implicit ctx: Context): tpd.Tree =
      new PCPCheckAndHeal(ctx).transform(tree)
  }

  private class PCPCheckAndHeal(@constructorOnly ictx: Context) extends TreeMapWithStages(ictx) {

    override def transform(tree: Tree)(implicit ctx: Context): Tree = {
      reporting.trace(i"PCPTransformer.transform $tree at $level", show = true) {
        tree match {
          case tree: DefDef if tree.symbol.is(Macro) =>
            if (level > 0) {
              super.transform(tree) // Ignore output, only check PCP
              EmptyTree // Already inlined
            }
            else if (enclosingInlineds.nonEmpty) {
              EmptyTree // Already checked at definition site and already inlined
            }
            else tree.rhs match {
              case InlineSplice(_) =>
                super.transform(tree) // Ignore output, only check PCP
                tree
              case _ =>
                ctx.error(
                  """Malformed macro.
                    |
                    |Expected the ~ to be at the top of the RHS:
                    |  inline def foo(inline x: X, ..., y: Y): Int = ~impl(x, ... '(y))
                    |
                    | * The contents of the splice must call a static method
                    | * All arguments must be quoted or inline
                  """.stripMargin, tree.rhs.sourcePos)
                tree
            }
          case _ =>
            checkLevel(super.transform(tree))
        }
      }
    }

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

    /** Transform splice
     *  - If inside a quote, transform 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 splice(splice: Select)(implicit ctx: Context): Tree = {
      if (level >= 1) {
        val body1 = transform(splice.qualifier)(spliceContext)
        val splice1 = cpy.Select(splice)(body1, splice.name)
        if (splice1.isType) splice1
        else addSpliceCast(splice1)
      }
      else if (enclosingInlineds.nonEmpty) { // level 0 in an inlined call
        val spliceCtx = ctx.outer // drop the last `inlineContext`
        val pos: SourcePosition = spliceCtx.source.atSpan(enclosingInlineds.head.span)
        val evaluatedSplice = Splicer.splice(splice.qualifier, pos, macroClassLoader)(spliceCtx).withSpan(splice.span)
        if (ctx.reporter.hasErrors) splice else transform(evaluatedSplice)
      }
      else if (!ctx.owner.isInlineMethod) { // level 0 outside an inline method
        ctx.error(i"splice outside quotes or inline method", splice.sourcePos)
        splice
      }
      else if (Splicer.canBeSpliced(splice.qualifier)) { // level 0 inside an inline definition
        transform(splice.qualifier)(spliceContext) // Just check PCP
        splice
      }
      else { // level 0 inside an inline definition
        ctx.error(
          "Malformed macro call. The contents of the ~ must call a static method and arguments must be quoted or inline.",<commit_msg>Fix typo<commit_after>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.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, heals types and expand macros.
 *
 *  Type healing consists in transforming a phase inconsistent type `T` into `implicitly[Type[T]].unary_~`.
 *
 *  For macro definitions we assume that we have a single ~ directly as the RHS.
 *  The Splicer is used to check that the RHS will be interpretable (with the `Splicer`) once inlined.
 */
class Staging extends MacroTransform {
  import tpd._
  import Staging._

  /** Classloader used for loading macros */
  private[this] var myMacroClassLoader: java.lang.ClassLoader = _
  private def macroClassLoader(implicit ctx: Context): ClassLoader = {
    if (myMacroClassLoader == null) {
      val urls = ctx.settings.classpath.value.split(java.io.File.pathSeparatorChar).map(cp => java.nio.file.Paths.get(cp).toUri.toURL)
      myMacroClassLoader = new java.net.URLClassLoader(urls, getClass.getClassLoader)
    }
    myMacroClassLoader
  }

  override def phaseName: String = Staging.name

  override def allowsImplicitSearch: Boolean = true

  override def checkPostCondition(tree: Tree)(implicit ctx: Context): Unit = {
    if (ctx.phase <= ctx.reifyQuotesPhase) {
      // Recheck that PCP holds but do not heal any inconsistent types as they should already have been heald
      tree match {
        case PackageDef(pid, _) if tree.symbol.owner == defn.RootClass =>
          val checker = new PCPCheckAndHeal(freshStagingContext) {
            override protected def addSpliceCast(tree: Tree)(implicit ctx: Context): Tree = tree

            override protected def tryHeal(sym: Symbol, tp: Type, pos: SourcePosition)(implicit ctx: Context): Option[tpd.Tree] = {
              def symStr =
                if (!tp.isInstanceOf[ThisType]) sym.show
                else if (sym.is(ModuleClass)) sym.sourceModule.show
                else i"${sym.name}.this"

              val errMsg = s"\nin ${ctx.owner.fullName}"
              assert(false,
                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""")

              None
            }
          }
          checker.transform(tree)
        case _ =>
      }
    }
  }

  override def run(implicit ctx: Context): Unit =
    if (ctx.compilationUnit.needsStaging) super.run(freshStagingContext)

  protected def newTransformer(implicit ctx: Context): Transformer = new Transformer {
    override def transform(tree: tpd.Tree)(implicit ctx: Context): tpd.Tree =
      new PCPCheckAndHeal(ctx).transform(tree)
  }

  private class PCPCheckAndHeal(@constructorOnly ictx: Context) extends TreeMapWithStages(ictx) {

    override def transform(tree: Tree)(implicit ctx: Context): Tree = {
      reporting.trace(i"PCPTransformer.transform $tree at $level", show = true) {
        tree match {
          case tree: DefDef if tree.symbol.is(Macro) =>
            if (level > 0) {
              super.transform(tree) // Ignore output, only check PCP
              EmptyTree // Already inlined
            }
            else if (enclosingInlineds.nonEmpty) {
              EmptyTree // Already checked at definition site and already inlined
            }
            else tree.rhs match {
              case InlineSplice(_) =>
                super.transform(tree) // Ignore output, only check PCP
                tree
              case _ =>
                ctx.error(
                  """Malformed macro.
                    |
                    |Expected the ~ to be at the top of the RHS:
                    |  inline def foo(inline x: X, ..., y: Y): Int = ~impl(x, ... '(y))
                    |
                    | * The contents of the splice must call a static method
                    | * All arguments must be quoted or inline
                  """.stripMargin, tree.rhs.sourcePos)
                tree
            }
          case _ =>
            checkLevel(super.transform(tree))
        }
      }
    }

    /** Transform quoted trees while maintaining phase correctness */
    override protected def quotation(body: Tree, quote: Tree)(implicit ctx: Context): Tree = {
      val body1 = transform(body)(quoteContext)
      super.quotation(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 splice(splice: Select)(implicit ctx: Context): Tree = {
      if (level >= 1) {
        val body1 = transform(splice.qualifier)(spliceContext)
        val splice1 = cpy.Select(splice)(body1, splice.name)
        if (splice1.isType) splice1
        else addSpliceCast(splice1)
      }
      else if (enclosingInlineds.nonEmpty) { // level 0 in an inlined call
        val spliceCtx = ctx.outer // drop the last `inlineContext`
        val pos: SourcePosition = spliceCtx.source.atSpan(enclosingInlineds.head.span)
        val evaluatedSplice = Splicer.splice(splice.qualifier, pos, macroClassLoader)(spliceCtx).withSpan(splice.span)
        if (ctx.reporter.hasErrors) splice else transform(evaluatedSplice)
      }
      else if (!ctx.owner.isInlineMethod) { // level 0 outside an inline method
        ctx.error(i"splice outside quotes or inline method", splice.sourcePos)
        splice
      }
      else if (Splicer.canBeSpliced(splice.qualifier)) { // level 0 inside an inline definition
        transform(splice.qualifier)(spliceContext) // Just check PCP
        splice
      }
      else { // level 0 inside an inline definition
        ctx.error(
          "Malformed macro call. The contents of the ~ must call a static method and arguments must be quoted or inline.",
{'fraction_non_alphanumeric': 0.0950343097469195, 'fraction_numerical': 0.002213532059322659, 'mean_word_length': 2.5141301529686286, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 21, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2647425', 'n_tokens_mistral': 4006, 'n_tokens_neox': 3879, 'n_words': 1095}