diff --git a/build.sbt b/build.sbt index e6e4e0800432..a3983cabedc5 100644 --- a/build.sbt +++ b/build.sbt @@ -1041,6 +1041,10 @@ lazy val junit = project.in(file("test") / "junit") javaOptions in Test += "-Xss1M", (forkOptions in Test) := (forkOptions in Test).value.withWorkingDirectory((baseDirectory in ThisBuild).value), (forkOptions in Test in testOnly) := (forkOptions in Test in testOnly).value.withWorkingDirectory((baseDirectory in ThisBuild).value), + (forkOptions in Test in run) := { + val existing = (forkOptions in Test in run).value + existing.withRunJVMOptions(existing.runJVMOptions ++ List("-Xmx2G", "-Xss1M")) + }, libraryDependencies ++= Seq(junitDep, junitInterfaceDep, jolDep), testOptions += Tests.Argument(TestFrameworks.JUnit, "-a", "-v"), unmanagedSourceDirectories in Compile := Nil, diff --git a/src/compiler/scala/tools/nsc/Global.scala b/src/compiler/scala/tools/nsc/Global.scala index cc15208bc866..b2efffd6b306 100644 --- a/src/compiler/scala/tools/nsc/Global.scala +++ b/src/compiler/scala/tools/nsc/Global.scala @@ -390,7 +390,9 @@ class Global(var currentSettings: Settings, reporter0: Reporter) // ------------ Phases -------------------------------------------} - var globalPhase: Phase = NoPhase + private[this] var _globalPhase: Phase = NoPhase + override final def globalPhase: Phase = _globalPhase + final def globalPhase_=(ph: Phase): Unit = _globalPhase = ph abstract class GlobalPhase(prev: Phase) extends Phase(prev) { phaseWithId(id) = this diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala index ede0c3bc3de0..c6326057a517 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala @@ -17,6 +17,7 @@ import scala.reflect.internal.Flags.{DEFERRED, SYNTHESIZE_IMPL_IN_SUBCLASS} import scala.tools.asm import scala.tools.nsc.backend.jvm.BTypes._ import scala.tools.nsc.backend.jvm.BackendReporting._ +import scala.tools.nsc.symtab.Flags /** * This class mainly contains the method classBTypeFromSymbol, which extracts the necessary @@ -701,7 +702,7 @@ abstract class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { // new instances via outerClassInstance.new InnerModuleClass$(). // TODO: do this early, mark the symbol private. val privateFlag = - sym.isPrivate || (sym.isPrimaryConstructor && isTopLevelModuleClass(sym.owner)) + (sym.isPrivate || sym.hasFlag(Flags.notPRIVATE)) || (sym.isPrimaryConstructor && isTopLevelModuleClass(sym.owner)) // Symbols marked in source as `final` have the FINAL flag. (In the past, the flag was also // added to modules and module classes, not anymore since 296b706). diff --git a/src/compiler/scala/tools/nsc/symtab/SymbolLoaders.scala b/src/compiler/scala/tools/nsc/symtab/SymbolLoaders.scala index 9bd91fa3d94e..e9cb807550b7 100644 --- a/src/compiler/scala/tools/nsc/symtab/SymbolLoaders.scala +++ b/src/compiler/scala/tools/nsc/symtab/SymbolLoaders.scala @@ -47,9 +47,13 @@ abstract class SymbolLoaders { protected def compileLate(srcfile: AbstractFile): Unit protected def enterIfNew(owner: Symbol, member: Symbol, completer: SymbolLoader): Symbol = { - assert(owner.info.decls.lookup(member.name) == NoSymbol, owner.fullName + "." + member.name) - owner.info.decls enter member - member + owner.info.decls.lookup(member.name) match { + case NoSymbol => + owner.info.decls enter member + member + case member => + member + } } protected def signalError(root: Symbol, ex: Throwable) { @@ -280,10 +284,30 @@ abstract class SymbolLoaders { val shownPackageName = if (packageName == ClassPath.RootPackage) "" else packageName s"package loader $shownPackageName" } + override lazy val decls = { + + val classPathEntries = classPath.list(packageName) + + if (!packageName == "") + for (entry <- classPathEntries.classesAndSources) initializeFromClassPath(root, entry) + if (!root.isEmptyPackageClass) { + for (pkg <- classPathEntries.packages) { + val fullName = pkg.name + + val name = + if (packageName == ClassPath.RootPackage) fullName + else fullName.substring(packageName.length + 1) + val packageLoader = new PackageLoader(fullName, classPath) + enterPackage(root, name, packageLoader) + } + + openPackageModule(root) + } + } protected def doComplete(root: Symbol) { assert(root.isPackageClass, root) - root.setInfo(new PackageClassInfoType(newScope, root)) + root.setInfo(new PackageClassInfoType(decls, root)) val classPathEntries = classPath.list(packageName) diff --git a/src/compiler/scala/tools/nsc/symtab/classfile/Pickler.scala b/src/compiler/scala/tools/nsc/symtab/classfile/Pickler.scala index ad68701b08fc..6a6c8317a515 100644 --- a/src/compiler/scala/tools/nsc/symtab/classfile/Pickler.scala +++ b/src/compiler/scala/tools/nsc/symtab/classfile/Pickler.scala @@ -54,6 +54,9 @@ abstract class Pickler extends SubComponent { syms.foreach { sym => pickle.putDecl(sym) } + if (sym.name.string_==("th")) + println(_entries.toList.iterator.filter(_ != null).map(_.toString.replace('\n', ';')).mkString("\n")) + } } diff --git a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala index 0159d261f1e7..47d5ea6f2a96 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala @@ -1207,6 +1207,8 @@ trait Contexts { self: Analyzer => } // we have a winner: record the symbol depth symbolDepth = (cx.depth - cx.scope.nestingLevel) + e1.depth + if (name.string_==("Throwable") && e1.sym.id == 2216) + getClass if (syms eq null) e1Sym else owner.newOverloaded(pre, syms.toList) diff --git a/src/compiler/scala/tools/nsc/typechecker/Namers.scala b/src/compiler/scala/tools/nsc/typechecker/Namers.scala index a4f5867d0402..a3c38e092d41 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Namers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Namers.scala @@ -762,7 +762,8 @@ trait Namers extends MethodSynthesis { def enterPackage(tree: PackageDef) { val sym = createPackageSymbol(tree.pos, tree.pid) tree.symbol = sym - newNamer(context.make(tree, sym.moduleClass, sym.info.decls)) enterSyms tree.stats + val scope = sym.rawInfo.typeSymbolDirect.rawInfo.decls + newNamer(context.make(tree, sym.moduleClass, scope)) enterSyms tree.stats } private def enterImport(tree: Import) = { @@ -2161,7 +2162,7 @@ trait Namers extends MethodSynthesis { // scala/bug#7264 Force the info of owners from previous compilation runs. // Doing this generally would trigger cycles; that's what we also // use the lower-level scan through the current Context as a fall back. - if (!currentRun.compiles(owner)) owner.initialize + if (!owner.isPackageClass && !currentRun.compiles(owner)) owner.initialize if (original.isModuleClass) original.sourceModule else if (!owner.isTerm && owner.hasCompleteInfo) diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index 03b2377ee29b..e4336cd29c38 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -1863,9 +1863,14 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper def typedModuleDef(mdef: ModuleDef): Tree = { // initialize all constructors of the linked class: the type completer (Namer.methodSig) // might add default getters to this object. example: "object T; class T(x: Int = 1)" - val linkedClass = companionSymbolOf(mdef.symbol, context) - if (linkedClass != NoSymbol) + val linkedClass = companionSymbolOf(mdef.symbol, context).suchThat { sym => + val isStale = sym.rawInfo.isInstanceOf[loaders.ClassfileLoader] + if (isStale) sym.owner.info.decls.unlink(sym) + !isStale + } + if (linkedClass != NoSymbol) { linkedClass.info.decl(nme.CONSTRUCTOR).alternatives foreach (_.initialize) + } val clazz = mdef.symbol.moduleClass currentRun.profiler.beforeTypedImplDef(clazz) diff --git a/src/reflect/scala/reflect/internal/Definitions.scala b/src/reflect/scala/reflect/internal/Definitions.scala index 9045e0534ca8..f568165337fe 100644 --- a/src/reflect/scala/reflect/internal/Definitions.scala +++ b/src/reflect/scala/reflect/internal/Definitions.scala @@ -195,8 +195,15 @@ trait Definitions extends api.StandardDefinitions { lazy val JavaLangPackageClass = JavaLangPackage.moduleClass.asClass lazy val ScalaPackage = getPackage("scala") lazy val ScalaPackageClass = ScalaPackage.moduleClass.asClass - lazy val RuntimePackage = getPackage("scala.runtime") + lazy val RuntimePackage = getOrCreatePackage(ScalaPackageClass, nme.runtime) lazy val RuntimePackageClass = RuntimePackage.moduleClass.asClass + lazy val AnnotationPackage = getOrCreatePackage(ScalaPackageClass, nme.annotation) + lazy val AnnotationPackageClass = AnnotationPackage.moduleClass.asClass + + private def getOrCreatePackage(owner: Symbol, name: TermName): Symbol = { + val rawInfo = owner.rawInfo + rawInfo.decl(name).orElse(rawInfo.decls.enter(owner.newPackage(name))) + } def javaTypeToValueClass(jtype: Class[_]): Symbol = jtype match { case java.lang.Void.TYPE => UnitClass @@ -1501,7 +1508,12 @@ trait Definitions extends api.StandardDefinitions { def init() { if (isInitialized) return ObjectClass.initialize - ScalaPackageClass.initialize + if (!isCompilerUniverse) { + ScalaPackageClass.initialize + } else { + RuntimePackageClass + AnnotationPackageClass + } symbolsNotPresentInBytecode NoSymbol isInitialized = true diff --git a/src/reflect/scala/reflect/internal/Mirrors.scala b/src/reflect/scala/reflect/internal/Mirrors.scala index 2f8c14a1c0ff..c90784d67df5 100644 --- a/src/reflect/scala/reflect/internal/Mirrors.scala +++ b/src/reflect/scala/reflect/internal/Mirrors.scala @@ -52,7 +52,10 @@ trait Mirrors extends api.Mirrors { else RootClass val name = toName(path.substring(point + 1, len)) - val sym = owner.info member name + val sym = if (!globalPhase.named && owner.safeOwner == RootClass && owner.name == nme.scala_) { + owner.rawInfo.typeSymbolDirect.rawInfo.decls.lookup(name) + } else + owner.info member name val result = if (name.isTermName) sym.suchThat(_ hasFlag MODULE) else sym if (result != NoSymbol) result else { diff --git a/src/reflect/scala/reflect/internal/Phase.scala b/src/reflect/scala/reflect/internal/Phase.scala index f6cf8dd5d938..7904ba0e6ecb 100644 --- a/src/reflect/scala/reflect/internal/Phase.scala +++ b/src/reflect/scala/reflect/internal/Phase.scala @@ -54,6 +54,7 @@ abstract class Phase(val prev: Phase) extends Ordered[Phase] { final val flatClasses: Boolean = ((prev ne null) && (prev ne NoPhase)) && (prev.name == "flatten" || prev.flatClasses) final val specialized: Boolean = ((prev ne null) && (prev ne NoPhase)) && (prev.name == "specialize" || prev.specialized) final val refChecked: Boolean = ((prev ne null) && (prev ne NoPhase)) && (prev.name == "refchecks" || prev.refChecked) + final val named: Boolean = ((prev ne null) && (prev ne NoPhase)) && (prev.name == "namer" || prev.named) // are we past the fields phase, so that: // - we should allow writing to vals (as part of type checking trait setters) diff --git a/src/reflect/scala/reflect/internal/SymbolTable.scala b/src/reflect/scala/reflect/internal/SymbolTable.scala index 95330eced4b1..cb1266ffe2e5 100644 --- a/src/reflect/scala/reflect/internal/SymbolTable.scala +++ b/src/reflect/scala/reflect/internal/SymbolTable.scala @@ -244,6 +244,7 @@ abstract class SymbolTable extends macros.Universe final def phase: Phase = { ph } + def globalPhase: Phase = phase def atPhaseStackMessage = atPhaseStack match { case Nil => "" @@ -352,6 +353,7 @@ abstract class SymbolTable extends macros.Universe def openPackageModule(container: Symbol, dest: Symbol) { // unlink existing symbols in the package + assert(globalPhase.name != "namer" && globalPhase.name != "") for (member <- container.info.decls.iterator) { if (!member.isPrivate && !member.isConstructor) { // todo: handle overlapping definitions in some way: mark as errors diff --git a/src/reflect/scala/reflect/internal/Symbols.scala b/src/reflect/scala/reflect/internal/Symbols.scala index 5a54d584e964..0a3830a60857 100644 --- a/src/reflect/scala/reflect/internal/Symbols.scala +++ b/src/reflect/scala/reflect/internal/Symbols.scala @@ -1583,7 +1583,11 @@ trait Symbols extends api.Symbols { self: SymbolTable => /** Set the info and enter this symbol into the owner's scope. */ def setInfoAndEnter(info: Type): this.type = { setInfo(info) - owner.info.decls enter this + if (!phase.named && owner == ScalaPackageClass) { + owner.rawInfo.decls.enter(this) + } else { + owner.info.decls enter this + } this } @@ -3054,6 +3058,8 @@ trait Symbols extends api.Symbols { self: SymbolTable => class AliasTypeSymbol protected[Symbols] (initOwner: Symbol, initPos: Position, initName: TypeName) extends TypeSymbol(initOwner, initPos, initName) { + if (initName.string_==("Throwable")) + getClass type TypeOfClonedSymbol = TypeSymbol override def variance = if (isLocalToThis) Bivariant else info.typeSymbol.variance override def isContravariant = variance.isContravariant diff --git a/src/reflect/scala/reflect/internal/Types.scala b/src/reflect/scala/reflect/internal/Types.scala index 506f4e8f9ddf..3884814956b4 100644 --- a/src/reflect/scala/reflect/internal/Types.scala +++ b/src/reflect/scala/reflect/internal/Types.scala @@ -3541,7 +3541,7 @@ trait Types // Creators --------------------------------------------------------------- /** Rebind symbol `sym` to an overriding member in type `pre`. */ - private def rebind(pre: Type, sym: Symbol): Symbol = { + private[scala] def rebind(pre: Type, sym: Symbol): Symbol = { if (!sym.isOverridableMember || sym.owner == pre.typeSymbol) sym else pre.nonPrivateMember(sym.name).suchThat { sym => // scala/bug#7928 `isModuleNotMethod` is here to avoid crashing with spuriously "overloaded" module accessor and module symbols. diff --git a/test/junit/scala/tools/nsc/DeterminismTest.scala b/test/junit/scala/tools/nsc/DeterminismTest.scala index 2fda91ccfbeb..efe9057b1549 100644 --- a/test/junit/scala/tools/nsc/DeterminismTest.scala +++ b/test/junit/scala/tools/nsc/DeterminismTest.scala @@ -1,19 +1,16 @@ package scala.tools.nsc -import java.io.OutputStreamWriter -import java.nio.charset.Charset -import java.nio.file.attribute.BasicFileAttributes -import java.nio.file.{FileVisitResult, Files, Path, SimpleFileVisitor} +import java.nio.file.{Files, Path, Paths} -import javax.tools.ToolProvider -import org.junit.Test +import org.junit.{Ignore, Test} -import scala.collection.JavaConverters.seqAsJavaListConverter import scala.reflect.internal.util.{BatchSourceFile, SourceFile} -import scala.tools.nsc.reporters.StoreReporter -import FileUtils._ +import scala.reflect.io.AbstractFile class DeterminismTest { + private val tester = new DeterminismTester + import tester.test + @Test def testLambdaLift(): Unit = { def code = List[SourceFile]( source("a.scala", @@ -300,65 +297,38 @@ class DeterminismTest { test(List(code)) } - def source(name: String, code: String): SourceFile = new BatchSourceFile(name, code) - private def test(groups: List[List[SourceFile]]): Unit = { - val referenceOutput = Files.createTempDirectory("reference") - - def compile(output: Path, files: List[SourceFile]): Unit = { - val g = new Global(new Settings) - g.settings.usejavacp.value = true - g.settings.classpath.value = output.toAbsolutePath.toString - g.settings.outputDirs.setSingleOutput(output.toString) - val storeReporter = new StoreReporter - g.reporter = storeReporter - import g._ - val r = new Run - // println("scalac " + files.mkString(" ")) - r.compileSources(files) - Predef.assert(!storeReporter.hasErrors, storeReporter.infos.mkString("\n")) - files.filter(_.file.name.endsWith(".java")) match { - case Nil => - case javaSources => - def tempFileFor(s: SourceFile): Path = { - val f = output.resolve(s.file.name) - Files.write(f, new String(s.content).getBytes(Charset.defaultCharset())) - } - val options = List("-d", output.toString) - val javac = ToolProvider.getSystemJavaCompiler - assert(javac != null, "No javac from getSystemJavaCompiler. If the java on your path isn't a JDK version, but $JAVA_HOME is, launch sbt with --java-home \"$JAVA_HOME\"") - val fileMan = javac.getStandardFileManager(null, null, null) - val javaFileObjects = fileMan.getJavaFileObjects(javaSources.map(s => tempFileFor(s).toAbsolutePath.toString): _*) - val task = javac.getTask(new OutputStreamWriter(System.out), fileMan, null, options.asJava, Nil.asJava, javaFileObjects) - val result = task.call() - Predef.assert(result) - } - } - - for (group <- groups.init) { - compile(referenceOutput, group) - } - compile(referenceOutput, groups.last) + @Test def testReferenceToInnerClassMadeNonPrivate(): Unit = { + def code = List[SourceFile]( + source("t.scala", + """ + | trait T { + | private class Inner + | class OtherInner { new Inner } // triggers makeNotPrivate of Inner + | private val v: Option[Inner] = None + | } + """.stripMargin), + source("c.scala","""class C extends T""") + ) + test(List(code)) + } - class CopyVisitor(src: Path, dest: Path) extends SimpleFileVisitor[Path] { - override def preVisitDirectory(dir: Path, attrs: BasicFileAttributes): FileVisitResult = { - Files.createDirectories(dest.resolve(src.relativize(dir))) - super.preVisitDirectory(dir, attrs) - } - override def visitFile(file: Path, attrs: BasicFileAttributes): FileVisitResult = { - Files.copy(file, dest.resolve(src.relativize(file))) - super.visitFile(file, attrs) - } - } - for (permutation <- permutationsWithSubsets(groups.last)) { - val recompileOutput = Files.createTempDirectory("recompileOutput") - copyRecursive(referenceOutput, recompileOutput) - compile(recompileOutput, permutation) - assertDirectorySame(referenceOutput, recompileOutput, permutation.toString) - deleteRecursive(recompileOutput) - } - deleteRecursive(referenceOutput) + @Test def testPackageObject(): Unit = { + val root = List(Paths.get("."), Paths.get("../..")).find(x => Files.exists(x.resolve(".git"))).get.toAbsolutePath.normalize() + def code = List[SourceFile]( + source(root.resolve("src/library/scala/package.scala")), + source("th.scala", "package scala; class th[T <: Throwable](cause: T = null)") + ) + test(code :: Nil) + } + @Test def testPackageObjectUserLand(): Unit = { + def code = List[SourceFile]( + source("package.scala", "package userland; object `package` { type Throwy = java.lang.Throwable }"), + source("th.scala", "package userland; class th[T <: Throwy](cause: T = null)") + ) + test(code :: Nil, permuter = _.reverse :: Nil) } - def permutationsWithSubsets[A](as: List[A]): List[List[A]] = - as.permutations.toList.flatMap(_.inits.filter(_.nonEmpty)).distinct + + def source(name: String, code: String): SourceFile = new BatchSourceFile(name, code) + def source(path: Path): SourceFile = new BatchSourceFile(AbstractFile.getFile(path.toFile)) } diff --git a/test/junit/scala/tools/nsc/DeterminismTester.scala b/test/junit/scala/tools/nsc/DeterminismTester.scala new file mode 100644 index 000000000000..a08c2bdd0f14 --- /dev/null +++ b/test/junit/scala/tools/nsc/DeterminismTester.scala @@ -0,0 +1,110 @@ +package scala.tools.nsc + +import java.io.OutputStreamWriter +import java.nio.charset.Charset +import java.nio.file.attribute.BasicFileAttributes +import java.nio.file.{FileVisitResult, Files, Path, Paths, SimpleFileVisitor} +import scala.collection.JavaConverters._ +import javax.tools.ToolProvider + +import scala.reflect.internal.util.SourceFile +import scala.reflect.io.AbstractFile +import scala.tools.nsc.FileUtils._ +import scala.tools.nsc.reporters.StoreReporter +import scala.reflect.internal.util.BatchSourceFile + +object DeterminismTester extends DeterminismTester { + def main(args: Array[String]): Unit = { + val (scalacOpts, sourceFilesPaths) = args.indexOf("--") match { + case -1 => (Nil, args.toList) + case i => + val tuple = args.toList.splitAt(i) + (tuple._1, tuple._2.drop(1)) + } + def isJavaOrScala(p: Path) = { + val name = p.getFileName.toString + name.endsWith(".java") || name.endsWith(".scala") + } + def expand(path: Path): Seq[Path] = { + if (Files.isDirectory(path)) + Files.walk(path).iterator().asScala.filter(isJavaOrScala).toList + else path :: Nil + } + val sourceFiles = sourceFilesPaths.map(Paths.get(_)).flatMap(expand).map(path => new BatchSourceFile(AbstractFile.getFile(path.toFile))) + test(sourceFiles :: Nil, scalacOpts) + } +} + +class DeterminismTester { + + def test(groups: List[List[SourceFile]]): Unit = test(groups, Nil) + def test(groups: List[List[SourceFile]], scalacOptions: List[String] = Nil, + permuter: List[SourceFile] => List[List[SourceFile]] = defaultPermuter(_)): Unit = { + val referenceOutput = Files.createTempDirectory("reference") + + def compile(output: Path, files: List[SourceFile]): Unit = { + println("====== compile: " + files.map(_.file.absolute).map("\"" + _ + "\"").mkString(",\n")) + val g = new Global(new Settings) + g.settings.usejavacp.value = true + g.settings.classpath.value = output.toAbsolutePath.toString + g.settings.outputDirs.setSingleOutput(output.toString) + g.settings.processArguments(scalacOptions, true) + val storeReporter = new StoreReporter + g.reporter = storeReporter + import g._ + val r = new Run + // println("scalac " + files.mkString(" ")) + r.compileSources(files) + Predef.assert(!storeReporter.hasErrors, storeReporter.infos.mkString("\n")) + files.filter(_.file.name.endsWith(".java")) match { + case Nil => + case javaSources => + def tempFileFor(s: SourceFile): Path = { + val f = output.resolve(s.file.name) + Files.write(f, new String(s.content).getBytes(Charset.defaultCharset())) + } + val options = List("-d", output.toString) + val javac = ToolProvider.getSystemJavaCompiler + assert(javac != null, "No javac from getSystemJavaCompiler. If the java on your path isn't a JDK version, but $JAVA_HOME is, launch sbt with --java-home \"$JAVA_HOME\"") + val fileMan = javac.getStandardFileManager(null, null, null) + val javaFileObjects = fileMan.getJavaFileObjects(javaSources.map(s => tempFileFor(s).toAbsolutePath.toString): _*) + val task = javac.getTask(new OutputStreamWriter(System.out), fileMan, null, options.asJava, Nil.asJava, javaFileObjects) + val result = task.call() + Predef.assert(result) + } + } + + for (group <- groups.init) { + compile(referenceOutput, group) + } + compile(referenceOutput, groups.last) + + class CopyVisitor(src: Path, dest: Path) extends SimpleFileVisitor[Path] { + override def preVisitDirectory(dir: Path, attrs: BasicFileAttributes): FileVisitResult = { + Files.createDirectories(dest.resolve(src.relativize(dir))) + super.preVisitDirectory(dir, attrs) + } + override def visitFile(file: Path, attrs: BasicFileAttributes): FileVisitResult = { + Files.copy(file, dest.resolve(src.relativize(file))) + super.visitFile(file, attrs) + } + } + val permutations: List[List[SourceFile]] = permuter(groups.last) + for (permutation <- permutations) { + val recompileOutput = Files.createTempDirectory("recompileOutput") + copyRecursive(referenceOutput, recompileOutput) + compile(recompileOutput, permutation) + assertDirectorySame(referenceOutput, recompileOutput, permutation.toString) + deleteRecursive(recompileOutput) + } + deleteRecursive(referenceOutput) + } + def defaultPermuter(sources: List[SourceFile]): List[List[SourceFile]] = { + if (sources.size > 32) { + sources.reverse :: sources.map(_ :: Nil) + } else permutationsWithSubsets(sources) + } + def permutationsWithSubsets[A](as: List[A]): List[List[A]] = + as.permutations.toList.flatMap(_.inits.filter(_.nonEmpty)).distinct + +}