-
Notifications
You must be signed in to change notification settings - Fork 939
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement name hashing algorithm in incremental compiler.
Provide implementation of invalidation logic that takes computed name hashes into account. The implementation is spread amongst two classes: 1. `IncrementalNameHashing` which implements a variant of incremental compilation algorithm that computes modified names and delegates to `MemberReferenceInvalidationStrategy` when invalidating member reference dependencies 2. `MemberReferenceInvalidationStrategy` which implements the core logic of dealing with dependencies introduced by member reference. See documentation of that class for details. The name hashing optimization is applied when invalidating source files having both internal and external dependencies (in initial iteration), check `invalidateByExternal` and `invalidateSource` methods for details. As seen in implementation of `MemberReferenceInvalidationStrategy` the name hashing optimization is not applied when implicit members change. NOTE: All functionality introduced in this commit is enabled only when `IncOptions.nameHashing` flag is set to true. The `source-dependencies/transitive-memberRef` test has been changed to test name hashing variant of incremental compilation. The change to invalidated files reflects the difference between the old and the new algorithm. Also, there a few new tests added that cover issues previously found while testing name hashing algorithm and are fixed in this commit. Each paragraph describes a single test. Add a test case which shows that detect properly changes to type aliases in the name hashing algorithm. See gkossakowski#6 for details. Add test covering bug with use of symbolic names (issue gkossakowski#5). Add a test which covers the case where we refer to a name that is declared in the same file. See issue gkossakowski#3 for details.
- Loading branch information
1 parent
1c0553c
commit 921c903
Showing
19 changed files
with
309 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
124 changes: 124 additions & 0 deletions
124
compile/inc/src/main/scala/sbt/inc/MemberRefInvalidator.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
package sbt.inc | ||
|
||
import sbt.Relation | ||
import java.io.File | ||
import sbt.Logger | ||
import xsbt.api.APIUtil | ||
|
||
/** | ||
* Implements various strategies for invalidating dependencies introduced by member reference. | ||
* | ||
* The strategy is represented as function T => Set[File] where T is a source file that other | ||
* source files depend on. When you apply that function to given element `src` you get set of | ||
* files that depend on `src` by member reference and should be invalidated due to api change | ||
* that was passed to a method constructing that function. There are two questions that arise: | ||
* | ||
* 1. Why is signature T => Set[File] and not T => Set[T] or File => Set[File]? | ||
* 2. Why would we apply that function to any other `src` that then one that got modified | ||
* and the modification is described by APIChange? | ||
* | ||
* Let's address the second question with the following example of source code structure: | ||
* | ||
* // A.scala | ||
* class A | ||
* | ||
* // B.scala | ||
* class B extends A | ||
* | ||
* // C.scala | ||
* class C { def foo(a: A) = ??? } | ||
* | ||
* // D.scala | ||
* class D { def bar(b: B) = ??? } | ||
* | ||
* Member reference dependencies on A.scala are B.scala, C.scala. When the api of A changes | ||
* then we would consider B and C for invalidation. However, B is also a dependency by inheritance | ||
* so we always invalidate it. The api change to A is relevant when B is considered (because | ||
* of how inheritance works) so we would invalidate B by inheritance and then we would like to | ||
* invalidate member reference dependencies of B as well. In other words, we have a function | ||
* because we want to apply it (with the same api change in mind) to all src files invalidated | ||
* by inheritance of the originally modified file. | ||
* | ||
* The first question is a bit more straightforward to answer. We always invalidate internal | ||
* source files (in given project) that are represented as File but they might depend either on | ||
* internal source files (then T=File) or they can depend on external class name (then T=String). | ||
* | ||
* The specific invalidation strategy is determined based on APIChange that describes a change to api | ||
* of a single source file. | ||
* | ||
* For example, if we get APIChangeDueToMacroDefinition then we invalidate all member reference | ||
* dependencies unconditionally. On the other hand, if api change is due to modified name hashes | ||
* of regular members then we'll invalidate sources that use those names. | ||
*/ | ||
private[inc] class MemberRefInvalidator(log: Logger) { | ||
def get[T](memberRef: Relation[File, T], usedNames: Relation[File, String], apiChange: APIChange[_]): | ||
T => Set[File] = apiChange match { | ||
case _: APIChangeDueToMacroDefinition[_] => | ||
new InvalidateUnconditionally(memberRef) | ||
case NamesChange(_, modifiedNames) if !modifiedNames.implicitNames.isEmpty => | ||
new InvalidateUnconditionally(memberRef) | ||
case NamesChange(modifiedSrcFile, modifiedNames) => | ||
new NameHashFilteredInvalidator[T](usedNames, memberRef, modifiedNames.regularNames) | ||
case _: SourceAPIChange[_] => | ||
sys.error(wrongAPIChangeMsg) | ||
} | ||
|
||
def invalidationReason(apiChange: APIChange[_]): String = apiChange match { | ||
case APIChangeDueToMacroDefinition(modifiedSrcFile) => | ||
s"The $modifiedSrcFile source file declares a macro." | ||
case NamesChange(modifiedSrcFile, modifiedNames) if !modifiedNames.implicitNames.isEmpty => | ||
s"""|The $modifiedSrcFile source file has the following implicit definitions changed: | ||
|\t${modifiedNames.implicitNames.mkString(", ")}.""".stripMargin | ||
case NamesChange(modifiedSrcFile, modifiedNames) => | ||
s"""|The $modifiedSrcFile source file has the following regular definitions changed: | ||
|\t${modifiedNames.regularNames.mkString(", ")}.""".stripMargin | ||
case _: SourceAPIChange[_] => | ||
sys.error(wrongAPIChangeMsg) | ||
} | ||
|
||
private val wrongAPIChangeMsg = | ||
"MemberReferenceInvalidator.get should be called when name hashing is enabled " + | ||
"and in that case we shouldn't have SourceAPIChange as an api change." | ||
|
||
private class InvalidateUnconditionally[T](memberRef: Relation[File, T]) extends (T => Set[File]) { | ||
def apply(from: T): Set[File] = { | ||
val invalidated = memberRef.reverse(from) | ||
if (!invalidated.isEmpty) | ||
log.debug(s"The following member ref dependencies of $from are invalidated:\n" + | ||
formatInvalidated(invalidated)) | ||
invalidated | ||
} | ||
private def formatInvalidated(invalidated: Set[File]): String = { | ||
val sortedFiles = invalidated.toSeq.sortBy(_.getAbsolutePath) | ||
sortedFiles.map(file => "\t"+file).mkString("\n") | ||
} | ||
} | ||
|
||
private class NameHashFilteredInvalidator[T]( | ||
usedNames: Relation[File, String], | ||
memberRef: Relation[File, T], | ||
modifiedNames: Set[String]) extends (T => Set[File]) { | ||
|
||
def apply(to: T): Set[File] = { | ||
val dependent = memberRef.reverse(to) | ||
filteredDependencies(dependent) | ||
} | ||
private def filteredDependencies(dependent: Set[File]): Set[File] = { | ||
dependent.filter { | ||
case from if APIUtil.isScalaSourceName(from.getName) => | ||
val usedNamesInDependent = usedNames.forward(from) | ||
val modifiedAndUsedNames = modifiedNames intersect usedNamesInDependent | ||
if (modifiedAndUsedNames.isEmpty) { | ||
log.debug(s"None of the modified names appears in $from. This dependency is not being considered for invalidation.") | ||
false | ||
} else { | ||
log.debug(s"The following modified names cause invalidation of $from: $modifiedAndUsedNames") | ||
true | ||
} | ||
case from => | ||
log.debug(s"Name hashing optimization doesn't apply to non-Scala dependency: $from") | ||
true | ||
} | ||
} | ||
} | ||
} |
3 changes: 3 additions & 0 deletions
3
sbt/src/sbt-test/source-dependencies/backtick-quoted-names/A.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
object A { | ||
def `=` = 3 | ||
} |
3 changes: 3 additions & 0 deletions
3
sbt/src/sbt-test/source-dependencies/backtick-quoted-names/B.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
object B extends App { | ||
println(A.`=`) | ||
} |
1 change: 1 addition & 0 deletions
1
sbt/src/sbt-test/source-dependencies/backtick-quoted-names/build.sbt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
incOptions := incOptions.value.withNameHashing(true) |
3 changes: 3 additions & 0 deletions
3
sbt/src/sbt-test/source-dependencies/backtick-quoted-names/changes/A.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
object A { | ||
def asdf = 3 | ||
} |
7 changes: 7 additions & 0 deletions
7
sbt/src/sbt-test/source-dependencies/backtick-quoted-names/test
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
> compile | ||
|
||
# rename def with symbolic name (`=`) | ||
$ copy-file changes/A.scala A.scala | ||
|
||
# Both A.scala and B.scala should be recompiled, producing a compile error | ||
-> compile |
8 changes: 8 additions & 0 deletions
8
sbt/src/sbt-test/source-dependencies/same-file-used-names/A.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
object A { | ||
def x = 3 | ||
|
||
def y = { | ||
import B._ | ||
x | ||
} | ||
} |
3 changes: 3 additions & 0 deletions
3
sbt/src/sbt-test/source-dependencies/same-file-used-names/B.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
object B { | ||
// def x = 3 | ||
} |
1 change: 1 addition & 0 deletions
1
sbt/src/sbt-test/source-dependencies/same-file-used-names/build.sbt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
incOptions := incOptions.value.withNameHashing(true) |
3 changes: 3 additions & 0 deletions
3
sbt/src/sbt-test/source-dependencies/same-file-used-names/changes/B.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
object B { | ||
def x = 3 | ||
} |
7 changes: 7 additions & 0 deletions
7
sbt/src/sbt-test/source-dependencies/same-file-used-names/test
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
> compile | ||
|
||
# uncomment definition of `x` that leads to ambiguity error in A | ||
$ copy-file changes/B.scala B.scala | ||
|
||
# Both A.scala and B.scala should be recompiled, producing a compile error | ||
-> compile |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
object A { | ||
type X = Option[Int] | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
object B { | ||
def y: A.X = Option(3) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
logLevel in compile := Level.Debug | ||
|
||
incOptions := incOptions.value.withNameHashing(true) |
3 changes: 3 additions & 0 deletions
3
sbt/src/sbt-test/source-dependencies/type-alias/changes/A.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
object A { | ||
type X = Int | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
> compile | ||
|
||
# change type alias | ||
$ copy-file changes/A.scala A.scala | ||
|
||
# Both A.scala and B.scala should be recompiled, producing a compile error | ||
-> compile |