From 7b0b24542456edd617c449c6c4f7fa4e47ea06d0 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Sun, 23 Jul 2017 17:54:10 +0200 Subject: [PATCH 01/14] Load compilation units from tasty --- compiler/src/dotty/tools/dotc/Compiler.scala | 1 + compiler/src/dotty/tools/dotc/FromTasty.scala | 25 +++++--- .../tools/dotc/config/ScalaSettings.scala | 1 + .../tools/dotc/core/SymDenotations.scala | 4 ++ .../dotc/core/classfile/ClassfileParser.scala | 11 ++++ .../dotty/tools/dotc/transform/LinkAll.scala | 63 +++++++++++++++++++ 6 files changed, 98 insertions(+), 7 deletions(-) create mode 100644 compiler/src/dotty/tools/dotc/transform/LinkAll.scala diff --git a/compiler/src/dotty/tools/dotc/Compiler.scala b/compiler/src/dotty/tools/dotc/Compiler.scala index 97e4833c5fbe..34d95b0a297f 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), // Link all 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..c9af879a883e 100644 --- a/compiler/src/dotty/tools/dotc/FromTasty.scala +++ b/compiler/src/dotty/tools/dotc/FromTasty.scala @@ -85,13 +85,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) mkUnit(clsd, unpickled) + 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") } @@ -101,4 +96,20 @@ object FromTasty extends Driver { } } } + + def loadCompilationUnit(clsd: ClassDenotation)(implicit ctx: Context): Option[CompilationUnit] = { + assert(ctx.settings.XlinkOptimise.value) + clsd.dottyUnpickler.flatMap { unpickler => + ctx.log("Loading compilation unit for: " + clsd) + val body = unpickler.body(ctx.addMode(Mode.ReadPositions)) + body.headOption.map(unpickled => mkUnit(clsd, unpickled)) + } + } + + private def mkUnit(clsd: ClassDenotation, unpickled: Tree)(implicit ctx: Context): CompilationUnit = { + val unit1 = new CompilationUnit(new SourceFile(clsd.symbol.sourceFile, Seq())) + unit1.tpdTree = unpickled + force.traverse(unit1.tpdTree) + unit1 + } } diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index b96bdb47f160..d6cb7daf7480 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", "Link class files.").withAbbreviation("-Xlink-optimize") /** Dottydoc specific settings */ val siteRoot = StringSetting( diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index 473ae32f9dc1..ced081e58237 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -19,6 +19,8 @@ import java.util.WeakHashMap import config.Config import config.Printers.{completions, incremental, noPrinter} +import dotty.tools.dotc.core.tasty.DottyUnpickler + trait SymDenotations { this: Context => import SymDenotations._ @@ -139,6 +141,8 @@ object SymDenotations { private[this] var myPrivateWithin: Symbol = initPrivateWithin private[this] var myAnnotations: List[Annotation] = Nil + private[dotc] var dottyUnpickler: Option[DottyUnpickler] = None + /** The owner of the symbol; overridden in NoDenotation */ def owner: Symbol = ownerIfExists diff --git a/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala b/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala index 4c73115cac50..0825c91de0da 100644 --- a/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala +++ b/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala @@ -15,6 +15,7 @@ import scala.annotation.switch import typer.Checking.checkNonCyclic import io.AbstractFile import scala.util.control.NonFatal +import tasty.DottyUnpickler object ClassfileParser { /** Marker trait for unpicklers that can be embedded in classfiles. */ @@ -162,6 +163,16 @@ class ClassfileParser( } } + if (ctx.settings.XlinkOptimise.value) { + // Save references to DottyUnpickler to be able to load compilation units from tasty + result match { + case result@Some(_: DottyUnpickler) => + classRoot.dottyUnpickler = result.asInstanceOf[Option[DottyUnpickler]] + moduleRoot.dottyUnpickler = result.asInstanceOf[Option[DottyUnpickler]] + case _ => + } + } + // eager load java enum definitions for exhaustivity check of pattern match if (isEnum) { instanceScope.toList.map(_.ensureCompleted()) 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..0fb815ca9498 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/LinkAll.scala @@ -0,0 +1,63 @@ +package dotty.tools.dotc.transform + +import dotty.tools.dotc.{CompilationUnit, FromTasty} +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._ + + override def phaseName = "linkAll" + + override def prepareForUnit(tree: tpd.Tree)(implicit ctx: Context): TreeTransform = NoTransform + + override def runOn(units: List[CompilationUnit])(implicit ctx: Context): List[CompilationUnit] = + if (ctx.settings.XlinkOptimise.value) super.runOn(allUnits(Set.empty, units.toSet, Set.empty)) + else super.runOn(units) + + /** Loads and processes new compilation units, possibly loading more units. */ + private 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 => FromTasty.loadCompilationUnit(cls)) + allUnits(processed ++ unprocessed, loadedUnits, loadedClasses ++ classesToLoad) + } + } + + /** 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 + } + } +} From 96747c84d3fc5338476dac8ab062dc2ec9f6c819 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Mon, 24 Jul 2017 14:18:25 +0200 Subject: [PATCH 02/14] Add link tests --- .../tools/dotc/config/ScalaSettings.scala | 1 + .../dotty/tools/dotc/CompilationTests.scala | 76 ++++++++++++++++++- .../dotty/tools/vulpix/ParallelTesting.scala | 13 +++- .../tools/vulpix/TestConfiguration.scala | 10 ++- tests/link/custom-lib/EmptyClass.scala | 1 + tests/link/custom-lib/EmptyObject.scala | 1 + tests/link/custom-lib/EmptyTrait.scala | 1 + tests/link/custom-lib/Map2.scala | 9 +++ tests/link/custom-lib/foo/package.scala | 3 + tests/link/on-custom-lib/loadClass.classcheck | 3 + tests/link/on-custom-lib/loadClass.scala | 5 ++ .../link/on-custom-lib/loadObject.classcheck | 4 + tests/link/on-custom-lib/loadObject.scala | 5 ++ .../on-custom-lib/loadOnExtends.classcheck | 4 + tests/link/on-custom-lib/loadOnExtends.scala | 7 ++ .../loadOnExtendsTrait.classcheck | 4 + .../on-custom-lib/loadOnExtendsTrait.scala | 7 ++ .../loadPackageObject.classcheck | 4 + .../on-custom-lib/loadPackageObject.scala | 5 ++ tests/link/on-custom-lib/map2.classcheck | 2 + tests/link/on-custom-lib/map2.scala | 7 ++ tests/link/strawman/hashing.check | 1 + tests/link/strawman/hashing.classcheck | 6 ++ tests/link/strawman/hashing.scala | 13 ++++ tests/link/strawman/iterator-1.check | 2 + tests/link/strawman/iterator-1.classcheck | 2 + tests/link/strawman/iterator-1.scala | 17 +++++ tests/link/strawman/nil.classcheck | 2 + tests/link/strawman/nil.scala | 6 ++ tests/link/strawman/red-black.classcheck | 4 + tests/link/strawman/red-black.scala | 15 ++++ 31 files changed, 232 insertions(+), 8 deletions(-) create mode 100644 tests/link/custom-lib/EmptyClass.scala create mode 100644 tests/link/custom-lib/EmptyObject.scala create mode 100644 tests/link/custom-lib/EmptyTrait.scala create mode 100644 tests/link/custom-lib/Map2.scala create mode 100644 tests/link/custom-lib/foo/package.scala create mode 100644 tests/link/on-custom-lib/loadClass.classcheck create mode 100644 tests/link/on-custom-lib/loadClass.scala create mode 100644 tests/link/on-custom-lib/loadObject.classcheck create mode 100644 tests/link/on-custom-lib/loadObject.scala create mode 100644 tests/link/on-custom-lib/loadOnExtends.classcheck create mode 100644 tests/link/on-custom-lib/loadOnExtends.scala create mode 100644 tests/link/on-custom-lib/loadOnExtendsTrait.classcheck create mode 100644 tests/link/on-custom-lib/loadOnExtendsTrait.scala create mode 100644 tests/link/on-custom-lib/loadPackageObject.classcheck create mode 100644 tests/link/on-custom-lib/loadPackageObject.scala create mode 100644 tests/link/on-custom-lib/map2.classcheck create mode 100644 tests/link/on-custom-lib/map2.scala create mode 100644 tests/link/strawman/hashing.check create mode 100644 tests/link/strawman/hashing.classcheck create mode 100644 tests/link/strawman/hashing.scala create mode 100644 tests/link/strawman/iterator-1.check create mode 100644 tests/link/strawman/iterator-1.classcheck create mode 100644 tests/link/strawman/iterator-1.scala create mode 100644 tests/link/strawman/nil.classcheck create mode 100644 tests/link/strawman/nil.scala create mode 100644 tests/link/strawman/red-black.classcheck create mode 100644 tests/link/strawman/red-black.scala diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index d6cb7daf7480..171e88b6ce5b 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -102,6 +102,7 @@ class ScalaSettings extends Settings.SettingGroup { val YcheckAllPatmat = BooleanSetting("-Ycheck-all-patmat", "Check exhaustivity and redundancy of all pattern matching (used for testing the algorithm)") val YretainTrees = BooleanSetting("-Yretain-trees", "Retain trees for top-level classes, accessible from ClassSymbol#tree") val YshowTreeIds = BooleanSetting("-Yshow-tree-ids", "Uniquely tag all tree nodes in debugging output.") + val YRunClasspath = PathSetting("-YRunClasspath", "Specify where to find user class files while executing run tests.", defaultClasspath) /** Area-specific debug output */ val Yexplainlowlevel = BooleanSetting("-Yexplain-lowlevel", "When explaining type errors, show types at a lower level.") diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index d8cfee48c423..6c34c456a44d 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -3,6 +3,7 @@ package tools package dotc import org.junit.{ Test, BeforeClass, AfterClass } +import org.junit.Assert._ import java.nio.file._ import java.util.stream.{ Stream => JStream } @@ -11,6 +12,7 @@ import scala.util.matching.Regex import scala.concurrent.duration._ import vulpix.{ ParallelTesting, SummaryReport, SummaryReporting, TestConfiguration } +import dotty.tools.io.JFile class CompilationTests extends ParallelTesting { @@ -29,7 +31,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) + @@ -292,6 +293,79 @@ class CompilationTests extends ParallelTesting { tests.foreach(_.delete()) } + + @Test def linkAll: 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 mkLinkClassPath(libPath: String) = + mkClassPath(libPath :: Jars.dottyTestDeps) ++ mkClassPath(Jars.dottyTestDeps, "-YRunClasspath") + val strawmanClassPath = mkLinkClassPath(defaultOutputDir + "strawmanLibrary/main/") + val customLibClassPath = mkLinkClassPath(defaultOutputDir + "linkCustomLib/custom-lib") + + // Link tests + val linkDir = "../tests/link" + val linkStramanDir = linkDir + "/strawman" + val linkCustomLibDir = linkDir + "/on-custom-lib" + def linkStrawmanTest = compileFilesInDir(linkStramanDir, basicLinkOptimise ++ strawmanClassPath) + def linkCustomLibTest = compileFilesInDir(linkCustomLibDir, basicLinkOptimise ++ customLibClassPath) + + 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() + + classFileChecks(linkStramanDir, "linkStrawmanTest") + classFileChecks(linkCustomLibDir, "linkCustomLibTest") + + (libraries + tests).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/vulpix/ParallelTesting.scala b/compiler/test/dotty/tools/vulpix/ParallelTesting.scala index 24b056b69653..4707097d1350 100644 --- a/compiler/test/dotty/tools/vulpix/ParallelTesting.scala +++ b/compiler/test/dotty/tools/vulpix/ParallelTesting.scala @@ -61,6 +61,14 @@ trait ParallelTesting extends RunnerOrchestration { self => .map(":" + _) .getOrElse("") + def runClassPath = { + flags + .dropWhile(_ != "-YRunClasspath") + .drop(1) + .headOption + .map(outDir.getAbsolutePath + ":" + _) + .getOrElse(classPath) + } def title: String = self match { case self: JointCompilationSource => @@ -503,7 +511,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 @@ -1124,8 +1132,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: Array[String], callingMethod: String = getCallingMethod())(implicit outDirectory: String): CompilationTest = { val outDir = outDirectory + callingMethod + "/" + testName + "/" // Directories in which to compile all containing files with `flags`: diff --git a/compiler/test/dotty/tools/vulpix/TestConfiguration.scala b/compiler/test/dotty/tools/vulpix/TestConfiguration.scala index 06022833dbb8..cf9fc6dfd955 100644 --- a/compiler/test/dotty/tools/vulpix/TestConfiguration.scala +++ b/compiler/test/dotty/tools/vulpix/TestConfiguration.scala @@ -25,8 +25,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], classPathFlag: String = "-classpath"): Array[String] = { + val paths = classPaths map { p => val file = new java.io.File(p) assert( file.exists, @@ -49,12 +49,13 @@ object TestConfiguration { file.getAbsolutePath } mkString (":") - Array("-classpath", paths) + Array(classPathFlag, paths) } val yCheckOptions = Array("-Ycheck:tailrec,resolveSuper,erasure,mixin,getClass,restoreScopes,labelDef") - val defaultUnoptimised = noCheckOptions ++ checkOptions ++ yCheckOptions ++ classPath + val basicDefaultOptions = noCheckOptions ++ checkOptions ++ yCheckOptions + val defaultUnoptimised = basicDefaultOptions ++ classPath val defaultOptimised = defaultUnoptimised :+ "-optimise" val defaultOptions = defaultUnoptimised val allowDeepSubtypes = defaultOptions diff Array("-Yno-deep-subtypes") @@ -67,4 +68,5 @@ object TestConfiguration { val scala2Mode = defaultOptions ++ Array("-language:Scala2") val explicitUTF8 = defaultOptions ++ Array("-encoding", "UTF8") val explicitUTF16 = defaultOptions ++ Array("-encoding", "UTF16") + val basicLinkOptimise = basicDefaultOptions ++ Array("-Xlink-optimise") } 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/Map2.scala b/tests/link/custom-lib/Map2.scala new file mode 100644 index 000000000000..80c1a0906ebc --- /dev/null +++ b/tests/link/custom-lib/Map2.scala @@ -0,0 +1,9 @@ +trait Map2[K] { + def get(k: K): K = k + def foo: K = { + this match { + case that: Map2[b] => that.get(3.asInstanceOf[b]) + case _ => get(5.asInstanceOf[K]) + } + } +} 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/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/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..d1e630010318 --- /dev/null +++ b/tests/link/on-custom-lib/map2.scala @@ -0,0 +1,7 @@ +object Test { + def main(args: Array[String]): Unit = { + // FIMXE +// 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 + } + } + } +} From da829a37786c0e4cc5f0d7f05d9d992e0e032c27 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Tue, 25 Jul 2017 08:49:08 +0200 Subject: [PATCH 03/14] Unify tree loaders --- compiler/src/dotty/tools/dotc/FromTasty.scala | 7 +-- .../tools/dotc/core/SymDenotations.scala | 4 -- .../dotty/tools/dotc/core/SymbolLoaders.scala | 14 +++--- .../src/dotty/tools/dotc/core/Symbols.scala | 44 ++++++++++++------- .../dotc/core/classfile/ClassfileParser.scala | 11 ----- .../src/dotty/tools/dotc/typer/Typer.scala | 5 +-- 6 files changed, 43 insertions(+), 42 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/FromTasty.scala b/compiler/src/dotty/tools/dotc/FromTasty.scala index c9af879a883e..5acc1c3454ec 100644 --- a/compiler/src/dotty/tools/dotc/FromTasty.scala +++ b/compiler/src/dotty/tools/dotc/FromTasty.scala @@ -99,10 +99,11 @@ object FromTasty extends Driver { def loadCompilationUnit(clsd: ClassDenotation)(implicit ctx: Context): Option[CompilationUnit] = { assert(ctx.settings.XlinkOptimise.value) - clsd.dottyUnpickler.flatMap { unpickler => + val tree = clsd.symbol.asClass.unitTree + if (tree.isEmpty) None + else { ctx.log("Loading compilation unit for: " + clsd) - val body = unpickler.body(ctx.addMode(Mode.ReadPositions)) - body.headOption.map(unpickled => mkUnit(clsd, unpickled)) + Some(mkUnit(clsd, tree)) } } diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index ced081e58237..473ae32f9dc1 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -19,8 +19,6 @@ import java.util.WeakHashMap import config.Config import config.Printers.{completions, incremental, noPrinter} -import dotty.tools.dotc.core.tasty.DottyUnpickler - trait SymDenotations { this: Context => import SymDenotations._ @@ -141,8 +139,6 @@ object SymDenotations { private[this] var myPrivateWithin: Symbol = initPrivateWithin private[this] var myAnnotations: List[Annotation] = Nil - private[dotc] var dottyUnpickler: Option[DottyUnpickler] = None - /** The owner of the symbol; overridden in NoDenotation */ def owner: Symbol = ownerIfExists 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..e945d285d74b 100644 --- a/compiler/src/dotty/tools/dotc/core/Symbols.scala +++ b/compiler/src/dotty/tools/dotc/core/Symbols.scala @@ -558,32 +558,44 @@ object Symbols { /** 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 */ = { + def tree(implicit ctx: Context): tpd.Tree /* tpd.TypeDef | tpd.EmptyTree */ = { + 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 t = unitTree + if (t.isEmpty) t + else findTree(tree).get + } + + /** If this is a top-level class, and if `-Yretain-trees` or `-YlinkOptimise` is set, + * return the PackageDef tree for this class, otherwise EmptyTree. + * This will force the info of the class. + */ + def unitTree(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 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/core/classfile/ClassfileParser.scala b/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala index 0825c91de0da..4c73115cac50 100644 --- a/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala +++ b/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala @@ -15,7 +15,6 @@ import scala.annotation.switch import typer.Checking.checkNonCyclic import io.AbstractFile import scala.util.control.NonFatal -import tasty.DottyUnpickler object ClassfileParser { /** Marker trait for unpicklers that can be embedded in classfiles. */ @@ -163,16 +162,6 @@ class ClassfileParser( } } - if (ctx.settings.XlinkOptimise.value) { - // Save references to DottyUnpickler to be able to load compilation units from tasty - result match { - case result@Some(_: DottyUnpickler) => - classRoot.dottyUnpickler = result.asInstanceOf[Option[DottyUnpickler]] - moduleRoot.dottyUnpickler = result.asInstanceOf[Option[DottyUnpickler]] - case _ => - } - } - // eager load java enum definitions for exhaustivity check of pattern match if (isEnum) { instanceScope.toList.map(_.ensureCompleted()) 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 From 7bbc52eb92eb0262023645722578d068c3d2181d Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Tue, 25 Jul 2017 13:52:30 +0200 Subject: [PATCH 04/14] Move loadCompilationUnit out of FromTasty --- .../dotty/tools/dotc/CompilationUnit.scala | 19 +++++++++++++++ compiler/src/dotty/tools/dotc/FromTasty.scala | 23 +------------------ .../dotty/tools/dotc/transform/LinkAll.scala | 17 +++++++++++++- 3 files changed, 36 insertions(+), 23 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/CompilationUnit.scala b/compiler/src/dotty/tools/dotc/CompilationUnit.scala index da8b3923be1e..834d271c96ed 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,19 @@ 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)(implicit ctx: Context): CompilationUnit = { + val unit1 = new CompilationUnit(new SourceFile(clsd.symbol.sourceFile, Seq())) + unit1.tpdTree = unpickled + 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/FromTasty.scala b/compiler/src/dotty/tools/dotc/FromTasty.scala index 5acc1c3454ec..e31a7daeac2d 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,7 +81,7 @@ object FromTasty extends Driver { case info: ClassfileLoader => info.load(clsd) val unpickled = clsd.symbol.asClass.tree - if (unpickled != null) mkUnit(clsd, unpickled) + if (unpickled != null) CompilationUnit.mkCompilationUnit(clsd, unpickled) 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") @@ -96,21 +92,4 @@ object FromTasty extends Driver { } } } - - def loadCompilationUnit(clsd: ClassDenotation)(implicit ctx: Context): Option[CompilationUnit] = { - assert(ctx.settings.XlinkOptimise.value) - val tree = clsd.symbol.asClass.unitTree - if (tree.isEmpty) None - else { - ctx.log("Loading compilation unit for: " + clsd) - Some(mkUnit(clsd, tree)) - } - } - - private def mkUnit(clsd: ClassDenotation, unpickled: Tree)(implicit ctx: Context): CompilationUnit = { - val unit1 = new CompilationUnit(new SourceFile(clsd.symbol.sourceFile, Seq())) - unit1.tpdTree = unpickled - force.traverse(unit1.tpdTree) - unit1 - } } diff --git a/compiler/src/dotty/tools/dotc/transform/LinkAll.scala b/compiler/src/dotty/tools/dotc/transform/LinkAll.scala index 0fb815ca9498..d9ddbdcfd895 100644 --- a/compiler/src/dotty/tools/dotc/transform/LinkAll.scala +++ b/compiler/src/dotty/tools/dotc/transform/LinkAll.scala @@ -16,6 +16,7 @@ import dotty.tools.dotc.transform.TreeTransforms._ */ class LinkAll extends MiniPhaseTransform { import tpd._ + import LinkAll._ override def phaseName = "linkAll" @@ -31,7 +32,7 @@ class LinkAll extends MiniPhaseTransform { 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 => FromTasty.loadCompilationUnit(cls)) + val loadedUnits = classesToLoad.flatMap(cls => loadCompilationUnit(cls)) allUnits(processed ++ unprocessed, loadedUnits, loadedClasses ++ classesToLoad) } } @@ -61,3 +62,17 @@ class LinkAll extends MiniPhaseTransform { } } } + +object LinkAll { + + private[LinkAll] def loadCompilationUnit(clsd: ClassDenotation)(implicit ctx: Context): Option[CompilationUnit] = { + assert(ctx.settings.XlinkOptimise.value) + val tree = clsd.symbol.asClass.unitTree + if (tree.isEmpty) None + else { + ctx.log("Loading compilation unit for: " + clsd) + Some(CompilationUnit.mkCompilationUnit(clsd, tree)) + } + } + +} From 23e2821a40a24553e4614135ef3457211e69d157 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Fri, 4 Aug 2017 14:55:40 +0200 Subject: [PATCH 05/14] Remove empty packages loaded from tasty --- compiler/src/dotty/tools/dotc/transform/FirstTransform.scala | 3 +++ 1 file changed, 3 insertions(+) diff --git a/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala b/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala index 745dc495b76e..f4f3c7602b8a 100644 --- a/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala +++ b/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala @@ -154,6 +154,9 @@ class FirstTransform extends MiniPhaseTransform with InfoTransformer with Annota cpy.Template(impl)(self = EmptyValDef) } + override def transformPackageDef(tree: PackageDef)(implicit ctx: Context, info: TransformerInfo): Tree = + if (tree.stats.isEmpty) theEmptyTree else tree + override def transformDefDef(ddef: DefDef)(implicit ctx: Context, info: TransformerInfo) = { if (ddef.symbol.hasAnnotation(defn.NativeAnnot)) { ddef.symbol.resetFlag(Deferred) From f52541e85a3d266397ff1d5f51d2cb6ff531987e Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Fri, 4 Aug 2017 14:56:26 +0200 Subject: [PATCH 06/14] Update tests --- .../custom-lib/{ => strawman/collection}/Map2.scala | 3 +++ .../strawman/collection/mutable/Builder2.scala | 9 +++++++++ tests/link/on-custom-lib/builder2.scala | 10 ++++++++++ tests/link/on-custom-lib/map2.scala | 8 +++++--- 4 files changed, 27 insertions(+), 3 deletions(-) rename tests/link/custom-lib/{ => strawman/collection}/Map2.scala (64%) create mode 100644 tests/link/custom-lib/strawman/collection/mutable/Builder2.scala create mode 100644 tests/link/on-custom-lib/builder2.scala diff --git a/tests/link/custom-lib/Map2.scala b/tests/link/custom-lib/strawman/collection/Map2.scala similarity index 64% rename from tests/link/custom-lib/Map2.scala rename to tests/link/custom-lib/strawman/collection/Map2.scala index 80c1a0906ebc..919df2fccdf1 100644 --- a/tests/link/custom-lib/Map2.scala +++ b/tests/link/custom-lib/strawman/collection/Map2.scala @@ -1,8 +1,11 @@ +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/map2.scala b/tests/link/on-custom-lib/map2.scala index d1e630010318..7a3b41dd4e88 100644 --- a/tests/link/on-custom-lib/map2.scala +++ b/tests/link/on-custom-lib/map2.scala @@ -1,7 +1,9 @@ +import strawman.collection._ + object Test { def main(args: Array[String]): Unit = { - // FIMXE -// val map2 = new Map2[Int] {} -// println(map2.foo) + // FIXME: Issue when loading type bindings from Tasty + // val map2 = new Map2[Int] {} + // println(map2.foo) } } From eb9afe2616d05089fca28bb5263f2ee23de6a154 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Fri, 4 Aug 2017 15:45:16 +0200 Subject: [PATCH 07/14] Remove unitTree and use tree instead --- .../src/dotty/tools/dotc/core/Symbols.scala | 24 +++---------------- .../tools/dotc/interactive/SourceTree.scala | 10 ++++---- .../dotty/tools/dotc/transform/LinkAll.scala | 2 +- 3 files changed, 10 insertions(+), 26 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Symbols.scala b/compiler/src/dotty/tools/dotc/core/Symbols.scala index e945d285d74b..576105a151d8 100644 --- a/compiler/src/dotty/tools/dotc/core/Symbols.scala +++ b/compiler/src/dotty/tools/dotc/core/Symbols.scala @@ -555,29 +555,11 @@ 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 */ = { - 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 t = unitTree - if (t.isEmpty) t - else findTree(tree).get - } - - /** If this is a top-level class, and if `-Yretain-trees` or `-YlinkOptimise` is set, - * return the PackageDef tree for this class, otherwise EmptyTree. + /** If this is a top-level class, and if `-Yretain-trees` or `-Xlink-optimise` is set, + * return the TypeDef tree (possibly wrapped inside PackageDefs) for this class, otherwise EmptyTree. * This will force the info of the class. */ - def unitTree(implicit ctx: Context): tpd.Tree /* tpd.PackageDef | tpd.TypeDef | tpd.EmptyTree */ = { + 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) { diff --git a/compiler/src/dotty/tools/dotc/interactive/SourceTree.scala b/compiler/src/dotty/tools/dotc/interactive/SourceTree.scala index 54a88ca1bf38..7ce981ca9764 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 findTree(tree: tpd.Tree): Option[SourceTree] = tree match { + case PackageDef(_, stats) => stats.flatMap(findTree).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 } + findTree(sym.tree) } } } diff --git a/compiler/src/dotty/tools/dotc/transform/LinkAll.scala b/compiler/src/dotty/tools/dotc/transform/LinkAll.scala index d9ddbdcfd895..f0dfbcbe94c9 100644 --- a/compiler/src/dotty/tools/dotc/transform/LinkAll.scala +++ b/compiler/src/dotty/tools/dotc/transform/LinkAll.scala @@ -67,7 +67,7 @@ object LinkAll { private[LinkAll] def loadCompilationUnit(clsd: ClassDenotation)(implicit ctx: Context): Option[CompilationUnit] = { assert(ctx.settings.XlinkOptimise.value) - val tree = clsd.symbol.asClass.unitTree + val tree = clsd.symbol.asClass.tree if (tree.isEmpty) None else { ctx.log("Loading compilation unit for: " + clsd) From a2d07839318d8b9b22300bae1cc14b3435ebd196 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Tue, 8 Aug 2017 16:55:11 +0200 Subject: [PATCH 08/14] Cleanup after check test failure --- compiler/test/dotty/tools/dotc/CompilationTests.scala | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index 6c34c456a44d..62fc7dc43998 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -340,10 +340,12 @@ class CompilationTests extends ParallelTesting { linkCustomLibTest }.keepOutput.checkRuns() - classFileChecks(linkStramanDir, "linkStrawmanTest") - classFileChecks(linkCustomLibDir, "linkCustomLibTest") - - (libraries + tests).delete() + try { + classFileChecks(linkStramanDir, "linkStrawmanTest") + classFileChecks(linkCustomLibDir, "linkCustomLibTest") + } finally { + (libraries + tests).delete() + } } private val (compilerSources, backendSources, backendJvmSources) = { From 78548e091eb845e3cde21c7659137748579cf01c Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Thu, 31 Aug 2017 16:31:52 +0200 Subject: [PATCH 09/14] Extract -Xlink-optimise tests from vulpix --- .../dotty/tools/dotc/CompilationTests.scala | 54 ------------ .../dotty/tools/dotc/LinkOptimiseTests.scala | 87 +++++++++++++++++++ 2 files changed, 87 insertions(+), 54 deletions(-) create mode 100644 compiler/test/dotty/tools/dotc/LinkOptimiseTests.scala diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index 62fc7dc43998..8fa7754caa75 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -294,60 +294,6 @@ class CompilationTests extends ParallelTesting { tests.foreach(_.delete()) } - @Test def linkAll: 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 mkLinkClassPath(libPath: String) = - mkClassPath(libPath :: Jars.dottyTestDeps) ++ mkClassPath(Jars.dottyTestDeps, "-YRunClasspath") - val strawmanClassPath = mkLinkClassPath(defaultOutputDir + "strawmanLibrary/main/") - val customLibClassPath = mkLinkClassPath(defaultOutputDir + "linkCustomLib/custom-lib") - - // Link tests - val linkDir = "../tests/link" - val linkStramanDir = linkDir + "/strawman" - val linkCustomLibDir = linkDir + "/on-custom-lib" - def linkStrawmanTest = compileFilesInDir(linkStramanDir, basicLinkOptimise ++ strawmanClassPath) - def linkCustomLibTest = compileFilesInDir(linkCustomLibDir, basicLinkOptimise ++ customLibClassPath) - - 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() - } - } - private val (compilerSources, backendSources, backendJvmSources) = { val compilerDir = Paths.get("../compiler/src") val compilerSources0 = sources(Files.walk(compilerDir)) diff --git a/compiler/test/dotty/tools/dotc/LinkOptimiseTests.scala b/compiler/test/dotty/tools/dotc/LinkOptimiseTests.scala new file mode 100644 index 000000000000..e2ce90cf1b1d --- /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.{ParallelTesting, SummaryReport, SummaryReporting, TestConfiguration} + +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 mkLinkClassPath(libPath: String) = + mkClassPath(libPath :: Jars.dottyTestDeps) ++ mkClassPath(Jars.dottyTestDeps, "-YRunClasspath") + val strawmanClassPath = mkLinkClassPath(defaultOutputDir + "strawmanLibrary/main/") + val customLibClassPath = mkLinkClassPath(defaultOutputDir + "linkCustomLib/custom-lib") + + // Link tests + val linkDir = "../tests/link" + val linkStramanDir = linkDir + "/strawman" + val linkCustomLibDir = linkDir + "/on-custom-lib" + def linkStrawmanTest = compileFilesInDir(linkStramanDir, basicLinkOptimise ++ strawmanClassPath) + def linkCustomLibTest = compileFilesInDir(linkCustomLibDir, basicLinkOptimise ++ customLibClassPath) + + 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() +} From 7906928d02a6e00b5bffcb18b33117a4b824be53 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Mon, 4 Sep 2017 11:22:50 +0200 Subject: [PATCH 10/14] Do not force trees when linking --- compiler/src/dotty/tools/dotc/CompilationUnit.scala | 5 +++-- compiler/src/dotty/tools/dotc/FromTasty.scala | 2 +- compiler/src/dotty/tools/dotc/transform/LinkAll.scala | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/CompilationUnit.scala b/compiler/src/dotty/tools/dotc/CompilationUnit.scala index 834d271c96ed..4552f90959dc 100644 --- a/compiler/src/dotty/tools/dotc/CompilationUnit.scala +++ b/compiler/src/dotty/tools/dotc/CompilationUnit.scala @@ -27,10 +27,11 @@ class CompilationUnit(val source: SourceFile) { object CompilationUnit { /** Make a compilation unit for top class `clsd` with the contends of the `unpickled` */ - def mkCompilationUnit(clsd: ClassDenotation, unpickled: Tree)(implicit ctx: Context): CompilationUnit = { + 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 - force.traverse(unit1.tpdTree) + if (forceTrees) + force.traverse(unit1.tpdTree) unit1 } diff --git a/compiler/src/dotty/tools/dotc/FromTasty.scala b/compiler/src/dotty/tools/dotc/FromTasty.scala index e31a7daeac2d..daa148e4a2c1 100644 --- a/compiler/src/dotty/tools/dotc/FromTasty.scala +++ b/compiler/src/dotty/tools/dotc/FromTasty.scala @@ -81,7 +81,7 @@ object FromTasty extends Driver { case info: ClassfileLoader => info.load(clsd) val unpickled = clsd.symbol.asClass.tree - if (unpickled != null) CompilationUnit.mkCompilationUnit(clsd, unpickled) + 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/transform/LinkAll.scala b/compiler/src/dotty/tools/dotc/transform/LinkAll.scala index f0dfbcbe94c9..8bb753978bd5 100644 --- a/compiler/src/dotty/tools/dotc/transform/LinkAll.scala +++ b/compiler/src/dotty/tools/dotc/transform/LinkAll.scala @@ -71,7 +71,7 @@ object LinkAll { if (tree.isEmpty) None else { ctx.log("Loading compilation unit for: " + clsd) - Some(CompilationUnit.mkCompilationUnit(clsd, tree)) + Some(CompilationUnit.mkCompilationUnit(clsd, tree, forceTrees = false)) } } From 5115c7fbc5a2505a2584a2aa593cc1fc85e61c5b Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Mon, 4 Sep 2017 11:47:24 +0200 Subject: [PATCH 11/14] Add documentation and improve code --- compiler/src/dotty/tools/dotc/Compiler.scala | 2 +- .../tools/dotc/config/ScalaSettings.scala | 2 +- .../src/dotty/tools/dotc/core/Symbols.scala | 8 ++++--- .../tools/dotc/interactive/SourceTree.scala | 6 ++--- .../tools/dotc/transform/FirstTransform.scala | 3 ++- .../dotty/tools/dotc/transform/LinkAll.scala | 24 ++++++++++--------- 6 files changed, 25 insertions(+), 20 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/Compiler.scala b/compiler/src/dotty/tools/dotc/Compiler.scala index 34d95b0a297f..ccefb6fc3a79 100644 --- a/compiler/src/dotty/tools/dotc/Compiler.scala +++ b/compiler/src/dotty/tools/dotc/Compiler.scala @@ -47,7 +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), // Link all + 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/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index 171e88b6ce5b..353a28741a93 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -114,7 +114,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", "Link class files.").withAbbreviation("-Xlink-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/Symbols.scala b/compiler/src/dotty/tools/dotc/core/Symbols.scala index 576105a151d8..8c6a7148c4d2 100644 --- a/compiler/src/dotty/tools/dotc/core/Symbols.scala +++ b/compiler/src/dotty/tools/dotc/core/Symbols.scala @@ -555,8 +555,10 @@ object Symbols { type ThisName = TypeName - /** If this is a top-level class, and if `-Yretain-trees` or `-Xlink-optimise` is set, - * return the TypeDef tree (possibly wrapped inside PackageDefs) for this class, otherwise 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 */ = { @@ -570,7 +572,7 @@ object Symbols { } myTree } - private var myTree: tpd.Tree /* tpd.PackageDef | tpd.TypeDef | tpd.EmptyTree */ = 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 = { diff --git a/compiler/src/dotty/tools/dotc/interactive/SourceTree.scala b/compiler/src/dotty/tools/dotc/interactive/SourceTree.scala index 7ce981ca9764..faa700bc58a3 100644 --- a/compiler/src/dotty/tools/dotc/interactive/SourceTree.scala +++ b/compiler/src/dotty/tools/dotc/interactive/SourceTree.scala @@ -44,14 +44,14 @@ object SourceTree { None else { import ast.Trees._ - def findTree(tree: tpd.Tree): Option[SourceTree] = tree match { - case PackageDef(_, stats) => stats.flatMap(findTree).headOption + 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 } - findTree(sym.tree) + sourceTreeOfClass(sym.tree) } } } diff --git a/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala b/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala index f4f3c7602b8a..953ffbfe1906 100644 --- a/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala +++ b/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala @@ -154,8 +154,9 @@ 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) theEmptyTree else tree + if (tree.stats.isEmpty) EmptyTree else tree override def transformDefDef(ddef: DefDef)(implicit ctx: Context, info: TransformerInfo) = { if (ddef.symbol.hasAnnotation(defn.NativeAnnot)) { diff --git a/compiler/src/dotty/tools/dotc/transform/LinkAll.scala b/compiler/src/dotty/tools/dotc/transform/LinkAll.scala index 8bb753978bd5..94d272c7ea2e 100644 --- a/compiler/src/dotty/tools/dotc/transform/LinkAll.scala +++ b/compiler/src/dotty/tools/dotc/transform/LinkAll.scala @@ -22,19 +22,21 @@ class LinkAll extends MiniPhaseTransform { override def prepareForUnit(tree: tpd.Tree)(implicit ctx: Context): TreeTransform = NoTransform - override def runOn(units: List[CompilationUnit])(implicit ctx: Context): List[CompilationUnit] = - if (ctx.settings.XlinkOptimise.value) super.runOn(allUnits(Set.empty, units.toSet, Set.empty)) - else super.runOn(units) + 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) - /** Loads and processes new compilation units, possibly loading more units. */ - private 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. */ From 86e0e32fbace1f70a478a01e71771ba4a142683f Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Mon, 4 Sep 2017 11:54:46 +0200 Subject: [PATCH 12/14] Add example with Template inside parent --- tests/link/on-custom-lib/loadRefsInParents.classcheck | 5 +++++ tests/link/on-custom-lib/loadRefsInParents.scala | 9 +++++++++ 2 files changed, 14 insertions(+) create mode 100644 tests/link/on-custom-lib/loadRefsInParents.classcheck create mode 100644 tests/link/on-custom-lib/loadRefsInParents.scala 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 }) From 1f3c305eb8065f792467cc9bd349144ea80ae80b Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Mon, 4 Sep 2017 12:11:05 +0200 Subject: [PATCH 13/14] Add documentation --- compiler/src/dotty/tools/dotc/transform/LinkAll.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/LinkAll.scala b/compiler/src/dotty/tools/dotc/transform/LinkAll.scala index 94d272c7ea2e..97e45ba93b05 100644 --- a/compiler/src/dotty/tools/dotc/transform/LinkAll.scala +++ b/compiler/src/dotty/tools/dotc/transform/LinkAll.scala @@ -1,6 +1,6 @@ package dotty.tools.dotc.transform -import dotty.tools.dotc.{CompilationUnit, FromTasty} +import dotty.tools.dotc.CompilationUnit import dotty.tools.dotc.ast.Trees._ import dotty.tools.dotc.ast.tpd import dotty.tools.dotc.core.Contexts._ @@ -20,6 +20,7 @@ class LinkAll extends MiniPhaseTransform { 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] = { @@ -31,7 +32,6 @@ class LinkAll extends MiniPhaseTransform { 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) - } } From 4f0b0706ca7359a8dfe24692d539a96e971aa6bc Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Mon, 4 Sep 2017 17:12:11 +0200 Subject: [PATCH 14/14] Add clear separation between flags and classpath in tests --- .../tools/dotc/config/ScalaSettings.scala | 1 - compiler/test/dotc/comptest.scala | 7 +-- .../dotty/tools/dotc/CompilationTests.scala | 10 ++-- .../dotty/tools/dotc/LinkOptimiseTests.scala | 14 +++--- .../tools/dotc/MissingCoreLibTests.scala | 9 ++-- .../transform/PatmatExhaustivityTest.scala | 2 +- .../dotty/tools/vulpix/ParallelTesting.scala | 50 +++++++------------ .../tools/vulpix/TestConfiguration.scala | 34 +++++-------- .../test/dotty/tools/vulpix/TestFlags.scala | 19 +++++++ 9 files changed, 66 insertions(+), 80 deletions(-) create mode 100644 compiler/test/dotty/tools/vulpix/TestFlags.scala diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index 353a28741a93..b0a1321a395c 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -102,7 +102,6 @@ class ScalaSettings extends Settings.SettingGroup { val YcheckAllPatmat = BooleanSetting("-Ycheck-all-patmat", "Check exhaustivity and redundancy of all pattern matching (used for testing the algorithm)") val YretainTrees = BooleanSetting("-Yretain-trees", "Retain trees for top-level classes, accessible from ClassSymbol#tree") val YshowTreeIds = BooleanSetting("-Yshow-tree-ids", "Uniquely tag all tree nodes in debugging output.") - val YRunClasspath = PathSetting("-YRunClasspath", "Specify where to find user class files while executing run tests.", defaultClasspath) /** Area-specific debug output */ val Yexplainlowlevel = BooleanSetting("-Yexplain-lowlevel", "When explaining type errors, show types at a lower level.") 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 8fa7754caa75..aa0720042eb5 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -10,8 +10,7 @@ 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 @@ -41,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) + @@ -223,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 = diff --git a/compiler/test/dotty/tools/dotc/LinkOptimiseTests.scala b/compiler/test/dotty/tools/dotc/LinkOptimiseTests.scala index e2ce90cf1b1d..70fa01c5f053 100644 --- a/compiler/test/dotty/tools/dotc/LinkOptimiseTests.scala +++ b/compiler/test/dotty/tools/dotc/LinkOptimiseTests.scala @@ -7,7 +7,7 @@ import java.nio.file.{Files, Path, Paths} import org.junit.{AfterClass, Test} import org.junit.Assert._ -import vulpix.{ParallelTesting, SummaryReport, SummaryReporting, TestConfiguration} +import vulpix._ import scala.concurrent.duration._ import scala.collection.JavaConverters._ @@ -38,17 +38,17 @@ class LinkOptimiseTests extends ParallelTesting { }.keepOutput.checkCompile() // Setup class paths - def mkLinkClassPath(libPath: String) = - mkClassPath(libPath :: Jars.dottyTestDeps) ++ mkClassPath(Jars.dottyTestDeps, "-YRunClasspath") - val strawmanClassPath = mkLinkClassPath(defaultOutputDir + "strawmanLibrary/main/") - val customLibClassPath = mkLinkClassPath(defaultOutputDir + "linkCustomLib/custom-lib") + 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, basicLinkOptimise ++ strawmanClassPath) - def linkCustomLibTest = compileFilesInDir(linkCustomLibDir, basicLinkOptimise ++ customLibClassPath) + def linkStrawmanTest = compileFilesInDir(linkStramanDir, strawmanClassPath) + def linkCustomLibTest = compileFilesInDir(linkCustomLibDir, customLibClassFlags) def classFileChecks(sourceDir: String, testName: String) = { val checkExt = ".classcheck" 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 4707097d1350..b5534337e82b 100644 --- a/compiler/test/dotty/tools/vulpix/ParallelTesting.scala +++ b/compiler/test/dotty/tools/vulpix/ParallelTesting.scala @@ -50,25 +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 = { - flags - .dropWhile(_ != "-YRunClasspath") - .drop(1) - .headOption - .map(outDir.getAbsolutePath + ":" + _) - .getOrElse(classPath) - } + def runClassPath: String = outDir.getAbsolutePath + ":" + flags.runClassPath def title: String = self match { case self: JointCompilationSource => @@ -82,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 } @@ -103,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 @@ -147,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) @@ -161,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 { @@ -342,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) @@ -367,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 @@ -395,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 { @@ -1073,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 @@ -1103,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) @@ -1132,7 +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], callingMethod: String = getCallingMethod())(implicit outDirectory: String): CompilationTest = { + 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`: @@ -1163,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) @@ -1183,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 cf9fc6dfd955..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(classPaths: List[String], classPathFlag: String = "-classpath"): Array[String] = { - val paths = classPaths map { p => + def mkClassPath(classPaths: List[String]): String = { + classPaths map { p => val file = new java.io.File(p) assert( file.exists, @@ -47,26 +40,23 @@ object TestConfiguration { |it in extras.""" ) file.getAbsolutePath - } mkString (":") - - Array(classPathFlag, paths) + } mkString(":") } val yCheckOptions = Array("-Ycheck:tailrec,resolveSuper,erasure,mixin,getClass,restoreScopes,labelDef") - val basicDefaultOptions = noCheckOptions ++ checkOptions ++ yCheckOptions - val defaultUnoptimised = basicDefaultOptions ++ 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 basicLinkOptimise = basicDefaultOptions ++ Array("-Xlink-optimise") + 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) +}