Skip to content

Commit

Permalink
Fixes undercompilation on local inheritance
Browse files Browse the repository at this point in the history
In sbt 0.13 days, we could ignore the relationship between two classes defined in the same `*.scala` source file, because they will be compiled anyway, and the invalidation was done at the source file level. With class-based namehashing, the invalidation is done at the class level, so we can no longer ignore inheritance relationship coming from the same source, but we still have old assumptions scattered around the xsbt-dependency implementation.

This change changes two if expressions that was used to filter out dependency info coming from the same source.

One might wonder why it's necessary to keep the local inheritance info, if two classes involved are compiled together anyways. The answer is transitive dependencies. Using trait-trait-211 as example, `gg.table.ObjectConcatHelpers` was changed, eventually causing `xx.EvaluatorTestSupport` to invalidate. However, because of the missing same-source inheritance, it did not invalidate `xx.EvaluatorSpecification`. This meant that neither `xx.StringLibSpecs` was invalidated, and resulting to a runtime error.

Fixes #417
  • Loading branch information
eed3si9n committed Oct 9, 2017
1 parent 30c9bb4 commit b4078d0
Show file tree
Hide file tree
Showing 12 changed files with 102 additions and 36 deletions.
21 changes: 13 additions & 8 deletions internal/compiler-bridge/src/main/scala/xsbt/Dependency.scala
Original file line number Diff line number Diff line change
Expand Up @@ -92,16 +92,21 @@ final class Dependency(val global: CallbackGlobal) extends LocateClassFile with
}

// Define processor reusing `processDependency` definition
val memberRef = processDependency(DependencyByMemberRef) _
val inheritance = processDependency(DependencyByInheritance) _
val localInheritance = processDependency(LocalDependencyByInheritance) _
val memberRef = processDependency(DependencyByMemberRef, false) _
val inheritance = processDependency(DependencyByInheritance, true) _
val localInheritance = processDependency(LocalDependencyByInheritance, true) _

@deprecated("Use processDependency that takes allowLocal.", "1.1.0")
def processDependency(context: DependencyContext)(dep: ClassDependency): Unit =
processDependency(context, true)(dep)

/*
* Handles dependency on given symbol by trying to figure out if represents a term
* that is coming from either source code (not necessarily compiled in this compilation
* run) or from class file and calls respective callback method.
*/
def processDependency(context: DependencyContext)(dep: ClassDependency): Unit = {
def processDependency(context: DependencyContext, allowLocal: Boolean)(
dep: ClassDependency): Unit = {
val fromClassName = classNameAsString(dep.from)

def binaryDependency(file: File, binaryClassName: String) =
Expand Down Expand Up @@ -133,11 +138,12 @@ final class Dependency(val global: CallbackGlobal) extends LocateClassFile with
case None =>
debuglog(Feedback.noOriginFileForExternalSymbol(dep.to))
}
} else if (onSource.file != sourceFile) {
// Dependency is internal -- but from other file / compilation unit
} else if (onSource.file != sourceFile || allowLocal) {
// We cannot ignore dependencies coming from the same source file because
// the dependency info needs to propagate. See source-dependencies/trait-trait-211.
val onClassName = classNameAsString(dep.to)
callback.classDependency(onClassName, fromClassName, context)
} else () // Comes from the same file, ignore
}
}
}

