Permalink
Browse files

unprivates important helpers in Namers.scala

This is the first of two commits that enable hooks necessary to implement
macro annotations in an honest, hackless compiler plugin.

This particular commit turns certain helpers into public methods. Of course,
there is a probability that with the evolution of macro paradise, I will need
more helper methods, and those will have to be called via reflection, but
at least for now it's nice to have a reflection-less plugin :)
  • Loading branch information...
xeno-by committed Dec 7, 2013
1 parent 6c7b003 commit 4d92aec651def608628a2275e1b6bf2d1fcbabe7
Showing with 379 additions and 33 deletions.
  1. +1 −1 src/compiler/scala/tools/nsc/typechecker/Analyzer.scala
  2. +66 −0 src/compiler/scala/tools/nsc/typechecker/AnalyzerPlugins.scala
  3. +14 −14 src/compiler/scala/tools/nsc/typechecker/Namers.scala
  4. +23 −17 src/compiler/scala/tools/nsc/typechecker/Typers.scala
  5. +1 −1 src/repl/scala/tools/nsc/interpreter/ReplGlobal.scala
  6. +2 −0 test/files/run/macroPlugins-macroArgs.check
  7. +11 −0 test/files/run/macroPlugins-macroArgs/Macros_2.scala
  8. +21 −0 test/files/run/macroPlugins-macroArgs/Plugin_1.scala
  9. +1 −0 test/files/run/macroPlugins-macroArgs/Test_3.flags
  10. +4 −0 test/files/run/macroPlugins-macroArgs/Test_3.scala
  11. +4 −0 test/files/run/macroPlugins-macroArgs/scalac-plugin.xml
  12. +2 −0 test/files/run/macroPlugins-macroExpand.check
  13. +18 −0 test/files/run/macroPlugins-macroExpand/Macros_2.scala
  14. +27 −0 test/files/run/macroPlugins-macroExpand/Plugin_1.scala
  15. +1 −0 test/files/run/macroPlugins-macroExpand/Test_3.flags
  16. +4 −0 test/files/run/macroPlugins-macroExpand/Test_3.scala
  17. +4 −0 test/files/run/macroPlugins-macroExpand/scalac-plugin.xml
  18. +2 −0 test/files/run/macroPlugins-macroRuntime.check
  19. +11 −0 test/files/run/macroPlugins-macroRuntime/Macros_2.scala
  20. +20 −0 test/files/run/macroPlugins-macroRuntime/Plugin_1.scala
  21. +1 −0 test/files/run/macroPlugins-macroRuntime/Test_3.flags
  22. +4 −0 test/files/run/macroPlugins-macroRuntime/Test_3.scala
  23. +4 −0 test/files/run/macroPlugins-macroRuntime/scalac-plugin.xml
  24. +45 −0 test/files/run/macroPlugins-namerHooks.check
  25. +38 −0 test/files/run/macroPlugins-namerHooks.scala
  26. +2 −0 test/files/run/macroPlugins-typedMacroBody.check
  27. +1 −0 test/files/run/macroPlugins-typedMacroBody/Macros_2.flags
  28. +18 −0 test/files/run/macroPlugins-typedMacroBody/Macros_2.scala
  29. +21 −0 test/files/run/macroPlugins-typedMacroBody/Plugin_1.scala
  30. +4 −0 test/files/run/macroPlugins-typedMacroBody/Test_3.scala
  31. +4 −0 test/files/run/macroPlugins-typedMacroBody/scalac-plugin.xml
