summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorEugene Burmako <xeno.by@gmail.com>2013-12-07 14:13:58 +0100
committerEugene Burmako <xeno.by@gmail.com>2013-12-30 19:07:04 +0300
commit68b8e23585b5bbf7ff40d585634a7f07680c278b (patch)
tree0f37b8119f14471ede754768ba62db60c08c4869 /src
parent279e2e3b500c63b867d6f2c26a99aa44babfb8e4 (diff)
downloadscala-68b8e23585b5bbf7ff40d585634a7f07680c278b.tar.gz
scala-68b8e23585b5bbf7ff40d585634a7f07680c278b.tar.bz2
scala-68b8e23585b5bbf7ff40d585634a7f07680c278b.zip
hooks for typecheck and expansion of macro defs
Creates MacroPlugin, a sister interface of AnalyzerPlugin in the namer/typer extensibility interface. Exposes `pluginsTypedMacroBody`, `pluginsMacroExpand`, `pluginsMacroArgs` and `pluginsMacroRuntime` in the macro plugin interface. This will make it easy to prototype changes to the macro engine without disturbing scala/scala.
Diffstat (limited to 'src')
-rw-r--r--src/compiler/scala/reflect/macros/runtime/MacroRuntimes.scala3
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/AnalyzerPlugins.scala137
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/Macros.scala10
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/Typers.scala6
4 files changed, 146 insertions, 10 deletions
diff --git a/src/compiler/scala/reflect/macros/runtime/MacroRuntimes.scala b/src/compiler/scala/reflect/macros/runtime/MacroRuntimes.scala
index 7de3341304..2b73d932d4 100644
--- a/src/compiler/scala/reflect/macros/runtime/MacroRuntimes.scala
+++ b/src/compiler/scala/reflect/macros/runtime/MacroRuntimes.scala
@@ -20,7 +20,8 @@ trait MacroRuntimes extends JavaReflectionRuntimes with ScalaReflectionRuntimes
* `null` otherwise.
*/
private val macroRuntimesCache = perRunCaches.newWeakMap[Symbol, MacroRuntime]
- def macroRuntime(macroDef: Symbol): MacroRuntime = {
+ def macroRuntime(expandee: Tree): MacroRuntime = {
+ val macroDef = expandee.symbol
macroLogVerbose(s"looking for macro implementation: $macroDef")
if (fastTrack contains macroDef) {
macroLogVerbose("macro expansion is serviced by a fast track")
diff --git a/src/compiler/scala/tools/nsc/typechecker/AnalyzerPlugins.scala b/src/compiler/scala/tools/nsc/typechecker/AnalyzerPlugins.scala
index 4e524323a6..09a6a553ee 100644
--- a/src/compiler/scala/tools/nsc/typechecker/AnalyzerPlugins.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/AnalyzerPlugins.scala
@@ -13,7 +13,6 @@ package typechecker
trait AnalyzerPlugins { self: Analyzer =>
import global._
-
trait AnalyzerPlugin {
/**
* Selectively activate this analyzer plugin, e.g. according to the compiler phase.
@@ -156,6 +155,82 @@ trait AnalyzerPlugins { self: Analyzer =>
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.typedMacroBody` 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.macroExpand` 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.macroArgs` 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.macroRuntime` 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
+ }
+
/** A list of registered analyzer plugins */
@@ -228,4 +303,64 @@ trait AnalyzerPlugins { self: Analyzer =>
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 = typedMacroBody(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 = macroExpand(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 = macroArgs(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 = macroRuntime(expandee)
+ def custom(plugin: MacroPlugin) = plugin.pluginsMacroRuntime(expandee)
+ })
}
diff --git a/src/compiler/scala/tools/nsc/typechecker/Macros.scala b/src/compiler/scala/tools/nsc/typechecker/Macros.scala
index 8efd27a6d1..72783a3a61 100644
--- a/src/compiler/scala/tools/nsc/typechecker/Macros.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/Macros.scala
@@ -562,7 +562,7 @@ trait Macros extends FastTrack with MacroRuntimes with Traces with Helpers {
onFailure(typer.infer.setError(expandee))
} else try {
val expanded = {
- val runtime = macroRuntime(expandee.symbol)
+ val runtime = pluginsMacroRuntime(expandee)
if (runtime != null) macroExpandWithRuntime(typer, expandee, runtime)
else macroExpandWithoutRuntime(typer, expandee)
}
@@ -689,7 +689,7 @@ trait Macros extends FastTrack with MacroRuntimes with Traces with Helpers {
else {
forced += delayed
typer.infer.inferExprInstance(delayed, typer.context.extractUndetparams(), outerPt, keepNothings = false)
- macroExpand(typer, delayed, mode, outerPt)
+ pluginsMacroExpand(typer, delayed, mode, outerPt)
}
} else delayed
}
@@ -731,12 +731,12 @@ trait Macros extends FastTrack with MacroRuntimes with Traces with Helpers {
case (false, true) =>
macroLogLite("macro expansion is delayed: %s".format(expandee))
delayed += expandee -> undetparams
- expandee updateAttachment MacroRuntimeAttachment(delayed = true, typerContext = typer.context, macroContext = Some(macroArgs(typer, expandee).c))
+ expandee updateAttachment MacroRuntimeAttachment(delayed = true, typerContext = typer.context, macroContext = Some(pluginsMacroArgs(typer, expandee).c))
Delay(expandee)
case (false, false) =>
import typer.TyperErrorGen._
macroLogLite("performing macro expansion %s at %s".format(expandee, expandee.pos))
- val args = macroArgs(typer, expandee)
+ val args = pluginsMacroArgs(typer, expandee)
try {
val numErrors = reporter.ERROR.count
def hasNewErrors = reporter.ERROR.count > numErrors
@@ -851,7 +851,7 @@ trait Macros extends FastTrack with MacroRuntimes with Traces with Helpers {
context.implicitsEnabled = typer.context.implicitsEnabled
context.enrichmentEnabled = typer.context.enrichmentEnabled
context.macrosEnabled = typer.context.macrosEnabled
- macroExpand(newTyper(context), tree, EXPRmode, WildcardType)
+ pluginsMacroExpand(newTyper(context), tree, EXPRmode, WildcardType)
case _ =>
tree
})
diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala
index f80ebbd7d0..5c1026394a 100644
--- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala
@@ -1112,7 +1112,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
if (tree.isType)
adaptType()
else if (mode.typingExprNotFun && treeInfo.isMacroApplication(tree) && !isMacroExpansionSuppressed(tree))
- macroExpand(this, tree, mode, pt)
+ pluginsMacroExpand(this, tree, mode, pt)
else if (mode.typingConstructorPattern)
typedConstructorPattern(tree, pt)
else if (shouldInsertApply(tree))
@@ -2209,7 +2209,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
} else if (meth.isMacro) {
// typechecking macro bodies is sort of unconventional
// that's why we employ our custom typing scheme orchestrated outside of the typer
- transformedOr(ddef.rhs, typedMacroBody(this, ddef))
+ transformedOr(ddef.rhs, pluginsTypedMacroBody(this, ddef))
} else {
transformedOrTyped(ddef.rhs, EXPRmode, tpt1.tpe)
}
@@ -5511,7 +5511,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
// here we guard against this case
transformed(ddef.rhs)
} else {
- val rhs1 = typedMacroBody(this, ddef)
+ val rhs1 = pluginsTypedMacroBody(this, ddef)
transformed(ddef.rhs) = rhs1
rhs1
}