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

org.ensime.noddy.NoddyPlugin.scala Maven / Gradle / Ivy

The newest version!
// Copyright: 2010 - 2016 Rory Graves, Sam Halliday
// License: http://www.apache.org/licenses/LICENSE-2.0
package org.ensime.noddy

import scala.collection.breakOut
import scala.reflect.internal.ModifierFlags
import scala.reflect.internal.util._
import scala.tools.nsc._
import scala.tools.nsc.plugins._
import scala.tools.nsc.transform._

/**
 * A major source of bugs in macros and compiler plugins is failure to
 * preserve mutable information when transforming `global.Tree`
 * instances.
 *
 * Copying a `global.Tree` will result in the `pos`, `symbol` and
 * `tpe` fields being reset.
 *
 * The `global.treeCopy._` methods should be used instead of `copy` on
 * `global.Tree` implementations.
 *
 * Note that the `Modifiers.withAnnotations` method adds to existing
 * methods instead of replacing, so we provide a more cananocial form
 * here in `copyWithAnns`.
 *
 * http://docs.scala-lang.org/overviews/reflection/symbols-trees-types#trees
 */
trait WithPos { this: BackCompat =>
  val global: Global

  // Modifiers is an inner class, oh joy
  implicit class RichModifiers(t: global.Modifiers) {
    /** withAnnotations appears to be broken */
    def copyWithAnns(anns: List[global.Tree]): global.Modifiers =
      t.copy(annotations = anns).setPositions(t.positions)
  }

  implicit class RichTree[T <: global.Tree](t: T) {
    /** when generating a tree, use this to generate positions all the way down. */
    def withAllPos(pos: Position): T = {
      t.foreach(_.setPos(new TransparentPosition(pos.source, pos.startOrCursor, pos.endOrCursor, pos.endOrCursor)))
      t
    }
  }
}

/**
 * Convenient way to tell if you are running in an interactive context.
 */
trait IsIde {
  val global: Global

  val isIde = global.isInstanceOf[tools.nsc.interactive.Global]
}

class NoddyPlugin(override val global: Global) extends Plugin {
  private val target = "noddy"
  override val description: String = s"Generates code when using an annotation named '$target'"
  override val name: String = "noddy"

  abstract class TransformingComponent(override val global: Global) extends PluginComponent
    with TypingTransformers with WithPos with IsIde with BackCompat {

    val Noddy = global.newTypeName(target)

    override def newPhase(prev: Phase): Phase = new StdPhase(prev) {
      override def apply(unit: global.CompilationUnit): Unit = newTransformer(unit).transformUnit(unit)
    }
    override val runsAfter: List[String] = "parser" :: Nil
    override val runsBefore: List[String] = "namer" :: Nil

    def newTransformer(unit: global.CompilationUnit) = new TypingTransformer(unit) {
      override def transform(tree: global.Tree): global.Tree = {
        TransformingComponent.this.transform(super.transform(tree))
      }
    }

    def transform: global.Tree => global.Tree
  }

  private val NoddyParameters = new TransformingComponent(global) {
    override val phaseName: String = "noddy-params"
    import global._

    def noddyParameters(mods: global.Modifiers) = {
      mods.annotations.map {
        case ann @ Apply(Select(New(Ident(Noddy)), nme.CONSTRUCTOR), _) =>
          // NOTE: ignores all parameters, even bad ones
          treeCopy.Apply(
            ann,
            ann.fun,
            Literal(Constant(true)) :: Literal(Constant(true)) :: Literal(Constant(0)) :: Nil
          )

        case other => other
      }
    }

    override def transform = {
      case t: ModuleDef if t.mods.hasAnnotationNamed(Noddy) =>
        val annotations = noddyParameters(t.mods)
        treeCopy.ModuleDef(t, t.mods.copyWithAnns(annotations), t.name, t.impl)

      case t: ClassDef if t.mods.hasAnnotationNamed(Noddy) =>
        val annotations = noddyParameters(t.mods)
        treeCopy.ClassDef(t, t.mods.copyWithAnns(annotations), t.name, t.tparams, t.impl)

      case t => t
    }
  }

