mirror of https://github.com/sbt/sbt.git
Include used types in the set of used names
This is a backport of https://github.com/sbt/zinc/pull/87 When `B2.scala` replaces `B.scala` in the new test `types-in-used-names-a`, the name hash of `listb` does not change because the signature of `C.listb` is still `List[B]`, however users of `C.listb` have to be recompiled since the subtyping relationships of its type have changed. This commit does this by extending the definition of "used names" to also include the names of the types of trees, even if these types do not appear in the source like `List[B]` in `D.scala` (since `B` has been invalidated, this will force the recompilation of `D.scala`). This commit does not fix every issue with used types as illustrated by the pending test `types-in-used-names-b`, `B.scala` is not recompiled because it uses the type `T` whose hash has not changed, but `T` is bounded by `S` and `S` has changed, so it should be recompiled. This should be fixable by including the type bounds underlying a `TypeRef` in `symbolsInType`. The test `as-seen-from-a` that did not work before shows that we may not have to worry about tracking prefixes in `ExtractAPI` anymore, see the discussion in sbt/zinc#87 for more information.
This commit is contained in:
parent
ce611fbdca
commit
cae569b313
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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 _ => ()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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 "<init>"
|
||||
"<init>")
|
||||
|
||||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
abstract class A {
|
||||
type T
|
||||
object X {
|
||||
def foo(x: T): T = x
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
class B extends A {
|
||||
type T = Int
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
object C extends B
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
object D {
|
||||
C.X.foo(12)
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
class B extends A {
|
||||
type T = String
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
> compile
|
||||
$ copy-file changes/B2.scala B.scala
|
||||
-> compile
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
abstract class A {
|
||||
type T <: S
|
||||
type S
|
||||
object X {
|
||||
def foo: T = null.asInstanceOf[T]
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
class B extends A {
|
||||
type S <: Int
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
object C extends B
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
object D {
|
||||
val x: Int = C.X.foo
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
class B extends A {
|
||||
type S <: String
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
> compile
|
||||
$ copy-file changes/B2.scala B.scala
|
||||
-> compile
|
||||
|
|
@ -0,0 +1 @@
|
|||
class A
|
||||
|
|
@ -0,0 +1 @@
|
|||
class B extends A
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
object C {
|
||||
val listb: List[B] = List(new B)
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
object D {
|
||||
val lista: List[A] = C.listb
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
class B
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
class A {
|
||||
type T <: S
|
||||
type S <: Int
|
||||
def foo: T = null.asInstanceOf[T]
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
object B {
|
||||
val x: Int = (new A).foo
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
class A {
|
||||
type T <: S
|
||||
type S <: String
|
||||
def foo: T = null.asInstanceOf[T]
|
||||
}
|
||||
|
|
@ -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
|
||||
Loading…
Reference in New Issue