alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

Scala example source code file (AnalyzerPlugins.scala)

This example Scala source code file (AnalyzerPlugins.scala) is included in my "Source Code Warehouse" project. The intent of this project is to help you more easily find Scala source code examples by using tags.

All credit for the original source code belongs to scala-lang.org; I'm just trying to make examples easier to find. (For my Scala work, see my Scala examples and tutorials.)

Scala tags/keywords

compiler, cumulativeop, list, macroplugin, mode, noncumulativeop, none, nsc, option, tree, type, typer

The AnalyzerPlugins.scala Scala example source code

/* NSC -- new Scala compiler
 * Copyright 2005-2013 LAMP/EPFL
 * @author  Martin Odersky
 */

package scala.tools.nsc
package typechecker

/**
 *  @author Lukas Rytz
 *  @version 1.0
 */
trait AnalyzerPlugins { self: Analyzer =>
  import global._

  trait AnalyzerPlugin {
    /**
     * Selectively activate this analyzer plugin, e.g. according to the compiler phase.
     *
     * Note that the current phase can differ from the global compiler phase (look for `enteringPhase`
     * invocations in the compiler). For instance, lazy types created by the UnPickler are completed
     * at the phase in which their symbol is created. Observations show that this can even be the
     * parser phase. Since symbol completion can trigger subtyping, typing etc, your plugin might
     * need to be active also in phases other than namer and typer.
     *
     * Typically, this method can be implemented as
     *
     *   global.phase.id < global.currentRun.picklerPhase.id
     */
    def isActive(): Boolean = true

    /**
     * Let analyzer plugins change the expected type before type checking a tree.
     */
    def pluginsPt(pt: Type, typer: Typer, tree: Tree, mode: Mode): Type = pt

    /**
     * Let analyzer plugins modify the type that has been computed for a tree.
     *
     * @param tpe   The type inferred by the type checker, initially (for first plugin) `tree.tpe`
     * @param typer The yper that type checked `tree`
     * @param tree  The type-checked tree
     * @param mode  Mode that was used for typing `tree`
     * @param pt    Expected type that was used for typing `tree`
     */
    def pluginsTyped(tpe: Type, typer: Typer, tree: Tree, mode: Mode, pt: Type): Type = tpe

    /**
     * Let analyzer plugins change the types assigned to definitions. For definitions that have
     * an annotated type, the assigned type is obtained by typing that type tree. Otherwise, the
     * type is inferred by typing the definition's righthand side.
     *
     * In order to know if the type was inferred, you can query the `wasEmpty` field in the `tpt`
     * TypeTree of the definition (for DefDef and ValDef).
     *
     * (*) If the type of a method or value is inferred, the type-checked tree is stored in the
     * `analyzer.transformed` hash map, indexed by the definition's rhs tree.
     *
     * NOTE: Invoking the type checker can lead to cyclic reference errors. For instance, if this
     * method is called from the type completer of a recursive method, type checking the mehtod
     * rhs will invoke the same completer again. It might be possible to avoid this situation by
     * assigning `tpe` to `defTree.symbol` (untested) - the final type computed by this method
     * will then be assigned to the definition's symbol by monoTypeCompleter (in Namers).
     *
     * The hooks into `typeSig` allow analyzer plugins to add annotations to (or change the types
     * of) definition symbols. This cannot not be achieved by using `pluginsTyped`: this method
     * is only called during type checking, so changing the type of a symbol at this point is too
     * late: references to the symbol might already be typed and therefore obtain the the original
     * type assigned during naming.
     *
     * @param defTree is the definition for which the type was computed. The different cases are
     * outlined below. Note that this type is untyped (for methods and values with inferred type,
     * the typed rhs trees are available in analyzer.transformed).
     *
     * Case defTree: Template
     *   - tpe  : A ClassInfoType for the template
     *   - typer: The typer for template members, i.e. expressions and definitions of defTree.body
     *   - pt   : WildcardType
     *   - the class symbol is accessible through typer.context.owner
     *
     * Case defTree: ClassDef
     *   - tpe  : A ClassInfoType, or a PolyType(params, ClassInfoType) for polymorphic classes.
     *            The class type is the one computed by templateSig, i.e. through the above case
     *   - typer: The typer for the class. Note that this typer has a different context than the
     *            typer for the template.
     *   - pt   : WildcardType
     *
     * Case defTree: ModuleDef
     *   - tpe  : A ClassInfoType computed by templateSig
     *   - typer: The typer for the module. context.owner of this typer is the module class symbol
     *   - pt   : WildcardType
     *
     * Case defTree: DefDef
     *   - tpe  : The type of the method (MethodType, PolyType or NullaryMethodType). (*)
     *   - typer: The typer the rhs of this method
     *   - pt   : If tpt.isEmpty, either the result type from the overridden method, or WildcardType.
     *            Otherwise the type obtained from typing tpt.
     *   - Note that for constructors, pt is the class type which the constructor creates. To type
     *     check the rhs of the constructor however, the expected type has to be WildcardType (see
     *     Typers.typedDefDef)
     *
     * Case defTree: ValDef
     *   - tpe  : The type of this value. (*)
     *   - typer: The typer for the rhs of this value
     *   - pt   : If tpt.isEmpty, WildcardType. Otherwise the type obtained from typing tpt.
     *   - Note that pluginsTypeSig might be called multiple times for the same ValDef since it is
     *     used to compute the types of the accessor methods (see `pluginsTypeSigAccessor`)
     *
     * Case defTree: TypeDef
     *   - tpe  : The type obtained from typing rhs (PolyType if the TypeDef defines a polymorphic type)
     *   - typer: The typer for the rhs of this type
     *   - pt   : WildcardType
     */
    def pluginsTypeSig(tpe: Type, typer: Typer, defTree: Tree, pt: Type): Type = tpe