  private val NoddyCompanion = new TransformingComponent(global) {
    override val phaseName: String = "noddy-companion"
    import global._

    /** generates a zero-functionality companion for a class */
    def genCompanion(clazz: ClassDef): ModuleDef = ModuleDef(
      Modifiers(),
      clazz.name.companionName,
      Template(
        List(Select(Ident(nme.scala_), nme.AnyRef.toTypeName)),
        emptyValDef, // deprecated in 2.11, but replacement not in 2.10
        List(
          DefDef(
            Modifiers(),
            nme.CONSTRUCTOR,
            List(),
            List(List()),
            TypeTree(),
            Block(
              List(
                Apply(Select(Super(This(tpnme.EMPTY), tpnme.EMPTY), nme.CONSTRUCTOR), List())
              ),
              Literal(Constant(()))
            )
          )
        )
      )
    )

    def genLog: DefDef = DefDef(
      Modifiers(ModifierFlags.PRIVATE),
      newTermName("log"),
      Nil,
      Nil,
      Select(Select(Select(Ident(nme.java), newTermName("util")), newTermName("logging")), newTypeName("Logger")),
      Literal(Constant(null))
    )

    def genProperty(name: String, tpt: Tree): DefDef = DefDef(
      Modifiers(),
      newTermName(name),
      Nil,
      Nil,
      tpt,
      Literal(Constant(null))
    )

    def addMethods(template: Template, methods: List[DefDef]): Template = {
      val body = methods ::: template.body
      treeCopy.Template(template, template.parents, template.self, body)
    }

    /** adds a log method to a class */
    def updateClass(clazz: ClassDef): ClassDef = {
      val log = genLog.withAllPos(clazz.pos)
      val impl = addMethods(clazz.impl, log :: Nil)
      treeCopy.ClassDef(clazz, clazz.mods, clazz.name, clazz.tparams, impl)
    }

    /** adds a apply and log methods to a companion to a class */
    def updateCompanion(clazz: ClassDef, companion: ModuleDef): ModuleDef = {
      // not sure these can be re-used, so create fresh ones
      //def name = newTermName(clazz.name.toString)
      def tpe = newTypeName(clazz.name.toString)
      def stripVariance(t: TypeDef): TypeDef = {
        val mods = t.mods &~ ModifierFlags.COVARIANT &~ ModifierFlags.CONTRAVARIANT
        val params = t.tparams.map(stripVariance)
        treeCopy.TypeDef(t, mods, t.name, params, t.rhs)
      }.duplicate

      clazz.impl.body.collectFirst {
        case d @ DefDef(_, nme.CONSTRUCTOR, _, params, _, _) => (params, d.pos)
      } match {
        case None => companion // traits don't have constructor parameters
        case Some((ps, pos)) =>
          val apply = DefDef(
            Modifiers(),
            newTermName("apply"),
            clazz.tparams.map(stripVariance),
            ps.map(_.map { v =>
              val mods = v.mods &~ ModifierFlags.PARAMACCESSOR
              treeCopy.ValDef(v, mods, v.name, v.tpt.duplicate, v.rhs)
            }),
            if (clazz.tparams.isEmpty) Ident(tpe)
            else AppliedTypeTree(Ident(tpe), clazz.tparams.map { t => Ident(t.name) }),
            // PC plugins don't need implementations
            Literal(Constant(null))
          ).withAllPos(pos)

          val log = genLog.withAllPos(pos)

          val properties = ps.flatMap {
            vs =>
              vs.flatMap {
                v =>
                  // 1. need to take the type of v: V and turn it into Future[V]
                  // 2. add the type parameters of the class to the method (taking only what is needed for the method)
                  //genProperty(v.name.toString, Ident("scala.concurrent.Future[Unit]"))
                  Nil
              }
          }

          treeCopy.ModuleDef(
            companion, companion.mods, companion.name, {
            addMethods(companion.impl, apply :: log :: properties)
          }
          )
      }
    }

    def hasNoddyClasses(t: PackageDef): Boolean =
      t.stats.collectFirst {
        case c: ClassDef if c.mods.hasAnnotationNamed(Noddy) => c
      }.isDefined

    override def transform = {
      case t: PackageDef if hasNoddyClasses(t) =>
        val classes: Map[TypeName, ClassDef] = t.stats.collect {
          case c: ClassDef => c.name -> c
        }(breakOut)

        val companions: Map[TermName, ModuleDef] = t.stats.collect {
          case m: ModuleDef => m.name -> m
        }(breakOut)

        object ClassNoCompanion {
          def unapply(t: Tree): Option[ClassDef] = t match {
            case c: ClassDef if !companions.contains(c.name.companionName) => Some(c)
            case _ => None
          }
        }

        object ClassHasCompanion {
          def unapply(t: Tree): Option[ClassDef] = t match {
            case c: ClassDef if companions.contains(c.name.companionName) => Some(c)
            case _ => None
          }
        }

        object CompanionAndClass {
          def unapply(t: Tree): Option[(ModuleDef, ClassDef)] = t match {
            case m: ModuleDef =>
              classes.get(m.name.companionName).map { c => (m, c) }
            case _ => None
          }
        }

        val updated = t.stats.flatMap {
          case ClassNoCompanion(c) if c.mods.hasAnnotationNamed(Noddy) =>
            val companion = updateCompanion(c, genCompanion(c)).withAllPos(c.pos)
            List(updateClass(c), companion)

          case ClassHasCompanion(c) if c.mods.hasAnnotationNamed(Noddy) =>
            List(updateClass(c))

          case CompanionAndClass(companion, c) if c.mods.hasAnnotationNamed(Noddy) =>
            List(updateCompanion(c, companion))

          case tr =>
            List(tr)
        }

        treeCopy.PackageDef(t, t.pid, updated)

      case t => t
    }

  }

  override val components = List(NoddyParameters, NoddyCompanion)
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy