From 70fecfe767f5d9a771a546e86238b3d2c06250a4 Mon Sep 17 00:00:00 2001 From: Martin Duhem Date: Tue, 4 Mar 2014 18:21:44 +0100 Subject: [PATCH] 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. --- .../src/main/scala/xsbt/Compat.scala | 38 ++++++++++++ .../src/main/scala/xsbt/Dependency.scala | 2 + .../main/scala/xsbt/ExtractUsedNames.scala | 62 ++++++++++++------- 3 files changed, 78 insertions(+), 24 deletions(-) diff --git a/compile/interface/src/main/scala/xsbt/Compat.scala b/compile/interface/src/main/scala/xsbt/Compat.scala index 17a1a8f6b..d92ba6e73 100644 --- a/compile/interface/src/main/scala/xsbt/Compat.scala +++ b/compile/interface/src/main/scala/xsbt/Compat.scala @@ -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] 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 + } + } + } + } } diff --git a/compile/interface/src/main/scala/xsbt/Dependency.scala b/compile/interface/src/main/scala/xsbt/Dependency.scala index e9b482ef9..b8a55c8a9 100644 --- a/compile/interface/src/main/scala/xsbt/Dependency.scala +++ b/compile/interface/src/main/scala/xsbt/Dependency.scala @@ -146,6 +146,8 @@ final class Dependency(val global: CallbackGlobal) extends LocateClassFile deps.foreach(addDependency) case Template(parents, self, body) => traverseTrees(body) + case MacroExpansionOf(original) => + this.traverse(original) case other => () } super.traverse(tree) diff --git a/compile/interface/src/main/scala/xsbt/ExtractUsedNames.scala b/compile/interface/src/main/scala/xsbt/ExtractUsedNames.scala index 9f89a3459..6ab01c9eb 100644 --- a/compile/interface/src/main/scala/xsbt/ExtractUsedNames.scala +++ b/compile/interface/src/main/scala/xsbt/ExtractUsedNames.scala @@ -38,7 +38,7 @@ import scala.tools.nsc._ * 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._ def extract(unit: CompilationUnit): Set[String] = { @@ -53,30 +53,44 @@ class ExtractUsedNames[GlobalType <: CallbackGlobal](val global: GlobalType) { val symbolNameAsString = symbol.name.decode.trim namesBuffer += symbolNameAsString } - def handleTreeNode(node: Tree): Unit = node match { - case _: DefTree | _: Template => () - // turns out that Import node has a TermSymbol associated with it - // 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 - // import node altogether and just process the selectors in the import node - case Import(_, selectors: List[ImportSelector]) => - def usedNameInImportSelector(name: Name): Unit = - if ((name != null) && (name != nme.WILDCARD)) namesBuffer += name.toString - selectors foreach { selector => - usedNameInImportSelector(selector.name) - usedNameInImportSelector(selector.rename) - } - // TODO: figure out whether we should process the original tree or walk the type - // the argument for processing the original tree: we process what user wrote - // the argument for processing the type: we catch all transformations that typer applies - // to types but that might be a bad thing because it might expand aliases eagerly which - // not what we need - case t: TypeTree if t.original != null => - t.original.foreach(handleTreeNode) - case t if t.hasSymbol && eligibleAsUsedName(t.symbol) => - addSymbol(t.symbol) - case _ => () + + def handleTreeNode(node: Tree): Unit = { + def handleMacroExpansion(original: Tree): Unit = original.foreach(handleTreeNode) + + def handleClassicTreeNode(node: Tree): Unit = node match { + case _: DefTree | _: Template => () + // turns out that Import node has a TermSymbol associated with it + // 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 + // import node altogether and just process the selectors in the import node + case Import(_, selectors: List[ImportSelector]) => + def usedNameInImportSelector(name: Name): Unit = + if ((name != null) && (name != nme.WILDCARD)) namesBuffer += name.toString + selectors foreach { selector => + usedNameInImportSelector(selector.name) + usedNameInImportSelector(selector.rename) + } + // TODO: figure out whether we should process the original tree or walk the type + // the argument for processing the original tree: we process what user wrote + // the argument for processing the type: we catch all transformations that typer applies + // to types but that might be a bad thing because it might expand aliases eagerly which + // not what we need + 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) namesBuffer.toSet }