Record dependencies on macro arguments

Macros take arguments as trees and return some other trees; both of
them have dependencies but we see trees only after expansion and
recorded only those dependencies.

This commit solves this problem by looking into the attachments of the
trees that are supposed to contain originals of macro expansions and
recording dependencies of the macro before its expansion.
This commit is contained in:
Martin Duhem
2014-03-19 22:14:58 +01:00
parent 2a98355c64
commit 70fecfe767
3 changed files with 78 additions and 24 deletions
@@ -91,4 +91,42 @@ abstract class Compat
private[this] def sourceCompatibilityOnly: Nothing = throw new RuntimeException("For source compatibility only: should not get here.") private[this] def sourceCompatibilityOnly: Nothing = throw new RuntimeException("For source compatibility only: should not get here.")
private[this] final implicit def miscCompat(n: AnyRef): MiscCompat = new MiscCompat private[this] final implicit def miscCompat(n: AnyRef): MiscCompat = new MiscCompat
object MacroExpansionOf {
def unapply(tree: Tree): Option[Tree] = {
// MacroExpansionAttachment (MEA) compatibility for 2.8.x and 2.9.x
object Compat {
class MacroExpansionAttachment(val original: Tree)
// Trees have no attachments in 2.8.x and 2.9.x
implicit def withAttachments(tree: Tree): WithAttachments = new WithAttachments(tree)
class WithAttachments(val tree: Tree) {
object EmptyAttachments {
def all = Set.empty[Any]
}
val attachments = EmptyAttachments
}
}
import Compat._
locally {
// Wildcard imports are necessary since 2.8.x and 2.9.x don't have `MacroExpansionAttachment` at all
import global._ // this is where MEA lives in 2.10.x
// `original` has been renamed to `expandee` in 2.11.x
implicit def withExpandee(att: MacroExpansionAttachment): WithExpandee = new WithExpandee(att)
class WithExpandee(att: MacroExpansionAttachment) {
def expandee: Tree = att.original
}
locally {
import analyzer._ // this is where MEA lives in 2.11.x
tree.attachments.all.collect {
case att: MacroExpansionAttachment => att.expandee
} headOption
}
}
}
}
} }
@@ -146,6 +146,8 @@ final class Dependency(val global: CallbackGlobal) extends LocateClassFile
deps.foreach(addDependency) deps.foreach(addDependency)
case Template(parents, self, body) => case Template(parents, self, body) =>
traverseTrees(body) traverseTrees(body)
case MacroExpansionOf(original) =>
this.traverse(original)
case other => () case other => ()
} }
super.traverse(tree) super.traverse(tree)
@@ -38,7 +38,7 @@ import scala.tools.nsc._
* The tree walking algorithm walks into TypeTree.original explicitly. * The tree walking algorithm walks into TypeTree.original explicitly.
* *
*/ */
class ExtractUsedNames[GlobalType <: CallbackGlobal](val global: GlobalType) { class ExtractUsedNames[GlobalType <: CallbackGlobal](val global: GlobalType) extends Compat {
import global._ import global._
def extract(unit: CompilationUnit): Set[String] = { def extract(unit: CompilationUnit): Set[String] = {
@@ -53,30 +53,44 @@ class ExtractUsedNames[GlobalType <: CallbackGlobal](val global: GlobalType) {
val symbolNameAsString = symbol.name.decode.trim val symbolNameAsString = symbol.name.decode.trim
namesBuffer += symbolNameAsString namesBuffer += symbolNameAsString
} }
def handleTreeNode(node: Tree): Unit = node match {
case _: DefTree | _: Template => () def handleTreeNode(node: Tree): Unit = {
// turns out that Import node has a TermSymbol associated with it def handleMacroExpansion(original: Tree): Unit = original.foreach(handleTreeNode)
// I (Grzegorz) tried to understand why it's there and what does it represent but
// that logic was introduced in 2005 without any justification I'll just ignore the def handleClassicTreeNode(node: Tree): Unit = node match {
// import node altogether and just process the selectors in the import node case _: DefTree | _: Template => ()
case Import(_, selectors: List[ImportSelector]) => // turns out that Import node has a TermSymbol associated with it
def usedNameInImportSelector(name: Name): Unit = // I (Grzegorz) tried to understand why it's there and what does it represent but
if ((name != null) && (name != nme.WILDCARD)) namesBuffer += name.toString // that logic was introduced in 2005 without any justification I'll just ignore the
selectors foreach { selector => // import node altogether and just process the selectors in the import node
usedNameInImportSelector(selector.name) case Import(_, selectors: List[ImportSelector]) =>
usedNameInImportSelector(selector.rename) def usedNameInImportSelector(name: Name): Unit =
} if ((name != null) && (name != nme.WILDCARD)) namesBuffer += name.toString
// TODO: figure out whether we should process the original tree or walk the type selectors foreach { selector =>
// the argument for processing the original tree: we process what user wrote usedNameInImportSelector(selector.name)
// the argument for processing the type: we catch all transformations that typer applies usedNameInImportSelector(selector.rename)
// to types but that might be a bad thing because it might expand aliases eagerly which }
// not what we need // TODO: figure out whether we should process the original tree or walk the type
case t: TypeTree if t.original != null => // the argument for processing the original tree: we process what user wrote
t.original.foreach(handleTreeNode) // the argument for processing the type: we catch all transformations that typer applies
case t if t.hasSymbol && eligibleAsUsedName(t.symbol) => // to types but that might be a bad thing because it might expand aliases eagerly which
addSymbol(t.symbol) // not what we need
case _ => () case t: TypeTree if t.original != null =>
t.original.foreach(handleTreeNode)
case t if t.hasSymbol && eligibleAsUsedName(t.symbol) =>
addSymbol(t.symbol)
case _ => ()
}
node match {
case MacroExpansionOf(original) =>
handleClassicTreeNode(node)
handleMacroExpansion(original)
case _ =>
handleClassicTreeNode(node)
}
} }
tree.foreach(handleTreeNode) tree.foreach(handleTreeNode)
namesBuffer.toSet namesBuffer.toSet
} }