diff --git a/.drone.yml b/.drone.yml index eb340cae3d24..bf0f60506a29 100644 --- a/.drone.yml +++ b/.drone.yml @@ -25,14 +25,16 @@ pipeline: image: lampepfl/dotty:2017-10-20 commands: - cp -R . /tmp/1/ && cd /tmp/1/ - - ./project/scripts/sbt ";compile ;testAll ;dotty-bench/jmh:run 1 1 tests/pos/alias.scala" + - ./project/scripts/sbt ";compile ;testAll" + - ./project/scripts/sbtTests test_bootstrapped: group: test image: lampepfl/dotty:2017-10-20 commands: - cp -R . /tmp/2/ && cd /tmp/2/ - - ./project/scripts/sbt ";dotty-bootstrapped/compile ;dotty-bootstrapped/testAll ;dotty-bench-bootstrapped/jmh:run 1 1 tests/pos/alias.scala" + - ./project/scripts/sbt ";dotty-bootstrapped/compile ;dotty-bootstrapped/testAll" + - ./project/scripts/sbtBootstrappedTests test_optimised: group: test diff --git a/compiler/src/dotty/tools/dotc/Driver.scala b/compiler/src/dotty/tools/dotc/Driver.scala index 8e2c1910dcd6..349147dc81a9 100644 --- a/compiler/src/dotty/tools/dotc/Driver.scala +++ b/compiler/src/dotty/tools/dotc/Driver.scala @@ -6,6 +6,7 @@ import core.Contexts.{Context, ContextBase} import util.DotClass import reporting._ import scala.util.control.NonFatal +import fromtasty.TASTYCompiler /** Run the Dotty compiler. * @@ -15,7 +16,9 @@ import scala.util.control.NonFatal */ class Driver extends DotClass { - protected def newCompiler(implicit ctx: Context): Compiler = new Compiler + protected def newCompiler(implicit ctx: Context): Compiler = + if (ctx.settings.tasty.value) new TASTYCompiler + else new Compiler protected def emptyReporter: Reporter = new StoreReporter(null) diff --git a/compiler/src/dotty/tools/dotc/FromTasty.scala b/compiler/src/dotty/tools/dotc/FromTasty.scala deleted file mode 100644 index a9114826b86d..000000000000 --- a/compiler/src/dotty/tools/dotc/FromTasty.scala +++ /dev/null @@ -1,114 +0,0 @@ -/* dotc - * Copyright 2005-2015 LAMP/EPFL - * @author Martin Odersky - */ -package dotty.tools -package dotc - -import core._ -import Contexts._ -import Symbols._ -import SymDenotations._ -import typer.FrontEnd -import Phases.Phase -import util._ -import Decorators._ -import dotty.tools.dotc.ast.tpd -import dotty.tools.dotc.core._ -import dotty.tools.dotc.core.Names._ -import dotty.tools.dotc.core.NameOps._ -import dotty.tools.dotc.transform.Pickler - -/** Compiler for TASTY files. - * Usage: - * - * scala dotty.tools.dotc.FromTasty (option | classname)* - * - * Options are as for dotc. - * Classnames are fully qualified names of top-level classes that need to have a TASTY attribute. - * Example: - * - * scala dotty.tools.dotc.FromTasty -Xprint:front extMethods.T - */ -object FromTasty extends Driver { - override def newCompiler(implicit ctx: Context): Compiler = new TASTYCompiler - - class TASTYCompiler extends Compiler { - - override def phases: List[List[Phase]] = { - val backendPhases = super.phases.dropWhile { - case List(_: Pickler) => false - case _ => true - }.tail - List(new ReadTastyTreesFromClasses) :: backendPhases - } - - override def newRun(implicit ctx: Context): Run = { - reset() - new TASTYRun(this, ctx) - } - } - - class TASTYRun(comp: Compiler, ictx: Context) extends Run(comp, ictx) { - override def compile(classNames: List[String]) = { - units = classNames.map(new TASTYCompilationUnit(_)) - compileUnits() - } - } - - class TASTYCompilationUnit(val className: String) extends CompilationUnit(NoSource) { - override def toString = s"class file $className" - } - - class ReadTastyTreesFromClasses extends FrontEnd { - - override def isTyper = false - - override def runOn(units: List[CompilationUnit])(implicit ctx: Context): List[CompilationUnit] = - units.flatMap(readTASTY) - - def readTASTY(unit: CompilationUnit)(implicit ctx: Context): Option[CompilationUnit] = unit match { - case unit: TASTYCompilationUnit => - assert(ctx.settings.YretainTrees.value) - val className = unit.className.toTypeName - def compilationUnit(className: TypeName): Option[CompilationUnit] = { - tree(className).flatMap { case (clsd, unpickled) => - if (unpickled.isEmpty) None - else Some(CompilationUnit.mkCompilationUnit(clsd, unpickled, forceTrees = true)) - } - } - // The TASTY section in a/b/C.class may either contain a class a.b.C, an object a.b.C, or both. - // We first try to load the class and fallback to loading the object if the class doesn't exist. - // Note that if both the class and the object are present, then loading the class will also load - // the object, this is why we use orElse here, otherwise we could load the object twice and - // create ambiguities! - compilationUnit(className).orElse(compilationUnit(className.moduleClassName)) - } - - private def tree(className: TypeName)(implicit ctx: Context): Option[(ClassDenotation, tpd.Tree)] = { - val clsd = ctx.base.staticRef(className) - ctx.base.staticRef(className) match { - case clsd: ClassDenotation => - def cannotUnpickle(reason: String) = - ctx.error(s"class $className cannot be unpickled because $reason") - def tryToLoad = clsd.infoOrCompleter match { - case info: ClassfileLoader => - info.load(clsd) - Option(clsd.symbol.asClass.tree).orElse { - cannotUnpickle(s"its class file ${info.classfile} does not have a TASTY attribute") - None - } - - case info => - cannotUnpickle(s"its info of type ${info.getClass} is not a ClassfileLoader") - None - } - Option(clsd.symbol.asClass.tree).orElse(tryToLoad).map(tree => (clsd, tree)) - - case _ => - ctx.error(s"class not found: $className") - None - } - } - } -} diff --git a/compiler/src/dotty/tools/dotc/Main.scala b/compiler/src/dotty/tools/dotc/Main.scala index 5ba3f35b0cc9..3288fded52a2 100644 --- a/compiler/src/dotty/tools/dotc/Main.scala +++ b/compiler/src/dotty/tools/dotc/Main.scala @@ -1,7 +1,5 @@ package dotty.tools package dotc -import core.Contexts.Context - /** Main class of the `dotc` batch compiler. */ object Main extends Driver diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index f921ce5041a9..776c9ee9d0d6 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -43,6 +43,7 @@ class ScalaSettings extends Settings.SettingGroup { val language = MultiStringSetting("-language", "feature", "Enable one or more language features.") val rewrite = OptionSetting[Rewrites]("-rewrite", "When used in conjunction with -language:Scala2 rewrites sources to migrate to new syntax") val silentWarnings = BooleanSetting("-nowarn", "Silence all warnings.") + val tasty = BooleanSetting("-tasty", "Compile classes from tasty in classpath. The arguments are used as class names.") /** -X "Advanced" settings */ diff --git a/compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala b/compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala index 895147f6065e..abe7ff5a2ae7 100644 --- a/compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala +++ b/compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala @@ -326,7 +326,7 @@ class ClassfileLoader(val classfile: AbstractFile) extends SymbolLoader { val (classRoot, moduleRoot) = rootDenots(root.asClass) val classfileParser = new ClassfileParser(classfile, classRoot, moduleRoot)(ctx) val result = classfileParser.run() - if (ctx.settings.YretainTrees.value || ctx.settings.XlinkOptimise.value) { + if (mayLoadTreesFromTasty) { result match { case Some(unpickler: tasty.DottyUnpickler) => classRoot.symbol.asClass.unpickler = unpickler @@ -335,6 +335,9 @@ class ClassfileLoader(val classfile: AbstractFile) extends SymbolLoader { } } } + + private def mayLoadTreesFromTasty(implicit ctx: Context): Boolean = + ctx.settings.YretainTrees.value || ctx.settings.XlinkOptimise.value || ctx.settings.tasty.value } class SourcefileLoader(val srcfile: AbstractFile) extends SymbolLoader { diff --git a/compiler/src/dotty/tools/dotc/fromtasty/ReadTastyTreesFromClasses.scala b/compiler/src/dotty/tools/dotc/fromtasty/ReadTastyTreesFromClasses.scala new file mode 100644 index 000000000000..9ba0a19ca311 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/fromtasty/ReadTastyTreesFromClasses.scala @@ -0,0 +1,65 @@ +package dotty.tools +package dotc +package fromtasty + +import dotty.tools.dotc.ast.tpd +import dotty.tools.dotc.core.Contexts.Context +import dotty.tools.dotc.core.Decorators._ +import dotty.tools.dotc.core.Names._ +import dotty.tools.dotc.core.NameOps._ +import dotty.tools.dotc.core.SymDenotations.ClassDenotation +import dotty.tools.dotc.core._ +import dotty.tools.dotc.typer.FrontEnd + +class ReadTastyTreesFromClasses extends FrontEnd { + + override def isTyper = false + + override def runOn(units: List[CompilationUnit])(implicit ctx: Context): List[CompilationUnit] = + units.flatMap(readTASTY) + + def readTASTY(unit: CompilationUnit)(implicit ctx: Context): Option[CompilationUnit] = unit match { + case unit: TASTYCompilationUnit => + val className = unit.className.toTypeName + def compilationUnit(className: TypeName): Option[CompilationUnit] = { + tree(className).flatMap { + case (clsd, unpickled) => + if (unpickled.isEmpty) None + else Some(CompilationUnit.mkCompilationUnit(clsd, unpickled, forceTrees = true)) + + } + } + // The TASTY section in a/b/C.class may either contain a class a.b.C, an object a.b.C, or both. + // We first try to load the class and fallback to loading the object if the class doesn't exist. + // Note that if both the class and the object are present, then loading the class will also load + // the object, this is why we use orElse here, otherwise we could load the object twice and + // create ambiguities! + compilationUnit(className).orElse(compilationUnit(className.moduleClassName)) + } + + private def tree(className: TypeName)(implicit ctx: Context): Option[(ClassDenotation, tpd.Tree)] = { + val clsd = ctx.base.staticRef(className) + ctx.base.staticRef(className) match { + case clsd: ClassDenotation => + def cannotUnpickle(reason: String) = + ctx.error(s"class $className cannot be unpickled because $reason") + def tryToLoad = clsd.infoOrCompleter match { + case info: ClassfileLoader => + info.load(clsd) + Option(clsd.symbol.asClass.tree).orElse { + cannotUnpickle(s"its class file ${info.classfile} does not have a TASTY attribute") + None + } + + case info => + cannotUnpickle(s"its info of type ${info.getClass} is not a ClassfileLoader") + None + } + Option(clsd.symbol.asClass.tree).orElse(tryToLoad).map(tree => (clsd, tree)) + + case _ => + ctx.error(s"class not found: $className") + None + } + } +} diff --git a/compiler/src/dotty/tools/dotc/fromtasty/TASTYCompilationUnit.scala b/compiler/src/dotty/tools/dotc/fromtasty/TASTYCompilationUnit.scala new file mode 100644 index 000000000000..1e7360a8662c --- /dev/null +++ b/compiler/src/dotty/tools/dotc/fromtasty/TASTYCompilationUnit.scala @@ -0,0 +1,8 @@ +package dotty.tools.dotc.fromtasty + +import dotty.tools.dotc.CompilationUnit +import dotty.tools.dotc.util.NoSource + +class TASTYCompilationUnit(val className: String) extends CompilationUnit(NoSource) { + override def toString = s"class file $className" +} diff --git a/compiler/src/dotty/tools/dotc/fromtasty/TASTYCompiler.scala b/compiler/src/dotty/tools/dotc/fromtasty/TASTYCompiler.scala new file mode 100644 index 000000000000..80184d05317b --- /dev/null +++ b/compiler/src/dotty/tools/dotc/fromtasty/TASTYCompiler.scala @@ -0,0 +1,24 @@ +package dotty.tools +package dotc +package fromtasty + +import core._ +import Contexts._ +import Phases.Phase +import dotty.tools.dotc.transform.Pickler + +class TASTYCompiler extends Compiler { + + override def phases: List[List[Phase]] = { + val backendPhases = super.phases.dropWhile { + case List(_: Pickler) => false + case _ => true + }.tail + List(new ReadTastyTreesFromClasses) :: backendPhases + } + + override def newRun(implicit ctx: Context): Run = { + reset() + new TASTYRun(this, ctx) + } +} diff --git a/compiler/src/dotty/tools/dotc/fromtasty/TASTYRun.scala b/compiler/src/dotty/tools/dotc/fromtasty/TASTYRun.scala new file mode 100644 index 000000000000..9b360ea1056f --- /dev/null +++ b/compiler/src/dotty/tools/dotc/fromtasty/TASTYRun.scala @@ -0,0 +1,12 @@ +package dotty.tools +package dotc +package fromtasty + +import core.Contexts._ + +class TASTYRun(comp: Compiler, ictx: Context) extends Run(comp, ictx) { + override def compile(classNames: List[String]) = { + units = classNames.map(new TASTYCompilationUnit(_)) + compileUnits() + } +} diff --git a/compiler/test/dotty/tools/vulpix/ParallelTesting.scala b/compiler/test/dotty/tools/vulpix/ParallelTesting.scala index deb7cf182bd1..ec9fd31ce5d8 100644 --- a/compiler/test/dotty/tools/vulpix/ParallelTesting.scala +++ b/compiler/test/dotty/tools/vulpix/ParallelTesting.scala @@ -395,7 +395,7 @@ trait ParallelTesting extends RunnerOrchestration { self => protected def compileFromTasty(flags0: TestFlags, suppressErrors: Boolean, targetDir: JFile): TestReporter = { val tastyOutput = new JFile(targetDir.getPath + "_from-tasty") tastyOutput.mkdir() - val flags = flags0 and ("-d", tastyOutput.getAbsolutePath) + val flags = flags0 and ("-d", tastyOutput.getAbsolutePath) and "-tasty" def hasTastyFileToClassName(f: JFile): String = targetDir.toPath.relativize(f.toPath).toString.dropRight(".hasTasty".length).replace('/', '.') @@ -405,9 +405,11 @@ trait ParallelTesting extends RunnerOrchestration { self => TestReporter.reporter(realStdout, logLevel = if (suppressErrors || suppressAllOutput) ERROR + 1 else ERROR) + val driver = new Driver + // Compile with a try to catch any StackTrace generated by the compiler: try { - dotc.FromTasty.process(flags.all ++ classes, reporter = reporter) + driver.process(flags.all ++ classes, reporter = reporter) } catch { case NonFatal(ex) => reporter.logStackTrace(ex) @@ -1097,7 +1099,7 @@ trait ParallelTesting extends RunnerOrchestration { self => val target = JointCompilationSource( testGroup.name, Array(sourceFile), - flags.withClasspath(tastySource.getPath) and "-Yretain-trees", + flags.withClasspath(tastySource.getPath) and "-tasty", tastySource, fromTasty = true ) diff --git a/dist/bin/dotc b/dist/bin/dotc index 3eb596fa15d7..15a34b8af32e 100755 --- a/dist/bin/dotc +++ b/dist/bin/dotc @@ -31,7 +31,6 @@ default_java_opts="-Xmx768m -Xms768m" bootcp=true CompilerMain=dotty.tools.dotc.Main -FromTasty=dotty.tools.dotc.FromTasty ReplMain=dotty.tools.repl.Main PROG_NAME=$CompilerMain @@ -82,7 +81,6 @@ case "$1" in # Optimize for short-running applications, see https://github.com/lampepfl/dotty/issues/222 -Oshort) addJava "-XX:+TieredCompilation -XX:TieredStopAtLevel=1" && shift ;; -repl) PROG_NAME="$ReplMain" && shift ;; - -tasty) PROG_NAME="$FromTasty" && shift ;; -compile) PROG_NAME="$CompilerMain" && shift ;; -run) PROG_NAME="$ReplMain" && shift ;; -bootcp) bootcp=true && shift ;; diff --git a/project/Build.scala b/project/Build.scala index 0e28abc47f51..56f49ae7dd31 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -501,52 +501,30 @@ object Build { // Override run to be able to run compiled classfiles dotr := { - val args: Seq[String] = spaceDelimited("").parsed + val args: List[String] = spaceDelimited("").parsed.toList val java: String = Process("which" :: "java" :: Nil).!! val attList = (dependencyClasspath in Runtime).value - val _ = packageAll.value + val _ = packageAll.value val scalaLib = attList .map(_.data.getAbsolutePath) .find(_.contains("scala-library")) .toList.mkString(":") - if (java == "") + if (args.isEmpty) { + println("Couldn't run `dotr` without args. Use `repl` to run the repl or add args to run the dotty application") + } else if (java == "") { println("Couldn't find java executable on path, please install java to a default location") - else if (scalaLib == "") { + } else if (scalaLib == "") { println("Couldn't find scala-library on classpath, please run using script in bin dir instead") } else { val dottyLib = packageAll.value("dotty-library") - s"""$java -classpath .:$dottyLib:$scalaLib ${args.mkString(" ")}""".! + val fullArgs = insertClasspathInArgs(args, s".:$dottyLib:$scalaLib") + s"$java ${fullArgs.mkString(" ")}".! } }, - run := Def.inputTaskDyn { - val dottyLib = packageAll.value("dotty-library") - val args: Seq[String] = spaceDelimited("").parsed - - val fullArgs = args.span(_ != "-classpath") match { - case (beforeCp, "-classpath" :: cp :: rest) => beforeCp ++ List("-classpath", cp + ":" + dottyLib) ++ rest - case (beforeCp, _) => beforeCp ++ List("-classpath", dottyLib) - } - - (runMain in Compile).toTask( - s" dotty.tools.dotc.Main " + fullArgs.mkString(" ") - ) - }.evaluated, - dotc := run.evaluated, - - repl := Def.inputTaskDyn { - val dottyLib = packageAll.value("dotty-library") - val args: Seq[String] = spaceDelimited("").parsed - - val fullArgs = args.span(_ != "-classpath") match { - case (beforeCp, "-classpath" :: cp :: rest) => beforeCp ++ List("-classpath", cp + ":" + dottyLib) ++ rest - case (beforeCp, _) => beforeCp ++ List("-classpath", dottyLib) - } - - (runMain in Compile).toTask( - s" dotty.tools.repl.Main " + fullArgs.mkString(" ") - ) - }.evaluated, + run := dotc.evaluated, + dotc := runCompilerMain(false).evaluated, + repl := runCompilerMain(true).evaluated, // enable verbose exception messages for JUnit testOptions in Test += Tests.Argument( @@ -640,6 +618,26 @@ object Build { } ) + def runCompilerMain(repl: Boolean) = Def.inputTaskDyn { + val dottyLib = packageAll.value("dotty-library") + val args0: List[String] = spaceDelimited("").parsed.toList + val args = args0.filter(arg => arg != "-repl") + + val main = + if (repl) "dotty.tools.repl.Main" + else "dotty.tools.dotc.Main" + + val fullArgs = main :: insertClasspathInArgs(args, dottyLib) + + (runMain in Compile).toTask(fullArgs.mkString(" ", " ", "")) + } + + def insertClasspathInArgs(args: List[String], cp: String): List[String] = { + val (beforeCp, fromCp) = args.span(_ != "-classpath") + val classpath = fromCp.drop(1).headOption.fold(cp)(_ + ":" + cp) + beforeCp ::: "-classpath" :: classpath :: fromCp.drop(2) + } + lazy val nonBootstrapedDottyCompilerSettings = commonDottyCompilerSettings ++ Seq( // Disable scaladoc generation, it's way too slow and we'll replace it // by dottydoc anyway. We still publish an empty -javadoc.jar to make diff --git a/project/scripts/sbtBootstrappedTests b/project/scripts/sbtBootstrappedTests new file mode 100755 index 000000000000..587b43cd54c2 --- /dev/null +++ b/project/scripts/sbtBootstrappedTests @@ -0,0 +1,35 @@ +#!/usr/bin/env bash + +# check that benchmarks can run +./project/scripts/sbt "dotty-bench-bootstrapped/jmh:run 1 1 tests/pos/alias.scala" + + +# setup for `dotc`/`dotr` script tests +./project/scripts/sbt dist-bootstrapped/pack + +# check that `dotc` compiles and `dotr` runs it +echo "testing sbt dotc and dotr" +mkdir out/scriptedtest0 +./bin/dotc tests/pos/sbtDotrTest.scala -d out/scriptedtest0 +# FIXME #3477 +#./bin/dotr -classpath out/scriptedtest0 dotrtest.Test" > sbtdotr1.out +#if grep -e "dotr test ok" sbtdotr1.out; then +# echo "output ok" +#else +# exit -1 +#fi + + +# check that `dotc` compiles and `dotr` runs it +echo "testing sbt dotc -tasty and dotr -classpath" +mkdir out/scriptedtest1 +mkdir out/scriptedtest2 +./bin/dotc tests/pos/sbtDotrTest.scala -d out/scriptedtest1/ +./bin/dotc -tasty -classpath out/scriptedtest1/ -d out/scriptedtest2/ dotrtest.Test +# FIXME #3477 +#./bin/dotr -classpath out/scriptedtest2/ dotrtest.Test" > sbtdotr2.out +#if grep -e "dotr test ok" sbtdotr2.out; then +# echo "output ok" +#else +# exit -1 +#fi diff --git a/project/scripts/sbtTests b/project/scripts/sbtTests new file mode 100755 index 000000000000..f1bc42113281 --- /dev/null +++ b/project/scripts/sbtTests @@ -0,0 +1,25 @@ +#!/usr/bin/env bash + +# check that benchmarks can run +./project/scripts/sbt "dotty-bench/jmh:run 1 1 tests/pos/alias.scala" + +# check that `sbt dotc` compiles and `sbt dotr` runs it +echo "testing sbt dotc and dotr" +mkdir out/scriptedtest0 +./project/scripts/sbt ";dotc tests/pos/sbtDotrTest.scala -d out/scriptedtest0 ;dotr -classpath out/scriptedtest0 dotrtest.Test" > sbtdotr1.out +if grep -e "dotr test ok" sbtdotr1.out; then + echo "output ok" +else + exit -1 +fi + +# check that `sbt dotc` compiles and `sbt dotr` runs it +echo "testing sbt dotc -tasty and dotr -classpath" +mkdir out/scriptedtest1 +mkdir out/scriptedtest2 +./project/scripts/sbt ";dotc tests/pos/sbtDotrTest.scala -d out/scriptedtest1/; dotc -tasty -classpath out/scriptedtest1/ -d out/scriptedtest2/ dotrtest.Test; dotr -classpath out/scriptedtest2/ dotrtest.Test" > sbtdotr2.out +if grep -e "dotr test ok" sbtdotr2.out; then + echo "output ok" +else + exit -1 +fi diff --git a/tests/pos/sbtDotrTest.scala b/tests/pos/sbtDotrTest.scala new file mode 100644 index 000000000000..b7e525886292 --- /dev/null +++ b/tests/pos/sbtDotrTest.scala @@ -0,0 +1,7 @@ +package dotrtest + +object Test { + def main(args: Array[String]): Unit = { + println("dotr test ok") + } +}