    /**
     * Modify the types of field accessors. The namer phase creates method types for getters and
     * setters based on the type of the corresponding field.
     *
     * Note: in order to compute the method type of an accessor, the namer calls `typeSig` on the
     * `ValDef` tree of the corresponding field. This implies that the `pluginsTypeSig` method
     * is potentially called multiple times for the same ValDef tree.
     *
     * @param tpe   The method type created by the namer for the accessor
     * @param typer The typer for the ValDef (not for the rhs)
     * @param tree  The ValDef corresponding to the accessor
     * @param sym   The accessor method symbol (getter, setter, beanGetter or beanSetter)
     */
    def pluginsTypeSigAccessor(tpe: Type, typer: Typer, tree: ValDef, sym: Symbol): Type = tpe

    /**
     * Decide whether this analyzer plugin can adapt a tree that has an annotated type to the
     * given type tp, taking into account the given mode (see method adapt in trait Typers).
     */
    def canAdaptAnnotations(tree: Tree, typer: Typer, mode: Mode, pt: Type): Boolean = false

    /**
     * Adapt a tree that has an annotated type to the given type tp, taking into account the given
     * mode (see method adapt in trait Typers).
     *
     * An implementation cannot rely on canAdaptAnnotations being called before. If the implementing
     * class cannot do the adapting, it should return the tree unchanged.
     */
    def adaptAnnotations(tree: Tree, typer: Typer, mode: Mode, pt: Type): Tree = tree

    /**
     * Modify the type of a return expression. By default, return expressions have type
     * NothingTpe.
     *
     * @param tpe   The type of the return expression
     * @param typer The typer that was used for typing the return tree
     * @param tree  The typed return expression tree
     * @param pt    The return type of the enclosing method
     */
    def pluginsTypedReturn(tpe: Type, typer: Typer, tree: Return, pt: Type): Type = tpe
  }

  /**
   * @define nonCumulativeReturnValueDoc Returns `None` if the plugin doesn't want to customize the default behavior
   * or something else if the plugin knows better that the implementation provided in scala-compiler.jar.
   * If multiple plugins return a non-empty result, it's going to be a compilation error.
   */
  trait MacroPlugin {
    /**
     * Selectively activate this analyzer plugin, e.g. according to the compiler phase.
     *
     * Note that the current phase can differ from the global compiler phase (look for `enteringPhase`
     * invocations in the compiler). For instance, lazy types created by the UnPickler are completed
     * at the phase in which their symbol is created. Observations show that this can even be the
     * parser phase. Since symbol completion can trigger subtyping, typing etc, your plugin might
     * need to be active also in phases other than namer and typer.
     *
     * Typically, this method can be implemented as
     *
     *   global.phase.id < global.currentRun.picklerPhase.id
     */
    def isActive(): Boolean = true

