-
Notifications
You must be signed in to change notification settings - Fork 939
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for tracking names used in Scala source files.
Tracking of used names is a component needed by the name hashing algorithm. The extraction and storage of used names is active only when `AnalysisCallback.nameHashing` flag is enabled and it's disabled by default. This change constists of two parts: 1. Modification of Relations to include a new `names` relation that allows us to track used names in Scala source files 2. Implementation of logic that extracts used names from Scala compilation units (that correspond to Scala source files) The first part is straightforward: add standard set of methods in Relations (along with their implementation) and update the logic which serializes and deserializes Relations. The second part is implemented as tree walk that collects all symbols associated with trees. For each symbol we extract a simple, decoded name and add it to a set of extracted names. Check documentation of `ExtractUsedNames` for discussion of implementation details. The `ExtractUsedNames` comes with unit tests grouped in `ExtractUsedNamesSpecification`. Check that class for details. Given the fact that we fork while running tests in `compiler-interface` subproject and tests are ran in parallel which involves allocating multiple Scala compiler instances we had to bump the default memory limit. This commit contains fixes for gkossakowski#3, gkossakowski#5 and gkossakowski#6 issues.
- Loading branch information
1 parent
3206f77
commit 81f5086
Showing
11 changed files
with
306 additions
and
22 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
103 changes: 103 additions & 0 deletions
103
compile/interface/src/main/scala/xsbt/ExtractUsedNames.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
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 `<import`>. | ||
* 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) { | ||
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] | ||
def addSymbol(symbol: Symbol): Unit = { | ||
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 _ => () | ||
} | ||
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) | ||
} | ||
} |
Oops, something went wrong.