@@ -40,7 +40,7 @@ trait Analyzer extends AnyRef
override def keepsTypeParams = false
def apply(unit: CompilationUnit) {
- newNamer(rootContext(unit)).enterSym(unit.body)
+ pluginsEnterSym(newNamer(rootContext(unit)), unit.body)
}
}
}
@@ -229,6 +229,41 @@ trait AnalyzerPlugins { self: Analyzer =>
* $nonCumulativeReturnValueDoc.
*/
def pluginsMacroRuntime(expandee: Tree): Option[MacroRuntime] = None
+
+ /**
+ * 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,
+ * 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.
+ *
+ * $nonCumulativeReturnValueDoc.
+ */
+ def pluginsEnterSym(namer: Namer, tree: Tree): Boolean = false
+
+ /**
+ * 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
+ * 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.
+ *
+ * $nonCumulativeReturnValueDoc.
+ */
+ def pluginsEnsureCompanionObject(namer: Namer, cdef: ClassDef, creator: ClassDef => Tree = companionModuleDef(_)): Option[Symbol] = None
+
+ /**
+ * Prepares a list of statements for being typechecked by performing domain-specific type-agnostic code synthesis.
+ *
+ * Trees passed into this method are going to be named, but not typed.
+ * In particular, you can rely on the compiler having called `enterSym` on every stat prior to passing calling this method.
+ *
+ * Default implementation does nothing. Current approaches to code syntheses (generation of underlying fields
+ * for getters/setters, creation of companion objects for case classes, etc) are too disparate and ad-hoc
+ * to be treated uniformly, so I'm leaving this for future work.
+ */
+ def pluginsEnterStats(typer: Typer, stats: List[Tree]): List[Tree] = stats
}
@@ -363,4 +398,35 @@ trait AnalyzerPlugins { self: Analyzer =>
def default = macroRuntime(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
+ }
+ })
+
+ /** @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 custom(plugin: MacroPlugin) = plugin.pluginsEnsureCompanionObject(namer, cdef, creator)
+ })
+
+ /** @see MacroPlugin.pluginsEnterStats */
+ def pluginsEnterStats(typer: Typer, stats: List[Tree]): List[Tree] = {
+ // performance opt
+ if (macroPlugins.isEmpty) stats
+ else macroPlugins.foldLeft(stats)((current, plugin) =>
+ if (!plugin.isActive()) current else plugin.pluginsEnterStats(typer, stats))
+ }
}
@@ -22,7 +22,7 @@ trait Namers extends MethodSynthesis {
import global._
import definitions._
- private var _lockedCount = 0
+ var _lockedCount = 0
def lockedCount = this._lockedCount
/** Replaces any Idents for which cond is true with fresh TypeTrees().
@@ -107,8 +107,8 @@ trait Namers extends MethodSynthesis {
}
protected def owner = context.owner
- private def contextFile = context.unit.source.file
- private def typeErrorHandler[T](tree: Tree, alt: T): PartialFunction[Throwable, T] = {
+ def contextFile = context.unit.source.file
+ def typeErrorHandler[T](tree: Tree, alt: T): PartialFunction[Throwable, T] = {
case ex: TypeError =>
// H@ need to ensure that we handle only cyclic references
TypeSigError(tree, ex)
@@ -253,7 +253,7 @@ trait Namers extends MethodSynthesis {
case tree @ ValDef(_, _, _, _) => enterValDef(tree)
case tree @ DefDef(_, _, _, _, _, _) => enterDefDef(tree)
case tree @ TypeDef(_, _, _, _) => enterTypeDef(tree)
- case DocDef(_, defn) => enterSym(defn)
+ case DocDef(_, defn) => pluginsEnterSym(this, defn)
case tree @ Import(_, _) =>
assignSymbol(tree)
returnContext = context.make(tree)
@@ -309,7 +309,7 @@ trait Namers extends MethodSynthesis {
* be transferred to the symbol as they are, supply a mask containing
* the flags to keep.
*/
- private def createMemberSymbol(tree: MemberDef, name: Name, mask: Long): Symbol = {
+ def createMemberSymbol(tree: MemberDef, name: Name, mask: Long): Symbol = {
val pos = tree.pos
val isParameter = tree.mods.isParameter
val flags = tree.mods.flags & mask
@@ -327,14 +327,14 @@ trait Namers extends MethodSynthesis {
else owner.newValue(name.toTermName, pos, flags)
}
}
- private def createFieldSymbol(tree: ValDef): TermSymbol =
+ def createFieldSymbol(tree: ValDef): TermSymbol =
owner.newValue(tree.localName, tree.pos, tree.mods.flags & FieldFlags | PrivateLocal)
- private def createImportSymbol(tree: Tree) =
+ def createImportSymbol(tree: Tree) =
NoSymbol.newImport(tree.pos) setInfo completerOf(tree)
/** All PackageClassInfoTypes come from here. */
- private def createPackageSymbol(pos: Position, pid: RefTree): Symbol = {
+ def createPackageSymbol(pos: Position, pid: RefTree): Symbol = {
val pkgOwner = pid match {
case Ident(_) => if (owner.isEmptyPackageClass) rootMirror.RootClass else owner
case Select(qual: RefTree, _) => createPackageSymbol(pos, qual).moduleClass
@@ -393,7 +393,7 @@ trait Namers extends MethodSynthesis {
/** Given a ClassDef or ModuleDef, verifies there isn't a companion which
* has been defined in a separate file.
*/
- private def validateCompanionDefs(tree: ImplDef) {
+ def validateCompanionDefs(tree: ImplDef) {
val sym = tree.symbol orElse { return }
val ctx = if (context.owner.isPackageObjectClass) context.outer else context
val module = if (sym.isModule) sym else ctx.scope lookupModule tree.name
@@ -452,7 +452,7 @@ trait Namers extends MethodSynthesis {
def enterSyms(trees: List[Tree]): Namer = {
trees.foldLeft(this: Namer) { (namer, t) =>
- val ctx = namer enterSym t
+ val ctx = pluginsEnterSym(namer, t)
// for Import trees, enterSym returns a changed context, so we need a new namer
if (ctx eq namer.context) namer
else newNamer(ctx)
@@ -662,15 +662,15 @@ trait Namers extends MethodSynthesis {
tree.symbol setInfo completerOf(tree)
if (mods.isCase) {
- val m = ensureCompanionObject(tree, caseModuleDef)
+ val m = pluginsEnsureCompanionObject(this, tree, caseModuleDef)
m.moduleClass.updateAttachment(new ClassForCaseCompanionAttachment(tree))
}
val hasDefault = impl.body exists {
case DefDef(_, nme.CONSTRUCTOR, _, vparamss, _, _) => mexists(vparamss)(_.mods.hasDefault)
case _ => false
}
if (hasDefault) {
- val m = ensureCompanionObject(tree)
+ val m = pluginsEnsureCompanionObject(this, tree)
m.updateAttachment(new ConstructorDefaultsAttachment(tree, null))
}
val owner = tree.symbol.owner
@@ -697,7 +697,7 @@ trait Namers extends MethodSynthesis {
def enterIfNotThere(sym: Symbol) { }
def enterSyntheticSym(tree: Tree): Symbol = {
- enterSym(tree)
+ pluginsEnterSym(this, tree)
context.unit.synthetics(tree.symbol) = tree
tree.symbol
}
@@ -931,7 +931,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.
- enclosingNamerWithScope(clazz.owner.rawInfo.decls).ensureCompanionObject(cdef)
+ pluginsEnsureCompanionObject(enclosingNamerWithScope(clazz.owner.rawInfo.decls), cdef)
}
pluginsTp
}
@@ -1863,12 +1863,15 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
}
protected def enterSym(txt: Context, tree: Tree): Context =
- if (txt eq context) namer.enterSym(tree)
- else newNamer(txt).enterSym(tree)
+ if (txt eq context) pluginsEnterSym(namer, tree)
+ else pluginsEnterSym(newNamer(txt), tree)
/** <!-- 2 --> Check that inner classes do not inherit from Annotation
*/
- def typedTemplate(templ: Template, parents1: List[Tree]): Template = {
+ def typedTemplate(templ0: Template, parents1: List[Tree]): Template = {
+ val templ = templ0
+ // please FIXME: uncommenting this line breaks everything
+ // val templ = treeCopy.Template(templ0, templ0.body, templ0.self, templ0.parents)
val clazz = context.owner
clazz.annotations.map(_.completeInfo())
if (templ.symbol == NoSymbol)
@@ -1896,7 +1899,8 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
)
// the following is necessary for templates generated later
assert(clazz.info.decls != EmptyScope, clazz)
- enterSyms(context.outer.make(templ, clazz, clazz.info.decls), templ.body)
+ val body1 = pluginsEnterStats(this, templ.body)
+ enterSyms(context.outer.make(templ, clazz, clazz.info.decls), body1)
if (!templ.isErrorTyped) // if `parentTypes` has invalidated the template, don't validate it anymore
validateParentClasses(parents1, selfType)
if (clazz.isCase)
@@ -1910,11 +1914,11 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
if (!phase.erasedTypes && !clazz.info.resultType.isError) // @S: prevent crash for duplicated type members
checkFinitary(clazz.info.resultType.asInstanceOf[ClassInfoType])
- val body = {
- val body =
- if (isPastTyper || reporter.hasErrors) templ.body
- else templ.body flatMap rewrappingWrapperTrees(namer.addDerivedTrees(Typer.this, _))
- val primaryCtor = treeInfo.firstConstructor(body)
+ val body2 = {
+ val body2 =
+ if (isPastTyper || reporter.hasErrors) body1
+ else body1 flatMap rewrappingWrapperTrees(namer.addDerivedTrees(Typer.this, _))
+ val primaryCtor = treeInfo.firstConstructor(body2)
val primaryCtor1 = primaryCtor match {
case DefDef(_, _, _, _, _, Block(earlyVals :+ global.pendingSuperCall, unit)) =>
val argss = superArgs(parents1.head) getOrElse Nil
@@ -1923,21 +1927,21 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
deriveDefDef(primaryCtor)(block => Block(earlyVals :+ superCall, unit) setPos pos) setPos pos
case _ => primaryCtor
}
- body mapConserve { case `primaryCtor` => primaryCtor1; case stat => stat }
+ body2 mapConserve { case `primaryCtor` => primaryCtor1; case stat => stat }
}
- val body1 = typedStats(body, templ.symbol)
+ val body3 = typedStats(body2, templ.symbol)
if (clazz.info.firstParent.typeSymbol == AnyValClass)
- validateDerivedValueClass(clazz, body1)
+ validateDerivedValueClass(clazz, body3)
if (clazz.isTrait) {
for (decl <- clazz.info.decls if decl.isTerm && decl.isEarlyInitialized) {
unit.warning(decl.pos, "Implementation restriction: early definitions in traits are not initialized before the super class is initialized.")
}
}
- treeCopy.Template(templ, parents1, self1, body1) setType clazz.tpe_*
+ treeCopy.Template(templ, parents1, self1, body3) setType clazz.tpe_*
}
/** Remove definition annotations from modifiers (they have been saved
@@ -2319,10 +2323,11 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
}
}
- def typedBlock(block: Block, mode: Mode, pt: Type): Block = {
+ def typedBlock(block0: Block, mode: Mode, pt: Type): Block = {
val syntheticPrivates = new ListBuffer[Symbol]
try {
- namer.enterSyms(block.stats)
+ namer.enterSyms(block0.stats)
+ val block = treeCopy.Block(block0, pluginsEnterStats(this, block0.stats), block0.expr)
for (stat <- block.stats) enterLabelDef(stat)
if (phaseId(currentPeriod) <= currentRun.typerPhase.id) {
@@ -3807,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) { namer.enterSym(wc); wc.symbol setFlag EXISTENTIAL }
+ if (wc.symbol == NoSymbol) { pluginsEnterSym(namer, wc); wc.symbol setFlag EXISTENTIAL }
else context.scope enter wc.symbol
val whereClauses1 = typedStats(tree.whereClauses, context.owner)
for (vd @ ValDef(_, _, _, _) <- whereClauses1)
@@ -4954,7 +4959,8 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
val sym: Symbol = tree.symbol
if ((sym ne null) && (sym ne NoSymbol)) sym.initialize
- def typedPackageDef(pdef: PackageDef) = {
+ def typedPackageDef(pdef0: PackageDef) = {
+ val pdef = treeCopy.PackageDef(pdef0, pdef0.pid, pluginsEnterStats(this, pdef0.stats))
val pid1 = typedQualifier(pdef.pid).asInstanceOf[RefTree]
assert(sym.moduleClass ne NoSymbol, sym)
val stats1 = newTyper(context.make(tree, sym.moduleClass, sym.info.decls))
@@ -52,7 +52,7 @@ trait ReplGlobal extends Global {
def newPhase(_prev: Phase): StdPhase = new StdPhase(_prev) {
def apply(unit: CompilationUnit) {
repldbg("Running replPhase on " + unit.body)
- // newNamer(rootContext(unit)).enterSym(unit.body)
+ // pluginsEnterSym(newNamer(rootContext(unit)), unit.body)
}
}
}
@@ -0,0 +1,2 @@
+hijacked 1
+hijacked 2
@@ -0,0 +1,11 @@
+import scala.language.experimental.macros
+import scala.reflect.macros.BlackboxContext
+
+object Macros {
+ def impl(c: BlackboxContext)(arg: c.Tree) = {
+ import c.universe._
+ q"""println($arg)"""
+ }
+
+ def foo(arg: String): Unit = macro impl
+}
@@ -0,0 +1,21 @@
+package macroArgs
+
+import scala.tools.nsc.Global
+import scala.tools.nsc.plugins.{Plugin => NscPlugin}
+
+class Plugin(val global: Global) extends NscPlugin {
+ import global._
+ import analyzer._
+
+ val name = "macroArgs"
+ val description = "A sample analyzer plugin that overrides macroArgs."
+ val components = Nil
+ addMacroPlugin(MacroPlugin)
+
+ object MacroPlugin extends MacroPlugin {
+ override def pluginsMacroArgs(typer: Typer, expandee: Tree): Option[MacroArgs] = {
+ val MacroArgs(c, List(Literal(Constant(s: String)))) = macroArgs(typer, expandee)
+ Some(MacroArgs(c, List(Literal(Constant("hijacked " + s)))))
+ }
+ }
+}
@@ -0,0 +1 @@
+-Xplugin:.
@@ -0,0 +1,4 @@
+object Test extends App {
+ Macros.foo("1")
+ Macros.foo("2")
+}
@@ -0,0 +1,4 @@
+<plugin>
+ <name>macro-args</name>
+ <classname>macroArgs.Plugin</classname>
+</plugin>
@@ -0,0 +1,2 @@
+expanded into println("impl1")
+expanded into println("impl2")
@@ -0,0 +1,18 @@
+import scala.language.experimental.macros
+import scala.reflect.macros.BlackboxContext
+
+object Macros {
+ def impl1(c: BlackboxContext) = {
+ import c.universe._
+ q"""println("impl1")"""
+ }
+
+ def impl2(c: BlackboxContext) = {
+ import c.universe._
+ q"""println("impl2")"""
+ }
+
+ def foo1: Unit = macro impl1
+
+ def foo2: Unit = macro impl2
+}
Oops, something went wrong.

0 comments on commit 4d92aec

Please sign in to comment.