    /**
     * Typechecks the right-hand side of a macro definition (which typically features
     * a mere reference to a macro implementation).
     *
     * Default implementation provided in `self.standardTypedMacroBody` makes sure that the rhs
     * resolves to a reference to a method in either a static object or a macro bundle,
     * verifies that the referred method is compatible with the macro def and upon success
     * attaches a macro impl binding to the macro def's symbol.
     *
     * $nonCumulativeReturnValueDoc.
     */
    def pluginsTypedMacroBody(typer: Typer, ddef: DefDef): Option[Tree] = None

    /**
     * Expands an application of a def macro (i.e. of a symbol that has the MACRO flag set),
     * possibly using the current typer mode and the provided prototype.
     *
     * Default implementation provided in `self.standardMacroExpand` figures out whether the `expandee`
     * needs to be expanded right away or its expansion has to be delayed until all undetermined
     * parameters are inferred, then loads the macro implementation using `self.pluginsMacroRuntime`,
     * prepares the invocation arguments for the macro implementation using `self.pluginsMacroArgs`,
     * and finally calls into the macro implementation. After the call returns, it typechecks
     * the expansion and performs some bookkeeping.
     *
     * This method is typically implemented if your plugin requires significant changes to the macro engine.
     * If you only need to customize the macro context, consider implementing `pluginsMacroArgs`.
     * If you only need to customize how macro implementation are invoked, consider going for `pluginsMacroRuntime`.
     *
     * $nonCumulativeReturnValueDoc.
     */
    def pluginsMacroExpand(typer: Typer, expandee: Tree, mode: Mode, pt: Type): Option[Tree] = None

    /**
     * Computes the arguments that need to be passed to the macro impl corresponding to a particular expandee.
     *
     * Default implementation provided in `self.standardMacroArgs` instantiates a `scala.reflect.macros.contexts.Context`,
     * gathers type and value arguments of the macro application and throws them together into `MacroArgs`.
     *
     * $nonCumulativeReturnValueDoc.
     */
    def pluginsMacroArgs(typer: Typer, expandee: Tree): Option[MacroArgs] = None

    /**
     * Summons a function that encapsulates macro implementation invocations for a particular expandee.
     *
     * Default implementation provided in `self.standardMacroRuntime` returns a function that
     * loads the macro implementation binding from the macro definition symbol,
     * then uses either Java or Scala reflection to acquire the method that corresponds to the impl,
     * and then reflectively calls into that method.
     *
     * $nonCumulativeReturnValueDoc.
     */
    def pluginsMacroRuntime(expandee: Tree): Option[MacroRuntime] = None

    /**
     * Creates a symbol for the given tree in lexical context encapsulated by the given namer.
     *
     * Default implementation provided in `namer.standardEnterSym` handles MemberDef's and Imports,
     * doing nothing for other trees (DocDef's are seen through and rewrapped). Typical implementation
     * of `enterSym` for a particular tree flavor creates a corresponding symbol, assigns it to the tree,
     * enters the symbol into scope and then might even perform some code generation.
     *
     * $nonCumulativeReturnValueDoc.
     */
    def pluginsEnterSym(namer: Namer, tree: Tree): Boolean = false

    /**
     * Makes sure that for the given class definition, there exists a companion object definition.
     *
     * Default implementation provided in `namer.standardEnsureCompanionObject` looks up a companion symbol for the class definition
     * and then checks whether the resulting symbol exists or not. If it exists, then nothing else is done.
     * If not, a synthetic object definition is created using the provided factory, which is then entered into namer's scope.
     *
     * $nonCumulativeReturnValueDoc.
     */
    def pluginsEnsureCompanionObject(namer: Namer, cdef: ClassDef, creator: ClassDef => Tree = companionModuleDef(_)): Option[Symbol] = None

    /**
     * Prepares a list of statements for being typechecked by performing domain-specific type-agnostic code synthesis.
     *
     * Trees passed into this method are going to be named, but not typed.
     * In particular, you can rely on the compiler having called `enterSym` on every stat prior to passing calling this method.
     *
     * Default implementation does nothing. Current approaches to code syntheses (generation of underlying fields
     * for getters/setters, creation of companion objects for case classes, etc) are too disparate and ad-hoc
     * to be treated uniformly, so I'm leaving this for future work.
     */
    def pluginsEnterStats(typer: Typer, stats: List[Tree]): List[Tree] = stats
  }



