mirror of https://github.com/sbt/sbt.git
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:
parent
8c73b2f221
commit
0f616294c4
|
|
@ -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
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue