From 68b8e23585b5bbf7ff40d585634a7f07680c278b Mon Sep 17 00:00:00 2001 From: Eugene Burmako Date: Sat, 7 Dec 2013 14:13:58 +0100 Subject: 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. --- .../reflect/macros/runtime/MacroRuntimes.scala | 3 +- .../tools/nsc/typechecker/AnalyzerPlugins.scala | 137 ++++++++++++++++++++- .../scala/tools/nsc/typechecker/Macros.scala | 10 +- .../scala/tools/nsc/typechecker/Typers.scala | 6 +- 4 files changed, 146 insertions(+), 10 deletions(-) (limited to 'src') 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 } -- cgit v1.2.3