diff --git a/internal/compiler-bridge/src/main/scala/scala/reflect/ReflectAccess.scala b/internal/compiler-bridge/src/main/scala/scala/reflect/ReflectAccess.scala new file mode 100644 index 0000000000..bdf8f342e3 --- /dev/null +++ b/internal/compiler-bridge/src/main/scala/scala/reflect/ReflectAccess.scala @@ -0,0 +1,21 @@ +package scala.reflect + +import scala.tools.nsc.Global + +// Gives access to `private[reflect] def compactifyName` +object ReflectAccess { + def flattenedOwner(g: Global)(sym: g.Symbol): g.Symbol = { + val chain = sym.owner.ownerChain.dropWhile(o => o.isClass && !o.hasPackageFlag) + if (chain.isEmpty) g.NoSymbol else chain.head + } + + def flattenedName(g: Global)(sym: g.Symbol): g.Name = { + val owner = sym.owner + val prefix = + if (owner.isRoot || owner == g.NoSymbol || owner.hasPackageFlag) "" + else "" + flattenedName(g)(owner) + NameTransformer.NAME_JOIN_STRING + val flat = prefix + sym.rawname + val nameString = if (owner.isJava) flat else g.compactifyName(flat) + if (sym.isType) g.newTypeNameCached(nameString) else g.newTermNameCached(nameString) + } +} diff --git a/internal/compiler-bridge/src/main/scala/xsbt/API.scala b/internal/compiler-bridge/src/main/scala/xsbt/API.scala index 8fcfae4c96..5274914ab2 100644 --- a/internal/compiler-bridge/src/main/scala/xsbt/API.scala +++ b/internal/compiler-bridge/src/main/scala/xsbt/API.scala @@ -126,7 +126,8 @@ final class API(val global: CallbackGlobal) extends Compat with GlobalHelpers wi * @param allClassSymbols The class symbols found in all the compilation units. */ def registerGeneratedClasses(classSymbols: Iterator[Symbol]): Unit = { - classSymbols.foreach { symbol => + // Guard against a local class in case it surreptitiously leaks here + classSymbols.filter(!_.isLocalClass).foreach { symbol => val sourceFile = symbol.sourceFile val sourceVF0 = if (sourceFile == null) symbol.enclosingTopLevelClass.sourceFile @@ -138,29 +139,26 @@ final class API(val global: CallbackGlobal) extends Compat with GlobalHelpers wi } def registerProductNames(names: FlattenedNames): Unit = { - // Guard against a local class in case it surreptitiously leaks here - if (!symbol.isLocalClass) { - val pathToClassFile = s"${names.binaryName}.class" - val classFile = { - JarUtils.outputJar match { - case Some(outputJar) => - new java.io.File(JarUtils.classNameInJar(outputJar, pathToClassFile)) - case None => - val outputDir = global.settings.outputDirs.outputDirFor(sourceFile).file - new java.io.File(outputDir, pathToClassFile) - } + val pathToClassFile = s"${names.binaryName}.class" + val classFile = { + JarUtils.outputJar match { + case Some(outputJar) => + new java.io.File(JarUtils.classNameInJar(outputJar, pathToClassFile)) + case None => + val outputDir = global.settings.outputDirs.outputDirFor(sourceFile).file + new java.io.File(outputDir, pathToClassFile) } - val zincClassName = names.className - val srcClassName = classNameAsString(symbol) - sourceVF foreach { source => - callback.generatedNonLocalClass( - source, - classFile.toPath, - zincClassName, - srcClassName - ) - } - } else () + } + val zincClassName = names.className + val srcClassName = classNameAsString(symbol) + sourceVF foreach { source => + callback.generatedNonLocalClass( + source, + classFile.toPath, + zincClassName, + srcClassName + ) + } } val names = FlattenedNames( diff --git a/internal/compiler-bridge/src/main/scala/xsbt/CallbackGlobal.scala b/internal/compiler-bridge/src/main/scala/xsbt/CallbackGlobal.scala index 0458b9866c..5c4f71c582 100644 --- a/internal/compiler-bridge/src/main/scala/xsbt/CallbackGlobal.scala +++ b/internal/compiler-bridge/src/main/scala/xsbt/CallbackGlobal.scala @@ -19,6 +19,7 @@ import io.AbstractFile import java.nio.file.{ Files, Path } import scala.reflect.io.PlainFile +import scala.reflect.ReflectAccess._ /** Defines the interface of the incremental compiler hiding implementation details. */ sealed abstract class CallbackGlobal( @@ -225,19 +226,19 @@ sealed class ZincCompiler(settings: Settings, dreporter: DelegatingReporter, out ): String = { var b: java.lang.StringBuffer = null def loop(size: Int, sym: Symbol): Unit = { - val symName = sym.name + val symName = flattenedName(this)(sym) // Use of encoded to produce correct paths for names that have symbols val encodedName = symName.encode val nSize = encodedName.length - (if (symName.endsWith(nme.LOCAL_SUFFIX_STRING)) 1 else 0) - if (sym.isRoot || sym.isRootPackage || sym == NoSymbol || sym.owner.isEffectiveRoot) { + val owner = flattenedOwner(this)(sym) + if (sym.isRoot || sym.isRootPackage || sym == NoSymbol || owner.isEffectiveRoot) { val capacity = size + nSize b = new java.lang.StringBuffer(capacity) b.append(chrs, encodedName.start, nSize) } else { - val next = if (sym.owner.isPackageObjectClass) sym.owner else sym.effectiveOwner.enclClass + val next = if (owner.isPackageObjectClass) owner else owner.skipPackageObject loop(size + nSize + 1, next) - // Addition to normal `fullName` to produce correct names for nested non-local classes - if (sym.isNestedClass) b.append(nme.MODULE_SUFFIX_STRING) else b.append(separator) + if (owner.isPackageObjectClass) b.append(nme.MODULE_SUFFIX_STRING) else b.append(separator) b.append(chrs, encodedName.start, nSize) } () diff --git a/internal/compiler-bridge/src/main/scala/xsbt/trace.scala b/internal/compiler-bridge/src/main/scala/xsbt/trace.scala new file mode 100644 index 0000000000..c219b1a444 --- /dev/null +++ b/internal/compiler-bridge/src/main/scala/xsbt/trace.scala @@ -0,0 +1,26 @@ +package xsbt + +object trace extends TraceSyntax { + object disable extends TraceSyntax { + override def apply[T](q: String)(op: => T): T = op + } + object enable extends TraceSyntax +} + +object TraceSyntax { + private var indent = 0 +} +trait TraceSyntax { + import TraceSyntax._ + def apply[T](q: String)(op: => T): T = { + val margin = " " * indent + println(s"$margin$q?") + val res = + try { + indent += 1 + op + } finally indent -= 1 + println(s"$margin$q = $res") + res + } +} diff --git a/internal/zinc-scripted/src/test/scala/sbt/internal/inc/IncHandler.scala b/internal/zinc-scripted/src/test/scala/sbt/internal/inc/IncHandler.scala index a856727211..29305fb7cb 100644 --- a/internal/zinc-scripted/src/test/scala/sbt/internal/inc/IncHandler.scala +++ b/internal/zinc-scripted/src/test/scala/sbt/internal/inc/IncHandler.scala @@ -232,6 +232,9 @@ class IncHandler(directory: Path, cacheDir: Path, scriptedLog: ManagedLogger, co onArgs("checkProducts") { case (p, src :: products, i) => p.checkProducts(i, dropRightColon(src), products) }, + onArgs("checkProductsExists") { + case (p, src :: Nil, i) => p.checkProductsExist(i, src) + }, onArgs("checkDependencies") { case (p, cls :: dependencies, i) => p.checkDependencies(i, dropRightColon(cls), dependencies) }, @@ -441,28 +444,32 @@ case class ProjectStructure( () } + def getProducts(analysis: Analysis)(srcFile: String): Set[VirtualFileRef] = + analysis.relations.products(converter.toVirtualFile(baseDirectory / srcFile)) + def checkProducts(i: IncState, src: String, expected: List[String]): Future[Unit] = compile(i).map { analysis => - // def isWindows: Boolean = sys.props("os.name").toLowerCase.startsWith("win") - // def relativeClassDir(f: File): File = f.relativeTo(classesDir) getOrElse f - // def normalizePath(path: String): String = { - // if (isWindows) path.replace('\\', '/') else path - // } - def products(srcFile: String): Set[String] = { - val productFiles = analysis.relations.products(converter.toVirtualFile(baseDirectory / src)) - productFiles.map { file: VirtualFileRef => - // if (JarUtils.isClassInJar(file)) { - // JarUtils.ClassInJar.fromPath(output.toPath).toClassFilePath - // } else { - // normalizePath(relativeClassDir(file).getPath) - // } - file.id - } - } def assertClasses(expected: Set[String], actual: Set[String]) = - assert(expected == actual, s"Expected $expected products, got $actual") + assert( + expected == actual, + s"""Mismatch: + |Expected:${expected.toList.sorted.map("\n " + _).mkString} + |Obtained:${actual.toList.sorted.map("\n " + _).mkString}""".stripMargin + ) + + assertClasses(expected.toSet, getProducts(analysis)(src).map(_.id)) + () + } - assertClasses(expected.toSet, products(src)) + def checkProductsExist(i: IncState, src: String): Future[Unit] = + compile(i).map { analysis => + val missing = + for (p <- getProducts(analysis)(src).toList if !Files.exists(converter.toPath(p))) yield p + assert( + missing.isEmpty, + s"""Missing ${missing.size} products:${missing.map("\n " + _).mkString} + |Generated:${generatedClassFiles.get.toList.map("\n " + _).mkString}""".stripMargin + ) () } @@ -829,6 +836,14 @@ case class ProjectStructure( } () } + + def assertShort(assertion: Boolean, message: => Any) = { + if (!assertion) { + val err = new AssertionError("assertion failed: " + message) + err.setStackTrace(Array()) + throw err + } + } } object IncHandler { diff --git a/zinc/src/sbt-test/source-dependencies/compactify-nested-class/main.scala b/zinc/src/sbt-test/source-dependencies/compactify-nested-class/main.scala new file mode 100644 index 0000000000..a7c36134b8 --- /dev/null +++ b/zinc/src/sbt-test/source-dependencies/compactify-nested-class/main.scala @@ -0,0 +1,11 @@ +package p1.p2 + +abstract class B { + def m(): L + abstract class L { + def i: I + abstract class I + } +} +class D extends B { override def m() = new L { override case object i extends I } } +class G extends B { override def m() = new L { override case object i extends I } } diff --git a/zinc/src/sbt-test/source-dependencies/compactify-nested-class/test b/zinc/src/sbt-test/source-dependencies/compactify-nested-class/test new file mode 100644 index 0000000000..d8e9c4509e --- /dev/null +++ b/zinc/src/sbt-test/source-dependencies/compactify-nested-class/test @@ -0,0 +1,2 @@ +> checkProducts main.scala: ${BASE}/target/classes/p1/p2/B$L$I.class ${BASE}/target/classes/p1/p2/B$L.class ${BASE}/target/classes/p1/p2/B.class ${BASE}/target/classes/p1/p2/D$$anon$1$i$.class ${BASE}/target/classes/p1/p2/D$$anon$1.class ${BASE}/target/classes/p1/p2/D.class ${BASE}/target/classes/p1/p2/G$$anon$2$i$.class ${BASE}/target/classes/p1/p2/G$$anon$2.class ${BASE}/target/classes/p1/p2/G.class +> checkProductsExists main.scala diff --git a/zinc/src/sbt-test/source-dependencies/compactify-nested/main.scala b/zinc/src/sbt-test/source-dependencies/compactify-nested/main.scala new file mode 100644 index 0000000000..8441d985ed --- /dev/null +++ b/zinc/src/sbt-test/source-dependencies/compactify-nested/main.scala @@ -0,0 +1,15 @@ +package p1.p2 + +class OuterLevelWithVeryVeryVeryLongClassName1 { + class OuterLevelWithVeryVeryVeryLongClassName2 { + class OuterLevelWithVeryVeryVeryLongClassName3 { + class OuterLevelWithVeryVeryVeryLongClassName4 { + class OuterLevelWithVeryVeryVeryLongClassName5 { + class OuterLevelWithVeryVeryVeryLongClassName6 { + class OuterLevelWithVeryVeryVeryLongClassName7 + } + } + } + } + } +} diff --git a/zinc/src/sbt-test/source-dependencies/compactify-nested/test b/zinc/src/sbt-test/source-dependencies/compactify-nested/test new file mode 100644 index 0000000000..bb8ca18162 --- /dev/null +++ b/zinc/src/sbt-test/source-dependencies/compactify-nested/test @@ -0,0 +1,2 @@ +> checkProducts main.scala: ${BASE}/target/classes/p1/p2/OuterLevelWithVeryVeryVeryLongClassName1.class ${BASE}/target/classes/p1/p2/OuterLevelWithVeryVeryVeryLongClassName1$OuterLevelWithVeryVeryVeryLongClassName2.class ${BASE}/target/classes/p1/p2/OuterLevelWithVeryVeryVeryLongClassName1$OuterLevelWithVeryVeryVeryLongClassName2$OuterLevelWithVeryVeryVeryLongClassName3.class ${BASE}/target/classes/p1/p2/OuterLevelWithVeryVeryVeryLongClassName1$OuterLevelWithVeryVeryVeryLongClassName2$OuterLevelWithVeryVeryVeryLongClassName3$OuterLevelWithVeryVeryVeryLongClassName4.class ${BASE}/target/classes/p1/p2/OuterLevelWithVeryVeryVeryLongClassName1$OuterLevelWithVeryVeryVeryLongClassName2$OuterLevelWithVeryVeryVeryLongClassName3$OuterLevelWithVeryVeryVeryLongClassName4$OuterLevelWithVeryVeryVeryLongClassName5.class ${BASE}/target/classes/p1/p2/OuterLevelWithVeryVeryVeryLongClassName1$OuterLevelWithVeryVeryVeryLongClassName2$OuterLevelWithVeryVeryVeryLongClassName3$OuterLevelWithVeryVeryVeryLongClassName4$OuterLevelWithVeryVeryVeryLongClassName5$OuterLevelWithVeryVeryVeryLongClassName6.class ${BASE}/target/classes/p1/p2/OuterLevelWithVeryVeryVeryLongClassName1$OuterLevelWithVeryVe$$$$6facba931fe42f8a8c3cee88c4087$$$$ryVeryLongClassName6$OuterLevelWithVeryVeryVeryLongClassName7.class +> checkProductsExists main.scala