@@ -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,117 @@ 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.standardTypedMacroBody` 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.standardMacroExpand` 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.standardMacroArgs` 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.standardMacroRuntime` 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
+ /**
234
+ * Creates a symbol for the given tree in lexical context encapsulated by the given namer.
235
+ *
236
+ * Default implementation provided in `namer.standardEnterSym` handles MemberDef's and Imports,
237
+ * doing nothing for other trees (DocDef's are seen through and rewrapped). Typical implementation
238
+ * of `enterSym` for a particular tree flavor creates a corresponding symbol, assigns it to the tree,
239
+ * enters the symbol into scope and then might even perform some code generation.
240
+ *
241
+ * $nonCumulativeReturnValueDoc.
242
+ */
243
+ def pluginsEnterSym (namer : Namer , tree : Tree ): Boolean = false
244
+
245
+ /**
246
+ * Makes sure that for the given class definition, there exists a companion object definition.
247
+ *
248
+ * Default implementation provided in `namer.standardEnsureCompanionObject` looks up a companion symbol for the class definition
249
+ * and then checks whether the resulting symbol exists or not. If it exists, then nothing else is done.
250
+ * If not, a synthetic object definition is created using the provided factory, which is then entered into namer's scope.
251
+ *
252
+ * $nonCumulativeReturnValueDoc.
253
+ */
254
+ def pluginsEnsureCompanionObject (namer : Namer , cdef : ClassDef , creator : ClassDef => Tree = companionModuleDef(_)): Option [Symbol ] = None
255
+
256
+ /**
257
+ * Prepares a list of statements for being typechecked by performing domain-specific type-agnostic code synthesis.
258
+ *
259
+ * Trees passed into this method are going to be named, but not typed.
260
+ * In particular, you can rely on the compiler having called `enterSym` on every stat prior to passing calling this method.
261
+ *
262
+ * Default implementation does nothing. Current approaches to code syntheses (generation of underlying fields
263
+ * for getters/setters, creation of companion objects for case classes, etc) are too disparate and ad-hoc
264
+ * to be treated uniformly, so I'm leaving this for future work.
265
+ */
266
+ def pluginsEnterStats (typer : Typer , stats : List [Tree ]): List [Tree ] = stats
267
+ }
268
+
159
269
160
270
161
271
/** A list of registered analyzer plugins */
@@ -167,59 +277,158 @@ trait AnalyzerPlugins { self: Analyzer =>
167
277
analyzerPlugins = plugin :: analyzerPlugins
168
278
}
169
279
280
+ private abstract class CumulativeOp [T ] {
281
+ def default : T
282
+ def accumulate : (T , AnalyzerPlugin ) => T
283
+ }
284
+
285
+ private def invoke [T ](op : CumulativeOp [T ]): T = {
286
+ if (analyzerPlugins.isEmpty) op.default
287
+ else analyzerPlugins.foldLeft(op.default)((current, plugin) =>
288
+ if (! plugin.isActive()) current else op.accumulate(current, plugin))
289
+ }
170
290
171
291
/** @see AnalyzerPlugin.pluginsPt */
172
292
def pluginsPt (pt : Type , typer : Typer , tree : Tree , mode : Mode ): Type =
293
+ // performance opt
173
294
if (analyzerPlugins.isEmpty) pt
174
- else analyzerPlugins.foldLeft(pt)((pt, plugin) =>
175
- if (! plugin.isActive()) pt else plugin.pluginsPt(pt, typer, tree, mode))
295
+ else invoke(new CumulativeOp [Type ] {
296
+ def default = pt
297
+ def accumulate = (pt, p) => p.pluginsPt(pt, typer, tree, mode)
298
+ })
176
299
177
300
/** @see AnalyzerPlugin.pluginsTyped */
178
- def pluginsTyped (tpe : Type , typer : Typer , tree : Tree , mode : Mode , pt : Type ): Type = {
179
- // support deprecated methods in annotation checkers
180
- val annotCheckersTpe = addAnnotations(tree, tpe)
181
- if (analyzerPlugins.isEmpty) annotCheckersTpe
182
- else analyzerPlugins.foldLeft(annotCheckersTpe)((tpe, plugin) =>
183
- if (! plugin.isActive()) tpe else plugin.pluginsTyped(tpe, typer, tree, mode, pt))
184
- }
301
+ def pluginsTyped (tpe : Type , typer : Typer , tree : Tree , mode : Mode , pt : Type ): Type =
302
+ // performance opt
303
+ if (analyzerPlugins.isEmpty) addAnnotations(tree, tpe)
304
+ else invoke(new CumulativeOp [Type ] {
305
+ // support deprecated methods in annotation checkers
306
+ def default = addAnnotations(tree, tpe)
307
+ def accumulate = (tpe, p) => p.pluginsTyped(tpe, typer, tree, mode, pt)
308
+ })
185
309
186
310
/** @see AnalyzerPlugin.pluginsTypeSig */
187
- def pluginsTypeSig (tpe : Type , typer : Typer , defTree : Tree , pt : Type ): Type =
188
- if (analyzerPlugins.isEmpty) tpe
189
- else analyzerPlugins.foldLeft (tpe)((tpe, plugin ) =>
190
- if ( ! plugin.isActive()) tpe else plugin.pluginsTypeSig(tpe, typer, defTree, pt) )
311
+ def pluginsTypeSig (tpe : Type , typer : Typer , defTree : Tree , pt : Type ): Type = invoke( new CumulativeOp [ Type ] {
312
+ def default = tpe
313
+ def accumulate = (tpe, p ) => p.pluginsTypeSig(tpe, typer, defTree, pt)
314
+ } )
191
315
192
316
/** @see AnalyzerPlugin.pluginsTypeSigAccessor */
193
- def pluginsTypeSigAccessor (tpe : Type , typer : Typer , tree : ValDef , sym : Symbol ): Type =
194
- if (analyzerPlugins.isEmpty) tpe
195
- else analyzerPlugins.foldLeft (tpe)((tpe, plugin ) =>
196
- if ( ! plugin.isActive()) tpe else plugin.pluginsTypeSigAccessor(tpe, typer, tree, sym) )
317
+ def pluginsTypeSigAccessor (tpe : Type , typer : Typer , tree : ValDef , sym : Symbol ): Type = invoke( new CumulativeOp [ Type ] {
318
+ def default = tpe
319
+ def accumulate = (tpe, p ) => p.pluginsTypeSigAccessor(tpe, typer, tree, sym)
320
+ } )
197
321
198
322
/** @see AnalyzerPlugin.canAdaptAnnotations */
199
- def canAdaptAnnotations (tree : Tree , typer : Typer , mode : Mode , pt : Type ): Boolean = {
323
+ def canAdaptAnnotations (tree : Tree , typer : Typer , mode : Mode , pt : Type ): Boolean = invoke( new CumulativeOp [ Boolean ] {
200
324
// support deprecated methods in annotation checkers
201
- val annotCheckersExists = global.canAdaptAnnotations(tree, mode, pt)
202
- annotCheckersExists || {
203
- if (analyzerPlugins.isEmpty) false
204
- else analyzerPlugins.exists(plugin =>
205
- plugin.isActive() && plugin.canAdaptAnnotations(tree, typer, mode, pt))
206
- }
207
- }
325
+ def default = global.canAdaptAnnotations(tree, mode, pt)
326
+ def accumulate = (curr, p) => curr || p.canAdaptAnnotations(tree, typer, mode, pt)
327
+ })
208
328
209
329
/** @see AnalyzerPlugin.adaptAnnotations */
210
- def adaptAnnotations (tree : Tree , typer : Typer , mode : Mode , pt : Type ): Tree = {
330
+ def adaptAnnotations (tree : Tree , typer : Typer , mode : Mode , pt : Type ): Tree = invoke( new CumulativeOp [ Tree ] {
211
331
// support deprecated methods in annotation checkers
212
- val annotCheckersTree = global.adaptAnnotations(tree, mode, pt)
213
- if (analyzerPlugins.isEmpty) annotCheckersTree
214
- else analyzerPlugins.foldLeft(annotCheckersTree)((tree, plugin) =>
215
- if (! plugin.isActive()) tree else plugin.adaptAnnotations(tree, typer, mode, pt))
216
- }
332
+ def default = global.adaptAnnotations(tree, mode, pt)
333
+ def accumulate = (tree, p) => p.adaptAnnotations(tree, typer, mode, pt)
334
+ })
217
335
218
336
/** @see AnalyzerPlugin.pluginsTypedReturn */
219
- def pluginsTypedReturn (tpe : Type , typer : Typer , tree : Return , pt : Type ): Type = {
220
- val annotCheckersType = adaptTypeOfReturn(tree.expr, pt, tpe)
221
- if (analyzerPlugins.isEmpty) annotCheckersType
222
- else analyzerPlugins.foldLeft(annotCheckersType)((tpe, plugin) =>
223
- if (! plugin.isActive()) tpe else plugin.pluginsTypedReturn(tpe, typer, tree, pt))
337
+ def pluginsTypedReturn (tpe : Type , typer : Typer , tree : Return , pt : Type ): Type = invoke(new CumulativeOp [Type ] {
338
+ def default = adaptTypeOfReturn(tree.expr, pt, tpe)
339
+ def accumulate = (tpe, p) => p.pluginsTypedReturn(tpe, typer, tree, pt)
340
+ })
341
+
342
+ /** A list of registered macro plugins */
343
+ private var macroPlugins : List [MacroPlugin ] = Nil
344
+
345
+ /** Registers a new macro plugin */
346
+ def addMacroPlugin (plugin : MacroPlugin ) {
347
+ if (! macroPlugins.contains(plugin))
348
+ macroPlugins = plugin :: macroPlugins
349
+ }
350
+
351
+ private abstract class NonCumulativeOp [T ] {
352
+ def position : Position
353
+ def description : String
354
+ def default : T
355
+ def custom (plugin : MacroPlugin ): Option [T ]
356
+ }
357
+
358
+ private def invoke [T ](op : NonCumulativeOp [T ]): T = {
359
+ if (macroPlugins.isEmpty) op.default
360
+ else {
361
+ val results = macroPlugins.filter(_.isActive()).map(plugin => (plugin, op.custom(plugin)))
362
+ results.flatMap { case (p, Some (result)) => Some ((p, result)); case _ => None } match {
363
+ case (p1, _) :: (p2, _) :: _ => typer.context.error(op.position, s " both $p1 and $p2 want to ${op.description}" ); op.default
364
+ case (_, custom) :: Nil => custom
365
+ case Nil => op.default
366
+ }
367
+ }
368
+ }
369
+
370
+ /** @see MacroPlugin.pluginsTypedMacroBody */
371
+ def pluginsTypedMacroBody (typer : Typer , ddef : DefDef ): Tree = invoke(new NonCumulativeOp [Tree ] {
372
+ def position = ddef.pos
373
+ def description = " typecheck this macro definition"
374
+ def default = standardTypedMacroBody(typer, ddef)
375
+ def custom (plugin : MacroPlugin ) = plugin.pluginsTypedMacroBody(typer, ddef)
376
+ })
377
+
378
+ /** @see MacroPlugin.pluginsMacroExpand */
379
+ def pluginsMacroExpand (typer : Typer , expandee : Tree , mode : Mode , pt : Type ): Tree = invoke(new NonCumulativeOp [Tree ] {
380
+ def position = expandee.pos
381
+ def description = " expand this macro application"
382
+ def default = standardMacroExpand(typer, expandee, mode, pt)
383
+ def custom (plugin : MacroPlugin ) = plugin.pluginsMacroExpand(typer, expandee, mode, pt)
384
+ })
385
+
386
+ /** @see MacroPlugin.pluginsMacroArgs */
387
+ def pluginsMacroArgs (typer : Typer , expandee : Tree ): MacroArgs = invoke(new NonCumulativeOp [MacroArgs ] {
388
+ def position = expandee.pos
389
+ def description = " compute macro arguments for this macro application"
390
+ def default = standardMacroArgs(typer, expandee)
391
+ def custom (plugin : MacroPlugin ) = plugin.pluginsMacroArgs(typer, expandee)
392
+ })
393
+
394
+ /** @see MacroPlugin.pluginsMacroRuntime */
395
+ def pluginsMacroRuntime (expandee : Tree ): MacroRuntime = invoke(new NonCumulativeOp [MacroRuntime ] {
396
+ def position = expandee.pos
397
+ def description = " compute macro runtime for this macro application"
398
+ def default = standardMacroRuntime(expandee)
399
+ def custom (plugin : MacroPlugin ) = plugin.pluginsMacroRuntime(expandee)
400
+ })
401
+
402
+ /** @see MacroPlugin.pluginsEnterSym */
403
+ def pluginsEnterSym (namer : Namer , tree : Tree ): Context =
404
+ if (macroPlugins.isEmpty) namer.standardEnterSym(tree)
405
+ else invoke(new NonCumulativeOp [Context ] {
406
+ def position = tree.pos
407
+ def description = " enter a symbol for this tree"
408
+ def default = namer.standardEnterSym(tree)
409
+ def custom (plugin : MacroPlugin ) = {
410
+ val hasExistingSym = tree.symbol != NoSymbol
411
+ val result = plugin.pluginsEnterSym(namer, tree)
412
+ if (result && hasExistingSym) Some (namer.context)
413
+ else if (result && tree.isInstanceOf [Import ]) Some (namer.context.make(tree))
414
+ else if (result) Some (namer.context)
415
+ else None
416
+ }
417
+ })
418
+
419
+ /** @see MacroPlugin.pluginsEnsureCompanionObject */
420
+ def pluginsEnsureCompanionObject (namer : Namer , cdef : ClassDef , creator : ClassDef => Tree = companionModuleDef(_)): Symbol = invoke(new NonCumulativeOp [Symbol ] {
421
+ def position = cdef.pos
422
+ def description = " enter a companion symbol for this tree"
423
+ def default = namer.standardEnsureCompanionObject(cdef, creator)
424
+ def custom (plugin : MacroPlugin ) = plugin.pluginsEnsureCompanionObject(namer, cdef, creator)
425
+ })
426
+
427
+ /** @see MacroPlugin.pluginsEnterStats */
428
+ def pluginsEnterStats (typer : Typer , stats : List [Tree ]): List [Tree ] = {
429
+ // performance opt
430
+ if (macroPlugins.isEmpty) stats
431
+ else macroPlugins.foldLeft(stats)((current, plugin) =>
432
+ if (! plugin.isActive()) current else plugin.pluginsEnterStats(typer, stats))
224
433
}
225
434
}
0 commit comments