@@ -13,7 +13,6 @@ package typechecker
13
13
trait AnalyzerPlugins { self : Analyzer =>
14
14
import global ._
15
15
16
-
17
16
trait AnalyzerPlugin {
18
17
/**
19
18
* Selectively activate this analyzer plugin, e.g. according to the compiler phase.
@@ -156,6 +155,82 @@ trait AnalyzerPlugins { self: Analyzer =>
156
155
def pluginsTypedReturn (tpe : Type , typer : Typer , tree : Return , pt : Type ): Type = tpe
157
156
}
158
157
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
+
159
234
160
235
161
236
/** A list of registered analyzer plugins */
@@ -228,4 +303,64 @@ trait AnalyzerPlugins { self: Analyzer =>
228
303
def default = adaptTypeOfReturn(tree.expr, pt, tpe)
229
304
def accumulate = (tpe, p) => p.pluginsTypedReturn(tpe, typer, tree, pt)
230
305
})
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
+ })
231
366
}
0 commit comments