Extract dependencies in one pass.

Also a bit more complete: handle SelectFromTypeTree,
consider the self type an inheritance dependency,
and flatten any refinement types in inherited types,
to get to the symbols of their parents, instead of the
useless symbol of the refinement class.

Include inheritance dependencies in regular ones

Also, update test to reflect the self type is now seen as an inheritance dependency.

self types are local, so don't treat them like inherited types

note inheritanceSymbols dealiases, where allSymbols is constructed differently

fix NPE in source-dependencies/macro-annotation
This commit is contained in:
Adriaan Moors 2015-12-11 11:51:32 -08:00 committed by Eugene Yokota
parent 8c73b2f221
commit 0f616294c4
2 changed files with 100 additions and 108 deletions

View File

@ -36,28 +36,26 @@ final class Dependency(val global: CallbackGlobal) extends LocateClassFile {
private class DependencyPhase(prev: Phase) extends Phase(prev) {
override def description = "Extracts dependency information"
def name = Dependency.name
def run {
def run: Unit = {
for (unit <- currentRun.units if !unit.isJava) {
// build dependencies structure
val sourceFile = unit.source.file.file
if (global.callback.nameHashing) {
val dependenciesByMemberRef = extractDependenciesByMemberRef(unit)
for (on <- dependenciesByMemberRef)
processDependency(on, context = DependencyByMemberRef)
val dependencyExtractor = new ExtractDependenciesTraverser
dependencyExtractor.traverse(unit.body)
val dependenciesByInheritance = extractDependenciesByInheritance(unit)
for (on <- dependenciesByInheritance)
processDependency(on, context = DependencyByInheritance)
dependencyExtractor.topLevelDependencies foreach processDependency(context = DependencyByMemberRef)
dependencyExtractor.topLevelInheritanceDependencies foreach processDependency(context = DependencyByInheritance)
} else {
for (on <- unit.depends) processDependency(on, context = DependencyByMemberRef)
for (on <- inheritedDependencies.getOrElse(sourceFile, Nil: Iterable[Symbol])) processDependency(on, context = DependencyByInheritance)
unit.depends foreach processDependency(context = DependencyByMemberRef)
inheritedDependencies.getOrElse(sourceFile, Nil: Iterable[Symbol]) foreach processDependency(context = DependencyByInheritance)
}
/**
* Handles dependency on given symbol by trying to figure out if represents a term
* that is coming from either source code (not necessarily compiled in this compilation
* run) or from class file and calls respective callback method.
*/
def processDependency(on: Symbol, context: DependencyContext): Unit = {
def processDependency(context: DependencyContext)(on: Symbol) = {
def binaryDependency(file: File, className: String) = callback.binaryDependency(file, className, sourceFile, context)
val onSource = on.sourceFile
if (onSource == null) {
@ -78,6 +76,91 @@ final class Dependency(val global: CallbackGlobal) extends LocateClassFile {
}
}
private class ExtractDependenciesTraverser extends Traverser {
private val _dependencies = collection.mutable.HashSet.empty[Symbol]
protected def addDependency(dep: Symbol): Unit = if (dep ne NoSymbol) _dependencies += dep
def dependencies: Iterator[Symbol] = _dependencies.iterator
def topLevelDependencies: Iterator[Symbol] = _dependencies.map(enclosingTopLevelClass).iterator
private val _inheritanceDependencies = collection.mutable.HashSet.empty[Symbol]
protected def addInheritanceDependency(dep: Symbol): Unit = if (dep ne NoSymbol) _inheritanceDependencies += dep
def inheritanceDependencies: Iterator[Symbol] = _inheritanceDependencies.iterator
def topLevelInheritanceDependencies: Iterator[Symbol] = _inheritanceDependencies.map(enclosingTopLevelClass).iterator
/*
* 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
*/
private val inspectedOriginalTrees = collection.mutable.Set.empty[Tree]
override def traverse(tree: Tree): Unit = tree match {
case Import(expr, selectors) =>
traverse(expr)
selectors.foreach {
case ImportSelector(nme.WILDCARD, _, null, _) =>
// in case of wildcard import we do not rely on any particular name being defined
// on `expr`; all symbols that are being used will get caught through selections
case ImportSelector(name: Name, _, _, _) =>
def lookupImported(name: Name) = expr.symbol.info.member(name)
// importing a name means importing both a term and a type (if they exist)
addDependency(lookupImported(name.toTermName))
addDependency(lookupImported(name.toTypeName))
}
/*
* Idents are used in number of situations:
* - to refer to local variable
* - to refer to a top-level package (other packages are nested selections)
* - to refer to a term defined in the same package as an enclosing class;
* this looks fishy, see this thread:
* https://groups.google.com/d/topic/scala-internals/Ms9WUAtokLo/discussion
*/
case id: Ident => addDependency(id.symbol)
case sel @ Select(qual, _) =>
traverse(qual); addDependency(sel.symbol)
case sel @ SelectFromTypeTree(qual, _) =>
traverse(qual); addDependency(sel.symbol)
case Template(parents, self, body) =>
// use typeSymbol to dealias type aliases -- we want to track the dependency on the real class in the alias's RHS
def flattenTypeToSymbols(tp: Type): List[Symbol] = if (tp eq null) Nil else tp match {
// rt.typeSymbol is redundant if we list out all parents, TODO: what about rt.decls?
case rt: RefinedType => rt.parents.flatMap(flattenTypeToSymbols)
case _ => List(tp.typeSymbol)
}
val inheritanceTypes = parents.map(_.tpe).toSet
val inheritanceSymbols = inheritanceTypes.flatMap(flattenTypeToSymbols)
debuglog("Parent types for " + tree.symbol + " (self: " + self.tpt.tpe + "): " + inheritanceTypes + " with symbols " + inheritanceSymbols.map(_.fullName))
inheritanceSymbols.foreach(addInheritanceDependency)
val allSymbols = (inheritanceTypes + self.tpt.tpe).flatMap(symbolsInType)
(allSymbols ++ inheritanceSymbols).foreach(addDependency)
traverseTrees(body)
// In some cases (eg. macro annotations), `typeTree.tpe` may be null. See sbt/sbt#1593 and sbt/sbt#1655.
case typeTree: TypeTree if typeTree.tpe != null => symbolsInType(typeTree.tpe) foreach addDependency
case MacroExpansionOf(original) if inspectedOriginalTrees.add(original) => traverse(original)
case other => super.traverse(other)
}
private def symbolsInType(tp: Type): Set[Symbol] = {
val typeSymbolCollector =
new CollectTypeTraverser({
case tpe if (tpe != null) && !tpe.typeSymbolDirect.isPackage => tpe.typeSymbolDirect
})
typeSymbolCollector.traverse(tp)
typeSymbolCollector.collected.toSet
}
}
/**
* Traverses given type and collects result of applying a partial function `pf`.
*
@ -94,106 +177,15 @@ final class Dependency(val global: CallbackGlobal) extends LocateClassFile {
}
}
private abstract class ExtractDependenciesTraverser extends Traverser {
private val deps = collection.mutable.HashSet.empty[Symbol]
protected def addDependency(dep: Symbol): Unit = if (dep ne NoSymbol) deps += dep
def dependencies: Iterator[Symbol] = deps.iterator
}
private class ExtractDependenciesByMemberRefTraverser extends ExtractDependenciesTraverser {
/*
* 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
*/
private val inspectedOriginalTrees = collection.mutable.Set.empty[Tree]
override def traverse(tree: Tree): Unit = {
tree match {
case Import(expr, selectors) =>
selectors.foreach {
case ImportSelector(nme.WILDCARD, _, null, _) =>
// in case of wildcard import we do not rely on any particular name being defined
// on `expr`; all symbols that are being used will get caught through selections
case ImportSelector(name: Name, _, _, _) =>
def lookupImported(name: Name) = expr.symbol.info.member(name)
// importing a name means importing both a term and a type (if they exist)
addDependency(lookupImported(name.toTermName))
addDependency(lookupImported(name.toTypeName))
}
case select: Select =>
addDependency(select.symbol)
/*
* Idents are used in number of situations:
* - to refer to local variable
* - to refer to a top-level package (other packages are nested selections)
* - to refer to a term defined in the same package as an enclosing class;
* this looks fishy, see this thread:
* https://groups.google.com/d/topic/scala-internals/Ms9WUAtokLo/discussion
*/
case ident: Ident =>
addDependency(ident.symbol)
// In some cases (eg. macro annotations), `typeTree.tpe` may be null.
// See sbt/sbt#1593 and sbt/sbt#1655.
case typeTree: TypeTree if typeTree.tpe != null =>
val typeSymbolCollector = new CollectTypeTraverser({
case tpe if !tpe.typeSymbol.isPackage => tpe.typeSymbol
})
typeSymbolCollector.traverse(typeTree.tpe)
val deps = typeSymbolCollector.collected.toSet
deps.foreach(addDependency)
case Template(parents, self, body) =>
traverseTrees(body)
case MacroExpansionOf(original) if inspectedOriginalTrees.add(original) =>
this.traverse(original)
case other => ()
}
super.traverse(tree)
}
}
private def extractDependenciesByMemberRef(unit: CompilationUnit): Iterator[Symbol] = {
val traverser = new ExtractDependenciesByMemberRefTraverser
traverser.traverse(unit.body)
val dependencies = traverser.dependencies
dependencies.map(enclosingTopLevelClass)
}
/** Copied straight from Scala 2.10 as it does not exist in Scala 2.9 compiler */
private final def debuglog(msg: => String): Unit = {
if (settings.debug.value)
log(msg)
}
private final class ExtractDependenciesByInheritanceTraverser extends ExtractDependenciesTraverser {
override def traverse(tree: Tree): Unit = tree match {
case Template(parents, self, body) =>
// we are using typeSymbol and not typeSymbolDirect because we want
// type aliases to be expanded
val parentTypeSymbols = parents.map(parent => parent.tpe.typeSymbol).toSet
debuglog("Parent type symbols for " + tree.pos + ": " + parentTypeSymbols.map(_.fullName))
parentTypeSymbols.foreach(addDependency)
traverseTrees(body)
case tree => super.traverse(tree)
}
}
private def extractDependenciesByInheritance(unit: CompilationUnit): Iterator[Symbol] = {
val traverser = new ExtractDependenciesByInheritanceTraverser
traverser.traverse(unit.body)
val dependencies = traverser.dependencies
dependencies.map(enclosingTopLevelClass)
}
private final def debuglog(msg: => String): Unit = if (settings.debug.value) log(msg)
/**
* We capture enclosing classes only because that's what CompilationUnit.depends does and we don't want
* to deviate from old behaviour too much for now.
*
* NOTE: for Scala 2.8 and 2.9 this method is provided through SymbolCompat
*/
private def enclosingTopLevelClass(sym: Symbol): Symbol =
// for Scala 2.8 and 2.9 this method is provided through SymbolCompat
sym.enclosingTopLevelClass
private def enclosingTopLevelClass(sym: Symbol): Symbol = sym.enclosingTopLevelClass
}

View File

@ -26,7 +26,7 @@ class DependencySpecification extends Specification {
inheritance('D) === Set.empty
memberRef('E) === Set.empty
inheritance('E) === Set.empty
memberRef('F) === Set('A, 'B, 'C, 'D, 'E)
memberRef('F) === Set('A, 'B, 'C, 'D, 'E, 'G)
inheritance('F) === Set('A, 'E)
memberRef('H) === Set('B, 'E, 'G)
// aliases and applied type constructors are expanded so we have inheritance dependency on B
@ -86,8 +86,8 @@ class DependencySpecification extends Specification {
|}""".stripMargin
val srcD = "class D[T]"
val srcE = "trait E[T]"
val srcF = "trait F extends A with E[D[B]] { self: C => }"
val srcG = "object G { type T[x] = B }"
val srcF = "trait F extends A with E[D[B]] { self: G.MyC => }"
val srcG = "object G { type T[x] = B ; type MyC = C }"
// T is a type constructor [x]B
// B extends D
// E verifies the core type gets pulled out