xsbt.ExtractUsedNames.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of compiler-interface-precompiled Show documentation
Show all versions of compiler-interface-precompiled Show documentation
compiler-interface-precompiled
package xsbt
import scala.tools.nsc._
/**
* Extracts simple names used in given compilation unit.
*
* Extracts simple (unqualified) names mentioned in given in non-definition position by collecting
* all symbols associated with non-definition trees and extracting names from all collected symbols.
*
* If given symbol is mentioned both in definition and in non-definition position (e.g. in member
* selection) then that symbol is collected. It means that names of symbols defined and used in the
* same compilation unit are extracted. We've considered not extracting names of those symbols
* as an optimization strategy. It turned out that this is not correct. Check
* https://github.com/gkossakowski/sbt/issues/3 for an example of scenario where it matters.
*
* All extracted names are returned in _decoded_ form. This way we stay consistent with the rest
* of incremental compiler which works with names in decoded form.
*
* Names mentioned in Import nodes are handled properly but require some special logic for two
* reasons:
*
* 1. import node itself has a term symbol associated with it with a name `.
* I (gkossakowski) tried to track down what role this symbol serves but I couldn't.
* It doesn't look like there are many places in Scala compiler that refer to
* that kind of symbols explicitly.
* 2. ImportSelector is not subtype of Tree therefore is not processed by `Tree.foreach`
*
* Another type of tree nodes that requires special handling is TypeTree. TypeTree nodes
* has a little bit odd representation:
*
* 1. TypeTree.hasSymbol always returns false even when TypeTree.symbol
* returns a symbol
* 2. The original tree from which given TypeTree was derived is stored
* in TypeTree.original but Tree.forech doesn't walk into original
* tree so we missed it
*
* The tree walking algorithm walks into TypeTree.original explicitly.
*
*/
class ExtractUsedNames[GlobalType <: CallbackGlobal](val global: GlobalType) extends Compat {
import global._
def extract(unit: CompilationUnit): Set[String] = {
val tree = unit.body
val extractedByTreeWalk = extractByTreeWalk(tree)
extractedByTreeWalk
}
private def extractByTreeWalk(tree: Tree): Set[String] = {
val namesBuffer = collection.mutable.ListBuffer.empty[String]
/*
* Some macros appear to contain themselves as original tree.
* We must check that we don't inspect the same tree over and over.
* See https://issues.scala-lang.org/browse/SI-8486
* https://github.com/sbt/sbt/issues/1237
* https://github.com/sbt/sbt/issues/1544
*/
val inspectedOriginalTrees = collection.mutable.Set.empty[Tree]
def addSymbol(symbol: Symbol): Unit = {
val symbolNameAsString = symbol.name.decode.trim
namesBuffer += symbolNameAsString
}
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) if inspectedOriginalTrees.add(original) =>
handleClassicTreeNode(node)
handleMacroExpansion(original)
case _ =>
handleClassicTreeNode(node)
}
}
tree.foreach(handleTreeNode)
namesBuffer.toSet
}
/**
* Needed for compatibility with Scala 2.8 which doesn't define `tpnme`
*/
private object tpnme {
val EMPTY = nme.EMPTY.toTypeName
val EMPTY_PACKAGE_NAME = nme.EMPTY_PACKAGE_NAME.toTypeName
}
private def eligibleAsUsedName(symbol: Symbol): Boolean = {
def emptyName(name: Name): Boolean = name match {
case nme.EMPTY | nme.EMPTY_PACKAGE_NAME | tpnme.EMPTY | tpnme.EMPTY_PACKAGE_NAME => true
case _ => false
}
(symbol != NoSymbol) &&
!symbol.isSynthetic &&
!emptyName(symbol.name)
}
}