  /** A list of registered analyzer plugins */
  private var analyzerPlugins: List[AnalyzerPlugin] = Nil

  /** Registers a new analyzer plugin */
  def addAnalyzerPlugin(plugin: AnalyzerPlugin) {
    if (!analyzerPlugins.contains(plugin))
      analyzerPlugins = plugin :: analyzerPlugins
  }

  private abstract class CumulativeOp[T] {
    def default: T
    def accumulate: (T, AnalyzerPlugin) => T
  }

  private def invoke[T](op: CumulativeOp[T]): T = {
    if (analyzerPlugins.isEmpty) op.default
    else analyzerPlugins.foldLeft(op.default)((current, plugin) =>
      if (!plugin.isActive()) current else op.accumulate(current, plugin))
  }

  /** @see AnalyzerPlugin.pluginsPt */
  def pluginsPt(pt: Type, typer: Typer, tree: Tree, mode: Mode): Type =
    // performance opt
    if (analyzerPlugins.isEmpty) pt
    else invoke(new CumulativeOp[Type] {
      def default = pt
      def accumulate = (pt, p) => p.pluginsPt(pt, typer, tree, mode)
    })

  /** @see AnalyzerPlugin.pluginsTyped */
  def pluginsTyped(tpe: Type, typer: Typer, tree: Tree, mode: Mode, pt: Type): Type =
    // performance opt
    if (analyzerPlugins.isEmpty) addAnnotations(tree, tpe)
    else invoke(new CumulativeOp[Type] {
      // support deprecated methods in annotation checkers
      def default = addAnnotations(tree, tpe)
      def accumulate = (tpe, p) => p.pluginsTyped(tpe, typer, tree, mode, pt)
    })

  /** @see AnalyzerPlugin.pluginsTypeSig */
  def pluginsTypeSig(tpe: Type, typer: Typer, defTree: Tree, pt: Type): Type = invoke(new CumulativeOp[Type] {
    def default = tpe
    def accumulate = (tpe, p) => p.pluginsTypeSig(tpe, typer, defTree, pt)
  })

  /** @see AnalyzerPlugin.pluginsTypeSigAccessor */
  def pluginsTypeSigAccessor(tpe: Type, typer: Typer, tree: ValDef, sym: Symbol): Type = invoke(new CumulativeOp[Type] {
    def default = tpe
    def accumulate = (tpe, p) => p.pluginsTypeSigAccessor(tpe, typer, tree, sym)
  })

  /** @see AnalyzerPlugin.canAdaptAnnotations */
  def canAdaptAnnotations(tree: Tree, typer: Typer, mode: Mode, pt: Type): Boolean = invoke(new CumulativeOp[Boolean] {
    // support deprecated methods in annotation checkers
    def default = global.canAdaptAnnotations(tree, mode, pt)
    def accumulate = (curr, p) => curr || p.canAdaptAnnotations(tree, typer, mode, pt)
  })

  /** @see AnalyzerPlugin.adaptAnnotations */
  def adaptAnnotations(tree: Tree, typer: Typer, mode: Mode, pt: Type): Tree = invoke(new CumulativeOp[Tree] {
    // support deprecated methods in annotation checkers
    def default = global.adaptAnnotations(tree, mode, pt)
    def accumulate = (tree, p) => p.adaptAnnotations(tree, typer, mode, pt)
  })

  /** @see AnalyzerPlugin.pluginsTypedReturn */
  def pluginsTypedReturn(tpe: Type, typer: Typer, tree: Return, pt: Type): Type = invoke(new CumulativeOp[Type] {
    def default = adaptTypeOfReturn(tree.expr, pt, tpe)
    def accumulate = (tpe, p) => p.pluginsTypedReturn(tpe, typer, tree, pt)
  })

  /** A list of registered macro plugins */
  private var macroPlugins: List[MacroPlugin] = Nil

  /** Registers a new macro plugin */
  def addMacroPlugin(plugin: MacroPlugin) {
    if (!macroPlugins.contains(plugin))
      macroPlugins = plugin :: macroPlugins
  }

  private abstract class NonCumulativeOp[T] {
    def position: Position
    def description: String
    def default: T
    def custom(plugin: MacroPlugin): Option[T]
  }

