From 87913661e199e3894190b7b8aa0900d7237feec0 Mon Sep 17 00:00:00 2001 From: Eugene Burmako Date: Mon, 9 Dec 2013 09:45:31 +0100 Subject: hooks for naming and synthesis in Namers.scala and Typers.scala Interestingly enough, despite of the implementation surface being quite noticeable, it is enough to hijack just `enterSym` and typechecking of stats for packages, templates and blocks in order to enable macro annotations. That and `ensureCompanionObject`, which I couldn't abstract away so far. An architectural note: given that a hooked method is called `X`, there are two implementations of this method. `pluginsX` is defined in AnalyzerPlugins.scala and lets macro plugins customize `X`. `standardX` is defined next to `X` and provides a default implementation. Finally `X` is changed to trivially forward to `pluginsX`. Existing and future callers of `X` now can be completely oblivious of the introduced hooks, because calls to `X` will continue working and will be correctly hooked. This makes the infrastructure more robust. The only downside is that in case when a macro plugin wants to call into the default implementation, it needs to call `standardX`, because `X` will lead to a stack overflow. However, in my opinion this not a big problem, because such failures are load and clear + for every `pluginsX` we actually provide documentation that says what is its standard impl is. --- .../reflect/macros/runtime/MacroRuntimes.scala | 7 ++- .../scala/tools/nsc/typechecker/Analyzer.scala | 2 +- .../tools/nsc/typechecker/AnalyzerPlugins.scala | 50 +++++++++++----------- .../scala/tools/nsc/typechecker/Macros.scala | 30 +++++++++---- .../scala/tools/nsc/typechecker/Namers.scala | 27 ++++++++---- .../scala/tools/nsc/typechecker/Typers.scala | 12 +++--- 6 files changed, 80 insertions(+), 48 deletions(-) (limited to 'src/compiler') diff --git a/src/compiler/scala/reflect/macros/runtime/MacroRuntimes.scala b/src/compiler/scala/reflect/macros/runtime/MacroRuntimes.scala index 2b73d932d4..14b0c6baba 100644 --- a/src/compiler/scala/reflect/macros/runtime/MacroRuntimes.scala +++ b/src/compiler/scala/reflect/macros/runtime/MacroRuntimes.scala @@ -19,8 +19,13 @@ trait MacroRuntimes extends JavaReflectionRuntimes with ScalaReflectionRuntimes * @return Requested runtime if macro implementation can be loaded successfully from either of the mirrors, * `null` otherwise. */ + def macroRuntime(expandee: Tree): MacroRuntime = pluginsMacroRuntime(expandee) + + /** Default implementation of `macroRuntime`. + * Can be overridden by analyzer plugins (see AnalyzerPlugins.pluginsMacroRuntime for more details) + */ private val macroRuntimesCache = perRunCaches.newWeakMap[Symbol, MacroRuntime] - def macroRuntime(expandee: Tree): MacroRuntime = { + def standardMacroRuntime(expandee: Tree): MacroRuntime = { val macroDef = expandee.symbol macroLogVerbose(s"looking for macro implementation: $macroDef") if (fastTrack contains macroDef) { diff --git a/src/compiler/scala/tools/nsc/typechecker/Analyzer.scala b/src/compiler/scala/tools/nsc/typechecker/Analyzer.scala index 70ff1051c1..5c02516c47 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Analyzer.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Analyzer.scala @@ -40,7 +40,7 @@ trait Analyzer extends AnyRef override def keepsTypeParams = false def apply(unit: CompilationUnit) { - pluginsEnterSym(newNamer(rootContext(unit)), unit.body) + newNamer(rootContext(unit)).enterSym(unit.body) } } } diff --git a/src/compiler/scala/tools/nsc/typechecker/AnalyzerPlugins.scala b/src/compiler/scala/tools/nsc/typechecker/AnalyzerPlugins.scala index 61becab483..fa6e5399eb 100644 --- a/src/compiler/scala/tools/nsc/typechecker/AnalyzerPlugins.scala +++ b/src/compiler/scala/tools/nsc/typechecker/AnalyzerPlugins.scala @@ -180,7 +180,7 @@ trait AnalyzerPlugins { self: Analyzer => * 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 + * Default implementation provided in `self.standardTypedMacroBody` 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. @@ -193,7 +193,7 @@ trait AnalyzerPlugins { self: Analyzer => * 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` + * Default implementation provided in `self.standardMacroExpand` 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`, @@ -211,7 +211,7 @@ trait AnalyzerPlugins { self: Analyzer => /** * 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`, + * Default implementation provided in `self.standardMacroArgs` instantiates a `scala.reflect.macros.contexts.Context`, * gathers type and value arguments of the macro application and throws them together into `MacroArgs`. * * $nonCumulativeReturnValueDoc. @@ -221,7 +221,7 @@ trait AnalyzerPlugins { self: Analyzer => /** * Summons a function that encapsulates macro implementation invocations for a particular expandee. * - * Default implementation provided in `self.macroRuntime` returns a function that + * Default implementation provided in `self.standardMacroRuntime` 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. @@ -233,7 +233,7 @@ trait AnalyzerPlugins { self: Analyzer => /** * Creates a symbol for the given tree in lexical context encapsulated by the given namer. * - * Default implementation provided in `namer.enterSym` handles MemberDef's and Imports, + * Default implementation provided in `namer.standardEnterSym` handles MemberDef's and Imports, * doing nothing for other trees (DocDef's are seen through and rewrapped). Typical implementation * of `enterSym` for a particular tree flavor creates a corresponding symbol, assigns it to the tree, * enters the symbol into scope and then might even perform some code generation. @@ -245,7 +245,7 @@ trait AnalyzerPlugins { self: Analyzer => /** * Makes sure that for the given class definition, there exists a companion object definition. * - * Default implementation provided in `namer.ensureCompanionObject` looks up a companion symbol for the class definition + * Default implementation provided in `namer.standardEnsureCompanionObject` looks up a companion symbol for the class definition * and then checks whether the resulting symbol exists or not. If it exists, then nothing else is done. * If not, a synthetic object definition is created using the provided factory, which is then entered into namer's scope. * @@ -371,7 +371,7 @@ trait AnalyzerPlugins { self: Analyzer => 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 default = standardTypedMacroBody(typer, ddef) def custom(plugin: MacroPlugin) = plugin.pluginsTypedMacroBody(typer, ddef) }) @@ -379,7 +379,7 @@ trait AnalyzerPlugins { self: Analyzer => 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 default = standardMacroExpand(typer, expandee, mode, pt) def custom(plugin: MacroPlugin) = plugin.pluginsMacroExpand(typer, expandee, mode, pt) }) @@ -387,7 +387,7 @@ trait AnalyzerPlugins { self: Analyzer => 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 default = standardMacroArgs(typer, expandee) def custom(plugin: MacroPlugin) = plugin.pluginsMacroArgs(typer, expandee) }) @@ -395,30 +395,32 @@ trait AnalyzerPlugins { self: Analyzer => 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 default = standardMacroRuntime(expandee) def custom(plugin: MacroPlugin) = plugin.pluginsMacroRuntime(expandee) }) /** @see MacroPlugin.pluginsEnterSym */ - def pluginsEnterSym(namer: Namer, tree: Tree): Context = invoke(new NonCumulativeOp[Context] { - def position = tree.pos - def description = "enter a symbol for this tree" - def default = namer.enterSym(tree) - def custom(plugin: MacroPlugin) = { - val hasExistingSym = tree.symbol != NoSymbol - val result = plugin.pluginsEnterSym(namer, tree) - if (result && hasExistingSym) Some(namer.context) - else if (result && tree.isInstanceOf[Import]) Some(namer.context.make(tree)) - else if (result) Some(namer.context) - else None - } - }) + def pluginsEnterSym(namer: Namer, tree: Tree): Context = + if (macroPlugins.isEmpty) namer.standardEnterSym(tree) + else invoke(new NonCumulativeOp[Context] { + def position = tree.pos + def description = "enter a symbol for this tree" + def default = namer.standardEnterSym(tree) + def custom(plugin: MacroPlugin) = { + val hasExistingSym = tree.symbol != NoSymbol + val result = plugin.pluginsEnterSym(namer, tree) + if (result && hasExistingSym) Some(namer.context) + else if (result && tree.isInstanceOf[Import]) Some(namer.context.make(tree)) + else if (result) Some(namer.context) + else None + } + }) /** @see MacroPlugin.pluginsEnsureCompanionObject */ def pluginsEnsureCompanionObject(namer: Namer, cdef: ClassDef, creator: ClassDef => Tree = companionModuleDef(_)): Symbol = invoke(new NonCumulativeOp[Symbol] { def position = cdef.pos def description = "enter a companion symbol for this tree" - def default = namer.ensureCompanionObject(cdef, creator) + def default = namer.standardEnsureCompanionObject(cdef, creator) def custom(plugin: MacroPlugin) = plugin.pluginsEnsureCompanionObject(namer, cdef, creator) }) diff --git a/src/compiler/scala/tools/nsc/typechecker/Macros.scala b/src/compiler/scala/tools/nsc/typechecker/Macros.scala index 105e975c86..006ab792fc 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Macros.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Macros.scala @@ -314,7 +314,12 @@ trait Macros extends FastTrack with MacroRuntimes with Traces with Helpers { * @return Macro impl reference for the given macro definition if everything is okay. * EmptyTree if an error occurs. */ - def typedMacroBody(typer: Typer, macroDdef: DefDef): Tree = { + def typedMacroBody(typer: Typer, macroDdef: DefDef): Tree = pluginsTypedMacroBody(typer, macroDdef) + + /** Default implementation of `typedMacroBody`. + * Can be overridden by analyzer plugins (see AnalyzerPlugins.pluginsTypedMacroBody for more details) + */ + def standardTypedMacroBody(typer: Typer, macroDdef: DefDef): Tree = { val macroDef = macroDdef.symbol assert(macroDef.isMacro, macroDdef) @@ -359,8 +364,12 @@ trait Macros extends FastTrack with MacroRuntimes with Traces with Helpers { /** Calculate the arguments to pass to a macro implementation when expanding the provided tree. */ case class MacroArgs(c: MacroContext, others: List[Any]) + def macroArgs(typer: Typer, expandee: Tree): MacroArgs = pluginsMacroArgs(typer, expandee) - def macroArgs(typer: Typer, expandee: Tree): MacroArgs = { + /** Default implementation of `macroArgs`. + * Can be overridden by analyzer plugins (see AnalyzerPlugins.pluginsMacroArgs for more details) + */ + def standardMacroArgs(typer: Typer, expandee: Tree): MacroArgs = { val macroDef = expandee.symbol val paramss = macroDef.paramss val treeInfo.Applied(core, targs, argss) = expandee @@ -561,7 +570,7 @@ trait Macros extends FastTrack with MacroRuntimes with Traces with Helpers { onFailure(typer.infer.setError(expandee)) } else try { val expanded = { - val runtime = pluginsMacroRuntime(expandee) + val runtime = macroRuntime(expandee) if (runtime != null) macroExpandWithRuntime(typer, expandee, runtime) else macroExpandWithoutRuntime(typer, expandee) } @@ -688,7 +697,7 @@ trait Macros extends FastTrack with MacroRuntimes with Traces with Helpers { else { forced += delayed typer.infer.inferExprInstance(delayed, typer.context.extractUndetparams(), outerPt, keepNothings = false) - pluginsMacroExpand(typer, delayed, mode, outerPt) + macroExpand(typer, delayed, mode, outerPt) } } else delayed } @@ -698,7 +707,12 @@ trait Macros extends FastTrack with MacroRuntimes with Traces with Helpers { /** Expands a term macro used in apply role as `M(2)(3)` in `val x = M(2)(3)`. * @see DefMacroExpander */ - def macroExpand(typer: Typer, expandee: Tree, mode: Mode, pt: Type): Tree = { + def macroExpand(typer: Typer, expandee: Tree, mode: Mode, pt: Type): Tree = pluginsMacroExpand(typer, expandee, mode, pt) + + /** Default implementation of `macroExpand`. + * Can be overridden by analyzer plugins (see AnalyzerPlugins.pluginsMacroExpand for more details) + */ + def standardMacroExpand(typer: Typer, expandee: Tree, mode: Mode, pt: Type): Tree = { val expander = new DefMacroExpander(typer, expandee, mode, pt) expander(expandee) } @@ -730,12 +744,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(pluginsMacroArgs(typer, expandee).c)) + expandee updateAttachment MacroRuntimeAttachment(delayed = true, typerContext = typer.context, macroContext = Some(macroArgs(typer, expandee).c)) Delay(expandee) case (false, false) => import typer.TyperErrorGen._ macroLogLite("performing macro expansion %s at %s".format(expandee, expandee.pos)) - val args = pluginsMacroArgs(typer, expandee) + val args = macroArgs(typer, expandee) try { val numErrors = reporter.ERROR.count def hasNewErrors = reporter.ERROR.count > numErrors @@ -850,7 +864,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 - pluginsMacroExpand(newTyper(context), tree, EXPRmode, WildcardType) + macroExpand(newTyper(context), tree, EXPRmode, WildcardType) case _ => tree }) diff --git a/src/compiler/scala/tools/nsc/typechecker/Namers.scala b/src/compiler/scala/tools/nsc/typechecker/Namers.scala index ee597528a9..6bb13c4e0b 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Namers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Namers.scala @@ -243,7 +243,12 @@ trait Namers extends MethodSynthesis { validate(sym2.companionClass) } - def enterSym(tree: Tree): Context = { + def enterSym(tree: Tree): Context = pluginsEnterSym(this, tree) + + /** Default implementation of `enterSym`. + * Can be overridden by analyzer plugins (see AnalyzerPlugins.pluginsEnterSym for more details) + */ + def standardEnterSym(tree: Tree): Context = { def dispatch() = { var returnContext = this.context tree match { @@ -253,7 +258,7 @@ trait Namers extends MethodSynthesis { case tree @ ValDef(_, _, _, _) => enterValDef(tree) case tree @ DefDef(_, _, _, _, _, _) => enterDefDef(tree) case tree @ TypeDef(_, _, _, _) => enterTypeDef(tree) - case DocDef(_, defn) => pluginsEnterSym(this, defn) + case DocDef(_, defn) => enterSym(defn) case tree @ Import(_, _) => assignSymbol(tree) returnContext = context.make(tree) @@ -452,7 +457,7 @@ trait Namers extends MethodSynthesis { def enterSyms(trees: List[Tree]): Namer = { trees.foldLeft(this: Namer) { (namer, t) => - val ctx = pluginsEnterSym(namer, t) + val ctx = namer enterSym t // for Import trees, enterSym returns a changed context, so we need a new namer if (ctx eq namer.context) namer else newNamer(ctx) @@ -466,7 +471,13 @@ trait Namers extends MethodSynthesis { * class definition tree. * @return the companion object symbol. */ - def ensureCompanionObject(cdef: ClassDef, creator: ClassDef => Tree = companionModuleDef(_)): Symbol = { + def ensureCompanionObject(cdef: ClassDef, creator: ClassDef => Tree = companionModuleDef(_)): Symbol = + pluginsEnsureCompanionObject(this, cdef, creator) + + /** Default implementation of `ensureCompanionObject`. + * Can be overridden by analyzer plugins (see AnalyzerPlugins.pluginsEnsureCompanionObject for more details) + */ + def standardEnsureCompanionObject(cdef: ClassDef, creator: ClassDef => Tree = companionModuleDef(_)): Symbol = { val m = companionSymbolOf(cdef.symbol, context) // @luc: not sure why "currentRun.compiles(m)" is needed, things breaks // otherwise. documentation welcome. @@ -662,7 +673,7 @@ trait Namers extends MethodSynthesis { tree.symbol setInfo completerOf(tree) if (mods.isCase) { - val m = pluginsEnsureCompanionObject(this, tree, caseModuleDef) + val m = ensureCompanionObject(tree, caseModuleDef) m.moduleClass.updateAttachment(new ClassForCaseCompanionAttachment(tree)) } val hasDefault = impl.body exists { @@ -670,7 +681,7 @@ trait Namers extends MethodSynthesis { case _ => false } if (hasDefault) { - val m = pluginsEnsureCompanionObject(this, tree) + val m = ensureCompanionObject(tree) m.updateAttachment(new ConstructorDefaultsAttachment(tree, null)) } val owner = tree.symbol.owner @@ -697,7 +708,7 @@ trait Namers extends MethodSynthesis { def enterIfNotThere(sym: Symbol) { } def enterSyntheticSym(tree: Tree): Symbol = { - pluginsEnterSym(this, tree) + enterSym(tree) context.unit.synthetics(tree.symbol) = tree tree.symbol } @@ -931,7 +942,7 @@ trait Namers extends MethodSynthesis { log("Ensuring companion for derived value class " + cdef.name + " at " + cdef.pos.show) clazz setFlag FINAL // Don't force the owner's info lest we create cycles as in SI-6357. - pluginsEnsureCompanionObject(enclosingNamerWithScope(clazz.owner.rawInfo.decls), cdef) + enclosingNamerWithScope(clazz.owner.rawInfo.decls).ensureCompanionObject(cdef) } pluginsTp } diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index 8ddaccf16c..38adb35bfa 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)) - pluginsMacroExpand(this, tree, mode, pt) + macroExpand(this, tree, mode, pt) else if (mode.typingConstructorPattern) typedConstructorPattern(tree, pt) else if (shouldInsertApply(tree)) @@ -1863,8 +1863,8 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper } protected def enterSym(txt: Context, tree: Tree): Context = - if (txt eq context) pluginsEnterSym(namer, tree) - else pluginsEnterSym(newNamer(txt), tree) + if (txt eq context) namer enterSym tree + else newNamer(txt) enterSym tree /** Check that inner classes do not inherit from Annotation */ @@ -2213,7 +2213,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, pluginsTypedMacroBody(this, ddef)) + transformedOr(ddef.rhs, typedMacroBody(this, ddef)) } else { transformedOrTyped(ddef.rhs, EXPRmode, tpt1.tpe) } @@ -3812,7 +3812,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper protected def typedExistentialTypeTree(tree: ExistentialTypeTree, mode: Mode): Tree = { for (wc <- tree.whereClauses) - if (wc.symbol == NoSymbol) { pluginsEnterSym(namer, wc); wc.symbol setFlag EXISTENTIAL } + if (wc.symbol == NoSymbol) { namer enterSym wc; wc.symbol setFlag EXISTENTIAL } else context.scope enter wc.symbol val whereClauses1 = typedStats(tree.whereClauses, context.owner) for (vd @ ValDef(_, _, _, _) <- whereClauses1) @@ -5517,7 +5517,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper // here we guard against this case transformed(ddef.rhs) } else { - val rhs1 = pluginsTypedMacroBody(this, ddef) + val rhs1 = typedMacroBody(this, ddef) transformed(ddef.rhs) = rhs1 rhs1 } -- cgit v1.2.3