Expand Down Expand Up @@ -227,7 +233,6 @@ final class Dependency(val global: CallbackGlobal) extends LocateClassFile with
val depClass = enclOrModuleClass(dep)
val dependency = ClassDependency(fromClass, depClass)
if (!cache.contains(dependency) &&
fromClass.associatedFile != depClass.associatedFile &&
!depClass.isRefinementClass) {
process(dependency)
cache.add(dependency)
Expand Down
33 changes: 15 additions & 18 deletions internal/zinc-core/src/main/scala/sbt/internal/inc/Relations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,7 @@ object Relations {
case o: ClassDependencies => internal == o.internal && external == o.external
case _ => false
}
override def toString: String = s"ClassDependencies(internal = $internal, external = $external)"

override def hashCode = (internal, external).hashCode
}
Expand Down Expand Up @@ -662,25 +663,21 @@ private class MRelationsNameHashing(
(srcProd :: libraryDep :: libraryClassName :: memberRef :: inheritance :: classes :: Nil).hashCode

override def toString = (
"""
s"""
|Relations (with name hashing enabled):
| products: %s
| library deps: %s
| library class names: %s
| class deps: %s
| ext deps: %s
| class names: %s
| used names: %s
| product class names: %s
""".trim.stripMargin.format(
List(srcProd,
libraryDep,
libraryClassName,
internalClassDep,
externalClassDep,
classes,
names,
productClassName) map relation_s: _*)
| products: ${relation_s(srcProd)}
| library deps: ${relation_s(libraryDep)}
| library class names: ${relation_s(libraryClassName)}
| internalDependencies: ${internalDependencies.dependencies map {
case (k, vs) => k + " " + relation_s(vs)
}}
| externalDependencies: ${externalDependencies.dependencies map {
case (k, vs) => k + " " + relation_s(vs)
}}
| class names: ${relation_s(classes)}
| used names: ${relation_s(names)}
| product class names: ${relation_s(productClassName)}
""".trim.stripMargin
)

}
Original file line number Diff line number Diff line change
Expand Up @@ -156,8 +156,8 @@ final class IncHandler(directory: File, cacheDir: File, scriptedLog: ManagedLogg
val analysis = p.compile(i)
p.discoverMainClasses(Some(analysis.apis)) match {
case Seq(mainClassName) =>
val classpath = i.si.allJars :+ p.classesDir
val loader = ClasspathUtilities.makeLoader(classpath, i.si, directory)
val cp = p.classpath(i)
val loader = ClasspathUtilities.makeLoader(cp, i.si, directory)
val main = p.getMainMethod(mainClassName, loader)
p.invokeMain(loader, main, params)
case _ =>
Expand Down Expand Up @@ -343,6 +343,10 @@ case class ProjectStructure(
()
}

def classpath(i: IncInstance): Array[File] = {
(i.si.allJars.toList ++ (unmanagedJars :+ classesDir) ++ internalClasspath).toArray
}

def compile(i: IncInstance): Analysis = {
dependsOnRef map { dep =>
dep.compile(i)
Expand Down Expand Up @@ -370,9 +374,8 @@ case class ProjectStructure(
optionProgress = None,
extra)

val classpath =
(i.si.allJars.toList ++ (unmanagedJars :+ classesDir) ++ internalClasspath).toArray
val in = compiler.inputs(classpath,
val cp = classpath(i)
val in = compiler.inputs(cp,
sources.toArray,
classesDir,
scalacOptions,
Expand Down
4 changes: 1 addition & 3 deletions zinc/src/sbt-test/source-dependencies/patMat-scope/test
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,7 @@ $ sleep 1000
$ exists target/classes/foo/SealedUsedInPatMatScope.class
$ exists target/classes/foo/SealedNameUsedInDefaultScope.class

# Default scopes should not change
$ newer beforeFirstCompilation target/classes/foo/SealedNameUsedInDefaultScope.class
# But PatMat and direct usage of child should be recompiled
# PatMat and direct usage of child should be recompiled
$ newer target/classes/foo/SealedUsedInPatMatScope.class beforeFirstCompilation

# Change type of Sealed
Expand Down
4 changes: 2 additions & 2 deletions zinc/src/sbt-test/source-dependencies/sealed/test
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ $ copy-file changes/A.scala A.scala

# D.scala needs recompiling because the pattern match in D
# is no longer exhaustive, which emits a warning
> checkRecompilations 1 A B C E
> checkRecompilations 2 D
> checkRecompilations 1
> checkRecompilations 2 A B C D E
13 changes: 13 additions & 0 deletions zinc/src/sbt-test/source-dependencies/trait-trait-211/build.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"projects": [
{
"name": "mirtest",
"scalaVersion": "2.11.8",
"dependsOn": ["gg"]
},
{
"name": "gg",
"scalaVersion": "2.11.8"
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package gg
package table

trait SliceTransforms extends ObjectConcatHelpers {
buildNonemptyObjects(0, 1)
}

trait ObjectConcatHelpers {
def buildNonemptyObjects(a: Int, b: Int): Unit = ()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package gg
package table

trait SliceTransforms extends ObjectConcatHelpers {
buildNonemptyObjects(0)
}

trait ObjectConcatHelpers {
def buildNonemptyObjects(a: Int): Unit = ()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package xx

import gg.table._

trait EvaluatorSpecification extends EvaluatorTestSupport { self =>
}

trait EvaluatorTestSupport extends SliceTransforms { outer =>
def consumeEval: String = "x"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package xx

import gg.table._

object Hello extends App {
def testEval: Unit = {
val consumer = new StringLibSpecs {}
consumer.consumeEval
}
testEval
}

trait StringLibSpecs extends EvaluatorSpecification { self =>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
apiDebug = true
relationsDebug = true
4 changes: 4 additions & 0 deletions zinc/src/sbt-test/source-dependencies/trait-trait-211/test
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
> mirtest/run

$ copy-file changes/SliceTransforms1.scala gg/SliceTransforms.scala
> mirtest/run

0 comments on commit b4078d0

Please sign in to comment.