Skip to content

Commit 4d92aec

Browse files
committed
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 :)
1 parent 6c7b003 commit 4d92aec

31 files changed

+379
-33
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ trait Analyzer extends AnyRef
4040
override def keepsTypeParams = false
4141

4242
def apply(unit: CompilationUnit) {
43-
newNamer(rootContext(unit)).enterSym(unit.body)
43+
pluginsEnterSym(newNamer(rootContext(unit)), unit.body)
4444
}
4545
}
4646
}

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

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,41 @@ trait AnalyzerPlugins { self: Analyzer =>
229229
* $nonCumulativeReturnValueDoc.
230230
*/
231231
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.enterSym` 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.ensureCompanionObject` 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
232267
}
233268

234269

@@ -363,4 +398,35 @@ trait AnalyzerPlugins { self: Analyzer =>
363398
def default = macroRuntime(expandee)
364399
def custom(plugin: MacroPlugin) = plugin.pluginsMacroRuntime(expandee)
365400
})
401+
402+
/** @see MacroPlugin.pluginsEnterSym */
403+
def pluginsEnterSym(namer: Namer, tree: Tree): Context = invoke(new NonCumulativeOp[Context] {
404+
def position = tree.pos
405+
def description = "enter a symbol for this tree"
406+
def default = namer.enterSym(tree)
407+
def custom(plugin: MacroPlugin) = {
408+
val hasExistingSym = tree.symbol != NoSymbol
409+
val result = plugin.pluginsEnterSym(namer, tree)
410+
if (result && hasExistingSym) Some(namer.context)
411+
else if (result && tree.isInstanceOf[Import]) Some(namer.context.make(tree))
412+
else if (result) Some(namer.context)
413+
else None
414+
}
415+
})
416+
417+
/** @see MacroPlugin.pluginsEnsureCompanionObject */
418+
def pluginsEnsureCompanionObject(namer: Namer, cdef: ClassDef, creator: ClassDef => Tree = companionModuleDef(_)): Symbol = invoke(new NonCumulativeOp[Symbol] {
419+
def position = cdef.pos
420+
def description = "enter a companion symbol for this tree"
421+
def default = namer.ensureCompanionObject(cdef, creator)
422+
def custom(plugin: MacroPlugin) = plugin.pluginsEnsureCompanionObject(namer, cdef, creator)
423+
})
424+
425+
/** @see MacroPlugin.pluginsEnterStats */
426+
def pluginsEnterStats(typer: Typer, stats: List[Tree]): List[Tree] = {
427+
// performance opt
428+
if (macroPlugins.isEmpty) stats
429+
else macroPlugins.foldLeft(stats)((current, plugin) =>
430+
if (!plugin.isActive()) current else plugin.pluginsEnterStats(typer, stats))
431+
}
366432
}

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

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ trait Namers extends MethodSynthesis {
2222
import global._
2323
import definitions._
2424

25-
private var _lockedCount = 0
25+
var _lockedCount = 0
2626
def lockedCount = this._lockedCount
2727

2828
/** Replaces any Idents for which cond is true with fresh TypeTrees().
@@ -107,8 +107,8 @@ trait Namers extends MethodSynthesis {
107107
}
108108

109109
protected def owner = context.owner
110-
private def contextFile = context.unit.source.file
111-
private def typeErrorHandler[T](tree: Tree, alt: T): PartialFunction[Throwable, T] = {
110+
def contextFile = context.unit.source.file
111+
def typeErrorHandler[T](tree: Tree, alt: T): PartialFunction[Throwable, T] = {
112112
case ex: TypeError =>
113113
// H@ need to ensure that we handle only cyclic references
114114
TypeSigError(tree, ex)
@@ -253,7 +253,7 @@ trait Namers extends MethodSynthesis {
253253
case tree @ ValDef(_, _, _, _) => enterValDef(tree)
254254
case tree @ DefDef(_, _, _, _, _, _) => enterDefDef(tree)
255255
case tree @ TypeDef(_, _, _, _) => enterTypeDef(tree)
256-
case DocDef(_, defn) => enterSym(defn)
256+
case DocDef(_, defn) => pluginsEnterSym(this, defn)
257257
case tree @ Import(_, _) =>
258258
assignSymbol(tree)
259259
returnContext = context.make(tree)
@@ -309,7 +309,7 @@ trait Namers extends MethodSynthesis {
309309
* be transferred to the symbol as they are, supply a mask containing
310310
* the flags to keep.
311311
*/
312-
private def createMemberSymbol(tree: MemberDef, name: Name, mask: Long): Symbol = {
312+
def createMemberSymbol(tree: MemberDef, name: Name, mask: Long): Symbol = {
313313
val pos = tree.pos
314314
val isParameter = tree.mods.isParameter
315315
val flags = tree.mods.flags & mask
@@ -327,14 +327,14 @@ trait Namers extends MethodSynthesis {
327327
else owner.newValue(name.toTermName, pos, flags)
328328
}
329329
}
330-
private def createFieldSymbol(tree: ValDef): TermSymbol =
330+
def createFieldSymbol(tree: ValDef): TermSymbol =
331331
owner.newValue(tree.localName, tree.pos, tree.mods.flags & FieldFlags | PrivateLocal)
332332

333-
private def createImportSymbol(tree: Tree) =
333+
def createImportSymbol(tree: Tree) =
334334
NoSymbol.newImport(tree.pos) setInfo completerOf(tree)
335335

336336
/** All PackageClassInfoTypes come from here. */
337-
private def createPackageSymbol(pos: Position, pid: RefTree): Symbol = {
337+
def createPackageSymbol(pos: Position, pid: RefTree): Symbol = {
338338
val pkgOwner = pid match {
339339
case Ident(_) => if (owner.isEmptyPackageClass) rootMirror.RootClass else owner
340340
case Select(qual: RefTree, _) => createPackageSymbol(pos, qual).moduleClass
@@ -393,7 +393,7 @@ trait Namers extends MethodSynthesis {
393393
/** Given a ClassDef or ModuleDef, verifies there isn't a companion which
394394
* has been defined in a separate file.
395395
*/
396-
private def validateCompanionDefs(tree: ImplDef) {
396+
def validateCompanionDefs(tree: ImplDef) {
397397
val sym = tree.symbol orElse { return }
398398
val ctx = if (context.owner.isPackageObjectClass) context.outer else context
399399
val module = if (sym.isModule) sym else ctx.scope lookupModule tree.name
@@ -452,7 +452,7 @@ trait Namers extends MethodSynthesis {
452452

453453
def enterSyms(trees: List[Tree]): Namer = {
454454
trees.foldLeft(this: Namer) { (namer, t) =>
455-
val ctx = namer enterSym t
455+
val ctx = pluginsEnterSym(namer, t)
456456
// for Import trees, enterSym returns a changed context, so we need a new namer
457457
if (ctx eq namer.context) namer
458458
else newNamer(ctx)
@@ -662,15 +662,15 @@ trait Namers extends MethodSynthesis {
662662
tree.symbol setInfo completerOf(tree)
663663

664664
if (mods.isCase) {
665-
val m = ensureCompanionObject(tree, caseModuleDef)
665+
val m = pluginsEnsureCompanionObject(this, tree, caseModuleDef)
666666
m.moduleClass.updateAttachment(new ClassForCaseCompanionAttachment(tree))
667667
}
668668
val hasDefault = impl.body exists {
669669
case DefDef(_, nme.CONSTRUCTOR, _, vparamss, _, _) => mexists(vparamss)(_.mods.hasDefault)
670670
case _ => false
671671
}
672672
if (hasDefault) {
673-
val m = ensureCompanionObject(tree)
673+
val m = pluginsEnsureCompanionObject(this, tree)
674674
m.updateAttachment(new ConstructorDefaultsAttachment(tree, null))
675675
}
676676
val owner = tree.symbol.owner
@@ -697,7 +697,7 @@ trait Namers extends MethodSynthesis {
697697
def enterIfNotThere(sym: Symbol) { }
698698

699699
def enterSyntheticSym(tree: Tree): Symbol = {
700-
enterSym(tree)
700+
pluginsEnterSym(this, tree)
701701
context.unit.synthetics(tree.symbol) = tree
702702
tree.symbol
703703
}
@@ -931,7 +931,7 @@ trait Namers extends MethodSynthesis {
931931
log("Ensuring companion for derived value class " + cdef.name + " at " + cdef.pos.show)
932932
clazz setFlag FINAL
933933
// Don't force the owner's info lest we create cycles as in SI-6357.
934-
enclosingNamerWithScope(clazz.owner.rawInfo.decls).ensureCompanionObject(cdef)
934+
pluginsEnsureCompanionObject(enclosingNamerWithScope(clazz.owner.rawInfo.decls), cdef)
935935
}
936936
pluginsTp
937937
}

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

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1863,12 +1863,15 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
18631863
}
18641864

18651865
protected def enterSym(txt: Context, tree: Tree): Context =
1866-
if (txt eq context) namer.enterSym(tree)
1867-
else newNamer(txt).enterSym(tree)
1866+
if (txt eq context) pluginsEnterSym(namer, tree)
1867+
else pluginsEnterSym(newNamer(txt), tree)
18681868

18691869
/** <!-- 2 --> Check that inner classes do not inherit from Annotation
18701870
*/
1871-
def typedTemplate(templ: Template, parents1: List[Tree]): Template = {
1871+
def typedTemplate(templ0: Template, parents1: List[Tree]): Template = {
1872+
val templ = templ0
1873+
// please FIXME: uncommenting this line breaks everything
1874+
// val templ = treeCopy.Template(templ0, templ0.body, templ0.self, templ0.parents)
18721875
val clazz = context.owner
18731876
clazz.annotations.map(_.completeInfo())
18741877
if (templ.symbol == NoSymbol)
@@ -1896,7 +1899,8 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
18961899
)
18971900
// the following is necessary for templates generated later
18981901
assert(clazz.info.decls != EmptyScope, clazz)
1899-
enterSyms(context.outer.make(templ, clazz, clazz.info.decls), templ.body)
1902+
val body1 = pluginsEnterStats(this, templ.body)
1903+
enterSyms(context.outer.make(templ, clazz, clazz.info.decls), body1)
19001904
if (!templ.isErrorTyped) // if `parentTypes` has invalidated the template, don't validate it anymore
19011905
validateParentClasses(parents1, selfType)
19021906
if (clazz.isCase)
@@ -1910,11 +1914,11 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
19101914
if (!phase.erasedTypes && !clazz.info.resultType.isError) // @S: prevent crash for duplicated type members
19111915
checkFinitary(clazz.info.resultType.asInstanceOf[ClassInfoType])
19121916

1913-
val body = {
1914-
val body =
1915-
if (isPastTyper || reporter.hasErrors) templ.body
1916-
else templ.body flatMap rewrappingWrapperTrees(namer.addDerivedTrees(Typer.this, _))
1917-
val primaryCtor = treeInfo.firstConstructor(body)
1917+
val body2 = {
1918+
val body2 =
1919+
if (isPastTyper || reporter.hasErrors) body1
1920+
else body1 flatMap rewrappingWrapperTrees(namer.addDerivedTrees(Typer.this, _))
1921+
val primaryCtor = treeInfo.firstConstructor(body2)
19181922
val primaryCtor1 = primaryCtor match {
19191923
case DefDef(_, _, _, _, _, Block(earlyVals :+ global.pendingSuperCall, unit)) =>
19201924
val argss = superArgs(parents1.head) getOrElse Nil
@@ -1923,21 +1927,21 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
19231927
deriveDefDef(primaryCtor)(block => Block(earlyVals :+ superCall, unit) setPos pos) setPos pos
19241928
case _ => primaryCtor
19251929
}
1926-
body mapConserve { case `primaryCtor` => primaryCtor1; case stat => stat }
1930+
body2 mapConserve { case `primaryCtor` => primaryCtor1; case stat => stat }
19271931
}
19281932

1929-
val body1 = typedStats(body, templ.symbol)
1933+
val body3 = typedStats(body2, templ.symbol)
19301934

19311935
if (clazz.info.firstParent.typeSymbol == AnyValClass)
1932-
validateDerivedValueClass(clazz, body1)
1936+
validateDerivedValueClass(clazz, body3)
19331937

19341938
if (clazz.isTrait) {
19351939
for (decl <- clazz.info.decls if decl.isTerm && decl.isEarlyInitialized) {
19361940
unit.warning(decl.pos, "Implementation restriction: early definitions in traits are not initialized before the super class is initialized.")
19371941
}
19381942
}
19391943

1940-
treeCopy.Template(templ, parents1, self1, body1) setType clazz.tpe_*
1944+
treeCopy.Template(templ, parents1, self1, body3) setType clazz.tpe_*
19411945
}
19421946

19431947
/** Remove definition annotations from modifiers (they have been saved
@@ -2319,10 +2323,11 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
23192323
}
23202324
}
23212325

2322-
def typedBlock(block: Block, mode: Mode, pt: Type): Block = {
2326+
def typedBlock(block0: Block, mode: Mode, pt: Type): Block = {
23232327
val syntheticPrivates = new ListBuffer[Symbol]
23242328
try {
2325-
namer.enterSyms(block.stats)
2329+
namer.enterSyms(block0.stats)
2330+
val block = treeCopy.Block(block0, pluginsEnterStats(this, block0.stats), block0.expr)
23262331
for (stat <- block.stats) enterLabelDef(stat)
23272332

23282333
if (phaseId(currentPeriod) <= currentRun.typerPhase.id) {
@@ -3807,7 +3812,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
38073812

38083813
protected def typedExistentialTypeTree(tree: ExistentialTypeTree, mode: Mode): Tree = {
38093814
for (wc <- tree.whereClauses)
3810-
if (wc.symbol == NoSymbol) { namer.enterSym(wc); wc.symbol setFlag EXISTENTIAL }
3815+
if (wc.symbol == NoSymbol) { pluginsEnterSym(namer, wc); wc.symbol setFlag EXISTENTIAL }
38113816
else context.scope enter wc.symbol
38123817
val whereClauses1 = typedStats(tree.whereClauses, context.owner)
38133818
for (vd @ ValDef(_, _, _, _) <- whereClauses1)
@@ -4954,7 +4959,8 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
49544959
val sym: Symbol = tree.symbol
49554960
if ((sym ne null) && (sym ne NoSymbol)) sym.initialize
49564961

4957-
def typedPackageDef(pdef: PackageDef) = {
4962+
def typedPackageDef(pdef0: PackageDef) = {
4963+
val pdef = treeCopy.PackageDef(pdef0, pdef0.pid, pluginsEnterStats(this, pdef0.stats))
49584964
val pid1 = typedQualifier(pdef.pid).asInstanceOf[RefTree]
49594965
assert(sym.moduleClass ne NoSymbol, sym)
49604966
val stats1 = newTyper(context.make(tree, sym.moduleClass, sym.info.decls))

src/repl/scala/tools/nsc/interpreter/ReplGlobal.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ trait ReplGlobal extends Global {
5252
def newPhase(_prev: Phase): StdPhase = new StdPhase(_prev) {
5353
def apply(unit: CompilationUnit) {
5454
repldbg("Running replPhase on " + unit.body)
55-
// newNamer(rootContext(unit)).enterSym(unit.body)
55+
// pluginsEnterSym(newNamer(rootContext(unit)), unit.body)
5656
}
5757
}
5858
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
hijacked 1
2+
hijacked 2
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import scala.language.experimental.macros
2+
import scala.reflect.macros.BlackboxContext
3+
4+
object Macros {
5+
def impl(c: BlackboxContext)(arg: c.Tree) = {
6+
import c.universe._
7+
q"""println($arg)"""
8+
}
9+
10+
def foo(arg: String): Unit = macro impl
11+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package macroArgs
2+
3+
import scala.tools.nsc.Global
4+
import scala.tools.nsc.plugins.{Plugin => NscPlugin}
5+
6+
class Plugin(val global: Global) extends NscPlugin {
7+
import global._
8+
import analyzer._
9+
10+
val name = "macroArgs"
11+
val description = "A sample analyzer plugin that overrides macroArgs."
12+
val components = Nil
13+
addMacroPlugin(MacroPlugin)
14+
15+
object MacroPlugin extends MacroPlugin {
16+
override def pluginsMacroArgs(typer: Typer, expandee: Tree): Option[MacroArgs] = {
17+
val MacroArgs(c, List(Literal(Constant(s: String)))) = macroArgs(typer, expandee)
18+
Some(MacroArgs(c, List(Literal(Constant("hijacked " + s)))))
19+
}
20+
}
21+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
-Xplugin:.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
object Test extends App {
2+
Macros.foo("1")
3+
Macros.foo("2")
4+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<plugin>
2+
<name>macro-args</name>
3+
<classname>macroArgs.Plugin</classname>
4+
</plugin>
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
expanded into println("impl1")
2+
expanded into println("impl2")
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import scala.language.experimental.macros
2+
import scala.reflect.macros.BlackboxContext
3+
4+
object Macros {
5+
def impl1(c: BlackboxContext) = {
6+
import c.universe._
7+
q"""println("impl1")"""
8+
}
9+
10+
def impl2(c: BlackboxContext) = {
11+
import c.universe._
12+
q"""println("impl2")"""
13+
}
14+
15+
def foo1: Unit = macro impl1
16+
17+
def foo2: Unit = macro impl2
18+
}

0 commit comments

Comments
 (0)