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

org.scalamacros.paradise.parser.SyntaxAnalyzer.scala Maven / Gradle / Ivy

package org.scalamacros.paradise
package parser

import scala.language.reflectiveCalls
import scala.tools.nsc.ast.parser.{SyntaxAnalyzer => NscSyntaxAnalyzer}
import scala.tools.nsc.ast.parser.BracePatch
import scala.tools.nsc.ast.parser.Tokens._
import scala.tools.nsc.{Global, Phase, SubComponent}
import scala.reflect.internal.Flags // no wildcard import because of ambiguity with Tokens._
import scala.reflect.internal.util.Collections._
import scala.collection.mutable.ListBuffer

abstract class SyntaxAnalyzer extends NscSyntaxAnalyzer {
  import global._
  import definitions._

  val runsAfter = List[String]()
  val runsRightAfter = None
  override val initial = true

  private def initialUnitBody(unit: CompilationUnit): Tree = {
    if (unit.isJava) new JavaUnitParser(unit).parse()
    else if (currentRun.parsing.incompleteHandled) new ParadiseUnitParser(unit).parse()
    else new ParadiseUnitParser(unit).smartParse()
  }

  def newUnitParser(unit: CompilationUnit): UnitParser = new ParadiseUnitParser(unit)
  private class ParadiseUnitParser(unit: global.CompilationUnit, patches: List[BracePatch]) extends UnitParser(unit, Nil) {
    def this(unit: global.CompilationUnit) = this(unit, Nil)
    // override def withPatches(patches: List[BracePatch]): UnitParser = new UnitParser(unit, patches)

    private val MetaInlineClass = rootMirror.getClassIfDefined("scala.meta.internal.inline.inline")
    private val MetaStatClass = rootMirror.getClassIfDefined("scala.meta.Stat")
    private val MetaTypeClass = rootMirror.getClassIfDefined("scala.meta.Type")

    private val INLINEkw = TermName("inline")
    private def isInline = in.token == IDENTIFIER && in.name == INLINEkw && skippingModifiers(in.token == DEF)
    private def skippingModifiers[T](op: => T): T = lookingAhead(if (isModifier) lookingAhead(skippingModifiers(op)) else op)
    override def isExprIntroToken(token: Token) = !isInline && super.isExprIntroToken(token)
    override def isDclIntro: Boolean = isInline || super.isDclIntro

    private def markInline(offset: Offset, mods: Modifiers): Modifiers = {
      if (MetaInlineClass != NoSymbol && MetaStatClass != NoSymbol) mods.withAnnotations(List(atPos(offset)(New(MetaInlineClass))))
      else { syntaxError(offset, "new-style (\"inline\") macros require scala.meta"); mods }
    }

    private def invoke(name: String, args: Any*): Any = {
      val meth = classOf[Parser].getDeclaredMethods().find(_.getName == name).get
      meth.setAccessible(true)
      meth.invoke(this, args.asInstanceOf[Seq[AnyRef]]: _*)
    }
    private def normalizeModifiers(mods: Modifiers): Modifiers = invoke("normalizeModifiers", mods).asInstanceOf[Modifiers]
    private def addMod(mods: Modifiers, mod: Long, pos: Position): Modifiers = invoke("addMod", mods, mod, pos).asInstanceOf[Modifiers]
    private def tokenRange(token: TokenData): Position = invoke("tokenRange", token).asInstanceOf[Position]
    private def flagTokens: Map[Int, Long] = invoke("flagTokens").asInstanceOf[Map[Int, Long]]
    override def modifiers(): Modifiers = normalizeModifiers {
      def loop(mods: Modifiers): Modifiers = in.token match {
        case IDENTIFIER if isInline =>
          val offset = in.offset
          in.nextToken()
          loop(markInline(offset, mods))
        case PRIVATE | PROTECTED =>
          loop(accessQualifierOpt(addMod(mods, flagTokens(in.token), tokenRange(in))))
        case ABSTRACT | FINAL | SEALED | OVERRIDE | IMPLICIT | LAZY =>
          loop(addMod(mods, flagTokens(in.token), tokenRange(in)))
        case NEWLINE =>
          in.nextToken()
          loop(mods)
        case _ =>
          mods
      }
      loop(NoMods)
    }
    override def localModifiers(): Modifiers = {
      def loop(mods: Modifiers): Modifiers = {
        if (isInline) { val offset = in.offset; in.nextToken(); loop(markInline(offset, mods)) }
        else if (isLocalModifier) loop(addMod(mods, flagTokens(in.token), tokenRange(in)))
        else mods
      }
      loop(NoMods)
    }

    // TODO: In the future, it would make sense to perform this transformation during typechecking.
    // However that strategy is going to be much more complicated, so it doesn't fit this prototype.

    override def topStat: PartialFunction[Token, List[Tree]] = {
      case PACKAGE  =>
        packageOrPackageObject(in.skipToken()) :: Nil
      case IMPORT =>
        in.flushDoc
        importClause()
      case _ if isAnnotation || isTemplateIntro || isModifier =>
        joinComment(translateNestedInlineDefs(topLevelTmplDef))
    }