  private def invoke[T](op: NonCumulativeOp[T]): T = {
    if (macroPlugins.isEmpty) op.default
    else {
      val results = macroPlugins.filter(_.isActive()).map(plugin => (plugin, op.custom(plugin)))
      results.flatMap { case (p, Some(result)) => Some((p, result)); case _ => None } match {
        case (p1, _) :: (p2, _) :: _ => typer.context.error(op.position, s"both $p1 and $p2 want to ${op.description}"); op.default
        case (_, custom) :: Nil => custom
        case Nil => op.default
      }
    }
  }

  /** @see MacroPlugin.pluginsTypedMacroBody */
  def pluginsTypedMacroBody(typer: Typer, ddef: DefDef): Tree = invoke(new NonCumulativeOp[Tree] {
    def position = ddef.pos
    def description = "typecheck this macro definition"
    def default = standardTypedMacroBody(typer, ddef)
    def custom(plugin: MacroPlugin) = plugin.pluginsTypedMacroBody(typer, ddef)
  })

  /** @see MacroPlugin.pluginsMacroExpand */
  def pluginsMacroExpand(typer: Typer, expandee: Tree, mode: Mode, pt: Type): Tree = invoke(new NonCumulativeOp[Tree] {
    def position = expandee.pos
    def description = "expand this macro application"
    def default = standardMacroExpand(typer, expandee, mode, pt)
    def custom(plugin: MacroPlugin) = plugin.pluginsMacroExpand(typer, expandee, mode, pt)
  })

  /** @see MacroPlugin.pluginsMacroArgs */
  def pluginsMacroArgs(typer: Typer, expandee: Tree): MacroArgs = invoke(new NonCumulativeOp[MacroArgs] {
    def position = expandee.pos
    def description = "compute macro arguments for this macro application"
    def default = standardMacroArgs(typer, expandee)
    def custom(plugin: MacroPlugin) = plugin.pluginsMacroArgs(typer, expandee)
  })

  /** @see MacroPlugin.pluginsMacroRuntime */
  def pluginsMacroRuntime(expandee: Tree): MacroRuntime = invoke(new NonCumulativeOp[MacroRuntime] {
    def position = expandee.pos
    def description = "compute macro runtime for this macro application"
    def default = standardMacroRuntime(expandee)
    def custom(plugin: MacroPlugin) = plugin.pluginsMacroRuntime(expandee)
  })

  /** @see MacroPlugin.pluginsEnterSym */
  def pluginsEnterSym(namer: Namer, tree: Tree): Context =
    if (macroPlugins.isEmpty) namer.standardEnterSym(tree)
    else invoke(new NonCumulativeOp[Context] {
      def position = tree.pos
      def description = "enter a symbol for this tree"
      def default = namer.standardEnterSym(tree)
      def custom(plugin: MacroPlugin) = {
        val hasExistingSym = tree.symbol != NoSymbol
        val result = plugin.pluginsEnterSym(namer, tree)
        if (result && hasExistingSym) Some(namer.context)
        else if (result && tree.isInstanceOf[Import]) Some(namer.context.make(tree))
        else if (result) Some(namer.context)
        else None
      }
    })

  /** @see MacroPlugin.pluginsEnsureCompanionObject */
  def pluginsEnsureCompanionObject(namer: Namer, cdef: ClassDef, creator: ClassDef => Tree = companionModuleDef(_)): Symbol = invoke(new NonCumulativeOp[Symbol] {
    def position = cdef.pos
    def description = "enter a companion symbol for this tree"
    def default = namer.standardEnsureCompanionObject(cdef, creator)
    def custom(plugin: MacroPlugin) = plugin.pluginsEnsureCompanionObject(namer, cdef, creator)
  })

  /** @see MacroPlugin.pluginsEnterStats */
  def pluginsEnterStats(typer: Typer, stats: List[Tree]): List[Tree] = {
    // performance opt
    if (macroPlugins.isEmpty) stats
    else macroPlugins.foldLeft(stats)((current, plugin) =>
      if (!plugin.isActive()) current else plugin.pluginsEnterStats(typer, stats))
  }
}

Other Scala source code examples

Here is a short list of links related to this Scala AnalyzerPlugins.scala source code file:

... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.