diff --git a/compiler/src/dotty/tools/dotc/CompilationUnit.scala b/compiler/src/dotty/tools/dotc/CompilationUnit.scala index da8b3923be1e..4552f90959dc 100644 --- a/compiler/src/dotty/tools/dotc/CompilationUnit.scala +++ b/compiler/src/dotty/tools/dotc/CompilationUnit.scala @@ -5,6 +5,9 @@ import dotty.tools.dotc.core.Types.Type import dotty.tools.dotc.core.tasty.{TastyUnpickler, TastyBuffer, TastyPickler} import util.SourceFile import ast.{tpd, untpd} +import dotty.tools.dotc.ast.tpd.{ Tree, TreeTraverser } +import dotty.tools.dotc.core.Contexts.Context +import dotty.tools.dotc.core.SymDenotations.ClassDenotation import dotty.tools.dotc.core.Symbols._ class CompilationUnit(val source: SourceFile) { @@ -20,3 +23,20 @@ class CompilationUnit(val source: SourceFile) { /** Pickled TASTY binaries, indexed by class. */ var pickled: Map[ClassSymbol, Array[Byte]] = Map() } + +object CompilationUnit { + + /** Make a compilation unit for top class `clsd` with the contends of the `unpickled` */ + def mkCompilationUnit(clsd: ClassDenotation, unpickled: Tree, forceTrees: Boolean)(implicit ctx: Context): CompilationUnit = { + val unit1 = new CompilationUnit(new SourceFile(clsd.symbol.sourceFile, Seq())) + unit1.tpdTree = unpickled + if (forceTrees) + force.traverse(unit1.tpdTree) + unit1 + } + + /** Force the tree to be loaded */ + private object force extends TreeTraverser { + def traverse(tree: Tree)(implicit ctx: Context): Unit = traverseChildren(tree) + } +} diff --git a/compiler/src/dotty/tools/dotc/Compiler.scala b/compiler/src/dotty/tools/dotc/Compiler.scala index 97e4833c5fbe..ccefb6fc3a79 100644 --- a/compiler/src/dotty/tools/dotc/Compiler.scala +++ b/compiler/src/dotty/tools/dotc/Compiler.scala @@ -47,6 +47,7 @@ class Compiler { List(new PostTyper), // Additional checks and cleanups after type checking List(new sbt.ExtractAPI), // Sends a representation of the API of classes to sbt via callbacks List(new Pickler), // Generate TASTY info + List(new LinkAll), // Reload compilation units from TASTY for library code (if needed) List(new FirstTransform, // Some transformations to put trees into a canonical form new CheckReentrant, // Internal use only: Check that compiled program has no data races involving global vars new ElimJavaPackages), // Eliminate syntactic references to Java packages diff --git a/compiler/src/dotty/tools/dotc/FromTasty.scala b/compiler/src/dotty/tools/dotc/FromTasty.scala index 2cb0aa79456a..daa148e4a2c1 100644 --- a/compiler/src/dotty/tools/dotc/FromTasty.scala +++ b/compiler/src/dotty/tools/dotc/FromTasty.scala @@ -60,10 +60,6 @@ object FromTasty extends Driver { override def toString = s"class file $className" } - object force extends TreeTraverser { - def traverse(tree: Tree)(implicit ctx: Context): Unit = traverseChildren(tree) - } - class ReadTastyTreesFromClasses extends FrontEnd { override def isTyper = false @@ -85,13 +81,8 @@ object FromTasty extends Driver { case info: ClassfileLoader => info.load(clsd) val unpickled = clsd.symbol.asClass.tree - if (unpickled != null) { - val unit1 = new CompilationUnit(new SourceFile(clsd.symbol.sourceFile, Seq())) - unit1.tpdTree = unpickled - force.traverse(unit1.tpdTree) - unit1 - } else - cannotUnpickle(s"its class file ${info.classfile} does not have a TASTY attribute") + if (unpickled != null) CompilationUnit.mkCompilationUnit(clsd, unpickled, forceTrees = true) + else cannotUnpickle(s"its class file ${info.classfile} does not have a TASTY attribute") case info => cannotUnpickle(s"its info of type ${info.getClass} is not a ClassfileLoader") } diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index b96bdb47f160..b0a1321a395c 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -113,6 +113,7 @@ class ScalaSettings extends Settings.SettingGroup { val YoptPhases = PhasesSetting("-Yopt-phases", "Restrict the optimisation phases to execute under -optimise.") val YoptFuel = IntSetting("-Yopt-fuel", "Maximum number of optimisations performed under -optimise.", -1) val optimise = BooleanSetting("-optimise", "Generates faster bytecode by applying local optimisations to the .program") withAbbreviation "-optimize" + val XlinkOptimise = BooleanSetting("-Xlink-optimise", "Recompile library code with the application.").withAbbreviation("-Xlink-optimize") /** Dottydoc specific settings */ val siteRoot = StringSetting( diff --git a/compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala b/compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala index c17cc00892ab..8b0f794cf660 100644 --- a/compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala +++ b/compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala @@ -323,11 +323,15 @@ class ClassfileLoader(val classfile: AbstractFile) extends SymbolLoader { def load(root: SymDenotation)(implicit ctx: Context): Unit = { val (classRoot, moduleRoot) = rootDenots(root.asClass) - (new ClassfileParser(classfile, classRoot, moduleRoot)(ctx)).run() match { - case Some(unpickler: tasty.DottyUnpickler) if ctx.settings.YretainTrees.value => - classRoot.symbol.asClass.unpickler = unpickler - moduleRoot.symbol.asClass.unpickler = unpickler - case _ => + val classfileParser = new ClassfileParser(classfile, classRoot, moduleRoot)(ctx) + val result = classfileParser.run() + if (ctx.settings.YretainTrees.value || ctx.settings.XlinkOptimise.value) { + result match { + case Some(unpickler: tasty.DottyUnpickler) => + classRoot.symbol.asClass.unpickler = unpickler + moduleRoot.symbol.asClass.unpickler = unpickler + case _ => + } } } } diff --git a/compiler/src/dotty/tools/dotc/core/Symbols.scala b/compiler/src/dotty/tools/dotc/core/Symbols.scala index b5c3baac2cf1..8c6a7148c4d2 100644 --- a/compiler/src/dotty/tools/dotc/core/Symbols.scala +++ b/compiler/src/dotty/tools/dotc/core/Symbols.scala @@ -555,35 +555,31 @@ object Symbols { type ThisName = TypeName - /** If this is a top-level class, and if `-Yretain-trees` is set, return the TypeDef tree - * for this class, otherwise EmptyTree. This will force the info of the class. - */ - def tree(implicit ctx: Context): tpd.Tree /* tpd.TypeDef | tpd.EmptyTree */ = { + /** If this is either: + * - a top-level class and `-Yretain-trees` is set + * - a top-level class loaded from TASTY and `-Xlink-optimise` is set + * then return the TypeDef tree (possibly wrapped inside PackageDefs) for this class, otherwise EmptyTree. + * This will force the info of the class. + */ + def tree(implicit ctx: Context): tpd.Tree /* tpd.PackageDef | tpd.TypeDef | tpd.EmptyTree */ = { denot.info // TODO: Consider storing this tree like we store lazy trees for inline functions if (unpickler != null && !denot.isAbsent) { assert(myTree.isEmpty) - - import ast.Trees._ - - def findTree(tree: tpd.Tree): Option[tpd.TypeDef] = tree match { - case PackageDef(_, stats) => - stats.flatMap(findTree).headOption - case tree: tpd.TypeDef if tree.symbol == this => - Some(tree) - case _ => - None - } - val List(unpickledTree) = unpickler.body(ctx.addMode(Mode.ReadPositions)) + val body = unpickler.body(ctx.addMode(Mode.ReadPositions)) + myTree = body.headOption.getOrElse(tpd.EmptyTree) unpickler = null - - myTree = findTree(unpickledTree).get } myTree } - private[dotc] var myTree: tpd.Tree = tpd.EmptyTree + private[this] var myTree: tpd.Tree /* tpd.PackageDef | tpd.TypeDef | tpd.EmptyTree */ = tpd.EmptyTree private[dotc] var unpickler: tasty.DottyUnpickler = _ + private[dotc] def registerTree(tree: tpd.TypeDef)(implicit ctx: Context): Unit = { + if (ctx.settings.YretainTrees.value) + myTree = tree + } + /** The source or class file from which this class was generated, null if not applicable. */ override def associatedFile(implicit ctx: Context): AbstractFile = if (assocFile != null || (this.owner is PackageClass) || this.isEffectiveRoot) assocFile diff --git a/compiler/src/dotty/tools/dotc/interactive/SourceTree.scala b/compiler/src/dotty/tools/dotc/interactive/SourceTree.scala index 54a88ca1bf38..faa700bc58a3 100644 --- a/compiler/src/dotty/tools/dotc/interactive/SourceTree.scala +++ b/compiler/src/dotty/tools/dotc/interactive/SourceTree.scala @@ -43,13 +43,15 @@ object SourceTree { sym.sourceFile == null) // FIXME: We cannot deal with external projects yet None else { - sym.tree match { - case tree: tpd.TypeDef => + import ast.Trees._ + def sourceTreeOfClass(tree: tpd.Tree): Option[SourceTree] = tree match { + case PackageDef(_, stats) => stats.flatMap(sourceTreeOfClass).headOption + case tree: tpd.TypeDef if tree.symbol == sym => val sourceFile = new SourceFile(sym.sourceFile, Codec.UTF8) Some(SourceTree(tree, sourceFile)) - case _ => - None + case _ => None } + sourceTreeOfClass(sym.tree) } } } diff --git a/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala b/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala index 745dc495b76e..953ffbfe1906 100644 --- a/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala +++ b/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala @@ -154,6 +154,10 @@ class FirstTransform extends MiniPhaseTransform with InfoTransformer with Annota cpy.Template(impl)(self = EmptyValDef) } + /** Eliminate empty package definitions that may have been stored in the TASTY trees */ + override def transformPackageDef(tree: PackageDef)(implicit ctx: Context, info: TransformerInfo): Tree = + if (tree.stats.isEmpty) EmptyTree else tree + override def transformDefDef(ddef: DefDef)(implicit ctx: Context, info: TransformerInfo) = { if (ddef.symbol.hasAnnotation(defn.NativeAnnot)) { ddef.symbol.resetFlag(Deferred) diff --git a/compiler/src/dotty/tools/dotc/transform/LinkAll.scala b/compiler/src/dotty/tools/dotc/transform/LinkAll.scala new file mode 100644 index 000000000000..97e45ba93b05 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/LinkAll.scala @@ -0,0 +1,80 @@ +package dotty.tools.dotc.transform + +import dotty.tools.dotc.CompilationUnit +import dotty.tools.dotc.ast.Trees._ +import dotty.tools.dotc.ast.tpd +import dotty.tools.dotc.core.Contexts._ +import dotty.tools.dotc.core.SymDenotations.ClassDenotation +import dotty.tools.dotc.core.Symbols._ +import dotty.tools.dotc.core.Flags._ +import dotty.tools.dotc.transform.TreeTransforms._ + +/** Loads all potentially reachable trees from tasty. ▲ + * Only performed on whole world optimization mode. ▲ ▲ + * + * TODO: Next step is to only load compilation units reachable in the call graph + */ +class LinkAll extends MiniPhaseTransform { + import tpd._ + import LinkAll._ + + override def phaseName = "linkAll" + + /** Do not transform the any tree, runOn will traverse the trees and reload compilation units if needed */ + override def prepareForUnit(tree: tpd.Tree)(implicit ctx: Context): TreeTransform = NoTransform + + override def runOn(units: List[CompilationUnit])(implicit ctx: Context): List[CompilationUnit] = { + /** Loads and processes new compilation units, possibly loading more units. */ + def allUnits(processed: Set[CompilationUnit], unprocessed: Set[CompilationUnit], loadedClasses: Set[ClassDenotation])(implicit ctx: Context): List[CompilationUnit] = { + if (unprocessed.isEmpty) processed.toList + else { + val accum = new ClassesToLoadAccumulator + val classesToLoad = unprocessed.foldLeft(Set.empty[ClassDenotation])((acc, unit) => accum.apply(acc, unit.tpdTree)) -- loadedClasses + val loadedUnits = classesToLoad.flatMap(cls => loadCompilationUnit(cls)) + allUnits(processed ++ unprocessed, loadedUnits, loadedClasses ++ classesToLoad) + } + } + + if (ctx.settings.XlinkOptimise.value) super.runOn(allUnits(Set.empty, units.toSet, Set.empty)) + else super.runOn(units) + } + + /** Collects all class denotations that may need to be loaded. */ + private class ClassesToLoadAccumulator extends TreeAccumulator[Set[ClassDenotation]] { + private var inParents = false + override def apply(acc: Set[ClassDenotation], tree: tpd.Tree)(implicit ctx: Context): Set[ClassDenotation] = tree match { + case New(tpt) => accum(acc, tpt.tpe.classSymbol) + case AppliedTypeTree(tpt, _) if inParents => accum(acc, tpt.symbol) + case tree: RefTree if inParents || tree.symbol.is(Module) => + foldOver(accum(acc, tree.symbol), tree) + case tree @ Template(constr, parents, self, _) => + val acc1 = this(acc, constr) + inParents = true + val acc2 = this(acc1, parents) + inParents = false + this(this(acc2, self), tree.body) + case _ => foldOver(acc, tree) + } + + /** Accumulate class denotation for `sym` if needed */ + private def accum(acc: Set[ClassDenotation], sym: Symbol)(implicit ctx: Context): Set[ClassDenotation] = { + val topClass = sym.topLevelClass.denot.asClass + if (topClass.is(JavaDefined) || topClass.is(Scala2x) || topClass.symbol == defn.ObjectClass) acc + else acc + topClass + } + } +} + +object LinkAll { + + private[LinkAll] def loadCompilationUnit(clsd: ClassDenotation)(implicit ctx: Context): Option[CompilationUnit] = { + assert(ctx.settings.XlinkOptimise.value) + val tree = clsd.symbol.asClass.tree + if (tree.isEmpty) None + else { + ctx.log("Loading compilation unit for: " + clsd) + Some(CompilationUnit.mkCompilationUnit(clsd, tree, forceTrees = false)) + } + } + +} diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index bd5ff738ff2d..fabb4e28e400 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1427,9 +1427,8 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit // check value class constraints checkDerivedValueClass(cls, body1) - if (ctx.settings.YretainTrees.value) { - cls.myTree = cdef1 - } + cls.registerTree(cdef1) + cdef1 // todo later: check that diff --git a/compiler/test/dotc/comptest.scala b/compiler/test/dotc/comptest.scala index 8737ef165f94..318f9cd80612 100644 --- a/compiler/test/dotc/comptest.scala +++ b/compiler/test/dotc/comptest.scala @@ -1,6 +1,6 @@ package dotc -import dotty.tools.vulpix.ParallelTesting +import dotty.tools.vulpix.{ParallelTesting, TestFlags} import scala.concurrent.duration._ @@ -26,9 +26,6 @@ object comptest extends ParallelTesting { dotcDir + "tools/dotc/core/Types.scala", dotcDir + "tools/dotc/ast/Trees.scala" ), - Array( - "-Ylog:frontend", - "-Xprompt" - ) + TestFlags("", Array("-Ylog:frontend", "-Xprompt")) ) } diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index d8cfee48c423..aa0720042eb5 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -3,14 +3,15 @@ package tools package dotc import org.junit.{ Test, BeforeClass, AfterClass } +import org.junit.Assert._ import java.nio.file._ import java.util.stream.{ Stream => JStream } import scala.collection.JavaConverters._ import scala.util.matching.Regex import scala.concurrent.duration._ - -import vulpix.{ ParallelTesting, SummaryReport, SummaryReporting, TestConfiguration } +import vulpix._ +import dotty.tools.io.JFile class CompilationTests extends ParallelTesting { @@ -29,7 +30,6 @@ class CompilationTests extends ParallelTesting { @Test def compilePos: Unit = { compileList("compileStdLib", StdLibSources.whitelisted, scala2Mode.and("-migration", "-Yno-inline")) + - compileDir("../collection-strawman/src/main", defaultOptions) + compileDir("../compiler/src/dotty/tools/dotc/ast", defaultOptions) + compileDir("../compiler/src/dotty/tools/dotc/config", defaultOptions) + compileDir("../compiler/src/dotty/tools/dotc/core", allowDeepSubtypes) + @@ -40,7 +40,7 @@ class CompilationTests extends ParallelTesting { compileDir("../compiler/src/dotty/tools/dotc/typer", defaultOptions) + compileDir("../compiler/src/dotty/tools/dotc/util", defaultOptions) + compileDir("../compiler/src/dotty/tools/io", defaultOptions) + - compileDir("../compiler/src/dotty/tools/dotc/core", noCheckOptions ++ classPath) + + compileDir("../compiler/src/dotty/tools/dotc/core", TestFlags(classPath, noCheckOptions)) + compileFile("../tests/pos/nullarify.scala", defaultOptions.and("-Ycheck:nullarify")) + compileFile("../tests/pos-scala2/rewrites.scala", scala2Mode.and("-rewrite")).copyToTarget() + compileFile("../tests/pos-special/t8146a.scala", allowDeepSubtypes) + @@ -222,14 +222,13 @@ class CompilationTests extends ParallelTesting { * version of Dotty */ @Test def tastyBootstrap: Unit = { - val opt = Array( - "-classpath", + val opt = TestFlags( // compile with bootstrapped library on cp: defaultOutputDir + "lib/src/:" + // as well as bootstrapped compiler: defaultOutputDir + "dotty1/dotty/:" + Jars.dottyInterfaces, - "-Ycheck-reentrant" + Array("-Ycheck-reentrant") ) def lib = @@ -292,6 +291,27 @@ class CompilationTests extends ParallelTesting { tests.foreach(_.delete()) } + + private val (compilerSources, backendSources, backendJvmSources) = { + val compilerDir = Paths.get("../compiler/src") + val compilerSources0 = sources(Files.walk(compilerDir)) + + val backendDir = Paths.get("../scala-backend/src/compiler/scala/tools/nsc/backend") + val backendJvmDir = Paths.get("../scala-backend/src/compiler/scala/tools/nsc/backend/jvm") + + // NOTE: Keep these exclusions synchronized with the ones in the sbt build (Build.scala) + val backendExcluded = + List("JavaPlatform.scala", "Platform.scala", "ScalaPrimitives.scala") + val backendJvmExcluded = + List("BCodeICodeCommon.scala", "GenASM.scala", "GenBCode.scala", "ScalacBackendInterface.scala", "BackendStats.scala") + + val backendSources0 = + sources(Files.list(backendDir), excludedFiles = backendExcluded) + val backendJvmSources0 = + sources(Files.list(backendJvmDir), excludedFiles = backendJvmExcluded) + + (compilerSources0, backendSources0, backendJvmSources0) + } } object CompilationTests { diff --git a/compiler/test/dotty/tools/dotc/LinkOptimiseTests.scala b/compiler/test/dotty/tools/dotc/LinkOptimiseTests.scala new file mode 100644 index 000000000000..70fa01c5f053 --- /dev/null +++ b/compiler/test/dotty/tools/dotc/LinkOptimiseTests.scala @@ -0,0 +1,87 @@ +package dotty +package tools +package dotc + +import java.io.{File => JFile} +import java.nio.file.{Files, Path, Paths} + +import org.junit.{AfterClass, Test} +import org.junit.Assert._ +import vulpix._ + +import scala.concurrent.duration._ +import scala.collection.JavaConverters._ + +class LinkOptimiseTests extends ParallelTesting { + import TestConfiguration._ + import LinkOptimiseTests._ + + // Test suite configuration -------------------------------------------------- + + def maxDuration = 30.seconds + def numberOfSlaves = 5 + def safeMode = Properties.testsSafeMode + def isInteractive = SummaryReport.isInteractive + def testFilter = Properties.testsFilter + + + @Test def linkOptimise: Unit = { + // Setup and compile libraries + def strawmanLibrary = + compileDir("../collection-strawman/src/main", defaultOptions) + def linkCustomLib = + compileDir("../tests/link/custom-lib", defaultOptions) + + val libraries = { + strawmanLibrary + + linkCustomLib + }.keepOutput.checkCompile() + + // Setup class paths + def mkLinkClassFlags(libPath: String) = + TestFlags(mkClassPath(libPath :: Jars.dottyTestDeps), mkClassPath(Jars.dottyTestDeps), basicDefaultOptions :+ "-Xlink-optimise") + val strawmanClassPath = mkLinkClassFlags(defaultOutputDir + "strawmanLibrary/main/") + val customLibClassFlags = mkLinkClassFlags(defaultOutputDir + "linkCustomLib/custom-lib") + + // Link tests + val linkDir = "../tests/link" + val linkStramanDir = linkDir + "/strawman" + val linkCustomLibDir = linkDir + "/on-custom-lib" + def linkStrawmanTest = compileFilesInDir(linkStramanDir, strawmanClassPath) + def linkCustomLibTest = compileFilesInDir(linkCustomLibDir, customLibClassFlags) + + def classFileChecks(sourceDir: String, testName: String) = { + val checkExt = ".classcheck" + for (check <- new JFile(sourceDir).listFiles().filter(_.toString.endsWith(checkExt))) { + val outDir = { + def path(str: String) = str.substring(linkDir.length, str.length - checkExt.length) + defaultOutputDir + testName + path(check.toString) + "/" + } + val expectedClasses = scala.io.Source.fromFile(check).getLines().toSet + val actualClasses = Files.walk(Paths.get(outDir)).iterator().asScala.collect { + case f if f.toString.endsWith(".class") => f.toString.substring(outDir.length, f.toString.length - ".class".length) + }.toSet + assertEquals(check.toString, expectedClasses, actualClasses) + } + } + + // Run all tests + val tests = { + linkStrawmanTest + + linkCustomLibTest + }.keepOutput.checkRuns() + + try { + classFileChecks(linkStramanDir, "linkStrawmanTest") + classFileChecks(linkCustomLibDir, "linkCustomLibTest") + } finally { + (libraries + tests).delete() + } + } + +} + +object LinkOptimiseTests { + implicit val summaryReport: SummaryReporting = new SummaryReport + @AfterClass def cleanup(): Unit = summaryReport.echoSummary() +} diff --git a/compiler/test/dotty/tools/dotc/MissingCoreLibTests.scala b/compiler/test/dotty/tools/dotc/MissingCoreLibTests.scala index 593121c6b0d4..9490abecdf94 100644 --- a/compiler/test/dotty/tools/dotc/MissingCoreLibTests.scala +++ b/compiler/test/dotty/tools/dotc/MissingCoreLibTests.scala @@ -2,9 +2,8 @@ package dotty package tools package dotc -import org.junit.{ Test, AfterClass } - -import vulpix.{ ParallelTesting, SummaryReport, SummaryReporting, TestConfiguration } +import org.junit.{AfterClass, Test} +import vulpix._ import scala.concurrent.duration._ @@ -22,8 +21,8 @@ class MissingCoreLibTests extends ParallelTesting { @Test def missingDottyLib: Unit = { val classPath = mkClassPath(Jars.dottyCompiler :: Jars.dottyInterfaces :: Jars.dottyExtras) // missing Jars.dottyLib - val options = noCheckOptions ++ checkOptions ++ yCheckOptions ++ classPath - compileFile("../tests/neg/nolib/Foo.scala", options).checkExpectedErrors() + val options = noCheckOptions ++ checkOptions ++ yCheckOptions + compileFile("../tests/neg/nolib/Foo.scala", TestFlags(classPath, options)).checkExpectedErrors() } } diff --git a/compiler/test/dotty/tools/dotc/transform/PatmatExhaustivityTest.scala b/compiler/test/dotty/tools/dotc/transform/PatmatExhaustivityTest.scala index ee2d05cc9ff7..e997ebedd85f 100644 --- a/compiler/test/dotty/tools/dotc/transform/PatmatExhaustivityTest.scala +++ b/compiler/test/dotty/tools/dotc/transform/PatmatExhaustivityTest.scala @@ -14,7 +14,7 @@ import vulpix.TestConfiguration class PatmatExhaustivityTest { val testsDir = "../tests/patmat" // stop-after: patmatexhaust-huge.scala crash compiler - val options = List("-color:never", "-Ystop-after:splitter", "-Ycheck-all-patmat") ++ TestConfiguration.classPath + val options = List("-color:never", "-Ystop-after:splitter", "-Ycheck-all-patmat", "-classpath", TestConfiguration.classPath) private def compileFile(file: File) = { val stringBuffer = new StringWriter() diff --git a/compiler/test/dotty/tools/vulpix/ParallelTesting.scala b/compiler/test/dotty/tools/vulpix/ParallelTesting.scala index 24b056b69653..b5534337e82b 100644 --- a/compiler/test/dotty/tools/vulpix/ParallelTesting.scala +++ b/compiler/test/dotty/tools/vulpix/ParallelTesting.scala @@ -50,17 +50,9 @@ trait ParallelTesting extends RunnerOrchestration { self => private sealed trait TestSource { self => def name: String def outDir: JFile - def flags: Array[String] - - def classPath: String = - outDir.getAbsolutePath + - flags - .dropWhile(_ != "-classpath") - .drop(1) - .headOption - .map(":" + _) - .getOrElse("") + def flags: TestFlags + def runClassPath: String = outDir.getAbsolutePath + ":" + flags.runClassPath def title: String = self match { case self: JointCompilationSource => @@ -74,11 +66,11 @@ trait ParallelTesting extends RunnerOrchestration { self => /** Adds the flags specified in `newFlags0` if they do not already exist */ def withFlags(newFlags0: String*) = { val newFlags = newFlags0.toArray - if (!flags.containsSlice(newFlags)) self match { + if (!flags.options.containsSlice(newFlags)) self match { case self: JointCompilationSource => - self.copy(flags = flags ++ newFlags) + self.copy(flags = flags.and(newFlags:_*)) case self: SeparateCompilationSource => - self.copy(flags = flags ++ newFlags) + self.copy(flags = flags.and(newFlags:_*)) } else self } @@ -95,7 +87,7 @@ trait ParallelTesting extends RunnerOrchestration { self => |the test can be reproduced by running:""".stripMargin ) sb.append("\n\n./bin/dotc ") - flags.foreach { arg => + flags.all.foreach { arg => if (lineLen > maxLen) { sb.append(" \\\n ") lineLen = 4 @@ -139,7 +131,7 @@ trait ParallelTesting extends RunnerOrchestration { self => private final case class JointCompilationSource( name: String, files: Array[JFile], - flags: Array[String], + flags: TestFlags, outDir: JFile ) extends TestSource { def sourceFiles: Array[JFile] = files.filter(isSourceFile) @@ -153,7 +145,7 @@ trait ParallelTesting extends RunnerOrchestration { self => private final case class SeparateCompilationSource( name: String, dir: JFile, - flags: Array[String], + flags: TestFlags, outDir: JFile ) extends TestSource { @@ -334,9 +326,9 @@ trait ParallelTesting extends RunnerOrchestration { self => } } - protected def compile(files0: Array[JFile], flags0: Array[String], suppressErrors: Boolean, targetDir: JFile): TestReporter = { + protected def compile(files0: Array[JFile], flags0: TestFlags, suppressErrors: Boolean, targetDir: JFile): TestReporter = { - val flags = flags0 ++ Array("-d", targetDir.getAbsolutePath) + val flags = flags0 and ("-d", targetDir.getAbsolutePath) def flattenFiles(f: JFile): Array[JFile] = if (f.isDirectory) f.listFiles.flatMap(flattenFiles) @@ -359,7 +351,7 @@ trait ParallelTesting extends RunnerOrchestration { self => "-encoding", "UTF-8", "-classpath", s".:${Jars.scalaLibrary}:${targetDir.getAbsolutePath}" - ) ++ flags.takeRight(2) ++ fs + ) ++ flags.all.takeRight(2) ++ fs val process = Runtime.getRuntime.exec(fullArgs) val output = Source.fromInputStream(process.getErrorStream).mkString @@ -387,7 +379,7 @@ trait ParallelTesting extends RunnerOrchestration { self => } } - val allArgs = addOutDir(flags) + val allArgs = addOutDir(flags.all) // Compile with a try to catch any StackTrace generated by the compiler: try { @@ -503,7 +495,7 @@ trait ParallelTesting extends RunnerOrchestration { self => private def verifyOutput(checkFile: Option[JFile], dir: JFile, testSource: TestSource, warnings: Int) = { if (Properties.testsNoRun) addNoRunWarning() - else runMain(testSource.classPath) match { + else runMain(testSource.runClassPath) match { case Success(_) if !checkFile.isDefined || !checkFile.get.exists => // success! case Success(output) => { val outputLines = output.lines.toArray @@ -1065,7 +1057,7 @@ trait ParallelTesting extends RunnerOrchestration { self => } /** Compiles a single file from the string path `f` using the supplied flags */ - def compileFile(f: String, flags: Array[String])(implicit outDirectory: String): CompilationTest = { + def compileFile(f: String, flags: TestFlags)(implicit outDirectory: String): CompilationTest = { val callingMethod = getCallingMethod() val sourceFile = new JFile(f) val parent = sourceFile.getParentFile @@ -1095,7 +1087,7 @@ trait ParallelTesting extends RunnerOrchestration { self => * By default, files are compiled in alphabetical order. An optional seed * can be used for randomization. */ - def compileDir(f: String, flags: Array[String], randomOrder: Option[Int] = None)(implicit outDirectory: String): CompilationTest = { + def compileDir(f: String, flags: TestFlags, randomOrder: Option[Int] = None)(implicit outDirectory: String): CompilationTest = { val callingMethod = getCallingMethod() val outDir = outDirectory + callingMethod + "/" val sourceDir = new JFile(f) @@ -1124,8 +1116,7 @@ trait ParallelTesting extends RunnerOrchestration { self => * `testName` since files can be in separate directories and or be otherwise * dissociated */ - def compileList(testName: String, files: List[String], flags: Array[String])(implicit outDirectory: String): CompilationTest = { - val callingMethod = getCallingMethod() + def compileList(testName: String, files: List[String], flags: TestFlags, callingMethod: String = getCallingMethod())(implicit outDirectory: String): CompilationTest = { val outDir = outDirectory + callingMethod + "/" + testName + "/" // Directories in which to compile all containing files with `flags`: @@ -1156,7 +1147,7 @@ trait ParallelTesting extends RunnerOrchestration { self => * - Directories can have an associated check-file, where the check file has * the same name as the directory (with the file extension `.check`) */ - def compileFilesInDir(f: String, flags: Array[String])(implicit outDirectory: String): CompilationTest = { + def compileFilesInDir(f: String, flags: TestFlags)(implicit outDirectory: String): CompilationTest = { val callingMethod = getCallingMethod() val outDir = outDirectory + callingMethod + "/" val sourceDir = new JFile(f) @@ -1176,7 +1167,7 @@ trait ParallelTesting extends RunnerOrchestration { self => * sub-directories and as such, does **not** perform separate compilation * tests. */ - def compileShallowFilesInDir(f: String, flags: Array[String])(implicit outDirectory: String): CompilationTest = { + def compileShallowFilesInDir(f: String, flags: TestFlags)(implicit outDirectory: String): CompilationTest = { val callingMethod = getCallingMethod() val outDir = outDirectory + callingMethod + "/" val sourceDir = new JFile(f) diff --git a/compiler/test/dotty/tools/vulpix/TestConfiguration.scala b/compiler/test/dotty/tools/vulpix/TestConfiguration.scala index 06022833dbb8..7a850c635e26 100644 --- a/compiler/test/dotty/tools/vulpix/TestConfiguration.scala +++ b/compiler/test/dotty/tools/vulpix/TestConfiguration.scala @@ -5,13 +5,6 @@ package vulpix object TestConfiguration { implicit val defaultOutputDir: String = "../out/" - implicit class RichStringArray(val xs: Array[String]) extends AnyVal { - def and(args: String*): Array[String] = { - val argsArr: Array[String] = args.toArray - xs ++ argsArr - } - } - val noCheckOptions = Array( "-pagewidth", "120", "-color:never" @@ -25,8 +18,8 @@ object TestConfiguration { val classPath = mkClassPath(Jars.dottyTestDeps) - def mkClassPath(deps: List[String]): Array[String] = { - val paths = deps map { p => + def mkClassPath(classPaths: List[String]): String = { + classPaths map { p => val file = new java.io.File(p) assert( file.exists, @@ -47,24 +40,23 @@ object TestConfiguration { |it in extras.""" ) file.getAbsolutePath - } mkString (":") - - Array("-classpath", paths) + } mkString(":") } val yCheckOptions = Array("-Ycheck:tailrec,resolveSuper,erasure,mixin,getClass,restoreScopes,labelDef") - val defaultUnoptimised = noCheckOptions ++ checkOptions ++ yCheckOptions ++ classPath - val defaultOptimised = defaultUnoptimised :+ "-optimise" + val basicDefaultOptions = checkOptions ++ noCheckOptions ++ yCheckOptions + val defaultUnoptimised = TestFlags(classPath, basicDefaultOptions) + val defaultOptimised = defaultUnoptimised and "-optimise" val defaultOptions = defaultUnoptimised - val allowDeepSubtypes = defaultOptions diff Array("-Yno-deep-subtypes") - val allowDoubleBindings = defaultOptions diff Array("-Yno-double-bindings") - val picklingOptions = defaultUnoptimised ++ Array( + val allowDeepSubtypes = defaultOptions without "-Yno-deep-subtypes" + val allowDoubleBindings = defaultOptions without "-Yno-double-bindings" + val picklingOptions = defaultUnoptimised and ( "-Xprint-types", "-Ytest-pickler", "-Yprintpos" ) - val scala2Mode = defaultOptions ++ Array("-language:Scala2") - val explicitUTF8 = defaultOptions ++ Array("-encoding", "UTF8") - val explicitUTF16 = defaultOptions ++ Array("-encoding", "UTF16") + val scala2Mode = defaultOptions and "-language:Scala2" + val explicitUTF8 = defaultOptions and ("-encoding", "UTF8") + val explicitUTF16 = defaultOptions and ("-encoding", "UTF16") } diff --git a/compiler/test/dotty/tools/vulpix/TestFlags.scala b/compiler/test/dotty/tools/vulpix/TestFlags.scala new file mode 100644 index 000000000000..ea83b82b4fd3 --- /dev/null +++ b/compiler/test/dotty/tools/vulpix/TestFlags.scala @@ -0,0 +1,19 @@ +package dotty.tools.vulpix + +final case class TestFlags( + defaultClassPath: String, + runClassPath: String, // class path that is used when running `run` tests (not compiling) + options: Array[String]) { + + def and(flags: String*): TestFlags = + TestFlags(defaultClassPath, runClassPath, options ++ flags) + + def without(flags: String*): TestFlags = + TestFlags(defaultClassPath, runClassPath, options diff flags) + + def all: Array[String] = Array("-classpath", defaultClassPath) ++ options +} + +object TestFlags { + def apply(classPath: String, flags: Array[String]): TestFlags = TestFlags(classPath, classPath, flags) +} diff --git a/tests/link/custom-lib/EmptyClass.scala b/tests/link/custom-lib/EmptyClass.scala new file mode 100644 index 000000000000..57120ebef341 --- /dev/null +++ b/tests/link/custom-lib/EmptyClass.scala @@ -0,0 +1 @@ +class EmptyClass diff --git a/tests/link/custom-lib/EmptyObject.scala b/tests/link/custom-lib/EmptyObject.scala new file mode 100644 index 000000000000..82c1becaebb6 --- /dev/null +++ b/tests/link/custom-lib/EmptyObject.scala @@ -0,0 +1 @@ +object EmptyObject diff --git a/tests/link/custom-lib/EmptyTrait.scala b/tests/link/custom-lib/EmptyTrait.scala new file mode 100644 index 000000000000..fcd1eed0c6af --- /dev/null +++ b/tests/link/custom-lib/EmptyTrait.scala @@ -0,0 +1 @@ +trait EmptyTrait diff --git a/tests/link/custom-lib/foo/package.scala b/tests/link/custom-lib/foo/package.scala new file mode 100644 index 000000000000..e577e7c95e5c --- /dev/null +++ b/tests/link/custom-lib/foo/package.scala @@ -0,0 +1,3 @@ +package object foo { + def bar = 42 +} diff --git a/tests/link/custom-lib/strawman/collection/Map2.scala b/tests/link/custom-lib/strawman/collection/Map2.scala new file mode 100644 index 000000000000..919df2fccdf1 --- /dev/null +++ b/tests/link/custom-lib/strawman/collection/Map2.scala @@ -0,0 +1,12 @@ +package strawman.collection + +trait Map2[K] { + def get(k: K): K = k + def foo: K = { + this match { + case that: Map2[b] => that.get(3.asInstanceOf[b]) + // case that: Map2[b] => that.get(3.asInstanceOf[K]) // FIXME + case _ => get(5.asInstanceOf[K]) + } + } +} diff --git a/tests/link/custom-lib/strawman/collection/mutable/Builder2.scala b/tests/link/custom-lib/strawman/collection/mutable/Builder2.scala new file mode 100644 index 000000000000..d9998a7b70a9 --- /dev/null +++ b/tests/link/custom-lib/strawman/collection/mutable/Builder2.scala @@ -0,0 +1,9 @@ +package strawman.collection.mutable + +trait Builder2[To] { + def result(): To +} + +class StringBuilder2 extends Builder2[String] { + def result() = "hello" +} diff --git a/tests/link/on-custom-lib/builder2.scala b/tests/link/on-custom-lib/builder2.scala new file mode 100644 index 000000000000..88c447900ca6 --- /dev/null +++ b/tests/link/on-custom-lib/builder2.scala @@ -0,0 +1,10 @@ + +import strawman.collection.mutable._ + +object Test { + def main(args: Array[String]): Unit = { + // FIXME issue loading class inheriting a type parameter +// val sb2 = new StringBuilder2 +// println(sb2.result()) + } +} diff --git a/tests/link/on-custom-lib/loadClass.classcheck b/tests/link/on-custom-lib/loadClass.classcheck new file mode 100644 index 000000000000..5ae464739d55 --- /dev/null +++ b/tests/link/on-custom-lib/loadClass.classcheck @@ -0,0 +1,3 @@ +EmptyClass +Test +Test$ diff --git a/tests/link/on-custom-lib/loadClass.scala b/tests/link/on-custom-lib/loadClass.scala new file mode 100644 index 000000000000..b3dfec17001a --- /dev/null +++ b/tests/link/on-custom-lib/loadClass.scala @@ -0,0 +1,5 @@ +object Test { + def main(args: Array[String]): Unit = { + new EmptyClass + } +} diff --git a/tests/link/on-custom-lib/loadObject.classcheck b/tests/link/on-custom-lib/loadObject.classcheck new file mode 100644 index 000000000000..665b6610bca3 --- /dev/null +++ b/tests/link/on-custom-lib/loadObject.classcheck @@ -0,0 +1,4 @@ +EmptyObject +EmptyObject$ +Test +Test$ diff --git a/tests/link/on-custom-lib/loadObject.scala b/tests/link/on-custom-lib/loadObject.scala new file mode 100644 index 000000000000..ac10654de681 --- /dev/null +++ b/tests/link/on-custom-lib/loadObject.scala @@ -0,0 +1,5 @@ +object Test { + def main(args: Array[String]): Unit = { + EmptyObject + } +} diff --git a/tests/link/on-custom-lib/loadOnExtends.classcheck b/tests/link/on-custom-lib/loadOnExtends.classcheck new file mode 100644 index 000000000000..e62ba9b04ca2 --- /dev/null +++ b/tests/link/on-custom-lib/loadOnExtends.classcheck @@ -0,0 +1,4 @@ +EmptyClass +MyClass +Test +Test$ diff --git a/tests/link/on-custom-lib/loadOnExtends.scala b/tests/link/on-custom-lib/loadOnExtends.scala new file mode 100644 index 000000000000..b50a7ef5df97 --- /dev/null +++ b/tests/link/on-custom-lib/loadOnExtends.scala @@ -0,0 +1,7 @@ +object Test { + def main(args: Array[String]): Unit = { + new MyClass + } +} + +class MyClass extends EmptyClass diff --git a/tests/link/on-custom-lib/loadOnExtendsTrait.classcheck b/tests/link/on-custom-lib/loadOnExtendsTrait.classcheck new file mode 100644 index 000000000000..5c351e373edd --- /dev/null +++ b/tests/link/on-custom-lib/loadOnExtendsTrait.classcheck @@ -0,0 +1,4 @@ +EmptyTrait +MyClass +Test +Test$ diff --git a/tests/link/on-custom-lib/loadOnExtendsTrait.scala b/tests/link/on-custom-lib/loadOnExtendsTrait.scala new file mode 100644 index 000000000000..ceaa6ca3b7fc --- /dev/null +++ b/tests/link/on-custom-lib/loadOnExtendsTrait.scala @@ -0,0 +1,7 @@ +object Test { + def main(args: Array[String]): Unit = { + new MyClass + } +} + +class MyClass extends EmptyTrait diff --git a/tests/link/on-custom-lib/loadPackageObject.classcheck b/tests/link/on-custom-lib/loadPackageObject.classcheck new file mode 100644 index 000000000000..abae15084bc7 --- /dev/null +++ b/tests/link/on-custom-lib/loadPackageObject.classcheck @@ -0,0 +1,4 @@ +foo/package +foo/package$ +Test +Test$ diff --git a/tests/link/on-custom-lib/loadPackageObject.scala b/tests/link/on-custom-lib/loadPackageObject.scala new file mode 100644 index 000000000000..ba6342f3062e --- /dev/null +++ b/tests/link/on-custom-lib/loadPackageObject.scala @@ -0,0 +1,5 @@ +object Test { + def main(args: Array[String]): Unit = { + println(foo.bar) + } +} diff --git a/tests/link/on-custom-lib/loadRefsInParents.classcheck b/tests/link/on-custom-lib/loadRefsInParents.classcheck new file mode 100644 index 000000000000..e9a49a9f7bee --- /dev/null +++ b/tests/link/on-custom-lib/loadRefsInParents.classcheck @@ -0,0 +1,5 @@ +A +B +EmptyClass +Test +Test$ diff --git a/tests/link/on-custom-lib/loadRefsInParents.scala b/tests/link/on-custom-lib/loadRefsInParents.scala new file mode 100644 index 000000000000..7d5c940e6d32 --- /dev/null +++ b/tests/link/on-custom-lib/loadRefsInParents.scala @@ -0,0 +1,9 @@ +object Test { + def main(args: Array[String]): Unit = { + new B + } +} + +class A(x: Int) + +class B extends A({ class Foo extends EmptyClass; 1 }) diff --git a/tests/link/on-custom-lib/map2.classcheck b/tests/link/on-custom-lib/map2.classcheck new file mode 100644 index 000000000000..7a429a9ff68b --- /dev/null +++ b/tests/link/on-custom-lib/map2.classcheck @@ -0,0 +1,2 @@ +Test +Test$ diff --git a/tests/link/on-custom-lib/map2.scala b/tests/link/on-custom-lib/map2.scala new file mode 100644 index 000000000000..7a3b41dd4e88 --- /dev/null +++ b/tests/link/on-custom-lib/map2.scala @@ -0,0 +1,9 @@ +import strawman.collection._ + +object Test { + def main(args: Array[String]): Unit = { + // FIXME: Issue when loading type bindings from Tasty + // val map2 = new Map2[Int] {} + // println(map2.foo) + } +} diff --git a/tests/link/strawman/hashing.check b/tests/link/strawman/hashing.check new file mode 100644 index 000000000000..8b188daa07c3 --- /dev/null +++ b/tests/link/strawman/hashing.check @@ -0,0 +1 @@ +-106205 diff --git a/tests/link/strawman/hashing.classcheck b/tests/link/strawman/hashing.classcheck new file mode 100644 index 000000000000..bfb1a7c04485 --- /dev/null +++ b/tests/link/strawman/hashing.classcheck @@ -0,0 +1,6 @@ +strawman/collection/Hash +strawman/collection/Hash$ +strawman/collection/Hashing +strawman/collection/Hashing$ +Test +Test$ diff --git a/tests/link/strawman/hashing.scala b/tests/link/strawman/hashing.scala new file mode 100644 index 000000000000..fe2468bbf75f --- /dev/null +++ b/tests/link/strawman/hashing.scala @@ -0,0 +1,13 @@ +object Test { + def main(args: Array[String]): Unit = { + println(strawman.collection.Hash.hash(42)) + } +} + +package strawman { + package collection { + object Hash { + def hash(n: Int) = Hashing.computeHash(n) + } + } +} diff --git a/tests/link/strawman/iterator-1.check b/tests/link/strawman/iterator-1.check new file mode 100644 index 000000000000..d65bc6d6ad1f --- /dev/null +++ b/tests/link/strawman/iterator-1.check @@ -0,0 +1,2 @@ +true +3 diff --git a/tests/link/strawman/iterator-1.classcheck b/tests/link/strawman/iterator-1.classcheck new file mode 100644 index 000000000000..7a429a9ff68b --- /dev/null +++ b/tests/link/strawman/iterator-1.classcheck @@ -0,0 +1,2 @@ +Test +Test$ diff --git a/tests/link/strawman/iterator-1.scala b/tests/link/strawman/iterator-1.scala new file mode 100644 index 000000000000..59ddd8e428a8 --- /dev/null +++ b/tests/link/strawman/iterator-1.scala @@ -0,0 +1,17 @@ +import strawman.collection.Iterator + +object Test { + def main(args: Array[String]): Unit = { + // FIXME: issue loading package object +// val it = new MyIterator +// println(it.hasNext) +// println(it.next()) + println(true) + println(3) + } +} + +//class MyIterator extends Iterator[Int] { +// override def hasNext: Boolean = true +// override def next(): Int = 3 +//} diff --git a/tests/link/strawman/nil.classcheck b/tests/link/strawman/nil.classcheck new file mode 100644 index 000000000000..7a429a9ff68b --- /dev/null +++ b/tests/link/strawman/nil.classcheck @@ -0,0 +1,2 @@ +Test +Test$ diff --git a/tests/link/strawman/nil.scala b/tests/link/strawman/nil.scala new file mode 100644 index 000000000000..3abc81bc0393 --- /dev/null +++ b/tests/link/strawman/nil.scala @@ -0,0 +1,6 @@ +object Test { + def main(args: Array[String]): Unit = { + // FIXME + // strawman.collection.immutable.Nil + } +} diff --git a/tests/link/strawman/red-black.classcheck b/tests/link/strawman/red-black.classcheck new file mode 100644 index 000000000000..e9ff6169134d --- /dev/null +++ b/tests/link/strawman/red-black.classcheck @@ -0,0 +1,4 @@ +strawman/collection/immutable/RedBlack +strawman/collection/immutable/RedBlack$ +Test +Test$ diff --git a/tests/link/strawman/red-black.scala b/tests/link/strawman/red-black.scala new file mode 100644 index 000000000000..9f9b1e59afa9 --- /dev/null +++ b/tests/link/strawman/red-black.scala @@ -0,0 +1,15 @@ +object Test { + def main(args: Array[String]): Unit = { + strawman.collection.immutable.RedBlack + } +} + +package strawman { + package collection { + package immutable { + object RedBlack { + // RedBlackTree + } + } + } +}