Skip to content

Commit e66cbcb

Browse files
committed
Include used types in the set of used names
This is a backport of sbt/zinc#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.
1 parent ce611fb commit e66cbcb

File tree

27 files changed

+169
-39
lines changed

27 files changed

+169
-39
lines changed

compile/interface/src/main/scala/xsbt/Compat.scala

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,22 @@ abstract class Compat {
5151
def transformedType(tpe: Type): Type = tpe
5252
}
5353

54+
/**
55+
* Traverses given type and collects result of applying a partial function `pf`.
56+
*
57+
* NOTE: This class exists in Scala 2.10 as CollectTypeCollector but does not in earlier
58+
* versions (like 2.9) of Scala compiler that incremental cmpiler supports so we had to
59+
* reimplement that class here.
60+
*/
61+
class CollectTypeTraverser[T](pf: PartialFunction[Type, T]) extends TypeTraverser {
62+
var collected: List[T] = Nil
63+
def traverse(tpe: Type): Unit = {
64+
if (pf.isDefinedAt(tpe))
65+
collected = pf(tpe) :: collected
66+
mapOver(tpe)
67+
}
68+
}
69+
5470
private[this] final class MiscCompat {
5571
// in 2.9, nme.LOCALCHILD was renamed to tpnme.LOCAL_CHILD
5672
def tpnme = nme

compile/interface/src/main/scala/xsbt/Dependency.scala

Lines changed: 10 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ object Dependency {
2929
* where it originates from. The Symbol->Classfile mapping is implemented by
3030
* LocateClassFile that we inherit from.
3131
*/
32-
final class Dependency(val global: CallbackGlobal) extends LocateClassFile {
32+
final class Dependency(val global: CallbackGlobal) extends LocateClassFile with GlobalHelpers {
3333
import global._
3434

3535
def newPhase(prev: Phase): Phase = new DependencyPhase(prev)
@@ -79,6 +79,12 @@ final class Dependency(val global: CallbackGlobal) extends LocateClassFile {
7979
private class ExtractDependenciesTraverser extends Traverser {
8080
private val _dependencies = collection.mutable.HashSet.empty[Symbol]
8181
protected def addDependency(dep: Symbol): Unit = if (dep ne NoSymbol) _dependencies += dep
82+
protected def addTreeDependency(tree: Tree): Unit = {
83+
addDependency(tree.symbol)
84+
if (tree.tpe != null)
85+
symbolsInType(tree.tpe).foreach(addDependency)
86+
()
87+
}
8288
def dependencies: Iterator[Symbol] = _dependencies.iterator
8389
def topLevelDependencies: Iterator[Symbol] = _dependencies.map(enclosingTopLevelClass).iterator
8490

@@ -118,11 +124,11 @@ final class Dependency(val global: CallbackGlobal) extends LocateClassFile {
118124
* this looks fishy, see this thread:
119125
* https://groups.google.com/d/topic/scala-internals/Ms9WUAtokLo/discussion
120126
*/
121-
case id: Ident => addDependency(id.symbol)
127+
case id: Ident => addTreeDependency(id)
122128
case sel @ Select(qual, _) =>
123-
traverse(qual); addDependency(sel.symbol)
129+
traverse(qual); addTreeDependency(sel)
124130
case sel @ SelectFromTypeTree(qual, _) =>
125-
traverse(qual); addDependency(sel.symbol)
131+
traverse(qual); addTreeDependency(sel)
126132

127133
case Template(parents, self, body) =>
128134
// 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 {
151157
super.traverse(m)
152158
case other => super.traverse(other)
153159
}
154-
155-
private def symbolsInType(tp: Type): Set[Symbol] = {
156-
val typeSymbolCollector =
157-
new CollectTypeTraverser({
158-
case tpe if (tpe != null) && !tpe.typeSymbolDirect.isPackage => tpe.typeSymbolDirect
159-
})
160-
161-
typeSymbolCollector.traverse(tp)
162-
typeSymbolCollector.collected.toSet
163-
}
164-
}
165-
166-
/**
167-
* Traverses given type and collects result of applying a partial function `pf`.
168-
*
169-
* NOTE: This class exists in Scala 2.10 as CollectTypeCollector but does not in earlier
170-
* versions (like 2.9) of Scala compiler that incremental cmpiler supports so we had to
171-
* reimplement that class here.
172-
*/
173-
private final class CollectTypeTraverser[T](pf: PartialFunction[Type, T]) extends TypeTraverser {
174-
var collected: List[T] = Nil
175-
def traverse(tpe: Type): Unit = {
176-
if (pf.isDefinedAt(tpe))
177-
collected = pf(tpe) :: collected
178-
mapOver(tpe)
179-
}
180160
}
181161

182162
/** Copied straight from Scala 2.10 as it does not exist in Scala 2.9 compiler */

compile/interface/src/main/scala/xsbt/ExtractUsedNames.scala

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import scala.tools.nsc._
77
*
88
* Extracts simple (unqualified) names mentioned in given in non-definition position by collecting
99
* all symbols associated with non-definition trees and extracting names from all collected symbols.
10+
* Also extract the names of the types of non-definition trees (see source-dependencies/types-in-used-names
11+
* for an example where this is required).
1012
*
1113
* If given symbol is mentioned both in definition and in non-definition position (e.g. in member
1214
* 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._
3840
* The tree walking algorithm walks into TypeTree.original explicitly.
3941
*
4042
*/
41-
class ExtractUsedNames[GlobalType <: CallbackGlobal](val global: GlobalType) extends Compat {
43+
class ExtractUsedNames[GlobalType <: CallbackGlobal](val global: GlobalType) extends Compat with GlobalHelpers {
4244
import global._
4345

4446
@inline def debug(msg: => String) = if (settings.verbose.value) inform(msg)
@@ -61,10 +63,12 @@ class ExtractUsedNames[GlobalType <: CallbackGlobal](val global: GlobalType) ext
6163
*/
6264
val inspectedOriginalTrees = collection.mutable.Set.empty[Tree]
6365

64-
def addSymbol(symbol: Symbol): Unit = {
65-
val symbolNameAsString = symbol.name.decode.trim
66-
namesBuffer += symbolNameAsString
67-
}
66+
def addSymbol(symbol: Symbol): Unit =
67+
if (eligibleAsUsedName(symbol)) {
68+
val symbolNameAsString = symbol.name.decode.trim
69+
namesBuffer += symbolNameAsString
70+
()
71+
}
6872

6973
def handleTreeNode(node: Tree): Unit = {
7074
def handleMacroExpansion(original: Tree): Unit = {
@@ -91,8 +95,10 @@ class ExtractUsedNames[GlobalType <: CallbackGlobal](val global: GlobalType) ext
9195
// not what we need
9296
case t: TypeTree if t.original != null =>
9397
t.original.foreach(handleTreeNode)
94-
case t if t.hasSymbol && eligibleAsUsedName(t.symbol) =>
98+
case t if t.hasSymbol =>
9599
addSymbol(t.symbol)
100+
if (t.tpe != null)
101+
symbolsInType(t.tpe).foreach(addSymbol)
96102
case _ => ()
97103
}
98104

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package xsbt
2+
3+
import scala.tools.nsc.Global
4+
5+
trait GlobalHelpers extends Compat {
6+
val global: CallbackGlobal
7+
import global.{ Tree, Type, Symbol, TypeTraverser }
8+
9+
def symbolsInType(tp: Type): Set[Symbol] = {
10+
val typeSymbolCollector =
11+
new CollectTypeTraverser({
12+
case tpe if (tpe != null) && !tpe.typeSymbolDirect.isPackage => tpe.typeSymbolDirect
13+
})
14+
15+
typeSymbolCollector.traverse(tp)
16+
typeSymbolCollector.collected.toSet
17+
}
18+
}

compile/interface/src/test/scala/xsbt/ExtractUsedNamesSpecification.scala

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,9 @@ class ExtractUsedNamesSpecification extends Specification {
1717
* definition.
1818
*/
1919
private val standardNames = Set(
20-
// AnyRef is added as default parent of a class
21-
"scala", "AnyRef",
20+
"scala",
21+
// The default parent of a class is "AnyRef" which is an alias for "Object"
22+
"AnyRef", "Object",
2223
// class receives a default constructor which is internally called "<init>"
2324
"<init>")
2425

@@ -69,7 +70,45 @@ class ExtractUsedNamesSpecification extends Specification {
6970
|}""".stripMargin
7071
val compilerForTesting = new ScalaCompilerForUnitTesting(nameHashing = true)
7172
val usedNames = compilerForTesting.extractUsedNamesFromSrc(srcA, srcB)
72-
val expectedNames = standardNames ++ Set("A", "a", "B", "=")
73+
val expectedNames = standardNames ++ Set("A", "a", "B", "=", "Int")
74+
usedNames === expectedNames
75+
}
76+
77+
"extract names in the types of trees" in {
78+
val src1 = """|class X0
79+
|class X1 extends X0
80+
|class Y
81+
|class A {
82+
| type T >: X1 <: X0
83+
|}
84+
|class M
85+
|class N
86+
|class P0
87+
|class P1 extends P0
88+
|object B {
89+
| type S = Y
90+
| val lista: List[A] = ???
91+
| val at: A#T = ???
92+
| val as: S = ???
93+
| def foo(m: M): N = ???
94+
| def bar[Param >: P1 <: P0](p: Param): Param = ???
95+
|}""".stripMargin
96+
val src2 = """|object Test {
97+
| val x = B.lista
98+
| val y = B.at
99+
| val z = B.as
100+
| B.foo(???)
101+
| B.bar(???)
102+
|}""".stripMargin
103+
val compilerForTesting = new ScalaCompilerForUnitTesting(nameHashing = true)
104+
val usedNames = compilerForTesting.extractUsedNamesFromSrc(src1, src2)
105+
val expectedNames = standardNames ++ Set("Test", "B", "x", "y", "z",
106+
"Predef", "???", "Nothing",
107+
"lista", "package", "List", "A",
108+
"at", "T",
109+
"as", "S",
110+
"foo", "M", "N",
111+
"bar", "Param", "P1", "P0")
73112
usedNames === expectedNames
74113
}
75114

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
abstract class A {
2+
type T
3+
object X {
4+
def foo(x: T): T = x
5+
}
6+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
class B extends A {
2+
type T = Int
3+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
object C extends B
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
object D {
2+
C.X.foo(12)
3+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
class B extends A {
2+
type T = String
3+
}

0 commit comments

Comments
 (0)