diff --git a/compile/interface/src/main/scala/xsbt/Dependency.scala b/compile/interface/src/main/scala/xsbt/Dependency.scala index 89e02d6d3..80d68c5ba 100644 --- a/compile/interface/src/main/scala/xsbt/Dependency.scala +++ b/compile/interface/src/main/scala/xsbt/Dependency.scala @@ -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 } diff --git a/compile/interface/src/test/scala/xsbt/DependencySpecification.scala b/compile/interface/src/test/scala/xsbt/DependencySpecification.scala index 192d0e000..db6719319 100644 --- a/compile/interface/src/test/scala/xsbt/DependencySpecification.scala +++ b/compile/interface/src/test/scala/xsbt/DependencySpecification.scala @@ -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