    // NOTE: Here's the sketch of the proposed translation scheme:
    //   * Retain the original signature with the body replaced by ???
    //     (for the analogue of def macros, we can probably turn it into = macro name$impl`,
    //     but for macro annotations it doesn't matter anyway, since it's invoked differently).
    //   * Create a synthetic impl method name$impl that contains a macro context,
    //     replaces type parameters, value parameters as well as the return type with c.Tree.
    //   * Transplant the body to the synthetic method according to the high-friction principle.
    //     Namely: everything is wrapped in an enclosing quasiquote, meta calls are transformed into unquotes.
    //
    // TODO: We need to think how exactly we want to make this work with objects
    // that define inline defs. Do we want to define another object on the side?
    // Do we want to have the synthetic $impl tag along in the original object?
    //
    // class main {
    //   inline def apply(defns: Any) = body
    // }
    // <=======>
    // class main {
    //   @inline def apply(defns: Any) = ???
    // }
    // object main$impl {
    //   def apply$impl(defns: scala.meta.Tree): scala.meta.Tree = {
    //     q"[[ body ]]"
    //   }
    // }
    private def translateNestedInlineDefs(tree: Tree): List[Tree] = {
      tree match {
        case stat @ ClassDef(mods, name, tparams, templ @ Template(parents, self, stats)) =>
          val xstats1 = stats.map {
            case stat @ DefDef(mods, name, tparams, vparamss, tpt, rhs) =>
              def isInline(tpt: Tree) = MetaInlineClass != NoSymbol && tpt.tpe != null && tpt.tpe.typeSymbol == MetaInlineClass
              val inlines = mods.annotations.collect{ case ann @ Apply(Select(New(tpt), nme.CONSTRUCTOR), Nil) if isInline(tpt) => ann }
              if (inlines.nonEmpty) {
                def mkImplVtparam(tdef: TypeDef): ValDef = {
                  atPos(tdef.pos.focus)(ValDef(Modifiers(Flags.PARAM), tdef.name.toTermName, Ident(MetaTypeClass), EmptyTree))
                }
                def mkImplVparam(vdef: ValDef): ValDef = {
                  atPos(vdef.pos.focus)(ValDef(Modifiers(Flags.PARAM), vdef.name, Ident(MetaStatClass), EmptyTree))
                }
                def mkImplTpt(tpt: Tree): Tree = {
                  atPos(tpt.pos.focus)(Ident(MetaStatClass))
                }
                def mkImplBody(body: Tree): Tree = atPos(body.pos.focus)({
                  object transformer extends Transformer {
                    override def transform(tree: Tree): Tree = tree match {
                      // TODO: In the future, it would make sense to perform this transformation during typechecking.
                      // However that strategy is going to be much more complicated, so it doesn't fit this prototype.
                      case Apply(Ident(TermName("meta")), List(arg)) => super.transform(arg)
                      case tree => super.transform(tree)
                    }
                  }
                  transformer.transform(body)
                })
                val signatureMethod = atPos(stat.pos.focus)(DefDef(mods, name, tparams, vparamss, tpt, Ident(Predef_???)))
                val implMethod = atPos(stat.pos.focus)({
                  val implVtparamss = if (tparams.nonEmpty) List(tparams.map(mkImplVtparam)) else Nil
                  val implVparamss = implVtparamss ++ mmap(vparamss)(mkImplVparam)
                  val implTpt = mkImplTpt(tpt)
                  val implBody = mkImplBody(rhs)
                  DefDef(NoMods, TermName(name + "$impl"), Nil, implVparamss, implTpt, implBody)
                })
                (signatureMethod, implMethod)
              } else {
                (stat, EmptyTree)
              }
            case stat =>
              (stat, EmptyTree)
          }
          val (stats1, impls1) = xstats1.unzip
          if (impls1.exists(_.nonEmpty)) {
            val stat1 = atPos(stat.pos)(ClassDef(mods, name, tparams, atPos(templ.pos)(Template(parents, self, stats1))))
            val implmstats = {
              val syntheticCtor = atPos(stat.pos.focus)(DefDef(Modifiers(), termNames.CONSTRUCTOR, List(), List(List()), TypeTree(), Block(List(Apply(Select(Super(This(typeNames.EMPTY), typeNames.EMPTY), termNames.CONSTRUCTOR), List())), Literal(Constant(())))))
              syntheticCtor +: impls1.filter(_.nonEmpty)
            }
            val implmdef = atPos(stat.pos.focus)(ModuleDef(NoMods, TermName(name + "$impl"), Template(List(Ident(TypeName("AnyRef"))), noSelfType, implmstats)))
            List(stat1, implmdef)
          } else {
            List(stat)
          }
        case other =>
          List(other)
      }
    }
  }

  override def newPhase(prev: Phase): StdPhase = new StdPhase(prev) {
    override val checkable = false
    override val keepsTypeParams = false

    def apply(unit: CompilationUnit) {
      informProgress("parsing " + unit)
      // if the body is already filled in, don't overwrite it
      // otherwise compileLate is going to overwrite bodies of synthetic source files
      if (unit.body == EmptyTree)
        unit.body = initialUnitBody(unit)

      if (settings.Yrangepos && !reporter.hasErrors)
        validatePositions(unit.body)

      if (settings.Ymemberpos.isSetByUser)
        new MemberPosReporter(unit) show (style = settings.Ymemberpos.value)
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy