diff --git a/compile/interface/src/main/scala/xsbt/Compat.scala b/compile/interface/src/main/scala/xsbt/Compat.scala index a4859275d..bbf5b9a41 100644 --- a/compile/interface/src/main/scala/xsbt/Compat.scala +++ b/compile/interface/src/main/scala/xsbt/Compat.scala @@ -51,6 +51,22 @@ abstract class Compat { def transformedType(tpe: Type): Type = tpe } + /** + * Traverses given type and collects result of applying a partial function `pf`. + * + * NOTE: This class exists in Scala 2.10 as CollectTypeCollector but does not in earlier + * versions (like 2.9) of Scala compiler that incremental cmpiler supports so we had to + * reimplement that class here. + */ + class CollectTypeTraverser[T](pf: PartialFunction[Type, T]) extends TypeTraverser { + var collected: List[T] = Nil + def traverse(tpe: Type): Unit = { + if (pf.isDefinedAt(tpe)) + collected = pf(tpe) :: collected + mapOver(tpe) + } + } + private[this] final class MiscCompat { // in 2.9, nme.LOCALCHILD was renamed to tpnme.LOCAL_CHILD def tpnme = nme diff --git a/compile/interface/src/main/scala/xsbt/Dependency.scala b/compile/interface/src/main/scala/xsbt/Dependency.scala index b10975ec8..34c237bb3 100644 --- a/compile/interface/src/main/scala/xsbt/Dependency.scala +++ b/compile/interface/src/main/scala/xsbt/Dependency.scala @@ -29,7 +29,7 @@ object Dependency { * where it originates from. The Symbol->Classfile mapping is implemented by * LocateClassFile that we inherit from. */ -final class Dependency(val global: CallbackGlobal) extends LocateClassFile { +final class Dependency(val global: CallbackGlobal) extends LocateClassFile with GlobalHelpers { import global._ def newPhase(prev: Phase): Phase = new DependencyPhase(prev) @@ -79,6 +79,12 @@ 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 + protected def addTreeDependency(tree: Tree): Unit = { + addDependency(tree.symbol) + if (tree.tpe != null) + symbolsInType(tree.tpe).foreach(addDependency) + () + } def dependencies: Iterator[Symbol] = _dependencies.iterator def topLevelDependencies: Iterator[Symbol] = _dependencies.map(enclosingTopLevelClass).iterator @@ -118,11 +124,11 @@ final class Dependency(val global: CallbackGlobal) extends LocateClassFile { * this looks fishy, see this thread: * https://groups.google.com/d/topic/scala-internals/Ms9WUAtokLo/discussion */ - case id: Ident => addDependency(id.symbol) + case id: Ident => addTreeDependency(id) case sel @ Select(qual, _) => - traverse(qual); addDependency(sel.symbol) + traverse(qual); addTreeDependency(sel) case sel @ SelectFromTypeTree(qual, _) => - traverse(qual); addDependency(sel.symbol) + traverse(qual); addTreeDependency(sel) 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 @@ -151,32 +157,6 @@ final class Dependency(val global: CallbackGlobal) extends LocateClassFile { super.traverse(m) 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`. - * - * NOTE: This class exists in Scala 2.10 as CollectTypeCollector but does not in earlier - * versions (like 2.9) of Scala compiler that incremental cmpiler supports so we had to - * reimplement that class here. - */ - private final class CollectTypeTraverser[T](pf: PartialFunction[Type, T]) extends TypeTraverser { - var collected: List[T] = Nil - def traverse(tpe: Type): Unit = { - if (pf.isDefinedAt(tpe)) - collected = pf(tpe) :: collected - mapOver(tpe) - } } /** Copied straight from Scala 2.10 as it does not exist in Scala 2.9 compiler */ diff --git a/compile/interface/src/main/scala/xsbt/ExtractUsedNames.scala b/compile/interface/src/main/scala/xsbt/ExtractUsedNames.scala index 281131736..56e55cc20 100644 --- a/compile/interface/src/main/scala/xsbt/ExtractUsedNames.scala +++ b/compile/interface/src/main/scala/xsbt/ExtractUsedNames.scala @@ -7,6 +7,8 @@ import scala.tools.nsc._ * * 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. + * Also extract the names of the types of non-definition trees (see source-dependencies/types-in-used-names + * for an example where this is required). * * 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 @@ -38,7 +40,7 @@ import scala.tools.nsc._ * The tree walking algorithm walks into TypeTree.original explicitly. * */ -class ExtractUsedNames[GlobalType <: CallbackGlobal](val global: GlobalType) extends Compat { +class ExtractUsedNames[GlobalType <: CallbackGlobal](val global: GlobalType) extends Compat with GlobalHelpers { import global._ @inline def debug(msg: => String) = if (settings.verbose.value) inform(msg) @@ -61,10 +63,12 @@ class ExtractUsedNames[GlobalType <: CallbackGlobal](val global: GlobalType) ext */ val inspectedOriginalTrees = collection.mutable.Set.empty[Tree] - def addSymbol(symbol: Symbol): Unit = { - val symbolNameAsString = symbol.name.decode.trim - namesBuffer += symbolNameAsString - } + def addSymbol(symbol: Symbol): Unit = + if (eligibleAsUsedName(symbol)) { + val symbolNameAsString = symbol.name.decode.trim + namesBuffer += symbolNameAsString + () + } def handleTreeNode(node: Tree): Unit = { def handleMacroExpansion(original: Tree): Unit = { @@ -91,8 +95,10 @@ class ExtractUsedNames[GlobalType <: CallbackGlobal](val global: GlobalType) ext // not what we need case t: TypeTree if t.original != null => t.original.foreach(handleTreeNode) - case t if t.hasSymbol && eligibleAsUsedName(t.symbol) => + case t if t.hasSymbol => addSymbol(t.symbol) + if (t.tpe != null) + symbolsInType(t.tpe).foreach(addSymbol) case _ => () } diff --git a/compile/interface/src/main/scala/xsbt/GlobalHelpers.scala b/compile/interface/src/main/scala/xsbt/GlobalHelpers.scala new file mode 100644 index 000000000..46f0ec5eb --- /dev/null +++ b/compile/interface/src/main/scala/xsbt/GlobalHelpers.scala @@ -0,0 +1,18 @@ +package xsbt + +import scala.tools.nsc.Global + +trait GlobalHelpers extends Compat { + val global: CallbackGlobal + import global.{ Tree, Type, Symbol, TypeTraverser } + + 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 + } +} diff --git a/compile/interface/src/test/scala/xsbt/ExtractUsedNamesSpecification.scala b/compile/interface/src/test/scala/xsbt/ExtractUsedNamesSpecification.scala index e9dcbf49e..00efc1b6f 100644 --- a/compile/interface/src/test/scala/xsbt/ExtractUsedNamesSpecification.scala +++ b/compile/interface/src/test/scala/xsbt/ExtractUsedNamesSpecification.scala @@ -17,8 +17,9 @@ class ExtractUsedNamesSpecification extends Specification { * definition. */ private val standardNames = Set( - // AnyRef is added as default parent of a class - "scala", "AnyRef", + "scala", + // The default parent of a class is "AnyRef" which is an alias for "Object" + "AnyRef", "Object", // class receives a default constructor which is internally called "" "") @@ -69,7 +70,45 @@ class ExtractUsedNamesSpecification extends Specification { |}""".stripMargin val compilerForTesting = new ScalaCompilerForUnitTesting(nameHashing = true) val usedNames = compilerForTesting.extractUsedNamesFromSrc(srcA, srcB) - val expectedNames = standardNames ++ Set("A", "a", "B", "=") + val expectedNames = standardNames ++ Set("A", "a", "B", "=", "Int") + usedNames === expectedNames + } + + "extract names in the types of trees" in { + val src1 = """|class X0 + |class X1 extends X0 + |class Y + |class A { + | type T >: X1 <: X0 + |} + |class M + |class N + |class P0 + |class P1 extends P0 + |object B { + | type S = Y + | val lista: List[A] = ??? + | val at: A#T = ??? + | val as: S = ??? + | def foo(m: M): N = ??? + | def bar[Param >: P1 <: P0](p: Param): Param = ??? + |}""".stripMargin + val src2 = """|object Test { + | val x = B.lista + | val y = B.at + | val z = B.as + | B.foo(???) + | B.bar(???) + |}""".stripMargin + val compilerForTesting = new ScalaCompilerForUnitTesting(nameHashing = true) + val usedNames = compilerForTesting.extractUsedNamesFromSrc(src1, src2) + val expectedNames = standardNames ++ Set("Test", "B", "x", "y", "z", + "Predef", "???", "Nothing", + "lista", "package", "List", "A", + "at", "T", + "as", "S", + "foo", "M", "N", + "bar", "Param", "P1", "P0") usedNames === expectedNames } diff --git a/sbt/src/sbt-test/source-dependencies/as-seen-from-a/A.scala b/sbt/src/sbt-test/source-dependencies/as-seen-from-a/A.scala new file mode 100644 index 000000000..fe6a784a4 --- /dev/null +++ b/sbt/src/sbt-test/source-dependencies/as-seen-from-a/A.scala @@ -0,0 +1,6 @@ +abstract class A { + type T + object X { + def foo(x: T): T = x + } +} diff --git a/sbt/src/sbt-test/source-dependencies/as-seen-from-a/B.scala b/sbt/src/sbt-test/source-dependencies/as-seen-from-a/B.scala new file mode 100644 index 000000000..60059966f --- /dev/null +++ b/sbt/src/sbt-test/source-dependencies/as-seen-from-a/B.scala @@ -0,0 +1,3 @@ +class B extends A { + type T = Int +} diff --git a/sbt/src/sbt-test/source-dependencies/as-seen-from-a/C.scala b/sbt/src/sbt-test/source-dependencies/as-seen-from-a/C.scala new file mode 100644 index 000000000..3753dbeec --- /dev/null +++ b/sbt/src/sbt-test/source-dependencies/as-seen-from-a/C.scala @@ -0,0 +1 @@ +object C extends B diff --git a/sbt/src/sbt-test/source-dependencies/as-seen-from-a/D.scala b/sbt/src/sbt-test/source-dependencies/as-seen-from-a/D.scala new file mode 100644 index 000000000..ace241491 --- /dev/null +++ b/sbt/src/sbt-test/source-dependencies/as-seen-from-a/D.scala @@ -0,0 +1,3 @@ +object D { + C.X.foo(12) +} diff --git a/sbt/src/sbt-test/source-dependencies/as-seen-from-a/changes/B2.scala b/sbt/src/sbt-test/source-dependencies/as-seen-from-a/changes/B2.scala new file mode 100644 index 000000000..18f3eefdd --- /dev/null +++ b/sbt/src/sbt-test/source-dependencies/as-seen-from-a/changes/B2.scala @@ -0,0 +1,3 @@ +class B extends A { + type T = String +} diff --git a/sbt/src/sbt-test/source-dependencies/as-seen-from-a/test b/sbt/src/sbt-test/source-dependencies/as-seen-from-a/test new file mode 100644 index 000000000..176a86399 --- /dev/null +++ b/sbt/src/sbt-test/source-dependencies/as-seen-from-a/test @@ -0,0 +1,3 @@ +> compile +$ copy-file changes/B2.scala B.scala +-> compile diff --git a/sbt/src/sbt-test/source-dependencies/as-seen-from-b/build.sbt b/sbt/src/sbt-test/source-dependencies/as-seen-from-b/build.sbt new file mode 100644 index 000000000..7333e8428 --- /dev/null +++ b/sbt/src/sbt-test/source-dependencies/as-seen-from-b/build.sbt @@ -0,0 +1,4 @@ +lazy val root = (project in file(".")). + settings( + scalaVersion := "2.11.8" + ) diff --git a/sbt/src/sbt-test/source-dependencies/as-seen-from-b/changes/A1.scala b/sbt/src/sbt-test/source-dependencies/as-seen-from-b/changes/A1.scala new file mode 100644 index 000000000..f4ae0b78d --- /dev/null +++ b/sbt/src/sbt-test/source-dependencies/as-seen-from-b/changes/A1.scala @@ -0,0 +1,7 @@ +abstract class A { + type T <: S + type S + object X { + def foo: T = null.asInstanceOf[T] + } +} diff --git a/sbt/src/sbt-test/source-dependencies/as-seen-from-b/changes/B1.scala b/sbt/src/sbt-test/source-dependencies/as-seen-from-b/changes/B1.scala new file mode 100644 index 000000000..f97007480 --- /dev/null +++ b/sbt/src/sbt-test/source-dependencies/as-seen-from-b/changes/B1.scala @@ -0,0 +1,3 @@ +class B extends A { + type S <: Int +} diff --git a/sbt/src/sbt-test/source-dependencies/as-seen-from-b/changes/B2.scala b/sbt/src/sbt-test/source-dependencies/as-seen-from-b/changes/B2.scala new file mode 100644 index 000000000..f8fa3ad03 --- /dev/null +++ b/sbt/src/sbt-test/source-dependencies/as-seen-from-b/changes/B2.scala @@ -0,0 +1,3 @@ +class B extends A { + type S <: String +} diff --git a/sbt/src/sbt-test/source-dependencies/as-seen-from-b/changes/C1.scala b/sbt/src/sbt-test/source-dependencies/as-seen-from-b/changes/C1.scala new file mode 100644 index 000000000..3753dbeec --- /dev/null +++ b/sbt/src/sbt-test/source-dependencies/as-seen-from-b/changes/C1.scala @@ -0,0 +1 @@ +object C extends B diff --git a/sbt/src/sbt-test/source-dependencies/as-seen-from-b/changes/D1.scala b/sbt/src/sbt-test/source-dependencies/as-seen-from-b/changes/D1.scala new file mode 100644 index 000000000..ec333ca60 --- /dev/null +++ b/sbt/src/sbt-test/source-dependencies/as-seen-from-b/changes/D1.scala @@ -0,0 +1,3 @@ +object D { + val x: Int = C.X.foo +} diff --git a/sbt/src/sbt-test/source-dependencies/as-seen-from-b/disabled b/sbt/src/sbt-test/source-dependencies/as-seen-from-b/disabled new file mode 100644 index 000000000..0b07aad6a --- /dev/null +++ b/sbt/src/sbt-test/source-dependencies/as-seen-from-b/disabled @@ -0,0 +1,22 @@ +$ copy-file changes/A1.scala A.scala + +> compile + +$ copy-file changes/B1.scala B.scala + +> compile + + +$ copy-file changes/C1.scala C.scala + +> compile + + +$ copy-file changes/D1.scala D.scala + +> compile + +$ copy-file changes/B2.scala B.scala +$ sleep 1000 + +-> compile diff --git a/sbt/src/sbt-test/source-dependencies/types-in-used-names-a/A.scala b/sbt/src/sbt-test/source-dependencies/types-in-used-names-a/A.scala new file mode 100644 index 000000000..83d15dc73 --- /dev/null +++ b/sbt/src/sbt-test/source-dependencies/types-in-used-names-a/A.scala @@ -0,0 +1 @@ +class A diff --git a/sbt/src/sbt-test/source-dependencies/types-in-used-names-a/B.scala b/sbt/src/sbt-test/source-dependencies/types-in-used-names-a/B.scala new file mode 100644 index 000000000..a18aec3db --- /dev/null +++ b/sbt/src/sbt-test/source-dependencies/types-in-used-names-a/B.scala @@ -0,0 +1 @@ +class B extends A diff --git a/sbt/src/sbt-test/source-dependencies/types-in-used-names-a/C.scala b/sbt/src/sbt-test/source-dependencies/types-in-used-names-a/C.scala new file mode 100644 index 000000000..d8c306253 --- /dev/null +++ b/sbt/src/sbt-test/source-dependencies/types-in-used-names-a/C.scala @@ -0,0 +1,3 @@ +object C { + val listb: List[B] = List(new B) +} diff --git a/sbt/src/sbt-test/source-dependencies/types-in-used-names-a/D.scala b/sbt/src/sbt-test/source-dependencies/types-in-used-names-a/D.scala new file mode 100644 index 000000000..fa8f13a1b --- /dev/null +++ b/sbt/src/sbt-test/source-dependencies/types-in-used-names-a/D.scala @@ -0,0 +1,3 @@ +object D { + val lista: List[A] = C.listb +} diff --git a/sbt/src/sbt-test/source-dependencies/types-in-used-names-a/changes/B2.scala b/sbt/src/sbt-test/source-dependencies/types-in-used-names-a/changes/B2.scala new file mode 100644 index 000000000..179f0d275 --- /dev/null +++ b/sbt/src/sbt-test/source-dependencies/types-in-used-names-a/changes/B2.scala @@ -0,0 +1 @@ +class B diff --git a/sbt/src/sbt-test/source-dependencies/types-in-used-names-a/test b/sbt/src/sbt-test/source-dependencies/types-in-used-names-a/test new file mode 100644 index 000000000..134321021 --- /dev/null +++ b/sbt/src/sbt-test/source-dependencies/types-in-used-names-a/test @@ -0,0 +1,4 @@ +> compile +$ copy-file changes/B2.scala B.scala +# Compilation of D.scala should fail because B is no longer a subtype of A +-> compile diff --git a/sbt/src/sbt-test/source-dependencies/types-in-used-names-b/A.scala b/sbt/src/sbt-test/source-dependencies/types-in-used-names-b/A.scala new file mode 100644 index 000000000..301b546cb --- /dev/null +++ b/sbt/src/sbt-test/source-dependencies/types-in-used-names-b/A.scala @@ -0,0 +1,5 @@ +class A { + type T <: S + type S <: Int + def foo: T = null.asInstanceOf[T] +} diff --git a/sbt/src/sbt-test/source-dependencies/types-in-used-names-b/B.scala b/sbt/src/sbt-test/source-dependencies/types-in-used-names-b/B.scala new file mode 100644 index 000000000..edfe2e6a8 --- /dev/null +++ b/sbt/src/sbt-test/source-dependencies/types-in-used-names-b/B.scala @@ -0,0 +1,3 @@ +object B { + val x: Int = (new A).foo +} diff --git a/sbt/src/sbt-test/source-dependencies/types-in-used-names-b/changes/A2.scala b/sbt/src/sbt-test/source-dependencies/types-in-used-names-b/changes/A2.scala new file mode 100644 index 000000000..bbe60f206 --- /dev/null +++ b/sbt/src/sbt-test/source-dependencies/types-in-used-names-b/changes/A2.scala @@ -0,0 +1,5 @@ +class A { + type T <: S + type S <: String + def foo: T = null.asInstanceOf[T] +} diff --git a/sbt/src/sbt-test/source-dependencies/types-in-used-names-b/disabled b/sbt/src/sbt-test/source-dependencies/types-in-used-names-b/disabled new file mode 100644 index 000000000..b6cce2d46 --- /dev/null +++ b/sbt/src/sbt-test/source-dependencies/types-in-used-names-b/disabled @@ -0,0 +1,5 @@ +> compile +$ copy-file changes/A2.scala A.scala +# Same workaround as #2565 +# Compilation of B.scala should fail because A#S is no longer a subtype of Int +-> compile