Skip to content

Commit 68b8e23

Browse files
committed
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.
1 parent 279e2e3 commit 68b8e23

File tree

4 files changed

+146
-10
lines changed

4 files changed

+146
-10
lines changed

src/compiler/scala/reflect/macros/runtime/MacroRuntimes.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ trait MacroRuntimes extends JavaReflectionRuntimes with ScalaReflectionRuntimes
2020
* `null` otherwise.
2121
*/
2222
private val macroRuntimesCache = perRunCaches.newWeakMap[Symbol, MacroRuntime]
23-
def macroRuntime(macroDef: Symbol): MacroRuntime = {
23+
def macroRuntime(expandee: Tree): MacroRuntime = {
24+
val macroDef = expandee.symbol
2425
macroLogVerbose(s"looking for macro implementation: $macroDef")
2526
if (fastTrack contains macroDef) {
2627
macroLogVerbose("macro expansion is serviced by a fast track")

src/compiler/scala/tools/nsc/typechecker/AnalyzerPlugins.scala

Lines changed: 136 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ package typechecker
1313
trait AnalyzerPlugins { self: Analyzer =>
1414
import global._
1515

16-
1716
trait AnalyzerPlugin {
1817
/**
1918
* Selectively activate this analyzer plugin, e.g. according to the compiler phase.
@@ -156,6 +155,82 @@ trait AnalyzerPlugins { self: Analyzer =>
156155
def pluginsTypedReturn(tpe: Type, typer: Typer, tree: Return, pt: Type): Type = tpe
157156
}
158157

158+
/**
159+
* @define nonCumulativeReturnValueDoc Returns `None` if the plugin doesn't want to customize the default behavior
160+
* or something else if the plugin knows better that the implementation provided in scala-compiler.jar.
161+
* If multiple plugins return a non-empty result, it's going to be a compilation error.
162+
*/
163+
trait MacroPlugin {
164+
/**
165+
* Selectively activate this analyzer plugin, e.g. according to the compiler phase.
166+
*
167+
* Note that the current phase can differ from the global compiler phase (look for `enteringPhase`
168+
* invocations in the compiler). For instance, lazy types created by the UnPickler are completed
169+
* at the phase in which their symbol is created. Observations show that this can even be the
170+
* parser phase. Since symbol completion can trigger subtyping, typing etc, your plugin might
171+
* need to be active also in phases other than namer and typer.
172+
*
173+
* Typically, this method can be implemented as
174+
*
175+
* global.phase.id < global.currentRun.picklerPhase.id
176+
*/
177+
def isActive(): Boolean = true
178+
179+
/**
180+
* Typechecks the right-hand side of a macro definition (which typically features
181+
* a mere reference to a macro implementation).
182+
*
183+
* Default implementation provided in `self.typedMacroBody` makes sure that the rhs
184+
* resolves to a reference to a method in either a static object or a macro bundle,
185+
* verifies that the referred method is compatible with the macro def and upon success
186+
* attaches a macro impl binding to the macro def's symbol.
187+
*
188+
* $nonCumulativeReturnValueDoc.
189+
*/
190+
def pluginsTypedMacroBody(typer: Typer, ddef: DefDef): Option[Tree] = None
191+
192+
/**
193+
* Expands an application of a def macro (i.e. of a symbol that has the MACRO flag set),
194+
* possibly using the current typer mode and the provided prototype.
195+
*
196+
* Default implementation provided in `self.macroExpand` figures out whether the `expandee`
197+
* needs to be expanded right away or its expansion has to be delayed until all undetermined
198+
* parameters are inferred, then loads the macro implementation using `self.pluginsMacroRuntime`,
199+
* prepares the invocation arguments for the macro implementation using `self.pluginsMacroArgs`,
200+
* and finally calls into the macro implementation. After the call returns, it typechecks
201+
* the expansion and performs some bookkeeping.
202+
*
203+
* This method is typically implemented if your plugin requires significant changes to the macro engine.
204+
* If you only need to customize the macro context, consider implementing `pluginsMacroArgs`.
205+
* If you only need to customize how macro implementation are invoked, consider going for `pluginsMacroRuntime`.
206+
*
207+
* $nonCumulativeReturnValueDoc.
208+
*/
209+
def pluginsMacroExpand(typer: Typer, expandee: Tree, mode: Mode, pt: Type): Option[Tree] = None
210+
211+
/**
212+
* Computes the arguments that need to be passed to the macro impl corresponding to a particular expandee.
213+
*
214+
* Default implementation provided in `self.macroArgs` instantiates a `scala.reflect.macros.contexts.Context`,
215+
* gathers type and value arguments of the macro application and throws them together into `MacroArgs`.
216+
*
217+
* $nonCumulativeReturnValueDoc.
218+
*/
219+
def pluginsMacroArgs(typer: Typer, expandee: Tree): Option[MacroArgs] = None
220+
221+
/**
222+
* Summons a function that encapsulates macro implementation invocations for a particular expandee.
223+
*
224+
* Default implementation provided in `self.macroRuntime` returns a function that
225+
* loads the macro implementation binding from the macro definition symbol,
226+
* then uses either Java or Scala reflection to acquire the method that corresponds to the impl,
227+
* and then reflectively calls into that method.
228+
*
229+
* $nonCumulativeReturnValueDoc.
230+
*/
231+
def pluginsMacroRuntime(expandee: Tree): Option[MacroRuntime] = None
232+
}
233+
159234

160235

161236
/** A list of registered analyzer plugins */
@@ -228,4 +303,64 @@ trait AnalyzerPlugins { self: Analyzer =>
228303
def default = adaptTypeOfReturn(tree.expr, pt, tpe)
229304
def accumulate = (tpe, p) => p.pluginsTypedReturn(tpe, typer, tree, pt)
230305
})
306+
307+
/** A list of registered macro plugins */
308+
private var macroPlugins: List[MacroPlugin] = Nil
309+
310+
/** Registers a new macro plugin */
311+
def addMacroPlugin(plugin: MacroPlugin) {
312+
if (!macroPlugins.contains(plugin))
313+
macroPlugins = plugin :: macroPlugins
314+
}
315+
316+
private abstract class NonCumulativeOp[T] {
317+
def position: Position
318+
def description: String
319+
def default: T
320+
def custom(plugin: MacroPlugin): Option[T]
321+
}
322+
323+
private def invoke[T](op: NonCumulativeOp[T]): T = {
324+
if (macroPlugins.isEmpty) op.default
325+
else {
326+
val results = macroPlugins.filter(_.isActive()).map(plugin => (plugin, op.custom(plugin)))
327+
results.flatMap { case (p, Some(result)) => Some((p, result)); case _ => None } match {
328+
case (p1, _) :: (p2, _) :: _ => typer.context.error(op.position, s"both $p1 and $p2 want to ${op.description}"); op.default
329+
case (_, custom) :: Nil => custom
330+
case Nil => op.default
331+
}
332+
}
333+
}
334+
335+
/** @see MacroPlugin.pluginsTypedMacroBody */
336+
def pluginsTypedMacroBody(typer: Typer, ddef: DefDef): Tree = invoke(new NonCumulativeOp[Tree] {
337+
def position = ddef.pos
338+
def description = "typecheck this macro definition"
339+
def default = typedMacroBody(typer, ddef)
340+
def custom(plugin: MacroPlugin) = plugin.pluginsTypedMacroBody(typer, ddef)
341+
})
342+
343+
/** @see MacroPlugin.pluginsMacroExpand */
344+
def pluginsMacroExpand(typer: Typer, expandee: Tree, mode: Mode, pt: Type): Tree = invoke(new NonCumulativeOp[Tree] {
345+
def position = expandee.pos
346+
def description = "expand this macro application"
347+
def default = macroExpand(typer, expandee, mode, pt)
348+
def custom(plugin: MacroPlugin) = plugin.pluginsMacroExpand(typer, expandee, mode, pt)
349+
})
350+
351+
/** @see MacroPlugin.pluginsMacroArgs */
352+
def pluginsMacroArgs(typer: Typer, expandee: Tree): MacroArgs = invoke(new NonCumulativeOp[MacroArgs] {
353+
def position = expandee.pos
354+
def description = "compute macro arguments for this macro application"
355+
def default = macroArgs(typer, expandee)
356+
def custom(plugin: MacroPlugin) = plugin.pluginsMacroArgs(typer, expandee)
357+
})
358+
359+
/** @see MacroPlugin.pluginsMacroRuntime */
360+
def pluginsMacroRuntime(expandee: Tree): MacroRuntime = invoke(new NonCumulativeOp[MacroRuntime] {
361+
def position = expandee.pos
362+
def description = "compute macro runtime for this macro application"
363+
def default = macroRuntime(expandee)
364+
def custom(plugin: MacroPlugin) = plugin.pluginsMacroRuntime(expandee)
365+
})
231366
}

src/compiler/scala/tools/nsc/typechecker/Macros.scala

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -562,7 +562,7 @@ trait Macros extends FastTrack with MacroRuntimes with Traces with Helpers {
562562
onFailure(typer.infer.setError(expandee))
563563
} else try {
564564
val expanded = {
565-
val runtime = macroRuntime(expandee.symbol)
565+
val runtime = pluginsMacroRuntime(expandee)
566566
if (runtime != null) macroExpandWithRuntime(typer, expandee, runtime)
567567
else macroExpandWithoutRuntime(typer, expandee)
568568
}
@@ -689,7 +689,7 @@ trait Macros extends FastTrack with MacroRuntimes with Traces with Helpers {
689689
else {
690690
forced += delayed
691691
typer.infer.inferExprInstance(delayed, typer.context.extractUndetparams(), outerPt, keepNothings = false)
692-
macroExpand(typer, delayed, mode, outerPt)
692+
pluginsMacroExpand(typer, delayed, mode, outerPt)
693693
}
694694
} else delayed
695695
}
@@ -731,12 +731,12 @@ trait Macros extends FastTrack with MacroRuntimes with Traces with Helpers {
731731
case (false, true) =>
732732
macroLogLite("macro expansion is delayed: %s".format(expandee))
733733
delayed += expandee -> undetparams
734-
expandee updateAttachment MacroRuntimeAttachment(delayed = true, typerContext = typer.context, macroContext = Some(macroArgs(typer, expandee).c))
734+
expandee updateAttachment MacroRuntimeAttachment(delayed = true, typerContext = typer.context, macroContext = Some(pluginsMacroArgs(typer, expandee).c))
735735
Delay(expandee)
736736
case (false, false) =>
737737
import typer.TyperErrorGen._
738738
macroLogLite("performing macro expansion %s at %s".format(expandee, expandee.pos))
739-
val args = macroArgs(typer, expandee)
739+
val args = pluginsMacroArgs(typer, expandee)
740740
try {
741741
val numErrors = reporter.ERROR.count
742742
def hasNewErrors = reporter.ERROR.count > numErrors
@@ -851,7 +851,7 @@ trait Macros extends FastTrack with MacroRuntimes with Traces with Helpers {
851851
context.implicitsEnabled = typer.context.implicitsEnabled
852852
context.enrichmentEnabled = typer.context.enrichmentEnabled
853853
context.macrosEnabled = typer.context.macrosEnabled
854-
macroExpand(newTyper(context), tree, EXPRmode, WildcardType)
854+
pluginsMacroExpand(newTyper(context), tree, EXPRmode, WildcardType)
855855
case _ =>
856856
tree
857857
})

src/compiler/scala/tools/nsc/typechecker/Typers.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1112,7 +1112,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
11121112
if (tree.isType)
11131113
adaptType()
11141114
else if (mode.typingExprNotFun && treeInfo.isMacroApplication(tree) && !isMacroExpansionSuppressed(tree))
1115-
macroExpand(this, tree, mode, pt)
1115+
pluginsMacroExpand(this, tree, mode, pt)
11161116
else if (mode.typingConstructorPattern)
11171117
typedConstructorPattern(tree, pt)
11181118
else if (shouldInsertApply(tree))
@@ -2209,7 +2209,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
22092209
} else if (meth.isMacro) {
22102210
// typechecking macro bodies is sort of unconventional
22112211
// that's why we employ our custom typing scheme orchestrated outside of the typer
2212-
transformedOr(ddef.rhs, typedMacroBody(this, ddef))
2212+
transformedOr(ddef.rhs, pluginsTypedMacroBody(this, ddef))
22132213
} else {
22142214
transformedOrTyped(ddef.rhs, EXPRmode, tpt1.tpe)
22152215
}
@@ -5511,7 +5511,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
55115511
// here we guard against this case
55125512
transformed(ddef.rhs)
55135513
} else {
5514-
val rhs1 = typedMacroBody(this, ddef)
5514+
val rhs1 = pluginsTypedMacroBody(this, ddef)
55155515
transformed(ddef.rhs) = rhs1
55165516
rhs1
55175517
}

0 commit comments

Comments
 (0)