Skip to content

Commit b7887e7

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 `source-dependencies/types-in-used-names`, 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`).
1 parent 9d666c5 commit b7887e7

File tree

11 files changed

+111
-39
lines changed

11 files changed

+111
-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
@@ -149,32 +155,6 @@ final class Dependency(val global: CallbackGlobal) extends LocateClassFile {
149155
case MacroExpansionOf(original) if inspectedOriginalTrees.add(original) => traverse(original)
150156
case other => super.traverse(other)
151157
}
152-
153-
private def symbolsInType(tp: Type): Set[Symbol] = {
154-
val typeSymbolCollector =
155-
new CollectTypeTraverser({
156-
case tpe if (tpe != null) && !tpe.typeSymbolDirect.isPackage => tpe.typeSymbolDirect
157-
})
158-
159-
typeSymbolCollector.traverse(tp)
160-
typeSymbolCollector.collected.toSet
161-
}
162-
}
163-
164-
/**
165-
* Traverses given type and collects result of applying a partial function `pf`.
166-
*
167-
* NOTE: This class exists in Scala 2.10 as CollectTypeCollector but does not in earlier
168-
* versions (like 2.9) of Scala compiler that incremental cmpiler supports so we had to
169-
* reimplement that class here.
170-
*/
171-
private final class CollectTypeTraverser[T](pf: PartialFunction[Type, T]) extends TypeTraverser {
172-
var collected: List[T] = Nil
173-
def traverse(tpe: Type): Unit = {
174-
if (pf.isDefinedAt(tpe))
175-
collected = pf(tpe) :: collected
176-
mapOver(tpe)
177-
}
178158
}
179159

180160
/** 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
def extract(unit: CompilationUnit): Set[String] = {
@@ -59,10 +61,12 @@ class ExtractUsedNames[GlobalType <: CallbackGlobal](val global: GlobalType) ext
5961
*/
6062
val inspectedOriginalTrees = collection.mutable.Set.empty[Tree]
6163

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

6771
def handleTreeNode(node: Tree): Unit = {
6872
def handleMacroExpansion(original: Tree): Unit = {
@@ -89,8 +93,10 @@ class ExtractUsedNames[GlobalType <: CallbackGlobal](val global: GlobalType) ext
8993
// not what we need
9094
case t: TypeTree if t.original != null =>
9195
t.original.foreach(handleTreeNode)
92-
case t if t.hasSymbol && eligibleAsUsedName(t.symbol) =>
96+
case t if t.hasSymbol =>
9397
addSymbol(t.symbol)
98+
if (t.tpe != null)
99+
symbolsInType(t.tpe).foreach(addSymbol)
94100
case _ => ()
95101
}
96102

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: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
class A
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
class B extends A
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
object C {
2+
val listb: List[B] = List(new B)
3+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
object D {
2+
val lista: List[A] = C.listb
3+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
class B

0 commit comments

Comments
 (0)