From 9e64cd85ca4cbb344f695b9adff61ea0f98c5330 Mon Sep 17 00:00:00 2001 From: liu fengyun Date: Fri, 3 Nov 2017 15:23:36 +0100 Subject: [PATCH 01/27] WIP - port plugin architecture from scalac --- compiler/src/dotty/tools/dotc/Run.scala | 3 +- .../tools/dotc/config/CompilerCommand.scala | 3 +- .../tools/dotc/config/ScalaSettings.scala | 8 + .../src/dotty/tools/dotc/core/Contexts.scala | 2 + .../src/dotty/tools/dotc/plugins/Plugin.scala | 167 ++++++++++++++++++ .../dotc/plugins/PluginDescription.scala | 48 +++++ .../dotty/tools/dotc/plugins/Plugins.scala | 116 ++++++++++++ compiler/src/dotty/tools/io/Jar.scala | 2 +- 8 files changed, 346 insertions(+), 3 deletions(-) create mode 100644 compiler/src/dotty/tools/dotc/plugins/Plugin.scala create mode 100644 compiler/src/dotty/tools/dotc/plugins/PluginDescription.scala create mode 100644 compiler/src/dotty/tools/dotc/plugins/Plugins.scala diff --git a/compiler/src/dotty/tools/dotc/Run.scala b/compiler/src/dotty/tools/dotc/Run.scala index b6c401f55f22..600f647b0afd 100644 --- a/compiler/src/dotty/tools/dotc/Run.scala +++ b/compiler/src/dotty/tools/dotc/Run.scala @@ -157,7 +157,8 @@ class Run(comp: Compiler, ictx: Context) extends ImplicitRunInfo with Constraint if (ctx.settings.YtestPickler.value) List("pickler") else ctx.settings.YstopAfter.value - val phases = ctx.squashPhases(ctx.phasePlan, + val pluginPlan = ctx.plugins.foldRight(ctx.phasePlan) { (plug, plan) => plug.init(plan) } + val phases = ctx.squashPhases(pluginPlan, ctx.settings.Yskip.value, ctx.settings.YstopBefore.value, stopAfter, ctx.settings.Ycheck.value) ctx.usePhases(phases) diff --git a/compiler/src/dotty/tools/dotc/config/CompilerCommand.scala b/compiler/src/dotty/tools/dotc/config/CompilerCommand.scala index 591021f19275..12b19b472431 100644 --- a/compiler/src/dotty/tools/dotc/config/CompilerCommand.scala +++ b/compiler/src/dotty/tools/dotc/config/CompilerCommand.scala @@ -114,7 +114,7 @@ object CompilerCommand extends DotClass { def shouldStopWithInfo = { import settings._ - Set(help, Xhelp, Yhelp) exists (_.value) + Set(help, Xhelp, Yhelp, showPlugins) exists (_.value) } def infoMessage: String = { @@ -122,6 +122,7 @@ object CompilerCommand extends DotClass { if (help.value) usageMessage else if (Xhelp.value) xusageMessage else if (Yhelp.value) yusageMessage + else if (showPlugins.value) ctx.pluginDescriptions else "" } diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index 17deaa5d88e2..c492e2f1c129 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -50,6 +50,14 @@ class ScalaSettings extends Settings.SettingGroup { val printTasty = BooleanSetting("-print-tasty", "Prints the raw tasty when decompiling.") val printLines = BooleanSetting("-print-lines", "Show source code line numbers.") + /** Plugin-related setting */ + val plugin = MultiStringSetting ("-Xplugin", "paths", "Load a plugin from each classpath.") + val disable = MultiStringSetting ("-Xplugin-disable", "plugin", "Disable plugins by name.") + val require = MultiStringSetting ("-Xplugin-require", "plugin", "Abort if a named plugin is not loaded.") + val showPlugins = BooleanSetting ("-Xplugin-list", "Print a synopsis of loaded plugins.") + val pluginsDir = StringSetting ("-Xpluginsdir", "path", "Path to search for plugin archives.", Defaults.scalaPluginPath) + val pluginOptions = MultiStringSetting ("-P", "plugin:opt", "Pass an option to a plugin, e.g. -P::") + /** -X "Advanced" settings */ val Xhelp = BooleanSetting("-X", "Print a synopsis of advanced options.") diff --git a/compiler/src/dotty/tools/dotc/core/Contexts.scala b/compiler/src/dotty/tools/dotc/core/Contexts.scala index 89d0bef4ba14..f00cb5c9050e 100644 --- a/compiler/src/dotty/tools/dotc/core/Contexts.scala +++ b/compiler/src/dotty/tools/dotc/core/Contexts.scala @@ -35,6 +35,7 @@ import dotty.tools.dotc.profile.Profiler import util.Property.Key import util.Store import xsbti.AnalysisCallback +import plugins._ object Contexts { @@ -76,6 +77,7 @@ object Contexts { with SymDenotations with Reporting with NamerContextOps + with Plugins with Cloneable { thiscontext => implicit def ctx: Context = this diff --git a/compiler/src/dotty/tools/dotc/plugins/Plugin.scala b/compiler/src/dotty/tools/dotc/plugins/Plugin.scala new file mode 100644 index 000000000000..c4d634f175ac --- /dev/null +++ b/compiler/src/dotty/tools/dotc/plugins/Plugin.scala @@ -0,0 +1,167 @@ +package dotty.tools.dotc +package plugins + +import core._ +import Contexts._ +import Phases._ +import dotty.tools.io._ + +import java.io.InputStream + +import scala.collection.mutable +import scala.util.{ Try, Success, Failure } + +trait Plugin { + /** The name of this plugin */ + def name: String + + /** A one-line description of the plugin */ + def description: String + + def options(implicit ctx: Context): List[String] = { + // Process plugin options of form plugin:option + def namec = name + ":" + ctx.settings.pluginOptions.value filter (_ startsWith namec) map (_ stripPrefix namec) + } + + /** Handle any plugin-specific options. + * The user writes `-P:plugname:opt1,opt2`, + * but the plugin sees `List(opt1, opt2)`. + * The plugin can opt out of further processing + * by returning false. For example, if the plugin + * has an "enable" flag, now would be a good time + * to sit on the bench. + * @param options plugin arguments + * @param error error function + * @return true to continue, or false to opt out + */ + def init(phases: List[List[Phase]])(implicit ctx: Context): List[List[Phase]] = phases + + /** A description of this plugin's options, suitable as a response + * to the -help command-line option. Conventionally, the options + * should be listed with the `-P:plugname:` part included. + */ + val optionsHelp: Option[String] = None +} + +/** ... + * + * @author Lex Spoon + * @version 1.0, 2007-5-21 + */ +object Plugin { + + private val PluginXML = "scalac-plugin.xml" + + /** Create a class loader with the specified locations plus + * the loader that loaded the Scala compiler. + */ + private def loaderFor(locations: Seq[Path]): ClassLoader = { + val compilerLoader = classOf[Plugin].getClassLoader + val urls = locations map (_.toURL) + + new java.net.URLClassLoader(urls.toArray, compilerLoader) + } + + /** Try to load a plugin description from the specified location. + */ + private def loadDescriptionFromJar(jarp: Path): Try[PluginDescription] = { + // XXX Return to this once we have more ARM support + def read(is: InputStream) = + if (is == null) throw new PluginLoadException(jarp.path, s"Missing $PluginXML in $jarp") + else PluginDescription.fromXML(is) + + val xmlEntry = new java.util.jar.JarEntry(PluginXML) + Try(read(new Jar(jarp.jfile).getEntryStream(xmlEntry))) + } + + private def loadDescriptionFromFile(f: Path): Try[PluginDescription] = + Try(PluginDescription.fromXML(new java.io.FileInputStream(f.jfile))) + + type AnyClass = Class[_] + + /** Use a class loader to load the plugin class. + */ + def load(classname: String, loader: ClassLoader): Try[AnyClass] = { + import scala.util.control.NonFatal + try { + Success[AnyClass](loader loadClass classname) + } catch { + case NonFatal(e) => + Failure(new PluginLoadException(classname, s"Error: unable to load class: $classname")) + case e: NoClassDefFoundError => + Failure(new PluginLoadException(classname, s"Error: class not found: ${e.getMessage} required by $classname")) + } + } + + /** Load all plugins specified by the arguments. + * Each location of `paths` must be a valid plugin archive or exploded archive. + * Each of `paths` must define one plugin. + * Each of `dirs` may be a directory containing arbitrary plugin archives. + * Skips all plugins named in `ignoring`. + * A classloader is created to load each plugin. + */ + def loadAllFrom( + paths: List[List[Path]], + dirs: List[Path], + ignoring: List[String]): List[Try[AnyClass]] = + { + // List[(jar, Try(descriptor))] in dir + def scan(d: Directory) = + d.files.toList sortBy (_.name) filter (Jar isJarOrZip _) map (j => (j, loadDescriptionFromJar(j))) + + type PDResults = List[Try[(PluginDescription, ClassLoader)]] + + // scan plugin dirs for jars containing plugins, ignoring dirs with none and other jars + val fromDirs: PDResults = dirs filter (_.isDirectory) flatMap { d => + scan(d.toDirectory) collect { + case (j, Success(pd)) => Success((pd, loaderFor(Seq(j)))) + } + } + + // scan jar paths for plugins, taking the first plugin you find. + // a path element can be either a plugin.jar or an exploded dir. + def findDescriptor(ps: List[Path]) = { + def loop(qs: List[Path]): Try[PluginDescription] = qs match { + case Nil => Failure(new MissingPluginException(ps)) + case p :: rest => + if (p.isDirectory) loadDescriptionFromFile(p.toDirectory / PluginXML) orElse loop(rest) + else if (p.isFile) loadDescriptionFromJar(p.toFile) orElse loop(rest) + else loop(rest) + } + loop(ps) + } + val fromPaths: PDResults = paths map (p => (p, findDescriptor(p))) map { + case (p, Success(pd)) => Success((pd, loaderFor(p))) + case (_, Failure(e)) => Failure(e) + } + + val seen = mutable.HashSet[String]() + val enabled = (fromPaths ::: fromDirs) map { + case Success((pd, loader)) if seen(pd.classname) => + // a nod to scala/bug#7494, take the plugin classes distinctly + Failure(new PluginLoadException(pd.name, s"Ignoring duplicate plugin ${pd.name} (${pd.classname})")) + case Success((pd, loader)) if ignoring contains pd.name => + Failure(new PluginLoadException(pd.name, s"Disabling plugin ${pd.name}")) + case Success((pd, loader)) => + seen += pd.classname + Plugin.load(pd.classname, loader) + case Failure(e) => + Failure(e) + } + enabled // distinct and not disabled + } + + /** Instantiate a plugin class, given the class and + * the compiler it is to be used in. + */ + def instantiate(clazz: AnyClass): Plugin = clazz.newInstance.asInstanceOf[Plugin] +} + +class PluginLoadException(val path: String, message: String, cause: Exception) extends Exception(message, cause) { + def this(path: String, message: String) = this(path, message, null) +} + +class MissingPluginException(path: String) extends PluginLoadException(path, s"No plugin in path $path") { + def this(paths: List[Path]) = this(paths mkString File.pathSeparator) +} diff --git a/compiler/src/dotty/tools/dotc/plugins/PluginDescription.scala b/compiler/src/dotty/tools/dotc/plugins/PluginDescription.scala new file mode 100644 index 000000000000..10c83985d585 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/plugins/PluginDescription.scala @@ -0,0 +1,48 @@ +package dotty.tools.dotc +package plugins + +/** A description of a compiler plugin, suitable for serialization + * to XML for inclusion in the plugin's .jar file. + * + * @author Lex Spoon + * @version 1.0, 2007-5-21 + * @author Adriaan Moors + * @version 2.0, 2013 + * @param name A short name of the plugin, used to identify it in + * various contexts. The phase defined by the plugin + * should have the same name. + * @param classname The name of the main Plugin class. + */ +case class PluginDescription(name: String, classname: String) { + /** An XML representation of this description. + * It should be stored inside the jar archive file. + */ + def toXML: String = + s"""| + | ${name} + | ${classname} + |""".stripMargin +} + +/** Utilities for the PluginDescription class. + * + * @author Lex Spoon + * @version 1.0, 2007-5-21 + * @author Adriaan Moors + * @version 2.0, 2013 + */ +object PluginDescription { + private def text(ns: org.w3c.dom.NodeList): String = + if (ns.getLength == 1) ns.item(0).getTextContent.trim + else throw new RuntimeException("Bad plugin descriptor.") + + def fromXML(xml: java.io.InputStream): PluginDescription = { + import javax.xml.parsers.DocumentBuilderFactory + val root = DocumentBuilderFactory.newInstance.newDocumentBuilder.parse(xml).getDocumentElement + root.normalize() + if (root.getNodeName != "plugin") + throw new RuntimeException("Plugin descriptor root element must be .") + + PluginDescription(text(root.getElementsByTagName("name")), text(root.getElementsByTagName("classname"))) + } +} diff --git a/compiler/src/dotty/tools/dotc/plugins/Plugins.scala b/compiler/src/dotty/tools/dotc/plugins/Plugins.scala new file mode 100644 index 000000000000..0daf18e25558 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/plugins/Plugins.scala @@ -0,0 +1,116 @@ +package dotty.tools.dotc +package plugins + +import core._ +import Contexts._ +import dotty.tools.dotc.config.PathResolver +import dotty.tools.io._ + +/** Support for run-time loading of compiler plugins. + * + * @author Lex Spoon + * @version 1.1, 2009/1/2 + * Updated 2009/1/2 by Anders Bach Nielsen: Added features to implement SIP 00002 + */ +trait Plugins { + self: Context => + + /** Load a rough list of the plugins. For speed, it + * does not instantiate a compiler run. Therefore it cannot + * test for same-named phases or other problems that are + * filtered from the final list of plugins. + */ + protected def loadRoughPluginsList(implicit ctx: Context): List[Plugin] = { + def asPath(p: String) = ClassPath split p + val paths = ctx.settings.plugin.value filter (_ != "") map (s => asPath(s) map Path.apply) + val dirs = { + def injectDefault(s: String) = if (s.isEmpty) PathResolver.Defaults.scalaPluginPath else s + asPath(ctx.settings.pluginsDir.value) map injectDefault map Path.apply + } + val maybes = Plugin.loadAllFrom(paths, dirs, ctx.settings.disable.value) + val (goods, errors) = maybes partition (_.isSuccess) + // Explicit parameterization of recover to avoid -Xlint warning about inferred Any + errors foreach (_.recover[Any] { + // legacy behavior ignores altogether, so at least warn devs + case e: MissingPluginException => warning(e.getMessage) + case e: Exception => inform(e.getMessage) + }) + val classes = goods map (_.get) // flatten + + // Each plugin must only be instantiated once. A common pattern + // is to register annotation checkers during object construction, so + // creating multiple plugin instances will leave behind stale checkers. + classes map (Plugin.instantiate(_)) + } + + private var _roughPluginsList: List[Plugin] = _ + protected def roughPluginsList(implicit ctx: Context): List[Plugin] = + if (_roughPluginsList == null) { + _roughPluginsList = loadRoughPluginsList + _roughPluginsList + } + else _roughPluginsList + + /** Load all available plugins. Skips plugins that + * either have the same name as another one, or which + * define a phase name that another one does. + */ + protected def loadPlugins(implicit ctx: Context): List[Plugin] = { + // remove any with conflicting names or subcomponent names + def pick( + plugins: List[Plugin], + plugNames: Set[String]): List[Plugin] = + { + if (plugins.isEmpty) return Nil // early return + + val plug :: tail = plugins + def withoutPlug = pick(tail, plugNames) + def withPlug = plug :: pick(tail, plugNames + plug.name) + + def note(msg: String): Unit = if (ctx.settings.verbose.value) inform(msg format plug.name) + def fail(msg: String) = { note(msg) ; withoutPlug } + + if (plugNames contains plug.name) + fail("[skipping a repeated plugin: %s]") + else if (ctx.settings.disable.value contains plug.name) + fail("[disabling plugin: %s]") + else { + note("[loaded plugin %s]") + withPlug + } + } + + val plugs = pick(roughPluginsList, Set()) + + // Verify required plugins are present. + for (req <- ctx.settings.require.value ; if !(plugs exists (_.name == req))) + ctx.error("Missing required plugin: " + req) + + // Verify no non-existent plugin given with -P + for { + opt <- ctx.settings.pluginOptions.value + if !(plugs exists (opt startsWith _.name + ":")) + } ctx.error("bad option: -P:" + opt) + + plugs + } + + private var _plugins: List[Plugin] = _ + def plugins(implicit ctx: Context): List[Plugin] = + if (_plugins == null) { + _plugins = loadPlugins + _plugins + } + else _plugins + + + /** A description of all the plugins that are loaded */ + def pluginDescriptions: String = + roughPluginsList map (x => "%s - %s".format(x.name, x.description)) mkString "\n" + + /** Summary of the options for all loaded plugins */ + def pluginOptionsHelp: String = + (for (plug <- roughPluginsList ; help <- plug.optionsHelp) yield { + "\nOptions for plugin '%s':\n%s\n".format(plug.name, help) + }).mkString +} diff --git a/compiler/src/dotty/tools/io/Jar.scala b/compiler/src/dotty/tools/io/Jar.scala index 64bcef22027b..03a42adcfceb 100644 --- a/compiler/src/dotty/tools/io/Jar.scala +++ b/compiler/src/dotty/tools/io/Jar.scala @@ -68,7 +68,7 @@ class Jar(file: File) extends Iterable[JarEntry] { } override def iterator: Iterator[JarEntry] = this.toList.iterator - private def getEntryStream(entry: JarEntry) = jarFile getInputStream entry match { + def getEntryStream(entry: JarEntry) = jarFile getInputStream entry match { case null => errorFn("No such entry: " + entry) ; null case x => x } From 38684850099e4f0d0d2b153cb7a94e6e28f26b97 Mon Sep 17 00:00:00 2001 From: liu fengyun Date: Fri, 3 Nov 2017 16:52:03 +0100 Subject: [PATCH 02/27] Create test infrastructure for plugins --- .../dotty/tools/dotc/CompilationTests.scala | 28 ++++++ .../tools/dotc/reporting/TestReporter.scala | 23 ++++- .../dotty/tools/vulpix/ParallelTesting.scala | 91 ++++++++++++++----- tests/plugins/neg/divideZero/Test_2.scala | 6 ++ tests/plugins/neg/divideZero/plugin_1.scala | 37 ++++++++ .../plugins/neg/divideZero/scalac-plugin.xml | 4 + tests/plugins/pos/divideZero/2.check | 3 + tests/plugins/pos/divideZero/Test_2.scala | 5 + tests/plugins/pos/divideZero/plugin_1.scala | 37 ++++++++ .../plugins/pos/divideZero/scalac-plugin.xml | 4 + 10 files changed, 212 insertions(+), 26 deletions(-) create mode 100644 tests/plugins/neg/divideZero/Test_2.scala create mode 100644 tests/plugins/neg/divideZero/plugin_1.scala create mode 100644 tests/plugins/neg/divideZero/scalac-plugin.xml create mode 100644 tests/plugins/pos/divideZero/2.check create mode 100644 tests/plugins/pos/divideZero/Test_2.scala create mode 100644 tests/plugins/pos/divideZero/plugin_1.scala create mode 100644 tests/plugins/pos/divideZero/scalac-plugin.xml diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index 1c5efcd57de7..47c6076a8d96 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -313,6 +313,34 @@ class CompilationTests extends ParallelTesting { compileFilesInDir("tests/neg", defaultOptimised).checkExpectedErrors() } + @Test def testPlugins: Unit = { + val xmlFile = "scalac-plugin.xml" + + // 1. hack with absolute path for -Xplugin + // 2. copy `xmlFile` to destination + def compileFilesInDir(dir: String): CompilationTest = { + val outDir = defaultOutputDir + "testPlugins/" + val sourceDir = new JFile(dir) + + val dirs = sourceDir.listFiles.foldLeft(List.empty[JFile]) { case (dirs, f) => + if (f.isDirectory) f :: dirs else dirs + } + + val targets = dirs.map { dir => + val compileDir = createOutputDirsForDir(dir, sourceDir, outDir) + import java.nio.file.StandardCopyOption.REPLACE_EXISTING + Files.copy(dir.toPath.resolve(xmlFile), compileDir.toPath.resolve(xmlFile), REPLACE_EXISTING) + val flags = TestFlags(classPath, noCheckOptions) and ("-Xplugin:" + compileDir.getAbsolutePath) + SeparateCompilationSource("testPlugins", dir, flags, compileDir) + } + + new CompilationTest(targets) + } + + compileFilesInDir("../tests/plugins/neg").checkExpectedErrors() + compileFilesInDir("../tests/plugins/pos").checkCompile(checkCompileOutput = true) + } + 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/reporting/TestReporter.scala b/compiler/test/dotty/tools/dotc/reporting/TestReporter.scala index 07aa7f9182db..f70e47887c62 100644 --- a/compiler/test/dotty/tools/dotc/reporting/TestReporter.scala +++ b/compiler/test/dotty/tools/dotc/reporting/TestReporter.scala @@ -2,12 +2,11 @@ package dotty.tools package dotc package reporting -import java.io.{ PrintStream, PrintWriter, File => JFile, FileOutputStream } +import java.io.{ FileOutputStream, PrintStream, PrintWriter, StringWriter, File => JFile } import java.text.SimpleDateFormat import java.util.Date import scala.collection.mutable - import util.SourcePosition import core.Contexts._ import Reporter._ @@ -79,6 +78,23 @@ extends Reporter with UniqueMessagePositions with HideNonSensicalMessages with M } } +class StoredTestReporter (val writer: StringWriter, val superWriter: PrintWriter, filePrintln: String => Unit, logLevel: Int) +extends TestReporter(superWriter, filePrintln, logLevel) { + override def doReport(m: MessageContainer)(implicit ctx: Context): Unit = { + super.doReport(m) + + val msg = + if (m.pos.exists) + "[" + diagnosticLevel(m) + "] Line " + (m.pos.line + 1) + ": " + m.contained().msg + else + "[" + diagnosticLevel(m) + "] " + m.contained().msg + + writer.write(msg + "\n") + } + + override def summary: String = "" +} + object TestReporter { private[this] var outFile: JFile = _ private[this] var logWriter: PrintWriter = _ @@ -115,6 +131,9 @@ object TestReporter { def reporter(ps: PrintStream, logLevel: Int): TestReporter = new TestReporter(new PrintWriter(ps, true), logPrintln, logLevel) + def storedReporter(ps: PrintStream, logLevel: Int): TestReporter = + new StoredTestReporter(new StringWriter(), new PrintWriter(ps, true), logPrintln, logLevel) + def simplifiedReporter(writer: PrintWriter): TestReporter = { val rep = new TestReporter(writer, logPrintln, WARNING) { /** Prints the message with the given position indication in a simplified manner */ diff --git a/compiler/test/dotty/tools/vulpix/ParallelTesting.scala b/compiler/test/dotty/tools/vulpix/ParallelTesting.scala index 7eba42f4b582..bf2bf86c3544 100644 --- a/compiler/test/dotty/tools/vulpix/ParallelTesting.scala +++ b/compiler/test/dotty/tools/vulpix/ParallelTesting.scala @@ -6,8 +6,8 @@ import java.io.{ File => JFile } import java.text.SimpleDateFormat import java.util.HashMap import java.nio.file.StandardCopyOption.REPLACE_EXISTING -import java.nio.file.{ Files, Path, Paths, NoSuchFileException } -import java.util.concurrent.{ Executors => JExecutors, TimeUnit, TimeoutException } +import java.nio.file.{ Files, NoSuchFileException, Path, Paths } +import java.util.concurrent.{ TimeUnit, TimeoutException, Executors => JExecutors } import scala.io.Source import scala.util.control.NonFatal @@ -15,9 +15,8 @@ import scala.util.Try import scala.collection.mutable import scala.util.matching.Regex import scala.util.Random - import dotc.core.Contexts._ -import dotc.reporting.{ Reporter, TestReporter } +import dotc.reporting.{ Reporter, StoredTestReporter, TestReporter } import dotc.reporting.diagnostic.MessageContainer import dotc.interfaces.Diagnostic.ERROR import dotc.util.DiffUtil @@ -48,7 +47,7 @@ trait ParallelTesting extends RunnerOrchestration { self => /** A test source whose files or directory of files is to be compiled * in a specific way defined by the `Test` */ - private sealed trait TestSource { self => + sealed trait TestSource { self => def name: String def outDir: JFile def flags: TestFlags @@ -129,7 +128,7 @@ trait ParallelTesting extends RunnerOrchestration { self => /** A group of files that may all be compiled together, with the same flags * and output directory */ - private case class JointCompilationSource( + case class JointCompilationSource( name: String, files: Array[JFile], flags: TestFlags, @@ -145,7 +144,7 @@ trait ParallelTesting extends RunnerOrchestration { self => /** A test source whose files will be compiled separately according to their * suffix `_X` */ - private case class SeparateCompilationSource( + case class SeparateCompilationSource( name: String, dir: JFile, flags: TestFlags, @@ -179,7 +178,7 @@ trait ParallelTesting extends RunnerOrchestration { self => /** Each `Test` takes the `testSources` and performs the compilation and assertions * according to the implementing class "neg", "run" or "pos". */ - private abstract class Test(testSources: List[TestSource], times: Int, threadLimit: Option[Int], suppressAllOutput: Boolean)(implicit val summaryReport: SummaryReporting) { test => + private abstract class Test(testSources: List[TestSource], times: Int, threadLimit: Option[Int], suppressAllOutput: Boolean, checkCompileOutput: Boolean = false)(implicit val summaryReport: SummaryReporting) { test => import summaryReport._ @@ -355,9 +354,12 @@ trait ParallelTesting extends RunnerOrchestration { self => else None } else None + val logLevel = if (suppressErrors || suppressAllOutput) ERROR + 1 else ERROR val reporter = - TestReporter.reporter(realStdout, logLevel = - if (suppressErrors || suppressAllOutput) ERROR + 1 else ERROR) + if (checkCompileOutput) + TestReporter.storedReporter(realStdout, logLevel = logLevel) + else + TestReporter.reporter(realStdout, logLevel = logLevel) val driver = if (times == 1) new Driver @@ -497,10 +499,28 @@ trait ParallelTesting extends RunnerOrchestration { self => private def flattenFiles(f: JFile): Array[JFile] = if (f.isDirectory) f.listFiles.flatMap(flattenFiles) else Array(f) + + protected def verifyCompileOutput(source: TestSource, checkFile: JFile, reporter: StoredTestReporter): Unit = { + reporter.writer.flush() + val checkLines = Source.fromFile(checkFile).getLines().mkString("\n") + val outputLines = reporter.writer.toString.trim.replaceAll("\\s+\n", "\n") + + if (outputLines != checkLines) { + val msg = s"Output from '${source.title}' did not match check file '${checkFile.getName}'." + println("===============================") + println("expected: \n" + checkLines) + println("actual: \n" + outputLines) + println("===============================") + + echo(msg) + addFailureInstruction(msg) + failTestSource(source) + } + } } - private final class PosTest(testSources: List[TestSource], times: Int, threadLimit: Option[Int], suppressAllOutput: Boolean)(implicit summaryReport: SummaryReporting) - extends Test(testSources, times, threadLimit, suppressAllOutput) { + private final class PosTest(testSources: List[TestSource], times: Int, threadLimit: Option[Int], suppressAllOutput: Boolean, checkCompileOutput: Boolean = false)(implicit summaryReport: SummaryReporting) + extends Test(testSources, times, threadLimit, suppressAllOutput, checkCompileOutput) { protected def encapsulatedCompilation(testSource: TestSource) = new LoggedRunnable { def checkTestSource(): Unit = tryCompile(testSource) { testSource match { @@ -578,6 +598,15 @@ trait ParallelTesting extends RunnerOrchestration { self => reporters.foreach(logReporterContents) logBuildInstructions(reporters.head, testSource, errorCount, warningCount) } + + // verify compilation check file + (1 to testSource.compilationGroups.length).foreach { index => + val checkFile = new JFile(dir.getAbsolutePath.reverse.dropWhile(_ == '/').reverse + "/" + index + ".check") + + if (checkFile.exists && checkCompileOutput) + verifyCompileOutput(testSource, checkFile, reporters(index).asInstanceOf[StoredTestReporter]) + } + } } } } @@ -701,8 +730,8 @@ trait ParallelTesting extends RunnerOrchestration { self => } } - private final class NegTest(testSources: List[TestSource], times: Int, threadLimit: Option[Int], suppressAllOutput: Boolean)(implicit summaryReport: SummaryReporting) - extends Test(testSources, times, threadLimit, suppressAllOutput) { + private final class NegTest(testSources: List[TestSource], times: Int, threadLimit: Option[Int], suppressAllOutput: Boolean, checkCompileOutput: Boolean = false)(implicit summaryReport: SummaryReporting) + extends Test(testSources, times, threadLimit, suppressAllOutput, checkCompileOutput) { protected def encapsulatedCompilation(testSource: TestSource) = new LoggedRunnable { def checkTestSource(): Unit = tryCompile(testSource) { // In neg-tests we allow two types of error annotations, @@ -779,6 +808,14 @@ trait ParallelTesting extends RunnerOrchestration { self => if (actualErrors > 0) reporters.foreach(logReporterContents) + // Compilation check file: for testing plugins + (1 to testSource.compilationGroups.length).foreach { index => + val checkFile = new JFile(dir.getAbsolutePath.reverse.dropWhile(_ == '/').reverse + "/" + index + ".check") + + if (checkFile.exists && checkCompileOutput) + verifyCompileOutput(testSource, checkFile, reporters(index).asInstanceOf[StoredTestReporter]) + } + (compilerCrashed, expectedErrors, actualErrors, () => getMissingExpectedErrors(errorMap, errors), errorMap) } } @@ -919,10 +956,10 @@ trait ParallelTesting extends RunnerOrchestration { self => ) { import org.junit.Assert.fail - private[ParallelTesting] def this(target: TestSource) = + def this(target: TestSource) = this(List(target), 1, true, None, false, false) - private[ParallelTesting] def this(targets: List[TestSource]) = + def this(targets: List[TestSource]) = this(targets, 1, true, None, false, false) /** Compose test targets from `this` with `other` @@ -951,8 +988,10 @@ trait ParallelTesting extends RunnerOrchestration { self => * compilation without generating errors and that they do not crash the * compiler */ - def checkCompile()(implicit summaryReport: SummaryReporting): this.type = { - val test = new PosTest(targets, times, threadLimit, shouldFail || shouldSuppressOutput).executeTestSuite() + def checkCompile(checkCompileOutput: Boolean = false)(implicit summaryReport: SummaryReporting): this.type = { + val test = new PosTest(targets, times, threadLimit, shouldFail || shouldSuppressOutput, checkCompileOutput).executeTestSuite() + + cleanup() if (!shouldFail && test.didFail) { fail(s"Expected no errors when compiling, failed for the following reason(s):\n${ reasonsForFailure(test) }") @@ -961,15 +1000,17 @@ trait ParallelTesting extends RunnerOrchestration { self => fail("Pos test should have failed, but didn't") } - cleanup() + this } /** Creates a "neg" test run, which makes sure that each test generates the * correct amount of errors at the correct positions. It also makes sure * that none of these tests crash the compiler */ - def checkExpectedErrors()(implicit summaryReport: SummaryReporting): this.type = { - val test = new NegTest(targets, times, threadLimit, shouldFail || shouldSuppressOutput).executeTestSuite() + def checkExpectedErrors(checkCompileOutput : Boolean = false)(implicit summaryReport: SummaryReporting): this.type = { + val test = new NegTest(targets, times, threadLimit, shouldFail || shouldSuppressOutput, checkCompileOutput).executeTestSuite() + + cleanup() if (!shouldFail && test.didFail) { fail(s"Neg test shouldn't have failed, but did. Reasons:\n${ reasonsForFailure(test) }") @@ -978,7 +1019,7 @@ trait ParallelTesting extends RunnerOrchestration { self => fail("Neg test should have failed, but did not") } - cleanup() + this } /** Creates a "run" test run, which is a superset of "pos". In addition to @@ -989,6 +1030,8 @@ trait ParallelTesting extends RunnerOrchestration { self => def checkRuns()(implicit summaryReport: SummaryReporting): this.type = { val test = new RunTest(targets, times, threadLimit, shouldFail || shouldSuppressOutput).executeTestSuite() + cleanup() + if (!shouldFail && test.didFail) { fail(s"Run test failed, but should not, reasons:\n${ reasonsForFailure(test) }") } @@ -996,7 +1039,7 @@ trait ParallelTesting extends RunnerOrchestration { self => fail("Run test should have failed, but did not") } - cleanup() + this } /** Deletes output directories and files */ @@ -1099,7 +1142,7 @@ trait ParallelTesting extends RunnerOrchestration { self => } /** Create out directory for directory `d` */ - private def createOutputDirsForDir(d: JFile, sourceDir: JFile, outDir: String): JFile = { + def createOutputDirsForDir(d: JFile, sourceDir: JFile, outDir: String): JFile = { val targetDir = new JFile(outDir + s"${sourceDir.getName}/${d.getName}") targetDir.mkdirs() targetDir diff --git a/tests/plugins/neg/divideZero/Test_2.scala b/tests/plugins/neg/divideZero/Test_2.scala new file mode 100644 index 000000000000..896932fb5274 --- /dev/null +++ b/tests/plugins/neg/divideZero/Test_2.scala @@ -0,0 +1,6 @@ +class Test { + val y = 5 / 0 // error + 100 + 6 / 0 // error + 6L / 0L // error + val z = 7 / 0.0 // error +} diff --git a/tests/plugins/neg/divideZero/plugin_1.scala b/tests/plugins/neg/divideZero/plugin_1.scala new file mode 100644 index 000000000000..2c1ce30dcf48 --- /dev/null +++ b/tests/plugins/neg/divideZero/plugin_1.scala @@ -0,0 +1,37 @@ +import dotty.tools.dotc._ +import core._ +import Contexts.Context +import plugins.Plugin +import Phases.Phase +import ast.tpd +import transform.MegaPhase.MiniPhase +import Decorators._ +import Symbols.Symbol +import Constants.Constant + +class DivideZero extends MiniPhase with Plugin { + val name: String = "divideZero" + override val description: String = "divide zero check" + + val phaseName = name + + override def init(phases: List[List[Phase]])(implicit ctx: Context): List[List[Phase]] = { + val (before, after) = phases.span(ps => !ps.exists(_.phaseName == "pickler")) + before ++ (List(this) :: after) + } + + private def isNumericDivide(sym: Symbol)(implicit ctx: Context): Boolean = { + def test(tpe: String): Boolean = + (sym.owner eq ctx.requiredClass(tpe.toTermName)) && sym.name.show == "/" + + test("scala.Int") || test("scala.Long") || test("scala.Short") || test("scala.FLoat") || test("scala.Double") + } + + override def transformApply(tree: tpd.Apply)(implicit ctx: Context): tpd.Tree = tree match { + case tpd.Apply(fun, tpd.Literal(Constants.Constant(v)) :: Nil) if isNumericDivide(fun.symbol) && v == 0 => + ctx.error("divide by zero", tree.pos) + tree + case _ => + tree + } +} diff --git a/tests/plugins/neg/divideZero/scalac-plugin.xml b/tests/plugins/neg/divideZero/scalac-plugin.xml new file mode 100644 index 000000000000..67ac36ab0562 --- /dev/null +++ b/tests/plugins/neg/divideZero/scalac-plugin.xml @@ -0,0 +1,4 @@ + + divideZero + DivideZero + diff --git a/tests/plugins/pos/divideZero/2.check b/tests/plugins/pos/divideZero/2.check new file mode 100644 index 000000000000..027477a8331b --- /dev/null +++ b/tests/plugins/pos/divideZero/2.check @@ -0,0 +1,3 @@ +[Warning] Line 2: divide by zero +[Warning] Line 3: divide by zero +[Warning] Line 4: divide by zero diff --git a/tests/plugins/pos/divideZero/Test_2.scala b/tests/plugins/pos/divideZero/Test_2.scala new file mode 100644 index 000000000000..e9eaf159b9c8 --- /dev/null +++ b/tests/plugins/pos/divideZero/Test_2.scala @@ -0,0 +1,5 @@ +class Test { + 100 + 6 / 0 + 6L / 0L + println(3 + 7 / 0.0) +} diff --git a/tests/plugins/pos/divideZero/plugin_1.scala b/tests/plugins/pos/divideZero/plugin_1.scala new file mode 100644 index 000000000000..d9bd7d3a504a --- /dev/null +++ b/tests/plugins/pos/divideZero/plugin_1.scala @@ -0,0 +1,37 @@ +import dotty.tools.dotc._ +import core._ +import Contexts.Context +import plugins.Plugin +import Phases.Phase +import ast.tpd +import transform.MegaPhase.MiniPhase +import Decorators._ +import Symbols.Symbol +import Constants.Constant + +class DivideZero extends MiniPhase with Plugin { + val name: String = "divideZero" + override val description: String = "divide by zero check" + + val phaseName = name + + override def init(phases: List[List[Phase]])(implicit ctx: Context): List[List[Phase]] = { + val (before, after) = phases.span(ps => !ps.exists(_.phaseName == "pickler")) + before ++ (List(this) :: after) + } + + private def isNumericDivide(sym: Symbol)(implicit ctx: Context): Boolean = { + def test(tpe: String): Boolean = + (sym.owner eq ctx.requiredClass(tpe.toTermName)) && sym.name.show == "/" + + test("scala.Int") || test("scala.Long") || test("scala.Short") || test("scala.FLoat") || test("scala.Double") + } + + override def transformApply(tree: tpd.Apply)(implicit ctx: Context): tpd.Tree = tree match { + case tpd.Apply(fun, tpd.Literal(Constants.Constant(v)) :: Nil) if isNumericDivide(fun.symbol) && v == 0 => + ctx.warning("divide by zero", tree.pos) + tree + case _ => + tree + } +} diff --git a/tests/plugins/pos/divideZero/scalac-plugin.xml b/tests/plugins/pos/divideZero/scalac-plugin.xml new file mode 100644 index 000000000000..519837dd4d8a --- /dev/null +++ b/tests/plugins/pos/divideZero/scalac-plugin.xml @@ -0,0 +1,4 @@ + + devideZero + DivideZero + From 3d67c33fb9256ecf598ac14c395127e7a5106e22 Mon Sep 17 00:00:00 2001 From: liu fengyun Date: Sat, 4 Nov 2017 00:58:36 +0100 Subject: [PATCH 03/27] refine error message --- .../test/dotty/tools/vulpix/ParallelTesting.scala | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/compiler/test/dotty/tools/vulpix/ParallelTesting.scala b/compiler/test/dotty/tools/vulpix/ParallelTesting.scala index bf2bf86c3544..5242d319928c 100644 --- a/compiler/test/dotty/tools/vulpix/ParallelTesting.scala +++ b/compiler/test/dotty/tools/vulpix/ParallelTesting.scala @@ -506,11 +506,16 @@ trait ParallelTesting extends RunnerOrchestration { self => val outputLines = reporter.writer.toString.trim.replaceAll("\\s+\n", "\n") if (outputLines != checkLines) { - val msg = s"Output from '${source.title}' did not match check file '${checkFile.getName}'." - println("===============================") - println("expected: \n" + checkLines) - println("actual: \n" + outputLines) - println("===============================") + val msg = + s"""|Output from '${source.title}' did not match check file '${checkFile.getName}'. + |------------------------------------- + |expected: + |$checkLines + | + |actual: + |$outputLines + |------------------------------------- + """.stripMargin echo(msg) addFailureInstruction(msg) From 7205c448d40e8a210a5e51a706bbb484660acdac Mon Sep 17 00:00:00 2001 From: liu fengyun Date: Thu, 16 Nov 2017 17:13:47 +0100 Subject: [PATCH 04/27] support both ordering and research plugin --- compiler/src/dotty/tools/dotc/Run.scala | 2 +- .../src/dotty/tools/dotc/plugins/Plugin.scala | 41 ++++++++---- .../dotty/tools/dotc/plugins/Plugins.scala | 66 ++++++++++++++++++- tests/plugins/neg/divideZero/plugin_1.scala | 2 + tests/plugins/pos/divideZero/plugin_1.scala | 2 + 5 files changed, 98 insertions(+), 15 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/Run.scala b/compiler/src/dotty/tools/dotc/Run.scala index 600f647b0afd..bcab1c50db65 100644 --- a/compiler/src/dotty/tools/dotc/Run.scala +++ b/compiler/src/dotty/tools/dotc/Run.scala @@ -157,7 +157,7 @@ class Run(comp: Compiler, ictx: Context) extends ImplicitRunInfo with Constraint if (ctx.settings.YtestPickler.value) List("pickler") else ctx.settings.YstopAfter.value - val pluginPlan = ctx.plugins.foldRight(ctx.phasePlan) { (plug, plan) => plug.init(plan) } + val pluginPlan = ctx.addPluginPhases(ctx.phasePlan) val phases = ctx.squashPhases(pluginPlan, ctx.settings.Yskip.value, ctx.settings.YstopBefore.value, stopAfter, ctx.settings.Ycheck.value) ctx.usePhases(phases) diff --git a/compiler/src/dotty/tools/dotc/plugins/Plugin.scala b/compiler/src/dotty/tools/dotc/plugins/Plugin.scala index c4d634f175ac..159f75077160 100644 --- a/compiler/src/dotty/tools/dotc/plugins/Plugin.scala +++ b/compiler/src/dotty/tools/dotc/plugins/Plugin.scala @@ -11,6 +11,10 @@ import java.io.InputStream import scala.collection.mutable import scala.util.{ Try, Success, Failure } +trait PluginPhase extends Phase { + def runsBefore: Set[Class[_ <: Phase]] = Set.empty +} + trait Plugin { /** The name of this plugin */ def name: String @@ -18,23 +22,36 @@ trait Plugin { /** A one-line description of the plugin */ def description: String + /** The phases that this plugin defines */ + def components: List[PluginPhase] = Nil + + /** Is this plugin a research plugin? + * + * Research plugin receives a phase plan and return a new phase plan, while + * non-research plugin returns a list of phases to be inserted. + */ + def research: Boolean = false + + /** Handle any plugin-specific options. + * The user writes `-P:plugname:opt1,opt2`, + * but the plugin sees `List(opt1, opt2)`. + */ def options(implicit ctx: Context): List[String] = { // Process plugin options of form plugin:option def namec = name + ":" ctx.settings.pluginOptions.value filter (_ startsWith namec) map (_ stripPrefix namec) } - /** Handle any plugin-specific options. - * The user writes `-P:plugname:opt1,opt2`, - * but the plugin sees `List(opt1, opt2)`. - * The plugin can opt out of further processing - * by returning false. For example, if the plugin - * has an "enable" flag, now would be a good time - * to sit on the bench. - * @param options plugin arguments - * @param error error function - * @return true to continue, or false to opt out + /** Non-research plugins should override this method to return the phases + * + * @return a list of phases to be added to the phase plan */ + def init()(implicit ctx: Context): List[Phase] = Nil + + /** Research plugins should override this method to return the new phase plan + * + * @return the new phase plan + */ def init(phases: List[List[Phase]])(implicit ctx: Context): List[List[Phase]] = phases /** A description of this plugin's options, suitable as a response @@ -72,11 +89,11 @@ object Plugin { else PluginDescription.fromXML(is) val xmlEntry = new java.util.jar.JarEntry(PluginXML) - Try(read(new Jar(jarp.jfile).getEntryStream(xmlEntry))) + Try(read(new Jar(jarp.jpath.toFile).getEntryStream(xmlEntry))) } private def loadDescriptionFromFile(f: Path): Try[PluginDescription] = - Try(PluginDescription.fromXML(new java.io.FileInputStream(f.jfile))) + Try(PluginDescription.fromXML(new java.io.FileInputStream(f.jpath.toFile))) type AnyClass = Class[_] diff --git a/compiler/src/dotty/tools/dotc/plugins/Plugins.scala b/compiler/src/dotty/tools/dotc/plugins/Plugins.scala index 0daf18e25558..dd01cad25810 100644 --- a/compiler/src/dotty/tools/dotc/plugins/Plugins.scala +++ b/compiler/src/dotty/tools/dotc/plugins/Plugins.scala @@ -3,8 +3,9 @@ package plugins import core._ import Contexts._ -import dotty.tools.dotc.config.PathResolver +import config.PathResolver import dotty.tools.io._ +import Phases._ /** Support for run-time loading of compiler plugins. * @@ -80,7 +81,7 @@ trait Plugins { } } - val plugs = pick(roughPluginsList, Set()) + val plugs = pick(roughPluginsList, ctx.phasePlan.flatten.map(_.phaseName).toSet) // Verify required plugins are present. for (req <- ctx.settings.require.value ; if !(plugs exists (_.name == req))) @@ -113,4 +114,65 @@ trait Plugins { (for (plug <- roughPluginsList ; help <- plug.optionsHelp) yield { "\nOptions for plugin '%s':\n%s\n".format(plug.name, help) }).mkString + + /** Add plugin phases to phase plan */ + def addPluginPhases(plan: List[List[Phase]])(implicit ctx: Context): List[List[Phase]] = { + import scala.collection.mutable.{ Set => MSet, Map => MMap } + type OrderingReq = (MSet[Class[_]], MSet[Class[_]]) + + val orderRequirements = MMap[Class[_], OrderingReq]() + + def updateOrdering(phase: PluginPhase): Unit = { + val runsBefore: MSet[Class[_]] = MSet(phase.runsBefore.toSeq: _*) + val runsAfter: MSet[Class[_]] = MSet(phase.runsAfter.toSeq: _*) + + if (!orderRequirements.contains(phase.getClass)) { + orderRequirements.update(phase.getClass, (runsBefore, runsAfter) ) + } + + runsBefore.foreach { phaseClass => + if (!orderRequirements.contains(phaseClass)) + orderRequirements.update(phaseClass, (MSet.empty, MSet.empty)) + val (_, runsAfter) = orderRequirements(phaseClass) + runsAfter += phase.getClass + } + + runsAfter.foreach { phaseClass => + if (!orderRequirements.contains(phaseClass)) + orderRequirements.update(phaseClass, (MSet.empty, MSet.empty)) + val (runsBefore, _) = orderRequirements(phaseClass) + runsBefore += phase.getClass + } + } + + // add non-research plugins + var updatedPlan = plan + plugins.filter(!_.research).foreach { plug => + plug.components.foreach { phase => + updateOrdering(phase) + + val beforePhases: MSet[Class[_]] = MSet(phase.runsBefore.toSeq: _*) + val afterPhases: MSet[Class[_]] = MSet(phase.runsAfter.toSeq: _*) + + val (before, after) = updatedPlan.span { ps => + val classes = ps.map(_.getClass) + afterPhases --= classes + !classes.exists(beforePhases.contains) // beforeReq satisfied + } + + // check afterReq + // error can occur if: a < b, b < c, c < a + after.foreach { ps => + val classes = ps.map(_.getClass) + if (classes.exists(afterPhases)) // afterReq satisfied + throw new Exception(s"Ordering conflict for plugin ${plug.name}") + } + + updatedPlan = before ++ (List(phase) :: after) + } + } + + // add research plugins + ctx.plugins.filter(_.research).foldRight(updatedPlan) { (plug, plan) => plug.init(plan) } + } } diff --git a/tests/plugins/neg/divideZero/plugin_1.scala b/tests/plugins/neg/divideZero/plugin_1.scala index 2c1ce30dcf48..730bc17d4bb5 100644 --- a/tests/plugins/neg/divideZero/plugin_1.scala +++ b/tests/plugins/neg/divideZero/plugin_1.scala @@ -13,6 +13,8 @@ class DivideZero extends MiniPhase with Plugin { val name: String = "divideZero" override val description: String = "divide zero check" + override val research = true + val phaseName = name override def init(phases: List[List[Phase]])(implicit ctx: Context): List[List[Phase]] = { diff --git a/tests/plugins/pos/divideZero/plugin_1.scala b/tests/plugins/pos/divideZero/plugin_1.scala index d9bd7d3a504a..95015f5bfed3 100644 --- a/tests/plugins/pos/divideZero/plugin_1.scala +++ b/tests/plugins/pos/divideZero/plugin_1.scala @@ -15,6 +15,8 @@ class DivideZero extends MiniPhase with Plugin { val phaseName = name + override val research = true + override def init(phases: List[List[Phase]])(implicit ctx: Context): List[List[Phase]] = { val (before, after) = phases.span(ps => !ps.exists(_.phaseName == "pickler")) before ++ (List(this) :: after) From a9c28899d63b5a750d0e2c73ba5ad40ea4330661 Mon Sep 17 00:00:00 2001 From: liu fengyun Date: Thu, 16 Nov 2017 17:27:39 +0100 Subject: [PATCH 05/27] fix syntax error after rebase --- compiler/test/dotty/tools/vulpix/ParallelTesting.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/compiler/test/dotty/tools/vulpix/ParallelTesting.scala b/compiler/test/dotty/tools/vulpix/ParallelTesting.scala index 5242d319928c..f3e674cfa42e 100644 --- a/compiler/test/dotty/tools/vulpix/ParallelTesting.scala +++ b/compiler/test/dotty/tools/vulpix/ParallelTesting.scala @@ -611,7 +611,6 @@ trait ParallelTesting extends RunnerOrchestration { self => if (checkFile.exists && checkCompileOutput) verifyCompileOutput(testSource, checkFile, reporters(index).asInstanceOf[StoredTestReporter]) } - } } } } From 38ed11a5e73bdecde8fa6e7134e200b4b78c4a8d Mon Sep 17 00:00:00 2001 From: liu fengyun Date: Thu, 16 Nov 2017 17:41:03 +0100 Subject: [PATCH 06/27] add non-research plugin tests --- .../src/dotty/tools/dotc/plugins/Plugin.scala | 10 ++--- .../dotty/tools/dotc/plugins/Plugins.scala | 2 +- .../neg/divideZero-research/Test_2.scala | 6 +++ .../neg/divideZero-research/plugin_1.scala | 39 +++++++++++++++++++ .../neg/divideZero-research/scalac-plugin.xml | 4 ++ tests/plugins/neg/divideZero/plugin_1.scala | 15 ++++--- 6 files changed, 61 insertions(+), 15 deletions(-) create mode 100644 tests/plugins/neg/divideZero-research/Test_2.scala create mode 100644 tests/plugins/neg/divideZero-research/plugin_1.scala create mode 100644 tests/plugins/neg/divideZero-research/scalac-plugin.xml diff --git a/compiler/src/dotty/tools/dotc/plugins/Plugin.scala b/compiler/src/dotty/tools/dotc/plugins/Plugin.scala index 159f75077160..3f3956c4c64d 100644 --- a/compiler/src/dotty/tools/dotc/plugins/Plugin.scala +++ b/compiler/src/dotty/tools/dotc/plugins/Plugin.scala @@ -5,13 +5,14 @@ import core._ import Contexts._ import Phases._ import dotty.tools.io._ +import transform.MegaPhase.MiniPhase import java.io.InputStream import scala.collection.mutable import scala.util.{ Try, Success, Failure } -trait PluginPhase extends Phase { +trait PluginPhase extends MiniPhase { def runsBefore: Set[Class[_ <: Phase]] = Set.empty } @@ -22,9 +23,6 @@ trait Plugin { /** A one-line description of the plugin */ def description: String - /** The phases that this plugin defines */ - def components: List[PluginPhase] = Nil - /** Is this plugin a research plugin? * * Research plugin receives a phase plan and return a new phase plan, while @@ -46,13 +44,13 @@ trait Plugin { * * @return a list of phases to be added to the phase plan */ - def init()(implicit ctx: Context): List[Phase] = Nil + def init()(implicit ctx: Context): List[PluginPhase] = ??? /** Research plugins should override this method to return the new phase plan * * @return the new phase plan */ - def init(phases: List[List[Phase]])(implicit ctx: Context): List[List[Phase]] = phases + def init(phases: List[List[Phase]])(implicit ctx: Context): List[List[Phase]] = ??? /** A description of this plugin's options, suitable as a response * to the -help command-line option. Conventionally, the options diff --git a/compiler/src/dotty/tools/dotc/plugins/Plugins.scala b/compiler/src/dotty/tools/dotc/plugins/Plugins.scala index dd01cad25810..ad5f2eaa7f23 100644 --- a/compiler/src/dotty/tools/dotc/plugins/Plugins.scala +++ b/compiler/src/dotty/tools/dotc/plugins/Plugins.scala @@ -148,7 +148,7 @@ trait Plugins { // add non-research plugins var updatedPlan = plan plugins.filter(!_.research).foreach { plug => - plug.components.foreach { phase => + plug.init().foreach { phase => updateOrdering(phase) val beforePhases: MSet[Class[_]] = MSet(phase.runsBefore.toSeq: _*) diff --git a/tests/plugins/neg/divideZero-research/Test_2.scala b/tests/plugins/neg/divideZero-research/Test_2.scala new file mode 100644 index 000000000000..896932fb5274 --- /dev/null +++ b/tests/plugins/neg/divideZero-research/Test_2.scala @@ -0,0 +1,6 @@ +class Test { + val y = 5 / 0 // error + 100 + 6 / 0 // error + 6L / 0L // error + val z = 7 / 0.0 // error +} diff --git a/tests/plugins/neg/divideZero-research/plugin_1.scala b/tests/plugins/neg/divideZero-research/plugin_1.scala new file mode 100644 index 000000000000..730bc17d4bb5 --- /dev/null +++ b/tests/plugins/neg/divideZero-research/plugin_1.scala @@ -0,0 +1,39 @@ +import dotty.tools.dotc._ +import core._ +import Contexts.Context +import plugins.Plugin +import Phases.Phase +import ast.tpd +import transform.MegaPhase.MiniPhase +import Decorators._ +import Symbols.Symbol +import Constants.Constant + +class DivideZero extends MiniPhase with Plugin { + val name: String = "divideZero" + override val description: String = "divide zero check" + + override val research = true + + val phaseName = name + + override def init(phases: List[List[Phase]])(implicit ctx: Context): List[List[Phase]] = { + val (before, after) = phases.span(ps => !ps.exists(_.phaseName == "pickler")) + before ++ (List(this) :: after) + } + + private def isNumericDivide(sym: Symbol)(implicit ctx: Context): Boolean = { + def test(tpe: String): Boolean = + (sym.owner eq ctx.requiredClass(tpe.toTermName)) && sym.name.show == "/" + + test("scala.Int") || test("scala.Long") || test("scala.Short") || test("scala.FLoat") || test("scala.Double") + } + + override def transformApply(tree: tpd.Apply)(implicit ctx: Context): tpd.Tree = tree match { + case tpd.Apply(fun, tpd.Literal(Constants.Constant(v)) :: Nil) if isNumericDivide(fun.symbol) && v == 0 => + ctx.error("divide by zero", tree.pos) + tree + case _ => + tree + } +} diff --git a/tests/plugins/neg/divideZero-research/scalac-plugin.xml b/tests/plugins/neg/divideZero-research/scalac-plugin.xml new file mode 100644 index 000000000000..67ac36ab0562 --- /dev/null +++ b/tests/plugins/neg/divideZero-research/scalac-plugin.xml @@ -0,0 +1,4 @@ + + divideZero + DivideZero + diff --git a/tests/plugins/neg/divideZero/plugin_1.scala b/tests/plugins/neg/divideZero/plugin_1.scala index 730bc17d4bb5..350d382845c7 100644 --- a/tests/plugins/neg/divideZero/plugin_1.scala +++ b/tests/plugins/neg/divideZero/plugin_1.scala @@ -1,26 +1,25 @@ import dotty.tools.dotc._ import core._ import Contexts.Context -import plugins.Plugin +import plugins.{Plugin, PluginPhase} import Phases.Phase import ast.tpd import transform.MegaPhase.MiniPhase import Decorators._ import Symbols.Symbol import Constants.Constant +import transform.{LinkAll, Pickler} -class DivideZero extends MiniPhase with Plugin { +class DivideZero extends PluginPhase with Plugin { val name: String = "divideZero" override val description: String = "divide zero check" - override val research = true - val phaseName = name - override def init(phases: List[List[Phase]])(implicit ctx: Context): List[List[Phase]] = { - val (before, after) = phases.span(ps => !ps.exists(_.phaseName == "pickler")) - before ++ (List(this) :: after) - } + override val runsAfter = Set(classOf[Pickler]) + override val runsBefore = Set(classOf[LinkAll]) + + override def init()(implicit ctx: Context): List[PluginPhase] = this :: Nil private def isNumericDivide(sym: Symbol)(implicit ctx: Context): Boolean = { def test(tpe: String): Boolean = From a1a8c1213d97c21681ef42de04f19eee9a58bc8e Mon Sep 17 00:00:00 2001 From: liu fengyun Date: Fri, 17 Nov 2017 10:49:20 +0100 Subject: [PATCH 07/27] refactor ordering algorithm for unit test --- .../src/dotty/tools/dotc/plugins/Plugin.scala | 24 +++++------- .../dotty/tools/dotc/plugins/Plugins.scala | 39 ++++++++++++++++--- .../neg/divideZero-research/plugin_1.scala | 2 +- tests/plugins/neg/divideZero/plugin_1.scala | 2 +- tests/plugins/pos/divideZero/plugin_1.scala | 2 +- 5 files changed, 45 insertions(+), 24 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/plugins/Plugin.scala b/compiler/src/dotty/tools/dotc/plugins/Plugin.scala index 3f3956c4c64d..0afa68bb5a84 100644 --- a/compiler/src/dotty/tools/dotc/plugins/Plugin.scala +++ b/compiler/src/dotty/tools/dotc/plugins/Plugin.scala @@ -30,27 +30,21 @@ trait Plugin { */ def research: Boolean = false - /** Handle any plugin-specific options. - * The user writes `-P:plugname:opt1,opt2`, - * but the plugin sees `List(opt1, opt2)`. - */ - def options(implicit ctx: Context): List[String] = { - // Process plugin options of form plugin:option - def namec = name + ":" - ctx.settings.pluginOptions.value filter (_ startsWith namec) map (_ stripPrefix namec) - } /** Non-research plugins should override this method to return the phases * + * @param options: commandline options to the plugin, `-P:plugname:opt1,opt2` * @return a list of phases to be added to the phase plan */ - def init()(implicit ctx: Context): List[PluginPhase] = ??? + def init(options: List[String]): List[PluginPhase] = ??? - /** Research plugins should override this method to return the new phase plan - * - * @return the new phase plan - */ - def init(phases: List[List[Phase]])(implicit ctx: Context): List[List[Phase]] = ??? + /** Research plugins should override this method to return the new phase plan + * + * @param options: commandline options to the plugin, `-P:plugname:opt1,opt2` + * @param plan: the given phase plan + * @return the new phase plan + */ + def init(options: List[String], plan: List[List[Phase]])(implicit ctx: Context): List[List[Phase]] = ??? /** A description of this plugin's options, suitable as a response * to the -help command-line option. Conventionally, the options diff --git a/compiler/src/dotty/tools/dotc/plugins/Plugins.scala b/compiler/src/dotty/tools/dotc/plugins/Plugins.scala index ad5f2eaa7f23..0ecb92bc5b46 100644 --- a/compiler/src/dotty/tools/dotc/plugins/Plugins.scala +++ b/compiler/src/dotty/tools/dotc/plugins/Plugins.scala @@ -117,6 +117,30 @@ trait Plugins { /** Add plugin phases to phase plan */ def addPluginPhases(plan: List[List[Phase]])(implicit ctx: Context): List[List[Phase]] = { + // plugin-specific options. + // The user writes `-P:plugname:opt1,opt2`, but the plugin sees `List(opt1, opt2)`. + def options(plugin: Plugin): List[String] = { + def namec = plugin.name + ":" + ctx.settings.pluginOptions.value filter (_ startsWith namec) map (_ stripPrefix namec) + } + + // schedule plugins according to ordering constraints + val updatedPlan = Plugins.schedule(plan, plugins.filter(!_.research), options) + + // add research plugins + plugins.filter(_.research).foldRight(updatedPlan) { (plug, plan) => plug.init(options(plug), plan) } + } +} + +object Plugins { + /** Insert plugin phases in the right place of the phase plan + * + * The scheduling makes sure the ordering constraints of plugin phases are satisfied. + * If the ordering constraints are unsatisfiable, an exception is thrown. + * + * Note: this algorithm is factored out for unit test. + */ + def schedule(plan: List[List[Phase]], plugins: List[Plugin], optionFn: Plugin => List[String]): List[List[Phase]] = { import scala.collection.mutable.{ Set => MSet, Map => MMap } type OrderingReq = (MSet[Class[_]], MSet[Class[_]]) @@ -145,19 +169,23 @@ trait Plugins { } } - // add non-research plugins var updatedPlan = plan - plugins.filter(!_.research).foreach { plug => - plug.init().foreach { phase => + plugins.foreach { plug => + plug.init(optionFn(plug)).foreach { phase => updateOrdering(phase) val beforePhases: MSet[Class[_]] = MSet(phase.runsBefore.toSeq: _*) val afterPhases: MSet[Class[_]] = MSet(phase.runsAfter.toSeq: _*) + // beforeReq met after the split val (before, after) = updatedPlan.span { ps => val classes = ps.map(_.getClass) afterPhases --= classes - !classes.exists(beforePhases.contains) // beforeReq satisfied + // Prefer the point immediately before the first beforePhases. + // If beforePhases not specified, insert at the point immediately + // after the last afterPhases. + !classes.exists(beforePhases.contains) && + !(beforePhases.isEmpty && afterPhases.isEmpty) } // check afterReq @@ -172,7 +200,6 @@ trait Plugins { } } - // add research plugins - ctx.plugins.filter(_.research).foldRight(updatedPlan) { (plug, plan) => plug.init(plan) } + updatedPlan } } diff --git a/tests/plugins/neg/divideZero-research/plugin_1.scala b/tests/plugins/neg/divideZero-research/plugin_1.scala index 730bc17d4bb5..7416d33fb434 100644 --- a/tests/plugins/neg/divideZero-research/plugin_1.scala +++ b/tests/plugins/neg/divideZero-research/plugin_1.scala @@ -17,7 +17,7 @@ class DivideZero extends MiniPhase with Plugin { val phaseName = name - override def init(phases: List[List[Phase]])(implicit ctx: Context): List[List[Phase]] = { + override def init(options: List[String], phases: List[List[Phase]])(implicit ctx: Context): List[List[Phase]] = { val (before, after) = phases.span(ps => !ps.exists(_.phaseName == "pickler")) before ++ (List(this) :: after) } diff --git a/tests/plugins/neg/divideZero/plugin_1.scala b/tests/plugins/neg/divideZero/plugin_1.scala index 350d382845c7..85ecbc822f8d 100644 --- a/tests/plugins/neg/divideZero/plugin_1.scala +++ b/tests/plugins/neg/divideZero/plugin_1.scala @@ -19,7 +19,7 @@ class DivideZero extends PluginPhase with Plugin { override val runsAfter = Set(classOf[Pickler]) override val runsBefore = Set(classOf[LinkAll]) - override def init()(implicit ctx: Context): List[PluginPhase] = this :: Nil + override def init(options: List[String]): List[PluginPhase] = this :: Nil private def isNumericDivide(sym: Symbol)(implicit ctx: Context): Boolean = { def test(tpe: String): Boolean = diff --git a/tests/plugins/pos/divideZero/plugin_1.scala b/tests/plugins/pos/divideZero/plugin_1.scala index 95015f5bfed3..94ff37bd2aea 100644 --- a/tests/plugins/pos/divideZero/plugin_1.scala +++ b/tests/plugins/pos/divideZero/plugin_1.scala @@ -17,7 +17,7 @@ class DivideZero extends MiniPhase with Plugin { override val research = true - override def init(phases: List[List[Phase]])(implicit ctx: Context): List[List[Phase]] = { + override def init(options: List[String], phases: List[List[Phase]])(implicit ctx: Context): List[List[Phase]] = { val (before, after) = phases.span(ps => !ps.exists(_.phaseName == "pickler")) before ++ (List(this) :: after) } From b05aa22f153855f05d617c148dda285df0cdfcb4 Mon Sep 17 00:00:00 2001 From: liu fengyun Date: Fri, 17 Nov 2017 12:32:17 +0100 Subject: [PATCH 08/27] unit test plugin schedule algorithm --- .../src/dotty/tools/dotc/plugins/Plugin.scala | 4 +- .../dotty/tools/dotc/plugins/Plugins.scala | 25 +++- .../tools/dotc/plugins/PluginsTest.scala | 135 ++++++++++++++++++ 3 files changed, 156 insertions(+), 8 deletions(-) create mode 100644 compiler/test/dotty/tools/dotc/plugins/PluginsTest.scala diff --git a/compiler/src/dotty/tools/dotc/plugins/Plugin.scala b/compiler/src/dotty/tools/dotc/plugins/Plugin.scala index 0afa68bb5a84..42df7d13feff 100644 --- a/compiler/src/dotty/tools/dotc/plugins/Plugin.scala +++ b/compiler/src/dotty/tools/dotc/plugins/Plugin.scala @@ -33,14 +33,14 @@ trait Plugin { /** Non-research plugins should override this method to return the phases * - * @param options: commandline options to the plugin, `-P:plugname:opt1,opt2` + * @param options: commandline options to the plugin, `-P:plugname:opt1,opt2` becomes List(opt1, opt2) * @return a list of phases to be added to the phase plan */ def init(options: List[String]): List[PluginPhase] = ??? /** Research plugins should override this method to return the new phase plan * - * @param options: commandline options to the plugin, `-P:plugname:opt1,opt2` + * @param options: commandline options to the plugin, `-P:plugname:opt1,opt2` becomes List(opt1, opt2) * @param plan: the given phase plan * @return the new phase plan */ diff --git a/compiler/src/dotty/tools/dotc/plugins/Plugins.scala b/compiler/src/dotty/tools/dotc/plugins/Plugins.scala index 0ecb92bc5b46..45a796d7b06d 100644 --- a/compiler/src/dotty/tools/dotc/plugins/Plugins.scala +++ b/compiler/src/dotty/tools/dotc/plugins/Plugins.scala @@ -145,6 +145,11 @@ object Plugins { type OrderingReq = (MSet[Class[_]], MSet[Class[_]]) val orderRequirements = MMap[Class[_], OrderingReq]() + val existingPhases = { + val set = MSet.empty[Class[_]] + for (ps <- plan; p <- ps) set += p.getClass + set + } def updateOrdering(phase: PluginPhase): Unit = { val runsBefore: MSet[Class[_]] = MSet(phase.runsBefore.toSeq: _*) @@ -152,6 +157,10 @@ object Plugins { if (!orderRequirements.contains(phase.getClass)) { orderRequirements.update(phase.getClass, (runsBefore, runsAfter) ) + } else { + val (runsBefore1, runsAfter1) = orderRequirements(phase.getClass) + runsAfter1 ++= runsAfter + runsBefore1 ++= runsBefore } runsBefore.foreach { phaseClass => @@ -174,28 +183,32 @@ object Plugins { plug.init(optionFn(plug)).foreach { phase => updateOrdering(phase) - val beforePhases: MSet[Class[_]] = MSet(phase.runsBefore.toSeq: _*) - val afterPhases: MSet[Class[_]] = MSet(phase.runsAfter.toSeq: _*) + val (runsBefore1, runsAfter1) = orderRequirements(phase.getClass) + val runsBefore: MSet[Class[_]] = runsBefore1 & existingPhases // MSet(runsBefore1.filter(existingPhases.contains).toSeq: _*) + val runsAfter: MSet[Class[_]] = runsAfter1 & existingPhases // MSet(runsAfter1.filter(existingPhases.contains).toSeq: _*) // beforeReq met after the split val (before, after) = updatedPlan.span { ps => val classes = ps.map(_.getClass) - afterPhases --= classes + val runsAfterSat = runsAfter.isEmpty + runsAfter --= classes // Prefer the point immediately before the first beforePhases. // If beforePhases not specified, insert at the point immediately // after the last afterPhases. - !classes.exists(beforePhases.contains) && - !(beforePhases.isEmpty && afterPhases.isEmpty) + !classes.exists(runsBefore.contains) && + !(runsBefore.isEmpty && runsAfterSat) } // check afterReq // error can occur if: a < b, b < c, c < a after.foreach { ps => val classes = ps.map(_.getClass) - if (classes.exists(afterPhases)) // afterReq satisfied + if (classes.exists(runsAfter)) // afterReq satisfied throw new Exception(s"Ordering conflict for plugin ${plug.name}") } + + existingPhases += phase.getClass updatedPlan = before ++ (List(phase) :: after) } } diff --git a/compiler/test/dotty/tools/dotc/plugins/PluginsTest.scala b/compiler/test/dotty/tools/dotc/plugins/PluginsTest.scala new file mode 100644 index 000000000000..351a2e66e51f --- /dev/null +++ b/compiler/test/dotty/tools/dotc/plugins/PluginsTest.scala @@ -0,0 +1,135 @@ +package dotty.tools.dotc.plugins + +import org.junit.Test + +import dotty.tools.dotc._ +import plugins._ +import transform.MegaPhase.MiniPhase + +class PluginsTest { + class TestPhase extends PluginPhase { def phaseName = this.getClass.getName } + class P1 extends TestPhase + class P2 extends TestPhase + class P3a extends TestPhase + class P3b extends TestPhase + class P3c extends TestPhase + class P3d extends TestPhase + class P3e extends TestPhase + class P4 extends TestPhase + class P5 extends TestPhase + class P6a extends TestPhase + class P6b extends TestPhase + class P6c extends TestPhase + class P6d extends TestPhase + class P6e extends TestPhase + class P7 extends TestPhase + class P8 extends TestPhase + + class TestPlugin extends TestPhase with Plugin { + def name = this.getClass.getName + override def description = "" + + override def init(options: List[String]): List[PluginPhase] = this :: Nil + } + + val basicPlan = List( + List(new P1), + List(new P2), + List(new P3a, new P3b, new P3c, new P3d, new P3e), + List(new P4), + List(new P5), + List(new P6a, new P6b, new P6c, new P6d, new P6e), + List(new P7), + List(new P8) + ) + + @Test + def insertAfter = { + object M1 extends TestPlugin { + override val runsAfter = Set(classOf[P3d]) + } + + val updatedPlan = Plugins.schedule(basicPlan, M1 :: Nil, _ => Nil) + assert(updatedPlan(3)(0) eq M1) + } + + @Test + def insertBefore = { + object ConstFold extends TestPlugin { + override val runsBefore = Set(classOf[P7]) + } + + val updatedPlan = Plugins.schedule(basicPlan, ConstFold :: Nil, _ => Nil) + assert(updatedPlan(6)(0) eq ConstFold) + } + + @Test + def insertBeforeAfter = { + object ConstFold extends TestPlugin { + override val runsAfter = Set(classOf[P3d]) + override val runsBefore = Set(classOf[P7], classOf[P8]) + } + + // prefers the runsBefore + val updatedPlan = Plugins.schedule(basicPlan, ConstFold :: Nil, _ => Nil) + assert(updatedPlan(6)(0) eq ConstFold) + } + + @Test + def constraintUnsatisfiable = { + object ConstFold extends TestPlugin { + override val runsAfter = Set(classOf[P6d]) + override val runsBefore = Set(classOf[P2], classOf[P8]) + } + + try { + Plugins.schedule(basicPlan, ConstFold :: Nil, _ => Nil) + assert(false, "unsatisfiable constraint should throw exception, but not") + } catch { + case _: Exception => + } + } + + @Test + def orderingTwoPlugins1 = { + object M1 extends TestPlugin { + override val runsAfter = Set(classOf[P3d]) + override val runsBefore = Set(M2.getClass, classOf[P7], classOf[P8]) + } + object M2 extends TestPlugin { + override val runsAfter = Set(classOf[P3d]) + override val runsBefore = Set(classOf[P7], classOf[P8]) + } + + // M1 inserted to plan first + val updatedPlan1 = Plugins.schedule(basicPlan, M1 :: M2 :: Nil, _ => Nil) + assert(updatedPlan1(6)(0) eq M1) + assert(updatedPlan1(7)(0) eq M2) + + // M2 inserted to plan first + val updatedPlan2 = Plugins.schedule(basicPlan, M2 :: M1 :: Nil, _ => Nil) + assert(updatedPlan2(6)(0) eq M1) + assert(updatedPlan2(7)(0) eq M2) + } + + @Test + def orderingTwoPlugins2 = { + object M1 extends TestPlugin { + override val runsAfter = Set(classOf[P3d], M2.getClass) + } + object M2 extends TestPlugin { + override val runsAfter = Set(classOf[P3d]) + override val runsBefore = Set(classOf[P7], classOf[P8]) + } + + // M1 inserted to plan first + val updatedPlan1 = Plugins.schedule(basicPlan, M1 :: M2 :: Nil, _ => Nil) + assert(updatedPlan1(4)(0) eq M1) + assert(updatedPlan1(3)(0) eq M2) + + // M2 inserted to plan first + val updatedPlan2 = Plugins.schedule(basicPlan, M2 :: M1 :: Nil, _ => Nil) + assert(updatedPlan2(7)(0) eq M1) + assert(updatedPlan2(6)(0) eq M2) + } +} From 7fccb8e53951814ac3fb5b6a402fd1873c591afb Mon Sep 17 00:00:00 2001 From: liu fengyun Date: Fri, 17 Nov 2017 14:05:31 +0100 Subject: [PATCH 09/27] make scheduling deterministic --- .../dotty/tools/dotc/plugins/Plugins.scala | 64 +++++++------- .../tools/dotc/plugins/PluginsTest.scala | 83 +++++++++++++------ 2 files changed, 91 insertions(+), 56 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/plugins/Plugins.scala b/compiler/src/dotty/tools/dotc/plugins/Plugins.scala index 45a796d7b06d..3a4afb73a536 100644 --- a/compiler/src/dotty/tools/dotc/plugins/Plugins.scala +++ b/compiler/src/dotty/tools/dotc/plugins/Plugins.scala @@ -7,6 +7,8 @@ import config.PathResolver import dotty.tools.io._ import Phases._ +import scala.collection.mutable.ListBuffer + /** Support for run-time loading of compiler plugins. * * @author Lex Spoon @@ -125,7 +127,8 @@ trait Plugins { } // schedule plugins according to ordering constraints - val updatedPlan = Plugins.schedule(plan, plugins.filter(!_.research), options) + val pluginPhases = plugins.filter(!_.research).flatMap(plug => plug.init(options(plug))) + val updatedPlan = Plugins.schedule(plan, pluginPhases) // add research plugins plugins.filter(_.research).foldRight(updatedPlan) { (plug, plan) => plug.init(options(plug), plan) } @@ -140,7 +143,7 @@ object Plugins { * * Note: this algorithm is factored out for unit test. */ - def schedule(plan: List[List[Phase]], plugins: List[Plugin], optionFn: Plugin => List[String]): List[List[Phase]] = { + def schedule(plan: List[List[Phase]], pluginPhases: List[PluginPhase]): List[List[Phase]] = { import scala.collection.mutable.{ Set => MSet, Map => MMap } type OrderingReq = (MSet[Class[_]], MSet[Class[_]]) @@ -178,39 +181,36 @@ object Plugins { } } + pluginPhases.foreach(updateOrdering) + var updatedPlan = plan - plugins.foreach { plug => - plug.init(optionFn(plug)).foreach { phase => - updateOrdering(phase) + pluginPhases.sortBy(_.phaseName).foreach { phase => + val (runsBefore1, runsAfter1) = orderRequirements(phase.getClass) + val runsBefore = runsBefore1 & existingPhases + val runsAfter = runsAfter1 & existingPhases + + // beforeReq met after the split + val (before, after) = updatedPlan.span { ps => + val classes = ps.map(_.getClass) + val runsAfterSat = runsAfter.isEmpty + runsAfter --= classes + // Prefer the point immediately before the first beforePhases. + // If beforePhases not specified, insert at the point immediately + // after the last afterPhases. + !classes.exists(runsBefore.contains) && + !(runsBefore.isEmpty && runsAfterSat) + } - val (runsBefore1, runsAfter1) = orderRequirements(phase.getClass) - val runsBefore: MSet[Class[_]] = runsBefore1 & existingPhases // MSet(runsBefore1.filter(existingPhases.contains).toSeq: _*) - val runsAfter: MSet[Class[_]] = runsAfter1 & existingPhases // MSet(runsAfter1.filter(existingPhases.contains).toSeq: _*) - - // beforeReq met after the split - val (before, after) = updatedPlan.span { ps => - val classes = ps.map(_.getClass) - val runsAfterSat = runsAfter.isEmpty - runsAfter --= classes - // Prefer the point immediately before the first beforePhases. - // If beforePhases not specified, insert at the point immediately - // after the last afterPhases. - !classes.exists(runsBefore.contains) && - !(runsBefore.isEmpty && runsAfterSat) - } - - // check afterReq - // error can occur if: a < b, b < c, c < a - after.foreach { ps => - val classes = ps.map(_.getClass) - if (classes.exists(runsAfter)) // afterReq satisfied - throw new Exception(s"Ordering conflict for plugin ${plug.name}") - } - - - existingPhases += phase.getClass - updatedPlan = before ++ (List(phase) :: after) + // check afterReq + // error can occur if: a < b, b < c, c < a + after.foreach { ps => + val classes = ps.map(_.getClass) + if (classes.exists(runsAfter)) // afterReq satisfied + throw new Exception(s"Ordering conflict for phase ${phase.phaseName}") } + + existingPhases += phase.getClass + updatedPlan = before ++ (List(phase) :: after) } updatedPlan diff --git a/compiler/test/dotty/tools/dotc/plugins/PluginsTest.scala b/compiler/test/dotty/tools/dotc/plugins/PluginsTest.scala index 351a2e66e51f..2a22084f77bb 100644 --- a/compiler/test/dotty/tools/dotc/plugins/PluginsTest.scala +++ b/compiler/test/dotty/tools/dotc/plugins/PluginsTest.scala @@ -25,13 +25,6 @@ class PluginsTest { class P7 extends TestPhase class P8 extends TestPhase - class TestPlugin extends TestPhase with Plugin { - def name = this.getClass.getName - override def description = "" - - override def init(options: List[String]): List[PluginPhase] = this :: Nil - } - val basicPlan = List( List(new P1), List(new P2), @@ -45,45 +38,45 @@ class PluginsTest { @Test def insertAfter = { - object M1 extends TestPlugin { + object M1 extends TestPhase { override val runsAfter = Set(classOf[P3d]) } - val updatedPlan = Plugins.schedule(basicPlan, M1 :: Nil, _ => Nil) + val updatedPlan = Plugins.schedule(basicPlan, M1 :: Nil) assert(updatedPlan(3)(0) eq M1) } @Test def insertBefore = { - object ConstFold extends TestPlugin { + object ConstFold extends TestPhase { override val runsBefore = Set(classOf[P7]) } - val updatedPlan = Plugins.schedule(basicPlan, ConstFold :: Nil, _ => Nil) + val updatedPlan = Plugins.schedule(basicPlan, ConstFold :: Nil) assert(updatedPlan(6)(0) eq ConstFold) } @Test def insertBeforeAfter = { - object ConstFold extends TestPlugin { + object ConstFold extends TestPhase { override val runsAfter = Set(classOf[P3d]) override val runsBefore = Set(classOf[P7], classOf[P8]) } // prefers the runsBefore - val updatedPlan = Plugins.schedule(basicPlan, ConstFold :: Nil, _ => Nil) + val updatedPlan = Plugins.schedule(basicPlan, ConstFold :: Nil) assert(updatedPlan(6)(0) eq ConstFold) } @Test def constraintUnsatisfiable = { - object ConstFold extends TestPlugin { + object ConstFold extends TestPhase { override val runsAfter = Set(classOf[P6d]) override val runsBefore = Set(classOf[P2], classOf[P8]) } try { - Plugins.schedule(basicPlan, ConstFold :: Nil, _ => Nil) + Plugins.schedule(basicPlan, ConstFold :: Nil) assert(false, "unsatisfiable constraint should throw exception, but not") } catch { case _: Exception => @@ -92,44 +85,86 @@ class PluginsTest { @Test def orderingTwoPlugins1 = { - object M1 extends TestPlugin { + object M1 extends TestPhase { override val runsAfter = Set(classOf[P3d]) override val runsBefore = Set(M2.getClass, classOf[P7], classOf[P8]) } - object M2 extends TestPlugin { + object M2 extends TestPhase { override val runsAfter = Set(classOf[P3d]) override val runsBefore = Set(classOf[P7], classOf[P8]) } // M1 inserted to plan first - val updatedPlan1 = Plugins.schedule(basicPlan, M1 :: M2 :: Nil, _ => Nil) + val updatedPlan1 = Plugins.schedule(basicPlan, M1 :: M2 :: Nil) assert(updatedPlan1(6)(0) eq M1) assert(updatedPlan1(7)(0) eq M2) // M2 inserted to plan first - val updatedPlan2 = Plugins.schedule(basicPlan, M2 :: M1 :: Nil, _ => Nil) + val updatedPlan2 = Plugins.schedule(basicPlan, M2 :: M1 :: Nil) assert(updatedPlan2(6)(0) eq M1) assert(updatedPlan2(7)(0) eq M2) } @Test def orderingTwoPlugins2 = { - object M1 extends TestPlugin { + object M1 extends TestPhase { override val runsAfter = Set(classOf[P3d], M2.getClass) } - object M2 extends TestPlugin { + object M2 extends TestPhase { override val runsAfter = Set(classOf[P3d]) override val runsBefore = Set(classOf[P7], classOf[P8]) } // M1 inserted to plan first - val updatedPlan1 = Plugins.schedule(basicPlan, M1 :: M2 :: Nil, _ => Nil) + val updatedPlan1 = Plugins.schedule(basicPlan, M1 :: M2 :: Nil) assert(updatedPlan1(4)(0) eq M1) assert(updatedPlan1(3)(0) eq M2) // M2 inserted to plan first - val updatedPlan2 = Plugins.schedule(basicPlan, M2 :: M1 :: Nil, _ => Nil) + val updatedPlan2 = Plugins.schedule(basicPlan, M2 :: M1 :: Nil) + assert(updatedPlan2(4)(0) eq M1) + assert(updatedPlan2(3)(0) eq M2) + } + + @Test + def orderingTwoPlugins3 = { + object M1 extends TestPhase { + override val runsAfter = Set(classOf[P3d], M2.getClass) + override val runsBefore = Set(classOf[P7], classOf[P8]) + } + object M2 extends TestPhase { + override val runsAfter = Set(classOf[P3d]) + override val runsBefore = Set(classOf[P5]) + } + + // M1 inserted to plan first + val updatedPlan1 = Plugins.schedule(basicPlan, M1 :: M2 :: Nil) + assert(updatedPlan1(7)(0) eq M1) + assert(updatedPlan1(4)(0) eq M2) + + // M2 inserted to plan first + val updatedPlan2 = Plugins.schedule(basicPlan, M2 :: M1 :: Nil) assert(updatedPlan2(7)(0) eq M1) - assert(updatedPlan2(6)(0) eq M2) + assert(updatedPlan2(4)(0) eq M2) + } + + @Test + def deterministic = { + object M1 extends TestPhase { + override val runsAfter = Set(classOf[P3d]) + override val runsBefore = Set(classOf[P7], classOf[P8]) + } + object M2 extends TestPhase { + override val runsAfter = Set(classOf[P3d]) + override val runsBefore = Set(classOf[P7], classOf[P8]) + } + + val updatedPlan1 = Plugins.schedule(basicPlan, M1 :: M2 :: Nil) + assert(updatedPlan1(6)(0) eq M1) + assert(updatedPlan1(7)(0) eq M2) + + val updatedPlan2 = Plugins.schedule(basicPlan, M2 :: M1 :: Nil) + assert(updatedPlan1(6)(0) eq M1) + assert(updatedPlan1(7)(0) eq M2) } } From cb4d90e909ce276013120a484290961c24e8bc2f Mon Sep 17 00:00:00 2001 From: liu fengyun Date: Fri, 17 Nov 2017 14:23:45 +0100 Subject: [PATCH 10/27] make plugin pass bootstrapped CI --- compiler/test/dotty/tools/dotc/plugins/PluginsTest.scala | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/compiler/test/dotty/tools/dotc/plugins/PluginsTest.scala b/compiler/test/dotty/tools/dotc/plugins/PluginsTest.scala index 2a22084f77bb..d734ccbd4403 100644 --- a/compiler/test/dotty/tools/dotc/plugins/PluginsTest.scala +++ b/compiler/test/dotty/tools/dotc/plugins/PluginsTest.scala @@ -36,6 +36,8 @@ class PluginsTest { List(new P8) ) + def classOfPhase(p: PluginPhase): Class[_ <: PluginPhase] = p.getClass.asInstanceOf[Class[_ <: PluginPhase]] + @Test def insertAfter = { object M1 extends TestPhase { @@ -87,7 +89,7 @@ class PluginsTest { def orderingTwoPlugins1 = { object M1 extends TestPhase { override val runsAfter = Set(classOf[P3d]) - override val runsBefore = Set(M2.getClass, classOf[P7], classOf[P8]) + override val runsBefore = Set(classOfPhase(M2), classOf[P7], classOf[P8]) } object M2 extends TestPhase { override val runsAfter = Set(classOf[P3d]) @@ -108,7 +110,7 @@ class PluginsTest { @Test def orderingTwoPlugins2 = { object M1 extends TestPhase { - override val runsAfter = Set(classOf[P3d], M2.getClass) + override val runsAfter = Set(classOf[P3d], classOfPhase(M2)) } object M2 extends TestPhase { override val runsAfter = Set(classOf[P3d]) @@ -129,7 +131,7 @@ class PluginsTest { @Test def orderingTwoPlugins3 = { object M1 extends TestPhase { - override val runsAfter = Set(classOf[P3d], M2.getClass) + override val runsAfter = Set(classOf[P3d], classOfPhase(M2)) override val runsBefore = Set(classOf[P7], classOf[P8]) } object M2 extends TestPhase { From 4510b182cab85b1b217bb77e609c7185ac2fbb4b Mon Sep 17 00:00:00 2001 From: liu fengyun Date: Fri, 17 Nov 2017 14:46:22 +0100 Subject: [PATCH 11/27] add failing test --- .../tools/dotc/plugins/PluginsTest.scala | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/compiler/test/dotty/tools/dotc/plugins/PluginsTest.scala b/compiler/test/dotty/tools/dotc/plugins/PluginsTest.scala index d734ccbd4403..ef877a461f90 100644 --- a/compiler/test/dotty/tools/dotc/plugins/PluginsTest.scala +++ b/compiler/test/dotty/tools/dotc/plugins/PluginsTest.scala @@ -150,6 +150,29 @@ class PluginsTest { assert(updatedPlan2(4)(0) eq M2) } + @Test + def orderingTwoPlugins4 = { + object M1 extends TestPhase { + override val runsAfter = Set(classOf[P3d]) + override val runsBefore = Set(classOfPhase(M2), classOf[P7]) + } + object M2 extends TestPhase { + override val runsAfter = Set(classOf[P3d]) + override val runsBefore = Set(classOf[P5], classOf[P8]) + } + + // M1 inserted to plan first + val updatedPlan1 = Plugins.schedule(basicPlan, M1 :: M2 :: Nil) + assert(updatedPlan1(6)(0) eq M1) + assert(updatedPlan1(7)(0) eq M2) + + // M2 inserted to plan first + val updatedPlan2 = Plugins.schedule(basicPlan, M2 :: M1 :: Nil) + assert(updatedPlan2(4)(0) eq M1) + assert(updatedPlan2(5)(0) eq M2) + } + + @Test def deterministic = { object M1 extends TestPhase { From 48483cb39bfcdd3a12d6747763196a54e61bd06b Mon Sep 17 00:00:00 2001 From: liu fengyun Date: Fri, 17 Nov 2017 15:08:58 +0100 Subject: [PATCH 12/27] Make scheduler deterministic by propagating odering constraints --- .../dotty/tools/dotc/config/Printers.scala | 1 + .../dotty/tools/dotc/plugins/Plugins.scala | 116 +++++++++++++----- .../tools/dotc/plugins/PluginsTest.scala | 4 +- 3 files changed, 87 insertions(+), 34 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/config/Printers.scala b/compiler/src/dotty/tools/dotc/config/Printers.scala index beae798c324a..bd8cb9844c0c 100644 --- a/compiler/src/dotty/tools/dotc/config/Printers.scala +++ b/compiler/src/dotty/tools/dotc/config/Printers.scala @@ -28,6 +28,7 @@ object Printers { val overload: Printer = noPrinter val patmatch: Printer = noPrinter val pickling: Printer = noPrinter + val plugins: Printer = noPrinter val simplify: Printer = noPrinter val subtyping: Printer = noPrinter val transforms: Printer = noPrinter diff --git a/compiler/src/dotty/tools/dotc/plugins/Plugins.scala b/compiler/src/dotty/tools/dotc/plugins/Plugins.scala index 3a4afb73a536..b4bb6097ab08 100644 --- a/compiler/src/dotty/tools/dotc/plugins/Plugins.scala +++ b/compiler/src/dotty/tools/dotc/plugins/Plugins.scala @@ -6,6 +6,7 @@ import Contexts._ import config.PathResolver import dotty.tools.io._ import Phases._ +import config.Printers.plugins.{ println => debug } import scala.collection.mutable.ListBuffer @@ -144,56 +145,107 @@ object Plugins { * Note: this algorithm is factored out for unit test. */ def schedule(plan: List[List[Phase]], pluginPhases: List[PluginPhase]): List[List[Phase]] = { - import scala.collection.mutable.{ Set => MSet, Map => MMap } - type OrderingReq = (MSet[Class[_]], MSet[Class[_]]) + import scala.collection.mutable.{ Map => MMap, Set => MSet } + type OrderingReq = (Set[Class[_]], Set[Class[_]]) val orderRequirements = MMap[Class[_], OrderingReq]() - val existingPhases = { - val set = MSet.empty[Class[_]] - for (ps <- plan; p <- ps) set += p.getClass - set + val primitivePhases = plan.flatMap(ps => ps.map(_.getClass.asInstanceOf[Class[_]])).toSet + + def isPrimitive(phase: Class[_]): Boolean = primitivePhases.contains(phase) + + def constraintConflict(phase: Phase): String = { + val (runsAfter, runsBefore) = orderRequirements(phase.getClass) + s""" + |Ordering conflict for phase ${phase.phaseName} + |after: ${runsAfter.mkString("[", ", ", "]")} + |before: ${runsBefore.mkString("[", ", ", "]")} + """.stripMargin } - def updateOrdering(phase: PluginPhase): Unit = { - val runsBefore: MSet[Class[_]] = MSet(phase.runsBefore.toSeq: _*) - val runsAfter: MSet[Class[_]] = MSet(phase.runsAfter.toSeq: _*) + // init ordering map, no propagation + pluginPhases.foreach { phase => + val runsAfter : Set[Class[_]] = phase.runsAfter.asInstanceOf[Set[Class[_]]] + val runsBefore : Set[Class[_]] = phase.runsBefore.asInstanceOf[Set[Class[_]]] - if (!orderRequirements.contains(phase.getClass)) { - orderRequirements.update(phase.getClass, (runsBefore, runsAfter) ) - } else { - val (runsBefore1, runsAfter1) = orderRequirements(phase.getClass) - runsAfter1 ++= runsAfter - runsBefore1 ++= runsBefore - } + orderRequirements.update(phase.getClass, (runsAfter, runsBefore)) + } + + // propagate ordering constraint : reflexivity + pluginPhases.foreach { phase => - runsBefore.foreach { phaseClass => - if (!orderRequirements.contains(phaseClass)) - orderRequirements.update(phaseClass, (MSet.empty, MSet.empty)) - val (_, runsAfter) = orderRequirements(phaseClass) - runsAfter += phase.getClass + var (runsAfter, runsBefore) = orderRequirements(phase.getClass) + + // propagate transitive constraints to related phases + runsAfter.filter(!isPrimitive(_)).foreach { phaseClass => + val (runsAfter1, runsBefore1) = orderRequirements(phaseClass) + orderRequirements.update(phaseClass, (runsAfter1, runsBefore1 + phase.getClass)) } - runsAfter.foreach { phaseClass => - if (!orderRequirements.contains(phaseClass)) - orderRequirements.update(phaseClass, (MSet.empty, MSet.empty)) - val (runsBefore, _) = orderRequirements(phaseClass) - runsBefore += phase.getClass + runsBefore.filter(!isPrimitive(_)).foreach { phaseClass => + val (runsAfter1, runsBefore1) = orderRequirements(phaseClass) + orderRequirements.update(phaseClass, (runsAfter1 + phase.getClass, runsBefore1)) } + } - pluginPhases.foreach(updateOrdering) + debug( + s""" reflexive constraints: + | ${orderRequirements.mkString("\n")} + """.stripMargin + ) + + // propagate transitive constraints from related phases to current phase: transitivity + def propagate(phase: Phase): OrderingReq = { + def propagateRunsBefore(beforePhase: Class[_]): Set[Class[_]] = + if (beforePhase == phase.getClass) + throw new Exception(constraintConflict(phase)) + else if (primitivePhases.contains(beforePhase)) + Set(beforePhase) + else { + val (_, runsBefore) = orderRequirements(beforePhase) + runsBefore.flatMap(propagateRunsBefore) + beforePhase + } + + def propagateRunsAfter(afterPhase: Class[_]): Set[Class[_]] = + if (afterPhase == phase.getClass) + throw new Exception(constraintConflict(phase)) + else if (primitivePhases.contains(afterPhase)) + Set(afterPhase) + else { + val (runsAfter, _) = orderRequirements(afterPhase) + runsAfter.flatMap(propagateRunsAfter) + afterPhase + } + + var (runsAfter, runsBefore) = orderRequirements(phase.getClass) + + runsAfter = runsAfter.flatMap(propagateRunsAfter) + runsBefore = runsBefore.flatMap(propagateRunsBefore) + + // orderRequirements.update(phase.getClass, (runsBefore, runsAfter) ) + + (runsAfter, runsBefore) + } var updatedPlan = plan + var insertedPhase = primitivePhases pluginPhases.sortBy(_.phaseName).foreach { phase => - val (runsBefore1, runsAfter1) = orderRequirements(phase.getClass) - val runsBefore = runsBefore1 & existingPhases - val runsAfter = runsAfter1 & existingPhases + var (runsAfter1, runsBefore1) = propagate(phase) + + debug( + s"""propagated constraints for ${phase}: + |after: ${runsAfter1.mkString("[", ", ", "]")} + |before: ${runsBefore1.mkString("[", ", ", "]")} + """.stripMargin + ) + + var runsAfter = runsAfter1 & insertedPhase + val runsBefore = runsBefore1 & insertedPhase // beforeReq met after the split val (before, after) = updatedPlan.span { ps => val classes = ps.map(_.getClass) val runsAfterSat = runsAfter.isEmpty - runsAfter --= classes + runsAfter = runsAfter -- classes // Prefer the point immediately before the first beforePhases. // If beforePhases not specified, insert at the point immediately // after the last afterPhases. @@ -209,7 +261,7 @@ object Plugins { throw new Exception(s"Ordering conflict for phase ${phase.phaseName}") } - existingPhases += phase.getClass + insertedPhase = insertedPhase + phase.getClass updatedPlan = before ++ (List(phase) :: after) } diff --git a/compiler/test/dotty/tools/dotc/plugins/PluginsTest.scala b/compiler/test/dotty/tools/dotc/plugins/PluginsTest.scala index ef877a461f90..414cbfc0ae8b 100644 --- a/compiler/test/dotty/tools/dotc/plugins/PluginsTest.scala +++ b/compiler/test/dotty/tools/dotc/plugins/PluginsTest.scala @@ -163,8 +163,8 @@ class PluginsTest { // M1 inserted to plan first val updatedPlan1 = Plugins.schedule(basicPlan, M1 :: M2 :: Nil) - assert(updatedPlan1(6)(0) eq M1) - assert(updatedPlan1(7)(0) eq M2) + assert(updatedPlan1(4)(0) eq M1) + assert(updatedPlan1(5)(0) eq M2) // M2 inserted to plan first val updatedPlan2 = Plugins.schedule(basicPlan, M2 :: M1 :: Nil) From 45ea80f093d674f4bae020e1dbaabb3148d6c3ee Mon Sep 17 00:00:00 2001 From: liu fengyun Date: Fri, 17 Nov 2017 17:18:39 +0100 Subject: [PATCH 13/27] add transitive ordering constraint example --- .../dotty/tools/dotc/plugins/Plugins.scala | 4 +-- .../tools/dotc/plugins/PluginsTest.scala | 33 +++++++++++++++++++ 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/plugins/Plugins.scala b/compiler/src/dotty/tools/dotc/plugins/Plugins.scala index b4bb6097ab08..4dd67ad24989 100644 --- a/compiler/src/dotty/tools/dotc/plugins/Plugins.scala +++ b/compiler/src/dotty/tools/dotc/plugins/Plugins.scala @@ -194,7 +194,7 @@ object Plugins { """.stripMargin ) - // propagate transitive constraints from related phases to current phase: transitivity + // propagate constraints from related phases to current phase: transitivity def propagate(phase: Phase): OrderingReq = { def propagateRunsBefore(beforePhase: Class[_]): Set[Class[_]] = if (beforePhase == phase.getClass) @@ -221,8 +221,6 @@ object Plugins { runsAfter = runsAfter.flatMap(propagateRunsAfter) runsBefore = runsBefore.flatMap(propagateRunsBefore) - // orderRequirements.update(phase.getClass, (runsBefore, runsAfter) ) - (runsAfter, runsBefore) } diff --git a/compiler/test/dotty/tools/dotc/plugins/PluginsTest.scala b/compiler/test/dotty/tools/dotc/plugins/PluginsTest.scala index 414cbfc0ae8b..f12904a6c3a9 100644 --- a/compiler/test/dotty/tools/dotc/plugins/PluginsTest.scala +++ b/compiler/test/dotty/tools/dotc/plugins/PluginsTest.scala @@ -5,6 +5,7 @@ import org.junit.Test import dotty.tools.dotc._ import plugins._ import transform.MegaPhase.MiniPhase +import core.Phases.Phase class PluginsTest { class TestPhase extends PluginPhase { def phaseName = this.getClass.getName } @@ -38,6 +39,10 @@ class PluginsTest { def classOfPhase(p: PluginPhase): Class[_ <: PluginPhase] = p.getClass.asInstanceOf[Class[_ <: PluginPhase]] + def debugPlan(plan: List[List[Phase]]): Unit = { + println(plan.mkString("plan:\n- ", "\n- ", "")) + } + @Test def insertAfter = { object M1 extends TestPhase { @@ -172,6 +177,34 @@ class PluginsTest { assert(updatedPlan2(5)(0) eq M2) } + @Test + def orderingTransitive = { + object M1 extends TestPhase { + override val runsAfter = Set(classOf[P3d]) + override val runsBefore = Set(classOfPhase(M2), classOf[P7]) + } + object M2 extends TestPhase { + override val runsAfter = Set(classOf[P3d]) + override val runsBefore = Set(classOf[P5], classOf[P8]) + } + object M3 extends TestPhase { + override val runsAfter = Set(classOfPhase(M2), classOf[P2]) + override val runsBefore = Set(classOf[P4], classOf[P8]) + } + + // M1 inserted to plan first + val updatedPlan1 = Plugins.schedule(basicPlan, M1 :: M2 :: M3 :: Nil) + assert(updatedPlan1(3)(0) eq M1) + assert(updatedPlan1(4)(0) eq M2) + assert(updatedPlan1(5)(0) eq M3) + + // M2 inserted to plan first + val updatedPlan2 = Plugins.schedule(basicPlan, M2 :: M1 :: M3 :: Nil) + assert(updatedPlan1(3)(0) eq M1) + assert(updatedPlan1(4)(0) eq M2) + assert(updatedPlan1(5)(0) eq M3) + } + @Test def deterministic = { From 44f2a326dacef478f2461631dcaa284887ed84cc Mon Sep 17 00:00:00 2001 From: liu fengyun Date: Fri, 17 Nov 2017 18:27:02 +0100 Subject: [PATCH 14/27] add sbt plugin test --- .../dotty/tools/dotc/plugins/Plugins.scala | 2 +- .../sbt-dotty/compiler-plugin/build.sbt | 19 +++++++++ .../compiler-plugin/plugin/DivideZero.scala | 40 +++++++++++++++++++ .../src/main/resources/scalac-plugin.xml | 4 ++ .../compiler-plugin/project/build.properties | 1 + .../compiler-plugin/project/plugins.sbt | 1 + .../src/main/scala/hello/Hello.scala | 13 ++++++ .../sbt-test/sbt-dotty/compiler-plugin/test | 5 +++ 8 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 sbt-dotty/sbt-test/sbt-dotty/compiler-plugin/build.sbt create mode 100644 sbt-dotty/sbt-test/sbt-dotty/compiler-plugin/plugin/DivideZero.scala create mode 100644 sbt-dotty/sbt-test/sbt-dotty/compiler-plugin/plugin/src/main/resources/scalac-plugin.xml create mode 100644 sbt-dotty/sbt-test/sbt-dotty/compiler-plugin/project/build.properties create mode 100644 sbt-dotty/sbt-test/sbt-dotty/compiler-plugin/project/plugins.sbt create mode 100644 sbt-dotty/sbt-test/sbt-dotty/compiler-plugin/src/main/scala/hello/Hello.scala create mode 100644 sbt-dotty/sbt-test/sbt-dotty/compiler-plugin/test diff --git a/compiler/src/dotty/tools/dotc/plugins/Plugins.scala b/compiler/src/dotty/tools/dotc/plugins/Plugins.scala index 4dd67ad24989..297daec6d91a 100644 --- a/compiler/src/dotty/tools/dotc/plugins/Plugins.scala +++ b/compiler/src/dotty/tools/dotc/plugins/Plugins.scala @@ -145,7 +145,7 @@ object Plugins { * Note: this algorithm is factored out for unit test. */ def schedule(plan: List[List[Phase]], pluginPhases: List[PluginPhase]): List[List[Phase]] = { - import scala.collection.mutable.{ Map => MMap, Set => MSet } + import scala.collection.mutable.{ Map => MMap } type OrderingReq = (Set[Class[_]], Set[Class[_]]) val orderRequirements = MMap[Class[_], OrderingReq]() diff --git a/sbt-dotty/sbt-test/sbt-dotty/compiler-plugin/build.sbt b/sbt-dotty/sbt-test/sbt-dotty/compiler-plugin/build.sbt new file mode 100644 index 000000000000..2d1fb1464776 --- /dev/null +++ b/sbt-dotty/sbt-test/sbt-dotty/compiler-plugin/build.sbt @@ -0,0 +1,19 @@ +lazy val dottyVersion = sys.props("plugin.scalaVersion") + +lazy val pluginSetting = Seq( + name := "dividezero", + version := "0.0.1", + organization := "ch.epfl.lamp", + scalaVersion := dottyVersion, + + libraryDependencies ++= Seq( + "ch.epfl.lamp" %% "dotty" % scalaVersion.value % "provided" + ) +) + +lazy val plugin = (project in file("plugin")).settings(pluginSetting: _*) + +lazy val app = (project in file(".")).settings( + scalaVersion := dottyVersion, + libraryDependencies += compilerPlugin("ch.epfl.lamp" %% "dividezero" % "0.0.1") +) diff --git a/sbt-dotty/sbt-test/sbt-dotty/compiler-plugin/plugin/DivideZero.scala b/sbt-dotty/sbt-test/sbt-dotty/compiler-plugin/plugin/DivideZero.scala new file mode 100644 index 000000000000..e82f7524163d --- /dev/null +++ b/sbt-dotty/sbt-test/sbt-dotty/compiler-plugin/plugin/DivideZero.scala @@ -0,0 +1,40 @@ +package dividezero + +import dotty.tools.dotc._ +import core._ +import Contexts.Context +import plugins.{Plugin, PluginPhase} +import Phases.Phase +import ast.tpd +import transform.MegaPhase.MiniPhase +import Decorators._ +import Symbols.Symbol +import Constants.Constant +import transform.{LinkAll, Pickler} + +class DivideZero extends PluginPhase with Plugin { + val name: String = "divideZero" + override val description: String = "divide zero check" + + val phaseName = name + + override val runsAfter = Set(classOf[Pickler]) + override val runsBefore = Set(classOf[LinkAll]) + + override def init(options: List[String]): List[PluginPhase] = this :: Nil + + private def isNumericDivide(sym: Symbol)(implicit ctx: Context): Boolean = { + def test(tpe: String): Boolean = + (sym.owner eq ctx.requiredClass(tpe.toTermName)) && sym.name.show == "/" + + test("scala.Int") || test("scala.Long") || test("scala.Short") || test("scala.FLoat") || test("scala.Double") + } + + override def transformApply(tree: tpd.Apply)(implicit ctx: Context): tpd.Tree = tree match { + case tpd.Apply(fun, tpd.Literal(Constants.Constant(v)) :: Nil) if isNumericDivide(fun.symbol) && v == 0 => + ctx.warning("divide by zero", tree.pos) + tpd.Literal(Constant(0)) + case _ => + tree + } +} diff --git a/sbt-dotty/sbt-test/sbt-dotty/compiler-plugin/plugin/src/main/resources/scalac-plugin.xml b/sbt-dotty/sbt-test/sbt-dotty/compiler-plugin/plugin/src/main/resources/scalac-plugin.xml new file mode 100644 index 000000000000..56d664377389 --- /dev/null +++ b/sbt-dotty/sbt-test/sbt-dotty/compiler-plugin/plugin/src/main/resources/scalac-plugin.xml @@ -0,0 +1,4 @@ + + divideZero + dividezero.DivideZero + diff --git a/sbt-dotty/sbt-test/sbt-dotty/compiler-plugin/project/build.properties b/sbt-dotty/sbt-test/sbt-dotty/compiler-plugin/project/build.properties new file mode 100644 index 000000000000..64317fdae59f --- /dev/null +++ b/sbt-dotty/sbt-test/sbt-dotty/compiler-plugin/project/build.properties @@ -0,0 +1 @@ +sbt.version=0.13.15 diff --git a/sbt-dotty/sbt-test/sbt-dotty/compiler-plugin/project/plugins.sbt b/sbt-dotty/sbt-test/sbt-dotty/compiler-plugin/project/plugins.sbt new file mode 100644 index 000000000000..c17caab2d98c --- /dev/null +++ b/sbt-dotty/sbt-test/sbt-dotty/compiler-plugin/project/plugins.sbt @@ -0,0 +1 @@ +addSbtPlugin("ch.epfl.lamp" % "sbt-dotty" % sys.props("plugin.version")) diff --git a/sbt-dotty/sbt-test/sbt-dotty/compiler-plugin/src/main/scala/hello/Hello.scala b/sbt-dotty/sbt-test/sbt-dotty/compiler-plugin/src/main/scala/hello/Hello.scala new file mode 100644 index 000000000000..99ae2d0d76a5 --- /dev/null +++ b/sbt-dotty/sbt-test/sbt-dotty/compiler-plugin/src/main/scala/hello/Hello.scala @@ -0,0 +1,13 @@ +package hello +object Hello { + def main(args: Array[String]): Unit = { + val dotty: Int | String = "dotty" + + val y = 5 / 0 // error + 100 + 6 / 0 // error + 6L / 0L // error + val z = 7 / 0.0 // error + + println(s"Hello $dotty!") + } +} diff --git a/sbt-dotty/sbt-test/sbt-dotty/compiler-plugin/test b/sbt-dotty/sbt-test/sbt-dotty/compiler-plugin/test new file mode 100644 index 000000000000..6d7fb06f66f3 --- /dev/null +++ b/sbt-dotty/sbt-test/sbt-dotty/compiler-plugin/test @@ -0,0 +1,5 @@ +> plugin/publishLocal +> run +> 'set initialCommands := "1 + 1" ' +# FIXME: does not work on the CI +#> console From 7fa7e7247bbdd7ca6eb80f6aaa3ddec908c6c228 Mon Sep 17 00:00:00 2001 From: liu fengyun Date: Fri, 17 Nov 2017 21:44:43 +0100 Subject: [PATCH 15/27] remove unnecessary changes to vulpix --- .../dotty/tools/dotc/CompilationTests.scala | 1 - .../tools/dotc/reporting/TestReporter.scala | 23 +----- .../dotty/tools/vulpix/ParallelTesting.scala | 73 ++++--------------- tests/plugins/pos/divideZero/2.check | 3 - tests/plugins/pos/divideZero/Test_2.scala | 5 -- .../plugins/pos/divideZero/scalac-plugin.xml | 4 - 6 files changed, 18 insertions(+), 91 deletions(-) delete mode 100644 tests/plugins/pos/divideZero/2.check delete mode 100644 tests/plugins/pos/divideZero/Test_2.scala delete mode 100644 tests/plugins/pos/divideZero/scalac-plugin.xml diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index 47c6076a8d96..1a6cfd277b9f 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -338,7 +338,6 @@ class CompilationTests extends ParallelTesting { } compileFilesInDir("../tests/plugins/neg").checkExpectedErrors() - compileFilesInDir("../tests/plugins/pos").checkCompile(checkCompileOutput = true) } private val (compilerSources, backendSources, backendJvmSources) = { diff --git a/compiler/test/dotty/tools/dotc/reporting/TestReporter.scala b/compiler/test/dotty/tools/dotc/reporting/TestReporter.scala index f70e47887c62..07aa7f9182db 100644 --- a/compiler/test/dotty/tools/dotc/reporting/TestReporter.scala +++ b/compiler/test/dotty/tools/dotc/reporting/TestReporter.scala @@ -2,11 +2,12 @@ package dotty.tools package dotc package reporting -import java.io.{ FileOutputStream, PrintStream, PrintWriter, StringWriter, File => JFile } +import java.io.{ PrintStream, PrintWriter, File => JFile, FileOutputStream } import java.text.SimpleDateFormat import java.util.Date import scala.collection.mutable + import util.SourcePosition import core.Contexts._ import Reporter._ @@ -78,23 +79,6 @@ extends Reporter with UniqueMessagePositions with HideNonSensicalMessages with M } } -class StoredTestReporter (val writer: StringWriter, val superWriter: PrintWriter, filePrintln: String => Unit, logLevel: Int) -extends TestReporter(superWriter, filePrintln, logLevel) { - override def doReport(m: MessageContainer)(implicit ctx: Context): Unit = { - super.doReport(m) - - val msg = - if (m.pos.exists) - "[" + diagnosticLevel(m) + "] Line " + (m.pos.line + 1) + ": " + m.contained().msg - else - "[" + diagnosticLevel(m) + "] " + m.contained().msg - - writer.write(msg + "\n") - } - - override def summary: String = "" -} - object TestReporter { private[this] var outFile: JFile = _ private[this] var logWriter: PrintWriter = _ @@ -131,9 +115,6 @@ object TestReporter { def reporter(ps: PrintStream, logLevel: Int): TestReporter = new TestReporter(new PrintWriter(ps, true), logPrintln, logLevel) - def storedReporter(ps: PrintStream, logLevel: Int): TestReporter = - new StoredTestReporter(new StringWriter(), new PrintWriter(ps, true), logPrintln, logLevel) - def simplifiedReporter(writer: PrintWriter): TestReporter = { val rep = new TestReporter(writer, logPrintln, WARNING) { /** Prints the message with the given position indication in a simplified manner */ diff --git a/compiler/test/dotty/tools/vulpix/ParallelTesting.scala b/compiler/test/dotty/tools/vulpix/ParallelTesting.scala index f3e674cfa42e..925fba597c91 100644 --- a/compiler/test/dotty/tools/vulpix/ParallelTesting.scala +++ b/compiler/test/dotty/tools/vulpix/ParallelTesting.scala @@ -6,8 +6,8 @@ import java.io.{ File => JFile } import java.text.SimpleDateFormat import java.util.HashMap import java.nio.file.StandardCopyOption.REPLACE_EXISTING -import java.nio.file.{ Files, NoSuchFileException, Path, Paths } -import java.util.concurrent.{ TimeUnit, TimeoutException, Executors => JExecutors } +import java.nio.file.{ Files, Path, Paths, NoSuchFileException } +import java.util.concurrent.{ Executors => JExecutors, TimeUnit, TimeoutException } import scala.io.Source import scala.util.control.NonFatal @@ -15,8 +15,9 @@ import scala.util.Try import scala.collection.mutable import scala.util.matching.Regex import scala.util.Random + import dotc.core.Contexts._ -import dotc.reporting.{ Reporter, StoredTestReporter, TestReporter } +import dotc.reporting.{ Reporter, TestReporter } import dotc.reporting.diagnostic.MessageContainer import dotc.interfaces.Diagnostic.ERROR import dotc.util.DiffUtil @@ -128,7 +129,7 @@ trait ParallelTesting extends RunnerOrchestration { self => /** A group of files that may all be compiled together, with the same flags * and output directory */ - case class JointCompilationSource( + private final case class JointCompilationSource( name: String, files: Array[JFile], flags: TestFlags, @@ -178,7 +179,7 @@ trait ParallelTesting extends RunnerOrchestration { self => /** Each `Test` takes the `testSources` and performs the compilation and assertions * according to the implementing class "neg", "run" or "pos". */ - private abstract class Test(testSources: List[TestSource], times: Int, threadLimit: Option[Int], suppressAllOutput: Boolean, checkCompileOutput: Boolean = false)(implicit val summaryReport: SummaryReporting) { test => + private abstract class Test(testSources: List[TestSource], times: Int, threadLimit: Option[Int], suppressAllOutput: Boolean)(implicit val summaryReport: SummaryReporting) { test => import summaryReport._ @@ -354,12 +355,9 @@ trait ParallelTesting extends RunnerOrchestration { self => else None } else None - val logLevel = if (suppressErrors || suppressAllOutput) ERROR + 1 else ERROR val reporter = - if (checkCompileOutput) - TestReporter.storedReporter(realStdout, logLevel = logLevel) - else - TestReporter.reporter(realStdout, logLevel = logLevel) + TestReporter.reporter(realStdout, logLevel = + if (suppressErrors || suppressAllOutput) ERROR + 1 else ERROR) val driver = if (times == 1) new Driver @@ -499,33 +497,10 @@ trait ParallelTesting extends RunnerOrchestration { self => private def flattenFiles(f: JFile): Array[JFile] = if (f.isDirectory) f.listFiles.flatMap(flattenFiles) else Array(f) - - protected def verifyCompileOutput(source: TestSource, checkFile: JFile, reporter: StoredTestReporter): Unit = { - reporter.writer.flush() - val checkLines = Source.fromFile(checkFile).getLines().mkString("\n") - val outputLines = reporter.writer.toString.trim.replaceAll("\\s+\n", "\n") - - if (outputLines != checkLines) { - val msg = - s"""|Output from '${source.title}' did not match check file '${checkFile.getName}'. - |------------------------------------- - |expected: - |$checkLines - | - |actual: - |$outputLines - |------------------------------------- - """.stripMargin - - echo(msg) - addFailureInstruction(msg) - failTestSource(source) - } - } } - private final class PosTest(testSources: List[TestSource], times: Int, threadLimit: Option[Int], suppressAllOutput: Boolean, checkCompileOutput: Boolean = false)(implicit summaryReport: SummaryReporting) - extends Test(testSources, times, threadLimit, suppressAllOutput, checkCompileOutput) { + private final class PosTest(testSources: List[TestSource], times: Int, threadLimit: Option[Int], suppressAllOutput: Boolean)(implicit summaryReport: SummaryReporting) + extends Test(testSources, times, threadLimit, suppressAllOutput) { protected def encapsulatedCompilation(testSource: TestSource) = new LoggedRunnable { def checkTestSource(): Unit = tryCompile(testSource) { testSource match { @@ -603,14 +578,6 @@ trait ParallelTesting extends RunnerOrchestration { self => reporters.foreach(logReporterContents) logBuildInstructions(reporters.head, testSource, errorCount, warningCount) } - - // verify compilation check file - (1 to testSource.compilationGroups.length).foreach { index => - val checkFile = new JFile(dir.getAbsolutePath.reverse.dropWhile(_ == '/').reverse + "/" + index + ".check") - - if (checkFile.exists && checkCompileOutput) - verifyCompileOutput(testSource, checkFile, reporters(index).asInstanceOf[StoredTestReporter]) - } } } } @@ -734,8 +701,8 @@ trait ParallelTesting extends RunnerOrchestration { self => } } - private final class NegTest(testSources: List[TestSource], times: Int, threadLimit: Option[Int], suppressAllOutput: Boolean, checkCompileOutput: Boolean = false)(implicit summaryReport: SummaryReporting) - extends Test(testSources, times, threadLimit, suppressAllOutput, checkCompileOutput) { + private final class NegTest(testSources: List[TestSource], times: Int, threadLimit: Option[Int], suppressAllOutput: Boolean)(implicit summaryReport: SummaryReporting) + extends Test(testSources, times, threadLimit, suppressAllOutput) { protected def encapsulatedCompilation(testSource: TestSource) = new LoggedRunnable { def checkTestSource(): Unit = tryCompile(testSource) { // In neg-tests we allow two types of error annotations, @@ -812,14 +779,6 @@ trait ParallelTesting extends RunnerOrchestration { self => if (actualErrors > 0) reporters.foreach(logReporterContents) - // Compilation check file: for testing plugins - (1 to testSource.compilationGroups.length).foreach { index => - val checkFile = new JFile(dir.getAbsolutePath.reverse.dropWhile(_ == '/').reverse + "/" + index + ".check") - - if (checkFile.exists && checkCompileOutput) - verifyCompileOutput(testSource, checkFile, reporters(index).asInstanceOf[StoredTestReporter]) - } - (compilerCrashed, expectedErrors, actualErrors, () => getMissingExpectedErrors(errorMap, errors), errorMap) } } @@ -992,8 +951,8 @@ trait ParallelTesting extends RunnerOrchestration { self => * compilation without generating errors and that they do not crash the * compiler */ - def checkCompile(checkCompileOutput: Boolean = false)(implicit summaryReport: SummaryReporting): this.type = { - val test = new PosTest(targets, times, threadLimit, shouldFail || shouldSuppressOutput, checkCompileOutput).executeTestSuite() + def checkCompile()(implicit summaryReport: SummaryReporting): this.type = { + val test = new PosTest(targets, times, threadLimit, shouldFail || shouldSuppressOutput).executeTestSuite() cleanup() @@ -1011,8 +970,8 @@ trait ParallelTesting extends RunnerOrchestration { self => * correct amount of errors at the correct positions. It also makes sure * that none of these tests crash the compiler */ - def checkExpectedErrors(checkCompileOutput : Boolean = false)(implicit summaryReport: SummaryReporting): this.type = { - val test = new NegTest(targets, times, threadLimit, shouldFail || shouldSuppressOutput, checkCompileOutput).executeTestSuite() + def checkExpectedErrors()(implicit summaryReport: SummaryReporting): this.type = { + val test = new NegTest(targets, times, threadLimit, shouldFail || shouldSuppressOutput).executeTestSuite() cleanup() diff --git a/tests/plugins/pos/divideZero/2.check b/tests/plugins/pos/divideZero/2.check deleted file mode 100644 index 027477a8331b..000000000000 --- a/tests/plugins/pos/divideZero/2.check +++ /dev/null @@ -1,3 +0,0 @@ -[Warning] Line 2: divide by zero -[Warning] Line 3: divide by zero -[Warning] Line 4: divide by zero diff --git a/tests/plugins/pos/divideZero/Test_2.scala b/tests/plugins/pos/divideZero/Test_2.scala deleted file mode 100644 index e9eaf159b9c8..000000000000 --- a/tests/plugins/pos/divideZero/Test_2.scala +++ /dev/null @@ -1,5 +0,0 @@ -class Test { - 100 + 6 / 0 - 6L / 0L - println(3 + 7 / 0.0) -} diff --git a/tests/plugins/pos/divideZero/scalac-plugin.xml b/tests/plugins/pos/divideZero/scalac-plugin.xml deleted file mode 100644 index 519837dd4d8a..000000000000 --- a/tests/plugins/pos/divideZero/scalac-plugin.xml +++ /dev/null @@ -1,4 +0,0 @@ - - devideZero - DivideZero - From bcee0afd3d9f5d8f39804dcb9779a269cc9d5203 Mon Sep 17 00:00:00 2001 From: liu fengyun Date: Tue, 19 Dec 2017 13:58:58 +0100 Subject: [PATCH 16/27] fix rebase conflict --- compiler/test/dotty/tools/vulpix/ParallelTesting.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/test/dotty/tools/vulpix/ParallelTesting.scala b/compiler/test/dotty/tools/vulpix/ParallelTesting.scala index 925fba597c91..39cf3ed6d61c 100644 --- a/compiler/test/dotty/tools/vulpix/ParallelTesting.scala +++ b/compiler/test/dotty/tools/vulpix/ParallelTesting.scala @@ -129,7 +129,7 @@ trait ParallelTesting extends RunnerOrchestration { self => /** A group of files that may all be compiled together, with the same flags * and output directory */ - private final case class JointCompilationSource( + private case class JointCompilationSource( name: String, files: Array[JFile], flags: TestFlags, From 5aac55dc44a0dd2dbef17c8f7bf7b081c9ec7b8d Mon Sep 17 00:00:00 2001 From: liu fengyun Date: Fri, 9 Mar 2018 15:48:13 +0100 Subject: [PATCH 17/27] fix path after rebase --- compiler/test/dotty/tools/dotc/CompilationTests.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index 1a6cfd277b9f..1e280a37215f 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -337,7 +337,7 @@ class CompilationTests extends ParallelTesting { new CompilationTest(targets) } - compileFilesInDir("../tests/plugins/neg").checkExpectedErrors() + compileFilesInDir("tests/plugins/neg").checkExpectedErrors() } private val (compilerSources, backendSources, backendJvmSources) = { From 90d707e16c0641f5ca17c62a0b285fcb6b8b23ca Mon Sep 17 00:00:00 2001 From: liu fengyun Date: Mon, 19 Mar 2018 09:46:13 +0100 Subject: [PATCH 18/27] fix typo in tests --- tests/plugins/neg/divideZero-research/plugin_1.scala | 5 +++-- tests/plugins/neg/divideZero/plugin_1.scala | 5 +++-- tests/plugins/pos/divideZero/plugin_1.scala | 6 ++++-- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/tests/plugins/neg/divideZero-research/plugin_1.scala b/tests/plugins/neg/divideZero-research/plugin_1.scala index 7416d33fb434..b08f725d6c56 100644 --- a/tests/plugins/neg/divideZero-research/plugin_1.scala +++ b/tests/plugins/neg/divideZero-research/plugin_1.scala @@ -8,6 +8,7 @@ import transform.MegaPhase.MiniPhase import Decorators._ import Symbols.Symbol import Constants.Constant +import StdNames._ class DivideZero extends MiniPhase with Plugin { val name: String = "divideZero" @@ -24,9 +25,9 @@ class DivideZero extends MiniPhase with Plugin { private def isNumericDivide(sym: Symbol)(implicit ctx: Context): Boolean = { def test(tpe: String): Boolean = - (sym.owner eq ctx.requiredClass(tpe.toTermName)) && sym.name.show == "/" + (sym.owner eq ctx.requiredClass(tpe.toTermName)) && sym.name == nme.DIV - test("scala.Int") || test("scala.Long") || test("scala.Short") || test("scala.FLoat") || test("scala.Double") + test("scala.Int") || test("scala.Long") || test("scala.Short") || test("scala.Float") || test("scala.Double") } override def transformApply(tree: tpd.Apply)(implicit ctx: Context): tpd.Tree = tree match { diff --git a/tests/plugins/neg/divideZero/plugin_1.scala b/tests/plugins/neg/divideZero/plugin_1.scala index 85ecbc822f8d..19a6ca434034 100644 --- a/tests/plugins/neg/divideZero/plugin_1.scala +++ b/tests/plugins/neg/divideZero/plugin_1.scala @@ -9,6 +9,7 @@ import Decorators._ import Symbols.Symbol import Constants.Constant import transform.{LinkAll, Pickler} +import StdNames._ class DivideZero extends PluginPhase with Plugin { val name: String = "divideZero" @@ -23,9 +24,9 @@ class DivideZero extends PluginPhase with Plugin { private def isNumericDivide(sym: Symbol)(implicit ctx: Context): Boolean = { def test(tpe: String): Boolean = - (sym.owner eq ctx.requiredClass(tpe.toTermName)) && sym.name.show == "/" + (sym.owner eq ctx.requiredClass(tpe.toTermName)) && sym.name == nme.DIV - test("scala.Int") || test("scala.Long") || test("scala.Short") || test("scala.FLoat") || test("scala.Double") + test("scala.Int") || test("scala.Long") || test("scala.Short") || test("scala.Float") || test("scala.Double") } override def transformApply(tree: tpd.Apply)(implicit ctx: Context): tpd.Tree = tree match { diff --git a/tests/plugins/pos/divideZero/plugin_1.scala b/tests/plugins/pos/divideZero/plugin_1.scala index 94ff37bd2aea..eca2e6fdf53b 100644 --- a/tests/plugins/pos/divideZero/plugin_1.scala +++ b/tests/plugins/pos/divideZero/plugin_1.scala @@ -8,6 +8,8 @@ import transform.MegaPhase.MiniPhase import Decorators._ import Symbols.Symbol import Constants.Constant +import StdNames._ + class DivideZero extends MiniPhase with Plugin { val name: String = "divideZero" @@ -24,9 +26,9 @@ class DivideZero extends MiniPhase with Plugin { private def isNumericDivide(sym: Symbol)(implicit ctx: Context): Boolean = { def test(tpe: String): Boolean = - (sym.owner eq ctx.requiredClass(tpe.toTermName)) && sym.name.show == "/" + (sym.owner eq ctx.requiredClass(tpe.toTermName)) && sym.name == nme.DIV - test("scala.Int") || test("scala.Long") || test("scala.Short") || test("scala.FLoat") || test("scala.Double") + test("scala.Int") || test("scala.Long") || test("scala.Short") || test("scala.Float") || test("scala.Double") } override def transformApply(tree: tpd.Apply)(implicit ctx: Context): tpd.Tree = tree match { From c49ad7fab2f95532461c1be67af13638aa675b2f Mon Sep 17 00:00:00 2001 From: liu fengyun Date: Mon, 19 Mar 2018 18:04:38 +0100 Subject: [PATCH 19/27] split Plugin to ResearchPlugin & StandardPlugin --- .../src/dotty/tools/dotc/plugins/Plugin.scala | 59 +++++++++---------- .../dotty/tools/dotc/plugins/Plugins.scala | 4 +- .../compiler-plugin/plugin/DivideZero.scala | 4 +- .../compiler-plugin/project/build.properties | 2 +- .../neg/divideZero-research/plugin_1.scala | 6 +- tests/plugins/neg/divideZero/plugin_1.scala | 4 +- 6 files changed, 37 insertions(+), 42 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/plugins/Plugin.scala b/compiler/src/dotty/tools/dotc/plugins/Plugin.scala index 42df7d13feff..560d2f681eda 100644 --- a/compiler/src/dotty/tools/dotc/plugins/Plugin.scala +++ b/compiler/src/dotty/tools/dotc/plugins/Plugin.scala @@ -16,7 +16,7 @@ trait PluginPhase extends MiniPhase { def runsBefore: Set[Class[_ <: Phase]] = Set.empty } -trait Plugin { +sealed trait Plugin { /** The name of this plugin */ def name: String @@ -28,36 +28,34 @@ trait Plugin { * Research plugin receives a phase plan and return a new phase plan, while * non-research plugin returns a list of phases to be inserted. */ - def research: Boolean = false + def research: Boolean = isInstanceOf[ResearchPlugin] + /** A description of this plugin's options, suitable as a response + * to the -help command-line option. Conventionally, the options + * should be listed with the `-P:plugname:` part included. + */ + val optionsHelp: Option[String] = None +} +trait StandardPlugin extends Plugin { /** Non-research plugins should override this method to return the phases * * @param options: commandline options to the plugin, `-P:plugname:opt1,opt2` becomes List(opt1, opt2) * @return a list of phases to be added to the phase plan */ - def init(options: List[String]): List[PluginPhase] = ??? + def init(options: List[String]): List[PluginPhase] +} +trait ResearchPlugin extends Plugin { /** Research plugins should override this method to return the new phase plan * * @param options: commandline options to the plugin, `-P:plugname:opt1,opt2` becomes List(opt1, opt2) * @param plan: the given phase plan * @return the new phase plan */ - def init(options: List[String], plan: List[List[Phase]])(implicit ctx: Context): List[List[Phase]] = ??? - - /** A description of this plugin's options, suitable as a response - * to the -help command-line option. Conventionally, the options - * should be listed with the `-P:plugname:` part included. - */ - val optionsHelp: Option[String] = None + def init(options: List[String], plan: List[List[Phase]])(implicit ctx: Context): List[List[Phase]] } -/** ... - * - * @author Lex Spoon - * @version 1.0, 2007-5-21 - */ object Plugin { private val PluginXML = "scalac-plugin.xml" @@ -72,21 +70,6 @@ object Plugin { new java.net.URLClassLoader(urls.toArray, compilerLoader) } - /** Try to load a plugin description from the specified location. - */ - private def loadDescriptionFromJar(jarp: Path): Try[PluginDescription] = { - // XXX Return to this once we have more ARM support - def read(is: InputStream) = - if (is == null) throw new PluginLoadException(jarp.path, s"Missing $PluginXML in $jarp") - else PluginDescription.fromXML(is) - - val xmlEntry = new java.util.jar.JarEntry(PluginXML) - Try(read(new Jar(jarp.jpath.toFile).getEntryStream(xmlEntry))) - } - - private def loadDescriptionFromFile(f: Path): Try[PluginDescription] = - Try(PluginDescription.fromXML(new java.io.FileInputStream(f.jpath.toFile))) - type AnyClass = Class[_] /** Use a class loader to load the plugin class. @@ -115,6 +98,20 @@ object Plugin { dirs: List[Path], ignoring: List[String]): List[Try[AnyClass]] = { + + def loadDescriptionFromDir(f: Path): Try[PluginDescription] = + Try(PluginDescription.fromXML(new java.io.FileInputStream((f / PluginXML).jpath.toFile))) + + def loadDescriptionFromJar(jarp: Path): Try[PluginDescription] = { + // XXX Return to this once we have more ARM support + def read(is: InputStream) = + if (is == null) throw new PluginLoadException(jarp.path, s"Missing $PluginXML in $jarp") + else PluginDescription.fromXML(is) + + val xmlEntry = new java.util.jar.JarEntry(PluginXML) + Try(read(new Jar(jarp.jpath.toFile).getEntryStream(xmlEntry))) + } + // List[(jar, Try(descriptor))] in dir def scan(d: Directory) = d.files.toList sortBy (_.name) filter (Jar isJarOrZip _) map (j => (j, loadDescriptionFromJar(j))) @@ -134,7 +131,7 @@ object Plugin { def loop(qs: List[Path]): Try[PluginDescription] = qs match { case Nil => Failure(new MissingPluginException(ps)) case p :: rest => - if (p.isDirectory) loadDescriptionFromFile(p.toDirectory / PluginXML) orElse loop(rest) + if (p.isDirectory) loadDescriptionFromDir(p.toDirectory) orElse loop(rest) else if (p.isFile) loadDescriptionFromJar(p.toFile) orElse loop(rest) else loop(rest) } diff --git a/compiler/src/dotty/tools/dotc/plugins/Plugins.scala b/compiler/src/dotty/tools/dotc/plugins/Plugins.scala index 297daec6d91a..60d94b0cf26c 100644 --- a/compiler/src/dotty/tools/dotc/plugins/Plugins.scala +++ b/compiler/src/dotty/tools/dotc/plugins/Plugins.scala @@ -128,11 +128,11 @@ trait Plugins { } // schedule plugins according to ordering constraints - val pluginPhases = plugins.filter(!_.research).flatMap(plug => plug.init(options(plug))) + val pluginPhases = plugins.collect { case p: StandardPlugin => p }.flatMap { plug => plug.init(options(plug)) } val updatedPlan = Plugins.schedule(plan, pluginPhases) // add research plugins - plugins.filter(_.research).foldRight(updatedPlan) { (plug, plan) => plug.init(options(plug), plan) } + plugins.collect { case p: ResearchPlugin => p }.foldRight(updatedPlan) { (plug, plan) => plug.init(options(plug), plan) } } } diff --git a/sbt-dotty/sbt-test/sbt-dotty/compiler-plugin/plugin/DivideZero.scala b/sbt-dotty/sbt-test/sbt-dotty/compiler-plugin/plugin/DivideZero.scala index e82f7524163d..f91b5ddc285e 100644 --- a/sbt-dotty/sbt-test/sbt-dotty/compiler-plugin/plugin/DivideZero.scala +++ b/sbt-dotty/sbt-test/sbt-dotty/compiler-plugin/plugin/DivideZero.scala @@ -3,7 +3,7 @@ package dividezero import dotty.tools.dotc._ import core._ import Contexts.Context -import plugins.{Plugin, PluginPhase} +import plugins._ import Phases.Phase import ast.tpd import transform.MegaPhase.MiniPhase @@ -12,7 +12,7 @@ import Symbols.Symbol import Constants.Constant import transform.{LinkAll, Pickler} -class DivideZero extends PluginPhase with Plugin { +class DivideZero extends PluginPhase with StandardPlugin { val name: String = "divideZero" override val description: String = "divide zero check" diff --git a/sbt-dotty/sbt-test/sbt-dotty/compiler-plugin/project/build.properties b/sbt-dotty/sbt-test/sbt-dotty/compiler-plugin/project/build.properties index 64317fdae59f..133a8f197e36 100644 --- a/sbt-dotty/sbt-test/sbt-dotty/compiler-plugin/project/build.properties +++ b/sbt-dotty/sbt-test/sbt-dotty/compiler-plugin/project/build.properties @@ -1 +1 @@ -sbt.version=0.13.15 +sbt.version=0.13.17 diff --git a/tests/plugins/neg/divideZero-research/plugin_1.scala b/tests/plugins/neg/divideZero-research/plugin_1.scala index b08f725d6c56..f7f4efbda87b 100644 --- a/tests/plugins/neg/divideZero-research/plugin_1.scala +++ b/tests/plugins/neg/divideZero-research/plugin_1.scala @@ -1,7 +1,7 @@ import dotty.tools.dotc._ import core._ import Contexts.Context -import plugins.Plugin +import plugins._ import Phases.Phase import ast.tpd import transform.MegaPhase.MiniPhase @@ -10,12 +10,10 @@ import Symbols.Symbol import Constants.Constant import StdNames._ -class DivideZero extends MiniPhase with Plugin { +class DivideZero extends MiniPhase with ResearchPlugin { val name: String = "divideZero" override val description: String = "divide zero check" - override val research = true - val phaseName = name override def init(options: List[String], phases: List[List[Phase]])(implicit ctx: Context): List[List[Phase]] = { diff --git a/tests/plugins/neg/divideZero/plugin_1.scala b/tests/plugins/neg/divideZero/plugin_1.scala index 19a6ca434034..7a3344bbbe50 100644 --- a/tests/plugins/neg/divideZero/plugin_1.scala +++ b/tests/plugins/neg/divideZero/plugin_1.scala @@ -1,7 +1,7 @@ import dotty.tools.dotc._ import core._ import Contexts.Context -import plugins.{Plugin, PluginPhase} +import plugins._ import Phases.Phase import ast.tpd import transform.MegaPhase.MiniPhase @@ -11,7 +11,7 @@ import Constants.Constant import transform.{LinkAll, Pickler} import StdNames._ -class DivideZero extends PluginPhase with Plugin { +class DivideZero extends PluginPhase with StandardPlugin { val name: String = "divideZero" override val description: String = "divide zero check" From 334636eb43bbb2096e8a6ce7334927c6a8b8e967 Mon Sep 17 00:00:00 2001 From: liu fengyun Date: Tue, 20 Mar 2018 13:52:59 +0100 Subject: [PATCH 20/27] fix more review feedback --- compiler/src/dotty/tools/dotc/plugins/Plugin.scala | 2 +- compiler/src/dotty/tools/dotc/plugins/Plugins.scala | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/plugins/Plugin.scala b/compiler/src/dotty/tools/dotc/plugins/Plugin.scala index 560d2f681eda..6e4e3d831751 100644 --- a/compiler/src/dotty/tools/dotc/plugins/Plugin.scala +++ b/compiler/src/dotty/tools/dotc/plugins/Plugin.scala @@ -80,7 +80,7 @@ object Plugin { Success[AnyClass](loader loadClass classname) } catch { case NonFatal(e) => - Failure(new PluginLoadException(classname, s"Error: unable to load class: $classname")) + Failure(new PluginLoadException(classname, s"Error: unable to load class $classname: ${e.getMessage}")) case e: NoClassDefFoundError => Failure(new PluginLoadException(classname, s"Error: class not found: ${e.getMessage} required by $classname")) } diff --git a/compiler/src/dotty/tools/dotc/plugins/Plugins.scala b/compiler/src/dotty/tools/dotc/plugins/Plugins.scala index 60d94b0cf26c..9216068d90ea 100644 --- a/compiler/src/dotty/tools/dotc/plugins/Plugins.scala +++ b/compiler/src/dotty/tools/dotc/plugins/Plugins.scala @@ -149,7 +149,7 @@ object Plugins { type OrderingReq = (Set[Class[_]], Set[Class[_]]) val orderRequirements = MMap[Class[_], OrderingReq]() - val primitivePhases = plan.flatMap(ps => ps.map(_.getClass.asInstanceOf[Class[_]])).toSet + val primitivePhases = plan.flatMap(ps => ps.map(_.getClass : Class[_])).toSet def isPrimitive(phase: Class[_]): Boolean = primitivePhases.contains(phase) @@ -239,19 +239,19 @@ object Plugins { var runsAfter = runsAfter1 & insertedPhase val runsBefore = runsBefore1 & insertedPhase - // beforeReq met after the split + // runsBefore met after the split val (before, after) = updatedPlan.span { ps => val classes = ps.map(_.getClass) val runsAfterSat = runsAfter.isEmpty runsAfter = runsAfter -- classes - // Prefer the point immediately before the first beforePhases. - // If beforePhases not specified, insert at the point immediately + // Prefer the point immediately before the first runsBefore. + // If runsBefore not specified, insert at the point immediately // after the last afterPhases. !classes.exists(runsBefore.contains) && !(runsBefore.isEmpty && runsAfterSat) } - // check afterReq + // check runsAfter // error can occur if: a < b, b < c, c < a after.foreach { ps => val classes = ps.map(_.getClass) From 7c3e289bb989be5132a3e1be700e2a4f3900310a Mon Sep 17 00:00:00 2001 From: liu fengyun Date: Tue, 20 Mar 2018 15:21:33 +0100 Subject: [PATCH 21/27] add comment for primitive phases --- .../dotty/tools/dotc/plugins/Plugins.scala | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/plugins/Plugins.scala b/compiler/src/dotty/tools/dotc/plugins/Plugins.scala index 9216068d90ea..415bb5db6ae7 100644 --- a/compiler/src/dotty/tools/dotc/plugins/Plugins.scala +++ b/compiler/src/dotty/tools/dotc/plugins/Plugins.scala @@ -149,9 +149,19 @@ object Plugins { type OrderingReq = (Set[Class[_]], Set[Class[_]]) val orderRequirements = MMap[Class[_], OrderingReq]() - val primitivePhases = plan.flatMap(ps => ps.map(_.getClass : Class[_])).toSet - def isPrimitive(phase: Class[_]): Boolean = primitivePhases.contains(phase) + // 1. already inserted phases don't need constraints themselves. + // 2. no need to propagate beyond boundary of inserted phases, as the information + // beyond boundary is less useful than the boundary. + // 3. unsatisfiable constraints will still be exposed by the first plugin in a loop + // due to its conflicting `runAfter` and `runBefore` after propagation. The ordering + // of primitive phases (`plan`) are used to check `runAfter` and `runBefore`, thus + // there is no need to propagate the primitive phases. + + var insertedPhase = plan.flatMap(ps => ps.map(_.getClass : Class[_])).toSet + def isInserted(phase: Class[_]): Boolean = insertedPhase.contains(phase) + + var updatedPlan = plan def constraintConflict(phase: Phase): String = { val (runsAfter, runsBefore) = orderRequirements(phase.getClass) @@ -176,12 +186,12 @@ object Plugins { var (runsAfter, runsBefore) = orderRequirements(phase.getClass) // propagate transitive constraints to related phases - runsAfter.filter(!isPrimitive(_)).foreach { phaseClass => + runsAfter.filter(!isInserted(_)).foreach { phaseClass => val (runsAfter1, runsBefore1) = orderRequirements(phaseClass) orderRequirements.update(phaseClass, (runsAfter1, runsBefore1 + phase.getClass)) } - runsBefore.filter(!isPrimitive(_)).foreach { phaseClass => + runsBefore.filter(!isInserted(_)).foreach { phaseClass => val (runsAfter1, runsBefore1) = orderRequirements(phaseClass) orderRequirements.update(phaseClass, (runsAfter1 + phase.getClass, runsBefore1)) } @@ -199,7 +209,7 @@ object Plugins { def propagateRunsBefore(beforePhase: Class[_]): Set[Class[_]] = if (beforePhase == phase.getClass) throw new Exception(constraintConflict(phase)) - else if (primitivePhases.contains(beforePhase)) + else if (isInserted(beforePhase)) Set(beforePhase) else { val (_, runsBefore) = orderRequirements(beforePhase) @@ -209,7 +219,7 @@ object Plugins { def propagateRunsAfter(afterPhase: Class[_]): Set[Class[_]] = if (afterPhase == phase.getClass) throw new Exception(constraintConflict(phase)) - else if (primitivePhases.contains(afterPhase)) + else if (isInserted(afterPhase)) Set(afterPhase) else { val (runsAfter, _) = orderRequirements(afterPhase) @@ -224,8 +234,6 @@ object Plugins { (runsAfter, runsBefore) } - var updatedPlan = plan - var insertedPhase = primitivePhases pluginPhases.sortBy(_.phaseName).foreach { phase => var (runsAfter1, runsBefore1) = propagate(phase) From 27378209323eb85cf0abf89372af4ab331fbdf5c Mon Sep 17 00:00:00 2001 From: liu fengyun Date: Tue, 20 Mar 2018 15:59:01 +0100 Subject: [PATCH 22/27] use Try.flatMap with List.map --- compiler/src/dotty/tools/dotc/plugins/Plugin.scala | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/plugins/Plugin.scala b/compiler/src/dotty/tools/dotc/plugins/Plugin.scala index 6e4e3d831751..e4f19ae705d2 100644 --- a/compiler/src/dotty/tools/dotc/plugins/Plugin.scala +++ b/compiler/src/dotty/tools/dotc/plugins/Plugin.scala @@ -143,18 +143,16 @@ object Plugin { } val seen = mutable.HashSet[String]() - val enabled = (fromPaths ::: fromDirs) map { - case Success((pd, loader)) if seen(pd.classname) => + val enabled = (fromPaths ::: fromDirs) map(_.flatMap { + case (pd, loader) if seen(pd.classname) => // a nod to scala/bug#7494, take the plugin classes distinctly Failure(new PluginLoadException(pd.name, s"Ignoring duplicate plugin ${pd.name} (${pd.classname})")) - case Success((pd, loader)) if ignoring contains pd.name => + case (pd, loader) if ignoring contains pd.name => Failure(new PluginLoadException(pd.name, s"Disabling plugin ${pd.name}")) - case Success((pd, loader)) => + case (pd, loader) => seen += pd.classname Plugin.load(pd.classname, loader) - case Failure(e) => - Failure(e) - } + }) enabled // distinct and not disabled } From 2212dc0ea072614fabda1004d9cbb23401cba351 Mon Sep 17 00:00:00 2001 From: liu fengyun Date: Tue, 20 Mar 2018 17:05:22 +0100 Subject: [PATCH 23/27] use string names for runsAfter Quote @sjrd: For inter-plugin dependencies, it will be impossible for a plugin A to get hold of the `Class[_]` value for a plugin B coming from a different jar (hence in a different `ClassLoader`, so not even reflectively available). https://github.com/lampepfl/dotty/pull/3438#discussion_r174495507 --- .../src/dotty/tools/dotc/core/Phases.scala | 21 +++----- .../src/dotty/tools/dotc/plugins/Plugin.scala | 2 +- .../dotty/tools/dotc/plugins/Plugins.scala | 52 +++++++++---------- .../src/dotty/tools/dotc/sbt/ExtractAPI.scala | 2 +- .../dotc/transform/AugmentScala2Traits.scala | 6 ++- .../tools/dotc/transform/ByNameClosures.scala | 6 ++- .../tools/dotc/transform/Constructors.scala | 10 ++-- .../DropEmptyCompanions.scala.disabled | 2 +- .../tools/dotc/transform/ElimByName.scala | 8 ++- .../dotc/transform/ElimErasedValueType.scala | 8 ++- .../dotc/transform/ElimOuterSelect.scala | 2 +- .../tools/dotc/transform/ElimRepeated.scala | 6 ++- .../dotty/tools/dotc/transform/Erasure.scala | 7 ++- .../tools/dotc/transform/ExpandPrivate.scala | 2 +- .../tools/dotc/transform/ExplicitOuter.scala | 6 ++- .../dotc/transform/ExtensionMethods.scala | 8 +-- .../tools/dotc/transform/FirstTransform.scala | 6 ++- .../dotc/transform/FunctionalInterfaces.scala | 7 ++- .../dotty/tools/dotc/transform/GetClass.scala | 2 +- .../tools/dotc/transform/HoistSuperArgs.scala | 8 ++- .../dotc/transform/InterceptedMethods.scala | 6 ++- .../tools/dotc/transform/LambdaLift.scala | 2 +- .../dotty/tools/dotc/transform/LazyVals.scala | 2 +- .../dotty/tools/dotc/transform/LinkAll.scala | 4 +- .../dotc/transform/LinkScala2Impls.scala | 2 +- .../tools/dotc/transform/MegaPhase.scala | 2 +- .../dotty/tools/dotc/transform/Memoize.scala | 10 ++-- .../dotty/tools/dotc/transform/Mixin.scala | 8 ++- .../tools/dotc/transform/MoveStatics.scala | 6 ++- .../dotc/transform/NonLocalReturns.scala | 2 +- .../tools/dotc/transform/PatternMatcher.scala | 8 +-- .../PatternMatcherOld.scala.disabled | 4 +- .../dotty/tools/dotc/transform/Pickler.scala | 6 ++- .../tools/dotc/transform/PostTyper.scala | 6 ++- .../dotc/transform/PrimitiveForwarders.scala | 2 +- .../tools/dotc/transform/RenameLifted.scala | 2 +- .../tools/dotc/transform/ResolveSuper.scala | 8 +-- .../tools/dotc/transform/SeqLiterals.scala | 2 +- .../dotty/tools/dotc/transform/Splitter.scala | 6 ++- .../dotty/tools/dotc/transform/TailRec.scala | 3 +- .../dotc/transform/TryCatchPatterns.scala | 2 +- .../tools/dotc/transform/UnusedDecls.scala | 4 +- .../dotc/transform/VCElideAllocations.scala | 2 +- .../dotc/transform/VCInlineMethods.scala | 4 +- .../dotty/tools/dotc/typer/RefChecks.scala | 2 +- .../tools/dotc/plugins/PluginsTest.scala | 14 ++--- .../compiler-plugin/plugin/DivideZero.scala | 4 +- tests/plugins/neg/divideZero/plugin_1.scala | 4 +- 48 files changed, 184 insertions(+), 114 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Phases.scala b/compiler/src/dotty/tools/dotc/core/Phases.scala index 0abc540448a0..262c15ac7ec3 100644 --- a/compiler/src/dotty/tools/dotc/core/Phases.scala +++ b/compiler/src/dotty/tools/dotc/core/Phases.scala @@ -82,7 +82,7 @@ object Phases { final def squashPhases(phasess: List[List[Phase]], phasesToSkip: List[String], stopBeforePhases: List[String], stopAfterPhases: List[String], YCheckAfter: List[String]): List[Phase] = { val squashedPhases = ListBuffer[Phase]() - var prevPhases: Set[Class[_ <: Phase]] = Set.empty + var prevPhases: Set[String] = Set.empty val YCheckAll = YCheckAfter.contains("all") var stop = false @@ -99,7 +99,6 @@ object Phases { val filteredPhaseBlock = filteredPhases(i) val phaseToAdd = if (filteredPhaseBlock.length > 1) { - val phasesInBlock: Set[String] = filteredPhaseBlock.map(_.phaseName).toSet for (phase <- filteredPhaseBlock) { phase match { case p: MiniPhase => @@ -112,11 +111,11 @@ object Phases { } } val superPhase = new MegaPhase(filteredPhaseBlock.asInstanceOf[List[MiniPhase]].toArray) - prevPhases ++= filteredPhaseBlock.map(_.getClazz) + prevPhases ++= filteredPhaseBlock.map(_.phaseName) superPhase } else { // block of a single phase, no squashing val phase = filteredPhaseBlock.head - prevPhases += phase.getClazz + prevPhases += phase.phaseName phase } squashedPhases += phaseToAdd @@ -147,7 +146,7 @@ object Phases { phases = (NoPhase :: flatPhases.toList ::: new TerminalPhase :: Nil).toArray setSpecificPhases() - var phasesAfter:Set[Class[_ <: Phase]] = Set.empty + var phasesAfter: Set[String] = Set.empty nextDenotTransformerId = new Array[Int](phases.length) denotTransformers = new Array[DenotTransformer](phases.length) @@ -161,7 +160,7 @@ object Phases { val unmetPrecedeRequirements = p.runsAfter -- phasesAfter assert(unmetPrecedeRequirements.isEmpty, s"phase ${p} has unmet requirement: ${unmetPrecedeRequirements.mkString(", ")} should precede this phase") - phasesAfter += p.getClazz + phasesAfter += p.phaseName } var i = 0 @@ -281,7 +280,7 @@ object Phases { def allowsImplicitSearch: Boolean = false /** List of names of phases that should precede this phase */ - def runsAfter: Set[Class[_ <: Phase]] = Set.empty + def runsAfter: Set[String] = Set.empty /** @pre `isRunnable` returns true */ def run(implicit ctx: Context): Unit @@ -393,12 +392,4 @@ object Phases { def replace(oldPhaseClass: Class[_ <: Phase], newPhases: Phase => List[Phase], current: List[List[Phase]]): List[List[Phase]] = current.map(_.flatMap(phase => if (oldPhaseClass.isInstance(phase)) newPhases(phase) else phase :: Nil)) - - /** Dotty deviation: getClass yields Class[_], instead of [Class <: ]. - * We can get back the old behavior using this decorator. We should also use the same - * trick for standard getClass. - */ - private implicit class getClassDeco[T](val x: T) extends AnyVal { - def getClazz: Class[_ <: T] = x.getClass.asInstanceOf[Class[_ <: T]] - } } diff --git a/compiler/src/dotty/tools/dotc/plugins/Plugin.scala b/compiler/src/dotty/tools/dotc/plugins/Plugin.scala index e4f19ae705d2..5b6c059599c9 100644 --- a/compiler/src/dotty/tools/dotc/plugins/Plugin.scala +++ b/compiler/src/dotty/tools/dotc/plugins/Plugin.scala @@ -13,7 +13,7 @@ import scala.collection.mutable import scala.util.{ Try, Success, Failure } trait PluginPhase extends MiniPhase { - def runsBefore: Set[Class[_ <: Phase]] = Set.empty + def runsBefore: Set[String] = Set.empty } sealed trait Plugin { diff --git a/compiler/src/dotty/tools/dotc/plugins/Plugins.scala b/compiler/src/dotty/tools/dotc/plugins/Plugins.scala index 415bb5db6ae7..f62ac22e6481 100644 --- a/compiler/src/dotty/tools/dotc/plugins/Plugins.scala +++ b/compiler/src/dotty/tools/dotc/plugins/Plugins.scala @@ -146,9 +146,9 @@ object Plugins { */ def schedule(plan: List[List[Phase]], pluginPhases: List[PluginPhase]): List[List[Phase]] = { import scala.collection.mutable.{ Map => MMap } - type OrderingReq = (Set[Class[_]], Set[Class[_]]) + type OrderingReq = (Set[String], Set[String]) - val orderRequirements = MMap[Class[_], OrderingReq]() + val orderRequirements = MMap[String, OrderingReq]() // 1. already inserted phases don't need constraints themselves. // 2. no need to propagate beyond boundary of inserted phases, as the information @@ -158,13 +158,13 @@ object Plugins { // of primitive phases (`plan`) are used to check `runAfter` and `runBefore`, thus // there is no need to propagate the primitive phases. - var insertedPhase = plan.flatMap(ps => ps.map(_.getClass : Class[_])).toSet - def isInserted(phase: Class[_]): Boolean = insertedPhase.contains(phase) + var insertedPhase = plan.flatMap(ps => ps.map(_.phaseName)).toSet + def isInserted(phase: String): Boolean = insertedPhase.contains(phase) var updatedPlan = plan def constraintConflict(phase: Phase): String = { - val (runsAfter, runsBefore) = orderRequirements(phase.getClass) + val (runsAfter, runsBefore) = orderRequirements(phase.phaseName) s""" |Ordering conflict for phase ${phase.phaseName} |after: ${runsAfter.mkString("[", ", ", "]")} @@ -174,26 +174,26 @@ object Plugins { // init ordering map, no propagation pluginPhases.foreach { phase => - val runsAfter : Set[Class[_]] = phase.runsAfter.asInstanceOf[Set[Class[_]]] - val runsBefore : Set[Class[_]] = phase.runsBefore.asInstanceOf[Set[Class[_]]] + val runsAfter = phase.runsAfter + val runsBefore = phase.runsBefore - orderRequirements.update(phase.getClass, (runsAfter, runsBefore)) + orderRequirements.update(phase.phaseName, (runsAfter, runsBefore)) } // propagate ordering constraint : reflexivity pluginPhases.foreach { phase => - var (runsAfter, runsBefore) = orderRequirements(phase.getClass) + var (runsAfter, runsBefore) = orderRequirements(phase.phaseName) // propagate transitive constraints to related phases - runsAfter.filter(!isInserted(_)).foreach { phaseClass => - val (runsAfter1, runsBefore1) = orderRequirements(phaseClass) - orderRequirements.update(phaseClass, (runsAfter1, runsBefore1 + phase.getClass)) + runsAfter.filter(!isInserted(_)).foreach { phaseName => + val (runsAfter1, runsBefore1) = orderRequirements(phaseName) + orderRequirements.update(phaseName, (runsAfter1, runsBefore1 + phase.phaseName)) } - runsBefore.filter(!isInserted(_)).foreach { phaseClass => - val (runsAfter1, runsBefore1) = orderRequirements(phaseClass) - orderRequirements.update(phaseClass, (runsAfter1 + phase.getClass, runsBefore1)) + runsBefore.filter(!isInserted(_)).foreach { phaseName => + val (runsAfter1, runsBefore1) = orderRequirements(phaseName) + orderRequirements.update(phaseName, (runsAfter1 + phase.phaseName, runsBefore1)) } } @@ -206,8 +206,8 @@ object Plugins { // propagate constraints from related phases to current phase: transitivity def propagate(phase: Phase): OrderingReq = { - def propagateRunsBefore(beforePhase: Class[_]): Set[Class[_]] = - if (beforePhase == phase.getClass) + def propagateRunsBefore(beforePhase: String): Set[String] = + if (beforePhase == phase.phaseName) throw new Exception(constraintConflict(phase)) else if (isInserted(beforePhase)) Set(beforePhase) @@ -216,8 +216,8 @@ object Plugins { runsBefore.flatMap(propagateRunsBefore) + beforePhase } - def propagateRunsAfter(afterPhase: Class[_]): Set[Class[_]] = - if (afterPhase == phase.getClass) + def propagateRunsAfter(afterPhase: String): Set[String] = + if (afterPhase == phase.phaseName) throw new Exception(constraintConflict(phase)) else if (isInserted(afterPhase)) Set(afterPhase) @@ -226,7 +226,7 @@ object Plugins { runsAfter.flatMap(propagateRunsAfter) + afterPhase } - var (runsAfter, runsBefore) = orderRequirements(phase.getClass) + var (runsAfter, runsBefore) = orderRequirements(phase.phaseName) runsAfter = runsAfter.flatMap(propagateRunsAfter) runsBefore = runsBefore.flatMap(propagateRunsBefore) @@ -249,25 +249,25 @@ object Plugins { // runsBefore met after the split val (before, after) = updatedPlan.span { ps => - val classes = ps.map(_.getClass) + val phases = ps.map(_.phaseName) val runsAfterSat = runsAfter.isEmpty - runsAfter = runsAfter -- classes + runsAfter = runsAfter -- phases // Prefer the point immediately before the first runsBefore. // If runsBefore not specified, insert at the point immediately // after the last afterPhases. - !classes.exists(runsBefore.contains) && + !phases.exists(runsBefore.contains) && !(runsBefore.isEmpty && runsAfterSat) } // check runsAfter // error can occur if: a < b, b < c, c < a after.foreach { ps => - val classes = ps.map(_.getClass) - if (classes.exists(runsAfter)) // afterReq satisfied + val phases = ps.map(_.phaseName) + if (phases.exists(runsAfter)) // afterReq satisfied throw new Exception(s"Ordering conflict for phase ${phase.phaseName}") } - insertedPhase = insertedPhase + phase.getClass + insertedPhase = insertedPhase + phase.phaseName updatedPlan = before ++ (List(phase) :: after) } diff --git a/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala b/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala index 312e2d4093db..1beb0f01af0e 100644 --- a/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala +++ b/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala @@ -38,7 +38,7 @@ class ExtractAPI extends Phase { // after `PostTyper` (unlike `ExtractDependencies`, the simplication to trees // done by `PostTyper` do not affect this phase because it only cares about // definitions, and `PostTyper` does not change definitions). - override def runsAfter = Set(classOf[transform.PostTyper]) + override def runsAfter = Set(transform.PostTyper.name) override def run(implicit ctx: Context): Unit = { val unit = ctx.compilationUnit diff --git a/compiler/src/dotty/tools/dotc/transform/AugmentScala2Traits.scala b/compiler/src/dotty/tools/dotc/transform/AugmentScala2Traits.scala index e208902d3589..940037903b56 100644 --- a/compiler/src/dotty/tools/dotc/transform/AugmentScala2Traits.scala +++ b/compiler/src/dotty/tools/dotc/transform/AugmentScala2Traits.scala @@ -17,6 +17,10 @@ import NameOps._ import NameKinds.{ExpandedName, TraitSetterName} import ast.Trees._ +object AugmentScala2Traits { + val name = "augmentScala2Traits" +} + /** This phase augments Scala2 traits with implementation classes and with additional members * needed for mixin composition. * These symbols would have been added between Unpickling and Mixin in the Scala2 pipeline. @@ -33,7 +37,7 @@ class AugmentScala2Traits extends MiniPhase with IdentityDenotTransformer with F override def changesMembers = true - override def phaseName: String = "augmentScala2Traits" + override def phaseName: String = AugmentScala2Traits.name override def rewiredTarget(referenced: Symbol, derived: Symbol)(implicit ctx: Context) = NoSymbol diff --git a/compiler/src/dotty/tools/dotc/transform/ByNameClosures.scala b/compiler/src/dotty/tools/dotc/transform/ByNameClosures.scala index c548fce4f9d1..919a1961f5fa 100644 --- a/compiler/src/dotty/tools/dotc/transform/ByNameClosures.scala +++ b/compiler/src/dotty/tools/dotc/transform/ByNameClosures.scala @@ -26,7 +26,7 @@ import core.StdNames.nme class ByNameClosures extends TransformByNameApply with IdentityDenotTransformer { thisPhase => import ast.tpd._ - override def phaseName: String = "byNameClosures" + override def phaseName: String = ByNameClosures.name override def mkByNameClosure(arg: Tree, argType: Type)(implicit ctx: Context): Tree = { val meth = ctx.newSymbol( @@ -34,3 +34,7 @@ class ByNameClosures extends TransformByNameApply with IdentityDenotTransformer Closure(meth, _ => arg.changeOwnerAfter(ctx.owner, meth, thisPhase)) } } + +object ByNameClosures { + val name = "byNameClosures" +} \ No newline at end of file diff --git a/compiler/src/dotty/tools/dotc/transform/Constructors.scala b/compiler/src/dotty/tools/dotc/transform/Constructors.scala index f90289a5af48..89a3d16772a4 100644 --- a/compiler/src/dotty/tools/dotc/transform/Constructors.scala +++ b/compiler/src/dotty/tools/dotc/transform/Constructors.scala @@ -20,6 +20,10 @@ import util.Positions._ import Constants.Constant import collection.mutable +object Constructors { + val name = "constructors" +} + /** This transform * - moves initializers from body to constructor. * - makes all supercalls explicit @@ -29,9 +33,9 @@ import collection.mutable class Constructors extends MiniPhase with IdentityDenotTransformer { thisPhase => import tpd._ - override def phaseName: String = "constructors" - override def runsAfter: Set[Class[_ <: Phase]] = Set(classOf[HoistSuperArgs]) - override def runsAfterGroupsOf: Set[Class[_ <: Phase]] = Set(classOf[Memoize]) + override def phaseName: String = Constructors.name + override def runsAfter = Set(HoistSuperArgs.name) + override def runsAfterGroupsOf = Set(Memoize.name) // Memoized needs to be finished because we depend on the ownerchain after Memoize // when checking whether an ident is an access in a constructor or outside it. // This test is done in the right-hand side of a value definition. If Memoize diff --git a/compiler/src/dotty/tools/dotc/transform/DropEmptyCompanions.scala.disabled b/compiler/src/dotty/tools/dotc/transform/DropEmptyCompanions.scala.disabled index 559ce68947a0..24e34cbbd336 100644 --- a/compiler/src/dotty/tools/dotc/transform/DropEmptyCompanions.scala.disabled +++ b/compiler/src/dotty/tools/dotc/transform/DropEmptyCompanions.scala.disabled @@ -31,7 +31,7 @@ import dotty.tools.dotc.transform.MegaPhase.TransformerInfo class DropEmptyCompanions extends MiniPhase { thisTransform => import ast.tpd._ override def phaseName = "dropEmptyCompanions" - override def runsAfter: Set[Class[_ <: Phase]] = Set(classOf[Flatten]) + override def runsAfter = Set(Flatten.name) override def transformPackageDef(pdef: PackageDef)(implicit ctx: Context) = { diff --git a/compiler/src/dotty/tools/dotc/transform/ElimByName.scala b/compiler/src/dotty/tools/dotc/transform/ElimByName.scala index 36c22f2e2068..adeb866f3443 100644 --- a/compiler/src/dotty/tools/dotc/transform/ElimByName.scala +++ b/compiler/src/dotty/tools/dotc/transform/ElimByName.scala @@ -40,9 +40,9 @@ import ast.Trees._ class ElimByName extends TransformByNameApply with InfoTransformer { import ast.tpd._ - override def phaseName: String = "elimByName" + override def phaseName: String = ElimByName.name - override def runsAfterGroupsOf = Set(classOf[Splitter]) + override def runsAfterGroupsOf = Set(Splitter.name) // I got errors running this phase in an earlier group, but I did not track them down. /** Map `tree` to `tree.apply()` is `ftree` was of ExprType and becomes now a function */ @@ -79,3 +79,7 @@ class ElimByName extends TransformByNameApply with InfoTransformer { override def mayChange(sym: Symbol)(implicit ctx: Context): Boolean = sym.isTerm } + +object ElimByName { + val name = "elimByName" +} \ No newline at end of file diff --git a/compiler/src/dotty/tools/dotc/transform/ElimErasedValueType.scala b/compiler/src/dotty/tools/dotc/transform/ElimErasedValueType.scala index 723e659d32fc..209127d85acc 100644 --- a/compiler/src/dotty/tools/dotc/transform/ElimErasedValueType.scala +++ b/compiler/src/dotty/tools/dotc/transform/ElimErasedValueType.scala @@ -8,6 +8,10 @@ import Types._, Contexts._, Constants._, Names._, NameOps._, Flags._, DenotTrans import SymDenotations._, Symbols._, StdNames._, Annotations._, Trees._, Scopes._, Denotations._ import TypeErasure.ErasedValueType, ValueClasses._ +object ElimErasedValueType { + val name = "elimErasedValueType" +} + /** This phase erases ErasedValueType to their underlying type. * It also removes the synthetic cast methods u2evt$ and evt2u$ which are * no longer needed afterwards. @@ -19,9 +23,9 @@ class ElimErasedValueType extends MiniPhase with InfoTransformer { import tpd._ - override def phaseName: String = "elimErasedValueType" + override def phaseName: String = ElimErasedValueType.name - override def runsAfter: Set[Class[_ <: Phase]] = Set(classOf[Erasure]) + override def runsAfter = Set(Erasure.name) def transformInfo(tp: Type, sym: Symbol)(implicit ctx: Context): Type = sym match { case sym: ClassSymbol if sym is ModuleClass => diff --git a/compiler/src/dotty/tools/dotc/transform/ElimOuterSelect.scala b/compiler/src/dotty/tools/dotc/transform/ElimOuterSelect.scala index cec86c88e651..7bc8c696e1a0 100644 --- a/compiler/src/dotty/tools/dotc/transform/ElimOuterSelect.scala +++ b/compiler/src/dotty/tools/dotc/transform/ElimOuterSelect.scala @@ -17,7 +17,7 @@ class ElimOuterSelect extends MiniPhase { override def phaseName: String = "elimOuterSelect" - override def runsAfterGroupsOf = Set(classOf[ExplicitOuter]) + override def runsAfterGroupsOf = Set(ExplicitOuter.name) // ExplicitOuter needs to have run to completion before so that all classes // that need an outer accessor have one. diff --git a/compiler/src/dotty/tools/dotc/transform/ElimRepeated.scala b/compiler/src/dotty/tools/dotc/transform/ElimRepeated.scala index 1518e8bba28e..23b6dfb4dd5e 100644 --- a/compiler/src/dotty/tools/dotc/transform/ElimRepeated.scala +++ b/compiler/src/dotty/tools/dotc/transform/ElimRepeated.scala @@ -22,13 +22,17 @@ import Names.Name import NameOps._ import TypeUtils._ +object ElimRepeated { + val name = "elimRepeated" +} + /** A transformer that removes repeated parameters (T*) from all types, replacing * them with Seq types. */ class ElimRepeated extends MiniPhase with InfoTransformer { thisPhase => import ast.tpd._ - override def phaseName = "elimRepeated" + override def phaseName = ElimRepeated.name override def changesMembers = true // the phase adds vararg bridges diff --git a/compiler/src/dotty/tools/dotc/transform/Erasure.scala b/compiler/src/dotty/tools/dotc/transform/Erasure.scala index fa185896ecfc..7f590e05beff 100644 --- a/compiler/src/dotty/tools/dotc/transform/Erasure.scala +++ b/compiler/src/dotty/tools/dotc/transform/Erasure.scala @@ -30,12 +30,13 @@ import ExplicitOuter._ import core.Mode import reporting.trace + class Erasure extends Phase with DenotTransformer { - override def phaseName: String = "erasure" + override def phaseName: String = Erasure.name /** List of names of phases that should precede this phase */ - override def runsAfter: Set[Class[_ <: Phase]] = Set(classOf[InterceptedMethods], classOf[Splitter], classOf[ElimRepeated]) + override def runsAfter = Set(InterceptedMethods.name, Splitter.name, ElimRepeated.name) override def changesMembers: Boolean = true // the phase adds bridges override def changesParents: Boolean = true // the phase drops Any @@ -146,6 +147,8 @@ object Erasure { import tpd._ import TypeTestsCasts._ + val name = "erasure" + object Boxing { def isUnbox(sym: Symbol)(implicit ctx: Context) = diff --git a/compiler/src/dotty/tools/dotc/transform/ExpandPrivate.scala b/compiler/src/dotty/tools/dotc/transform/ExpandPrivate.scala index e5ee8556ec88..ec82050366d9 100644 --- a/compiler/src/dotty/tools/dotc/transform/ExpandPrivate.scala +++ b/compiler/src/dotty/tools/dotc/transform/ExpandPrivate.scala @@ -41,7 +41,7 @@ class ExpandPrivate extends MiniPhase with IdentityDenotTransformer { thisPhase override def phaseName: String = "expandPrivate" // This phase moves methods around (in infotransform) so it may need to make other methods public - override def runsAfter: Set[Class[_ <: Phase]] = Set(classOf[MoveStatics]) + override def runsAfter = Set(MoveStatics.name) override def changesMembers = true // the phase introduces new members with mangled names diff --git a/compiler/src/dotty/tools/dotc/transform/ExplicitOuter.scala b/compiler/src/dotty/tools/dotc/transform/ExplicitOuter.scala index 39fabb732d5b..f115af33e8b4 100644 --- a/compiler/src/dotty/tools/dotc/transform/ExplicitOuter.scala +++ b/compiler/src/dotty/tools/dotc/transform/ExplicitOuter.scala @@ -37,12 +37,12 @@ class ExplicitOuter extends MiniPhase with InfoTransformer { thisPhase => import ExplicitOuter._ import ast.tpd._ - override def phaseName: String = "explicitOuter" + override def phaseName: String = ExplicitOuter.name /** List of names of phases that should have finished their processing of all compilation units * before this phase starts */ - override def runsAfter: Set[Class[_ <: Phase]] = Set(classOf[PatternMatcher], classOf[HoistSuperArgs]) + override def runsAfter = Set(PatternMatcher.name, HoistSuperArgs.name) override def changesMembers = true // the phase adds outer accessors @@ -128,6 +128,8 @@ class ExplicitOuter extends MiniPhase with InfoTransformer { thisPhase => object ExplicitOuter { import ast.tpd._ + val name = "explicitOuter" + /** Ensure that class `cls` has outer accessors */ def ensureOuterAccessors(cls: ClassSymbol)(implicit ctx: Context): Unit = ctx.atPhase(ctx.explicitOuterPhase.next) { implicit ctx => diff --git a/compiler/src/dotty/tools/dotc/transform/ExtensionMethods.scala b/compiler/src/dotty/tools/dotc/transform/ExtensionMethods.scala index 3e743ebe52b7..dc86447c20a6 100644 --- a/compiler/src/dotty/tools/dotc/transform/ExtensionMethods.scala +++ b/compiler/src/dotty/tools/dotc/transform/ExtensionMethods.scala @@ -43,11 +43,11 @@ class ExtensionMethods extends MiniPhase with DenotTransformer with FullParamete import ExtensionMethods._ /** the following two members override abstract members in Transform */ - override def phaseName: String = "extmethods" + override def phaseName: String = ExtensionMethods.name - override def runsAfter: Set[Class[_ <: Phase]] = Set(classOf[ElimRepeated]) + override def runsAfter = Set(ElimRepeated.name) - override def runsAfterGroupsOf = Set(classOf[FirstTransform]) // need companion objects to exist + override def runsAfterGroupsOf = Set(FirstTransform.name) // need companion objects to exist override def changesMembers = true // the phase adds extension methods @@ -182,6 +182,8 @@ class ExtensionMethods extends MiniPhase with DenotTransformer with FullParamete } object ExtensionMethods { + val name = "extmethods" + /** Generate stream of possible names for the extension version of given instance method `imeth`. * If the method is not overloaded, this stream consists of just "imeth$extension". * If the method is overloaded, the stream has as first element "imeth$extenionX", where X is the diff --git a/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala b/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala index f988e519862d..394f5aaf95c0 100644 --- a/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala +++ b/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala @@ -24,6 +24,10 @@ import NameOps._ import NameKinds.{AvoidClashName, OuterSelectName} import StdNames._ +object FirstTransform { + val name = "firstTransform" +} + /** The first tree transform * - ensures there are companion objects for all classes except module classes @@ -39,7 +43,7 @@ import StdNames._ class FirstTransform extends MiniPhase with InfoTransformer { thisPhase => import ast.tpd._ - override def phaseName = "firstTransform" + override def phaseName = FirstTransform.name private[this] var addCompanionPhases: List[NeedsCompanions] = _ diff --git a/compiler/src/dotty/tools/dotc/transform/FunctionalInterfaces.scala b/compiler/src/dotty/tools/dotc/transform/FunctionalInterfaces.scala index 228e73e1d5fa..2912d5a1391c 100644 --- a/compiler/src/dotty/tools/dotc/transform/FunctionalInterfaces.scala +++ b/compiler/src/dotty/tools/dotc/transform/FunctionalInterfaces.scala @@ -18,13 +18,18 @@ import dotty.tools.dotc.ast.tpd import collection.{ mutable, immutable } import collection.mutable.{ LinkedHashMap, LinkedHashSet, TreeSet } + +object FunctionalInterfaces { + val name = "functionalInterfaces" +} + /** * Rewires closures to implement more specific types of Functions. */ class FunctionalInterfaces extends MiniPhase { import tpd._ - def phaseName: String = "functionalInterfaces" + def phaseName: String = FunctionalInterfaces.name val functionName = "JFunction".toTermName val functionPackage = "scala.compat.java8.".toTermName diff --git a/compiler/src/dotty/tools/dotc/transform/GetClass.scala b/compiler/src/dotty/tools/dotc/transform/GetClass.scala index e83363d3dea0..ef3f7b6e5ca3 100644 --- a/compiler/src/dotty/tools/dotc/transform/GetClass.scala +++ b/compiler/src/dotty/tools/dotc/transform/GetClass.scala @@ -21,7 +21,7 @@ class GetClass extends MiniPhase { override def phaseName: String = "getClass" // getClass transformation should be applied to specialized methods - override def runsAfter: Set[Class[_ <: Phase]] = Set(classOf[Erasure], classOf[FunctionalInterfaces]) + override def runsAfter = Set(Erasure.name, FunctionalInterfaces.name) override def transformApply(tree: Apply)(implicit ctx: Context): Tree = { import ast.Trees._ diff --git a/compiler/src/dotty/tools/dotc/transform/HoistSuperArgs.scala b/compiler/src/dotty/tools/dotc/transform/HoistSuperArgs.scala index d0d55d735329..d6d8377a4252 100644 --- a/compiler/src/dotty/tools/dotc/transform/HoistSuperArgs.scala +++ b/compiler/src/dotty/tools/dotc/transform/HoistSuperArgs.scala @@ -15,6 +15,10 @@ import core.Names.TermName import core.NameKinds.SuperArgName import SymUtils._ +object HoistSuperArgs { + val name = "hoistSuperArgs" +} + /** This phase hoists complex arguments of supercalls and this-calls out of the enclosing class. * Example: * @@ -40,9 +44,9 @@ import SymUtils._ class HoistSuperArgs extends MiniPhase with IdentityDenotTransformer { thisPhase => import ast.tpd._ - def phaseName = "hoistSuperArgs" + def phaseName = HoistSuperArgs.name - override def runsAfter = Set(classOf[ByNameClosures]) + override def runsAfter = Set(ByNameClosures.name) // By name closures need to be introduced first in order to be hoisted out here. // There's an interaction with by name closures in that the marker // application should not be hoisted, but be left at the point of call. diff --git a/compiler/src/dotty/tools/dotc/transform/InterceptedMethods.scala b/compiler/src/dotty/tools/dotc/transform/InterceptedMethods.scala index 1a96fc1b0ebb..04f9b3fbce55 100644 --- a/compiler/src/dotty/tools/dotc/transform/InterceptedMethods.scala +++ b/compiler/src/dotty/tools/dotc/transform/InterceptedMethods.scala @@ -27,6 +27,10 @@ import dotty.tools.dotc.core.SymDenotations.SymDenotation import StdNames._ import Phases.Phase +object InterceptedMethods { + val name = "intercepted" +} + /** Replace member references as follows: * * - `x != y` for != in class Any becomes `!(x == y)` with == in class Any. @@ -38,7 +42,7 @@ import Phases.Phase class InterceptedMethods extends MiniPhase { import tpd._ - override def phaseName: String = "intercepted" + override def phaseName: String = InterceptedMethods.name private[this] var primitiveGetClassMethods: Set[Symbol] = _ diff --git a/compiler/src/dotty/tools/dotc/transform/LambdaLift.scala b/compiler/src/dotty/tools/dotc/transform/LambdaLift.scala index 13f58b483658..fcae7ddbd0ee 100644 --- a/compiler/src/dotty/tools/dotc/transform/LambdaLift.scala +++ b/compiler/src/dotty/tools/dotc/transform/LambdaLift.scala @@ -493,7 +493,7 @@ class LambdaLift extends MiniPhase with IdentityDenotTransformer { thisPhase => override def relaxedTypingInGroup = true // Because it adds free vars as additional proxy parameters - override def runsAfterGroupsOf: Set[Class[_ <: Phase]] = Set(classOf[Constructors], classOf[HoistSuperArgs]) + override def runsAfterGroupsOf = Set(Constructors.name, HoistSuperArgs.name) // Constructors has to happen before LambdaLift because the lambda lift logic // becomes simpler if it can assume that parameter accessors have already been // converted to parameters in super calls. Without this it is very hard to get diff --git a/compiler/src/dotty/tools/dotc/transform/LazyVals.scala b/compiler/src/dotty/tools/dotc/transform/LazyVals.scala index 2d22dea0d86f..4c49504cf4f8 100644 --- a/compiler/src/dotty/tools/dotc/transform/LazyVals.scala +++ b/compiler/src/dotty/tools/dotc/transform/LazyVals.scala @@ -40,7 +40,7 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer { /** List of names of phases that should have finished processing of tree * before this phase starts processing same tree */ - override def runsAfter = Set(classOf[Mixin]) + override def runsAfter = Set(Mixin.name) override def changesMembers = true // the phase adds lazy val accessors diff --git a/compiler/src/dotty/tools/dotc/transform/LinkAll.scala b/compiler/src/dotty/tools/dotc/transform/LinkAll.scala index 66544d9264b5..5fa9436f297a 100644 --- a/compiler/src/dotty/tools/dotc/transform/LinkAll.scala +++ b/compiler/src/dotty/tools/dotc/transform/LinkAll.scala @@ -19,7 +19,7 @@ class LinkAll extends Phase { import tpd._ import LinkAll._ - def phaseName: String = "linkAll" + def phaseName: String = LinkAll.name def run(implicit ctx: Context): Unit = () @@ -67,6 +67,8 @@ class LinkAll extends Phase { object LinkAll { + val name = "linkAll" + private[LinkAll] def loadCompilationUnit(clsd: ClassDenotation)(implicit ctx: Context): Option[CompilationUnit] = { assert(ctx.settings.Xlink.value) val tree = clsd.symbol.asClass.tree(ctx.addMode(Mode.ReadPositions)) diff --git a/compiler/src/dotty/tools/dotc/transform/LinkScala2Impls.scala b/compiler/src/dotty/tools/dotc/transform/LinkScala2Impls.scala index 4b1b93ab63f4..042c7cff4cbf 100644 --- a/compiler/src/dotty/tools/dotc/transform/LinkScala2Impls.scala +++ b/compiler/src/dotty/tools/dotc/transform/LinkScala2Impls.scala @@ -40,7 +40,7 @@ class LinkScala2Impls extends MiniPhase with IdentityDenotTransformer { thisPhas override def phaseName: String = "linkScala2Impls" override def changesMembers = true - override def runsAfterGroupsOf: Set[Class[_ <: Phase]] = Set(classOf[Mixin]) + override def runsAfterGroupsOf = Set(Mixin.name) // Adds as a side effect static members to traits which can confuse Mixin, // that's why it is runsAfterGroupOf diff --git a/compiler/src/dotty/tools/dotc/transform/MegaPhase.scala b/compiler/src/dotty/tools/dotc/transform/MegaPhase.scala index ede670d74e9f..2d2cbf4de3aa 100644 --- a/compiler/src/dotty/tools/dotc/transform/MegaPhase.scala +++ b/compiler/src/dotty/tools/dotc/transform/MegaPhase.scala @@ -42,7 +42,7 @@ object MegaPhase { /** List of names of phases that should have finished their processing of all compilation units * before this phase starts */ - def runsAfterGroupsOf: Set[Class[_ <: Phase]] = Set.empty + def runsAfterGroupsOf: Set[String] = Set.empty final override def relaxedTyping = superPhase.relaxedTyping diff --git a/compiler/src/dotty/tools/dotc/transform/Memoize.scala b/compiler/src/dotty/tools/dotc/transform/Memoize.scala index 1f9ffcdf2eba..aee354a077c7 100644 --- a/compiler/src/dotty/tools/dotc/transform/Memoize.scala +++ b/compiler/src/dotty/tools/dotc/transform/Memoize.scala @@ -17,6 +17,10 @@ import NameOps._ import Flags._ import Decorators._ +object Memoize { + val name = "memoize" +} + /** Provides the implementations of all getters and setters, introducing * fields to hold the value accessed by them. * TODO: Make LazyVals a part of this phase? @@ -32,10 +36,10 @@ import Decorators._ * def x_=(y: T): Unit = () * --> def x_=(y: T): Unit = x = y */ - class Memoize extends MiniPhase with IdentityDenotTransformer { thisPhase => +class Memoize extends MiniPhase with IdentityDenotTransformer { thisPhase => import ast.tpd._ - override def phaseName = "memoize" + override def phaseName = Memoize.name /* Makes sure that, after getters and constructors gen, there doesn't * exist non-deferred definitions that are not implemented. */ @@ -64,7 +68,7 @@ import Decorators._ * class that contains the concrete getter rather than the trait * that defines it. */ - override def runsAfter: Set[Class[_ <: Phase]] = Set(classOf[Mixin]) + override def runsAfter = Set(Mixin.name) override def transformDefDef(tree: DefDef)(implicit ctx: Context): Tree = { val sym = tree.symbol diff --git a/compiler/src/dotty/tools/dotc/transform/Mixin.scala b/compiler/src/dotty/tools/dotc/transform/Mixin.scala index c3236872d607..437a8a45af3c 100644 --- a/compiler/src/dotty/tools/dotc/transform/Mixin.scala +++ b/compiler/src/dotty/tools/dotc/transform/Mixin.scala @@ -19,6 +19,10 @@ import ast.untpd import ast.Trees._ import collection.mutable +object Mixin { + val name = "mixin" +} + /** This phase performs the following transformations: * * 1. (done in `traitDefs` and `transformSym`) Map every concrete trait getter @@ -94,12 +98,12 @@ import collection.mutable class Mixin extends MiniPhase with SymTransformer { thisPhase => import ast.tpd._ - override def phaseName: String = "mixin" + override def phaseName: String = Mixin.name override def relaxedTypingInGroup = true // Because it changes number of parameters in trait initializers - override def runsAfter: Set[Class[_ <: Phase]] = Set(classOf[Erasure]) + override def runsAfter = Set(Erasure.name) override def changesMembers = true // the phase adds implementions of mixin accessors diff --git a/compiler/src/dotty/tools/dotc/transform/MoveStatics.scala b/compiler/src/dotty/tools/dotc/transform/MoveStatics.scala index 484e538e0bf4..c71dba2b9640 100644 --- a/compiler/src/dotty/tools/dotc/transform/MoveStatics.scala +++ b/compiler/src/dotty/tools/dotc/transform/MoveStatics.scala @@ -15,11 +15,15 @@ import dotty.tools.dotc.core.Symbols._ import dotty.tools.dotc.core.Types.MethodType import dotty.tools.dotc.transform.MegaPhase.MiniPhase +object MoveStatics { + val name = "moveStatic" +} + /** Move static methods from companion to the class itself */ class MoveStatics extends MiniPhase with SymTransformer { import tpd._ - override def phaseName = "moveStatic" + override def phaseName = MoveStatics.name def transformSym(sym: SymDenotation)(implicit ctx: Context): SymDenotation = { if (sym.hasAnnotation(defn.ScalaStaticAnnot) && sym.owner.is(Flags.Module) && sym.owner.companionClass.exists) { diff --git a/compiler/src/dotty/tools/dotc/transform/NonLocalReturns.scala b/compiler/src/dotty/tools/dotc/transform/NonLocalReturns.scala index 18eb68f0c9fd..c93bcd9639b9 100644 --- a/compiler/src/dotty/tools/dotc/transform/NonLocalReturns.scala +++ b/compiler/src/dotty/tools/dotc/transform/NonLocalReturns.scala @@ -22,7 +22,7 @@ class NonLocalReturns extends MiniPhase { import NonLocalReturns._ import ast.tpd._ - override def runsAfter: Set[Class[_ <: Phase]] = Set(classOf[ElimByName]) + override def runsAfter = Set(ElimByName.name) private def ensureConforms(tree: Tree, pt: Type)(implicit ctx: Context) = if (tree.tpe <:< pt) tree diff --git a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala index 9f1eae55ddde..8fa12812799a 100644 --- a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala +++ b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala @@ -24,9 +24,9 @@ class PatternMatcher extends MiniPhase { import ast.tpd._ import PatternMatcher._ - override def phaseName = "patternMatcher" - override def runsAfter = Set(classOf[ElimRepeated]) - override def runsAfterGroupsOf = Set(classOf[TailRec]) // tailrec is not capable of reversing the patmat tranformation made for tree + override def phaseName = PatternMatcher.name + override def runsAfter = Set(ElimRepeated.name) + override def runsAfterGroupsOf = Set(TailRec.name) // tailrec is not capable of reversing the patmat tranformation made for tree override def transformMatch(tree: Match)(implicit ctx: Context): Tree = { val translated = new Translator(tree.tpe, this).translateMatch(tree) @@ -46,6 +46,8 @@ class PatternMatcher extends MiniPhase { object PatternMatcher { import ast.tpd._ + val name = "patternMatcher" + final val selfCheck = false // debug option, if on we check that no case gets generated twice /** Minimal number of cases to emit a switch */ diff --git a/compiler/src/dotty/tools/dotc/transform/PatternMatcherOld.scala.disabled b/compiler/src/dotty/tools/dotc/transform/PatternMatcherOld.scala.disabled index c0ab94b02386..cf1439f802ed 100644 --- a/compiler/src/dotty/tools/dotc/transform/PatternMatcherOld.scala.disabled +++ b/compiler/src/dotty/tools/dotc/transform/PatternMatcherOld.scala.disabled @@ -39,9 +39,9 @@ class PatternMatcherOld extends MiniPhase with DenotTransformer { override def transform(ref: SingleDenotation)(implicit ctx: Context): SingleDenotation = ref - override def runsAfter = Set(classOf[ElimRepeated]) + override def runsAfter = Set(ElimRepeated.name) - override def runsAfterGroupsOf = Set(classOf[TailRec]) // tailrec is not capable of reversing the patmat tranformation made for tree + override def runsAfterGroupsOf = Set(TailRec.name) // tailrec is not capable of reversing the patmat tranformation made for tree override def phaseName = "patternMatcher" diff --git a/compiler/src/dotty/tools/dotc/transform/Pickler.scala b/compiler/src/dotty/tools/dotc/transform/Pickler.scala index 01fc17941746..b36386b9cf1a 100644 --- a/compiler/src/dotty/tools/dotc/transform/Pickler.scala +++ b/compiler/src/dotty/tools/dotc/transform/Pickler.scala @@ -14,11 +14,15 @@ import Flags.Module import reporting.ThrowingReporter import collection.mutable +object Pickler { + val name = "pickler" +} + /** This phase pickles trees */ class Pickler extends Phase { import ast.tpd._ - override def phaseName: String = "pickler" + override def phaseName: String = Pickler.name private def output(name: String, msg: String) = { val s = new PrintStream(name) diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index 2371bd3e4c70..1bb6ff7fa7c0 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -16,6 +16,10 @@ import config.Printers.typr import Symbols._, TypeUtils._, SymUtils._ import reporting.diagnostic.messages.{NotAMember, SuperCallsNotAllowedInline} +object PostTyper { + val name = "posttyper" +} + /** A macro transform that runs immediately after typer and that performs the following functions: * * (1) Add super accessors and protected accessors (@see SuperAccessors) @@ -55,7 +59,7 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase import tpd._ /** the following two members override abstract members in Transform */ - override def phaseName: String = "posttyper" + override def phaseName: String = PostTyper.name override def changesMembers = true // the phase adds super accessors and synthetic methods diff --git a/compiler/src/dotty/tools/dotc/transform/PrimitiveForwarders.scala b/compiler/src/dotty/tools/dotc/transform/PrimitiveForwarders.scala index 17535ac45cfe..853c74c815c9 100644 --- a/compiler/src/dotty/tools/dotc/transform/PrimitiveForwarders.scala +++ b/compiler/src/dotty/tools/dotc/transform/PrimitiveForwarders.scala @@ -35,7 +35,7 @@ class PrimitiveForwarders extends MiniPhase with IdentityDenotTransformer { this override def phaseName: String = "primitiveForwarders" - override def runsAfter = Set(classOf[ResolveSuper]) + override def runsAfter = Set(ResolveSuper.name) override def changesMembers = true // the phase adds primitive forwarders diff --git a/compiler/src/dotty/tools/dotc/transform/RenameLifted.scala b/compiler/src/dotty/tools/dotc/transform/RenameLifted.scala index beef392a3eb2..f6ae2ff3954e 100644 --- a/compiler/src/dotty/tools/dotc/transform/RenameLifted.scala +++ b/compiler/src/dotty/tools/dotc/transform/RenameLifted.scala @@ -17,7 +17,7 @@ class RenameLifted extends MiniPhase with SymTransformer { override def phaseName = "renameLifted" // Not clear why this should run after restoreScopes - // override def runsAfterGroupsOf: Set[Class[_ <: Phases.Phase]] = Set(classOf[RestoreScopes]) + // override def runsAfterGroupsOf = Set(RestoreScopes.name) def transformSym(ref: SymDenotation)(implicit ctx: Context): SymDenotation = if (needsRefresh(ref.symbol)) ref.copySymDenotation(name = refreshedName(ref.symbol)) diff --git a/compiler/src/dotty/tools/dotc/transform/ResolveSuper.scala b/compiler/src/dotty/tools/dotc/transform/ResolveSuper.scala index e347b1460fe1..940ce9199a08 100644 --- a/compiler/src/dotty/tools/dotc/transform/ResolveSuper.scala +++ b/compiler/src/dotty/tools/dotc/transform/ResolveSuper.scala @@ -48,10 +48,10 @@ import ResolveSuper._ class ResolveSuper extends MiniPhase with IdentityDenotTransformer { thisPhase => import ast.tpd._ - override def phaseName: String = "resolveSuper" + override def phaseName: String = ResolveSuper.name - override def runsAfter = Set(classOf[ElimByName], // verified empirically, need to figure out what the reason is. - classOf[AugmentScala2Traits]) + override def runsAfter = Set(ElimByName.name, // verified empirically, need to figure out what the reason is. + AugmentScala2Traits.name) override def changesMembers = true // the phase adds super accessors and method forwarders @@ -95,6 +95,8 @@ class ResolveSuper extends MiniPhase with IdentityDenotTransformer { thisPhase = } object ResolveSuper { + val name = "resolveSuper" + /** Returns the symbol that is accessed by a super-accessor in a mixin composition. * * @param base The class in which everything is mixed together diff --git a/compiler/src/dotty/tools/dotc/transform/SeqLiterals.scala b/compiler/src/dotty/tools/dotc/transform/SeqLiterals.scala index 139424bb111a..756e4f045cb6 100644 --- a/compiler/src/dotty/tools/dotc/transform/SeqLiterals.scala +++ b/compiler/src/dotty/tools/dotc/transform/SeqLiterals.scala @@ -22,7 +22,7 @@ class SeqLiterals extends MiniPhase { import ast.tpd._ override def phaseName = "seqLiterals" - override def runsAfter: Set[Class[_ <: Phase]] = Set(classOf[PatternMatcher]) + override def runsAfter = Set(PatternMatcher.name) override def checkPostCondition(tree: Tree)(implicit ctx: Context): Unit = tree match { case tpd: SeqLiteral => assert(tpd.isInstanceOf[JavaSeqLiteral]) diff --git a/compiler/src/dotty/tools/dotc/transform/Splitter.scala b/compiler/src/dotty/tools/dotc/transform/Splitter.scala index 88f5f986632a..f8093d0f8cf5 100644 --- a/compiler/src/dotty/tools/dotc/transform/Splitter.scala +++ b/compiler/src/dotty/tools/dotc/transform/Splitter.scala @@ -6,12 +6,16 @@ import ast.Trees._ import core._ import Contexts._, Types._, Decorators._, Denotations._, Symbols._, SymDenotations._, Names._ +object Splitter { + val name = "splitter" +} + /** Distribute applications into Block and If nodes */ class Splitter extends MiniPhase { import ast.tpd._ - override def phaseName: String = "splitter" + override def phaseName: String = Splitter.name /** Distribute arguments among splitted branches */ def distribute(tree: GenericApply[Type], rebuild: (Tree, List[Tree]) => Context => Tree)(implicit ctx: Context) = { diff --git a/compiler/src/dotty/tools/dotc/transform/TailRec.scala b/compiler/src/dotty/tools/dotc/transform/TailRec.scala index c5631caf3536..61d4b232ba3b 100644 --- a/compiler/src/dotty/tools/dotc/transform/TailRec.scala +++ b/compiler/src/dotty/tools/dotc/transform/TailRec.scala @@ -68,7 +68,7 @@ class TailRec extends MiniPhase with FullParameterization { import dotty.tools.dotc.ast.tpd._ - override def phaseName: String = "tailrec" + override def phaseName: String = TailRec.name final val labelFlags = Flags.Synthetic | Flags.Label @@ -380,6 +380,7 @@ class TailRec extends MiniPhase with FullParameterization { } object TailRec { + val name = "tailrec" final class TailContext(val tailPos: Boolean) extends AnyVal diff --git a/compiler/src/dotty/tools/dotc/transform/TryCatchPatterns.scala b/compiler/src/dotty/tools/dotc/transform/TryCatchPatterns.scala index 59be424e746c..530b1a39a92c 100644 --- a/compiler/src/dotty/tools/dotc/transform/TryCatchPatterns.scala +++ b/compiler/src/dotty/tools/dotc/transform/TryCatchPatterns.scala @@ -45,7 +45,7 @@ class TryCatchPatterns extends MiniPhase { def phaseName: String = "tryCatchPatterns" - override def runsAfter = Set(classOf[ElimRepeated]) + override def runsAfter = Set(ElimRepeated.name) override def checkPostCondition(tree: Tree)(implicit ctx: Context): Unit = tree match { case Try(_, cases, _) => diff --git a/compiler/src/dotty/tools/dotc/transform/UnusedDecls.scala b/compiler/src/dotty/tools/dotc/transform/UnusedDecls.scala index 0cc5060e3125..77422f2deb18 100644 --- a/compiler/src/dotty/tools/dotc/transform/UnusedDecls.scala +++ b/compiler/src/dotty/tools/dotc/transform/UnusedDecls.scala @@ -18,8 +18,8 @@ class ErasedDecls extends MiniPhase with InfoTransformer { override def phaseName: String = "erasedDecls" - override def runsAfterGroupsOf: Set[Class[_ <: Phase]] = Set( - classOf[PatternMatcher] // Make sure pattern match errors are emitted + override def runsAfterGroupsOf = Set( + PatternMatcher.name // Make sure pattern match errors are emitted ) /** Check what the phase achieves, to be called at any point after it is finished. */ diff --git a/compiler/src/dotty/tools/dotc/transform/VCElideAllocations.scala b/compiler/src/dotty/tools/dotc/transform/VCElideAllocations.scala index 91076cbc3cc3..898fc225a405 100644 --- a/compiler/src/dotty/tools/dotc/transform/VCElideAllocations.scala +++ b/compiler/src/dotty/tools/dotc/transform/VCElideAllocations.scala @@ -20,7 +20,7 @@ class VCElideAllocations extends MiniPhase with IdentityDenotTransformer { override def phaseName: String = "vcElideAllocations" - override def runsAfter: Set[Class[_ <: Phase]] = Set(classOf[ElimErasedValueType]) + override def runsAfter = Set(ElimErasedValueType.name) override def transformApply(tree: Apply)(implicit ctx: Context): Tree = tree match { diff --git a/compiler/src/dotty/tools/dotc/transform/VCInlineMethods.scala b/compiler/src/dotty/tools/dotc/transform/VCInlineMethods.scala index 4d4e7eedeae3..2a526ebe3ae3 100644 --- a/compiler/src/dotty/tools/dotc/transform/VCInlineMethods.scala +++ b/compiler/src/dotty/tools/dotc/transform/VCInlineMethods.scala @@ -44,8 +44,8 @@ class VCInlineMethods extends MiniPhase with IdentityDenotTransformer { override def phaseName: String = "vcInlineMethods" - override def runsAfter: Set[Class[_ <: Phase]] = - Set(classOf[ExtensionMethods], classOf[PatternMatcher]) + override def runsAfter = + Set(ExtensionMethods.name, PatternMatcher.name) /** Replace a value class method call by a call to the corresponding extension method. * diff --git a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala index 2d0ae6dd29f8..4a4e8339ffd6 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala @@ -913,7 +913,7 @@ class RefChecks extends MiniPhase { thisPhase => override def phaseName: String = "refchecks" // Needs to run after ElimRepeated for override checks involving varargs methods - override def runsAfter = Set(classOf[ElimRepeated]) + override def runsAfter = Set(ElimRepeated.name) private var LevelInfo: Store.Location[OptLevelInfo] = _ private def currentLevel(implicit ctx: Context): OptLevelInfo = ctx.store(LevelInfo) diff --git a/compiler/test/dotty/tools/dotc/plugins/PluginsTest.scala b/compiler/test/dotty/tools/dotc/plugins/PluginsTest.scala index f12904a6c3a9..5438d998dac1 100644 --- a/compiler/test/dotty/tools/dotc/plugins/PluginsTest.scala +++ b/compiler/test/dotty/tools/dotc/plugins/PluginsTest.scala @@ -37,7 +37,7 @@ class PluginsTest { List(new P8) ) - def classOfPhase(p: PluginPhase): Class[_ <: PluginPhase] = p.getClass.asInstanceOf[Class[_ <: PluginPhase]] + implicit def clazzToName(cls: Class[_]): String = cls.getName def debugPlan(plan: List[List[Phase]]): Unit = { println(plan.mkString("plan:\n- ", "\n- ", "")) @@ -94,7 +94,7 @@ class PluginsTest { def orderingTwoPlugins1 = { object M1 extends TestPhase { override val runsAfter = Set(classOf[P3d]) - override val runsBefore = Set(classOfPhase(M2), classOf[P7], classOf[P8]) + override val runsBefore = Set(M2.phaseName, classOf[P7], classOf[P8]) } object M2 extends TestPhase { override val runsAfter = Set(classOf[P3d]) @@ -115,7 +115,7 @@ class PluginsTest { @Test def orderingTwoPlugins2 = { object M1 extends TestPhase { - override val runsAfter = Set(classOf[P3d], classOfPhase(M2)) + override val runsAfter = Set(classOf[P3d], M2.phaseName) } object M2 extends TestPhase { override val runsAfter = Set(classOf[P3d]) @@ -136,7 +136,7 @@ class PluginsTest { @Test def orderingTwoPlugins3 = { object M1 extends TestPhase { - override val runsAfter = Set(classOf[P3d], classOfPhase(M2)) + override val runsAfter = Set(classOf[P3d], M2.phaseName) override val runsBefore = Set(classOf[P7], classOf[P8]) } object M2 extends TestPhase { @@ -159,7 +159,7 @@ class PluginsTest { def orderingTwoPlugins4 = { object M1 extends TestPhase { override val runsAfter = Set(classOf[P3d]) - override val runsBefore = Set(classOfPhase(M2), classOf[P7]) + override val runsBefore = Set(M2.phaseName, classOf[P7]) } object M2 extends TestPhase { override val runsAfter = Set(classOf[P3d]) @@ -181,14 +181,14 @@ class PluginsTest { def orderingTransitive = { object M1 extends TestPhase { override val runsAfter = Set(classOf[P3d]) - override val runsBefore = Set(classOfPhase(M2), classOf[P7]) + override val runsBefore = Set(M2.phaseName, classOf[P7]) } object M2 extends TestPhase { override val runsAfter = Set(classOf[P3d]) override val runsBefore = Set(classOf[P5], classOf[P8]) } object M3 extends TestPhase { - override val runsAfter = Set(classOfPhase(M2), classOf[P2]) + override val runsAfter = Set(M2.phaseName, classOf[P2]) override val runsBefore = Set(classOf[P4], classOf[P8]) } diff --git a/sbt-dotty/sbt-test/sbt-dotty/compiler-plugin/plugin/DivideZero.scala b/sbt-dotty/sbt-test/sbt-dotty/compiler-plugin/plugin/DivideZero.scala index f91b5ddc285e..6b2c421ae584 100644 --- a/sbt-dotty/sbt-test/sbt-dotty/compiler-plugin/plugin/DivideZero.scala +++ b/sbt-dotty/sbt-test/sbt-dotty/compiler-plugin/plugin/DivideZero.scala @@ -18,8 +18,8 @@ class DivideZero extends PluginPhase with StandardPlugin { val phaseName = name - override val runsAfter = Set(classOf[Pickler]) - override val runsBefore = Set(classOf[LinkAll]) + override val runsAfter = Set(Pickler.phaseName) + override val runsBefore = Set(LinkAll.phaseName) override def init(options: List[String]): List[PluginPhase] = this :: Nil diff --git a/tests/plugins/neg/divideZero/plugin_1.scala b/tests/plugins/neg/divideZero/plugin_1.scala index 7a3344bbbe50..04e0bd9941e9 100644 --- a/tests/plugins/neg/divideZero/plugin_1.scala +++ b/tests/plugins/neg/divideZero/plugin_1.scala @@ -17,8 +17,8 @@ class DivideZero extends PluginPhase with StandardPlugin { val phaseName = name - override val runsAfter = Set(classOf[Pickler]) - override val runsBefore = Set(classOf[LinkAll]) + override val runsAfter = Set(Pickler.name) + override val runsBefore = Set(LinkAll.name) override def init(options: List[String]): List[PluginPhase] = this :: Nil From d2a63fb50972f647c63fc5a20eba471be60033cf Mon Sep 17 00:00:00 2001 From: liu fengyun Date: Tue, 20 Mar 2018 17:22:07 +0100 Subject: [PATCH 24/27] add plugin authors [CI SKIP] --- AUTHORS.md | 4 ++++ .../sbt-dotty/compiler-plugin/plugin/DivideZero.scala | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/AUTHORS.md b/AUTHORS.md index 76a1d8e89acb..4b3cc7780434 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -71,3 +71,7 @@ The majority of the dotty codebase is new code, with the exception of the compon > the [compiler bridge in sbt 0.13](https://github.com/sbt/sbt/tree/0.13/compile/interface/src/main/scala/xsbt), > but has been heavily adapted and refactored. > Original authors were Mark Harrah, Grzegorz Kossakowski, Martin Duhemm, Adriaan Moors and others. + +`dotty.tools.dotc.plugins` + +> Adapted from [scala/scala](https://github.com/scala/scala) with some modifications. They were originally authored by Lex Spoon, Som Snytt, Adriaan Moors, Paul Phillips and others. diff --git a/sbt-dotty/sbt-test/sbt-dotty/compiler-plugin/plugin/DivideZero.scala b/sbt-dotty/sbt-test/sbt-dotty/compiler-plugin/plugin/DivideZero.scala index 6b2c421ae584..1dfe23cf5152 100644 --- a/sbt-dotty/sbt-test/sbt-dotty/compiler-plugin/plugin/DivideZero.scala +++ b/sbt-dotty/sbt-test/sbt-dotty/compiler-plugin/plugin/DivideZero.scala @@ -27,7 +27,7 @@ class DivideZero extends PluginPhase with StandardPlugin { def test(tpe: String): Boolean = (sym.owner eq ctx.requiredClass(tpe.toTermName)) && sym.name.show == "/" - test("scala.Int") || test("scala.Long") || test("scala.Short") || test("scala.FLoat") || test("scala.Double") + test("scala.Int") || test("scala.Long") || test("scala.Short") || test("scala.Float") || test("scala.Double") } override def transformApply(tree: tpd.Apply)(implicit ctx: Context): tpd.Tree = tree match { From 32e6faa856c9c79305f190365928103796ed5149 Mon Sep 17 00:00:00 2001 From: liu fengyun Date: Tue, 20 Mar 2018 18:34:06 +0100 Subject: [PATCH 25/27] remove xml file --- .../src/dotty/tools/dotc/plugins/Plugin.scala | 58 ++++++++++++------- .../dotc/plugins/PluginDescription.scala | 48 --------------- .../dotty/tools/dotc/plugins/Plugins.scala | 6 +- .../dotty/tools/dotc/CompilationTests.scala | 6 +- .../plugin/src/main/resources/plugin | 1 + .../src/main/resources/scalac-plugin.xml | 4 -- tests/plugins/neg/divideZero-research/plugin | 1 + .../neg/divideZero-research/scalac-plugin.xml | 4 -- tests/plugins/neg/divideZero/plugin | 1 + .../plugins/neg/divideZero/scalac-plugin.xml | 4 -- 10 files changed, 43 insertions(+), 90 deletions(-) delete mode 100644 compiler/src/dotty/tools/dotc/plugins/PluginDescription.scala create mode 100644 sbt-dotty/sbt-test/sbt-dotty/compiler-plugin/plugin/src/main/resources/plugin delete mode 100644 sbt-dotty/sbt-test/sbt-dotty/compiler-plugin/plugin/src/main/resources/scalac-plugin.xml create mode 100644 tests/plugins/neg/divideZero-research/plugin delete mode 100644 tests/plugins/neg/divideZero-research/scalac-plugin.xml create mode 100644 tests/plugins/neg/divideZero/plugin delete mode 100644 tests/plugins/neg/divideZero/scalac-plugin.xml diff --git a/compiler/src/dotty/tools/dotc/plugins/Plugin.scala b/compiler/src/dotty/tools/dotc/plugins/Plugin.scala index 5b6c059599c9..2b62c1b967ec 100644 --- a/compiler/src/dotty/tools/dotc/plugins/Plugin.scala +++ b/compiler/src/dotty/tools/dotc/plugins/Plugin.scala @@ -8,6 +8,7 @@ import dotty.tools.io._ import transform.MegaPhase.MiniPhase import java.io.InputStream +import java.util.Scanner import scala.collection.mutable import scala.util.{ Try, Success, Failure } @@ -58,7 +59,7 @@ trait ResearchPlugin extends Plugin { object Plugin { - private val PluginXML = "scalac-plugin.xml" + private val PluginFile = "plugin" /** Create a class loader with the specified locations plus * the loader that loaded the Scala compiler. @@ -96,27 +97,34 @@ object Plugin { def loadAllFrom( paths: List[List[Path]], dirs: List[Path], - ignoring: List[String]): List[Try[AnyClass]] = + ignoring: List[String]): List[Try[Plugin]] = { - def loadDescriptionFromDir(f: Path): Try[PluginDescription] = - Try(PluginDescription.fromXML(new java.io.FileInputStream((f / PluginXML).jpath.toFile))) + def fromFile(inputStream: InputStream): String = { + val s = new Scanner(inputStream) - def loadDescriptionFromJar(jarp: Path): Try[PluginDescription] = { + if (s.hasNext) s.nextLine.trim + else throw new RuntimeException("Bad plugin descriptor.") + } + + def loadDescriptionFromDir(f: Path): Try[String] = + Try(fromFile(new java.io.FileInputStream((f / PluginFile).jpath.toFile))) + + def loadDescriptionFromJar(jarp: Path): Try[String] = { // XXX Return to this once we have more ARM support def read(is: InputStream) = - if (is == null) throw new PluginLoadException(jarp.path, s"Missing $PluginXML in $jarp") - else PluginDescription.fromXML(is) + if (is == null) throw new PluginLoadException(jarp.path, s"Missing $PluginFile in $jarp") + else fromFile(is) - val xmlEntry = new java.util.jar.JarEntry(PluginXML) - Try(read(new Jar(jarp.jpath.toFile).getEntryStream(xmlEntry))) + val fileEntry = new java.util.jar.JarEntry(PluginFile) + Try(read(new Jar(jarp.jpath.toFile).getEntryStream(fileEntry))) } // List[(jar, Try(descriptor))] in dir def scan(d: Directory) = d.files.toList sortBy (_.name) filter (Jar isJarOrZip _) map (j => (j, loadDescriptionFromJar(j))) - type PDResults = List[Try[(PluginDescription, ClassLoader)]] + type PDResults = List[Try[(String, ClassLoader)]] // scan plugin dirs for jars containing plugins, ignoring dirs with none and other jars val fromDirs: PDResults = dirs filter (_.isDirectory) flatMap { d => @@ -128,7 +136,7 @@ object Plugin { // scan jar paths for plugins, taking the first plugin you find. // a path element can be either a plugin.jar or an exploded dir. def findDescriptor(ps: List[Path]) = { - def loop(qs: List[Path]): Try[PluginDescription] = qs match { + def loop(qs: List[Path]): Try[String] = qs match { case Nil => Failure(new MissingPluginException(ps)) case p :: rest => if (p.isDirectory) loadDescriptionFromDir(p.toDirectory) orElse loop(rest) @@ -137,21 +145,27 @@ object Plugin { } loop(ps) } - val fromPaths: PDResults = paths map (p => (p, findDescriptor(p))) map { - case (p, Success(pd)) => Success((pd, loaderFor(p))) - case (_, Failure(e)) => Failure(e) - } + + val fromPaths: PDResults = paths map (p => findDescriptor(p) match { + case Success(classname) => Success((classname, loaderFor(p))) + case Failure(e) => Failure(e) + }) val seen = mutable.HashSet[String]() val enabled = (fromPaths ::: fromDirs) map(_.flatMap { - case (pd, loader) if seen(pd.classname) => + case (classname, loader) => // a nod to scala/bug#7494, take the plugin classes distinctly - Failure(new PluginLoadException(pd.name, s"Ignoring duplicate plugin ${pd.name} (${pd.classname})")) - case (pd, loader) if ignoring contains pd.name => - Failure(new PluginLoadException(pd.name, s"Disabling plugin ${pd.name}")) - case (pd, loader) => - seen += pd.classname - Plugin.load(pd.classname, loader) + Plugin.load(classname, loader).flatMap { clazz => + val plugin = instantiate(clazz) + if (seen(classname)) + Failure(new PluginLoadException(plugin.name, s"Ignoring duplicate plugin ${plugin.name} (${classname})")) + else if (ignoring contains plugin.name) + Failure(new PluginLoadException(plugin.name, s"Disabling plugin ${plugin.name}")) + else { + seen += classname + Success(plugin) + } + } }) enabled // distinct and not disabled } diff --git a/compiler/src/dotty/tools/dotc/plugins/PluginDescription.scala b/compiler/src/dotty/tools/dotc/plugins/PluginDescription.scala deleted file mode 100644 index 10c83985d585..000000000000 --- a/compiler/src/dotty/tools/dotc/plugins/PluginDescription.scala +++ /dev/null @@ -1,48 +0,0 @@ -package dotty.tools.dotc -package plugins - -/** A description of a compiler plugin, suitable for serialization - * to XML for inclusion in the plugin's .jar file. - * - * @author Lex Spoon - * @version 1.0, 2007-5-21 - * @author Adriaan Moors - * @version 2.0, 2013 - * @param name A short name of the plugin, used to identify it in - * various contexts. The phase defined by the plugin - * should have the same name. - * @param classname The name of the main Plugin class. - */ -case class PluginDescription(name: String, classname: String) { - /** An XML representation of this description. - * It should be stored inside the jar archive file. - */ - def toXML: String = - s"""| - | ${name} - | ${classname} - |""".stripMargin -} - -/** Utilities for the PluginDescription class. - * - * @author Lex Spoon - * @version 1.0, 2007-5-21 - * @author Adriaan Moors - * @version 2.0, 2013 - */ -object PluginDescription { - private def text(ns: org.w3c.dom.NodeList): String = - if (ns.getLength == 1) ns.item(0).getTextContent.trim - else throw new RuntimeException("Bad plugin descriptor.") - - def fromXML(xml: java.io.InputStream): PluginDescription = { - import javax.xml.parsers.DocumentBuilderFactory - val root = DocumentBuilderFactory.newInstance.newDocumentBuilder.parse(xml).getDocumentElement - root.normalize() - if (root.getNodeName != "plugin") - throw new RuntimeException("Plugin descriptor root element must be .") - - PluginDescription(text(root.getElementsByTagName("name")), text(root.getElementsByTagName("classname"))) - } -} diff --git a/compiler/src/dotty/tools/dotc/plugins/Plugins.scala b/compiler/src/dotty/tools/dotc/plugins/Plugins.scala index f62ac22e6481..1f32b793ea11 100644 --- a/compiler/src/dotty/tools/dotc/plugins/Plugins.scala +++ b/compiler/src/dotty/tools/dotc/plugins/Plugins.scala @@ -39,12 +39,8 @@ trait Plugins { case e: MissingPluginException => warning(e.getMessage) case e: Exception => inform(e.getMessage) }) - val classes = goods map (_.get) // flatten - // Each plugin must only be instantiated once. A common pattern - // is to register annotation checkers during object construction, so - // creating multiple plugin instances will leave behind stale checkers. - classes map (Plugin.instantiate(_)) + goods map (_.get) } private var _roughPluginsList: List[Plugin] = _ diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index 1e280a37215f..065964d90081 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -314,10 +314,10 @@ class CompilationTests extends ParallelTesting { } @Test def testPlugins: Unit = { - val xmlFile = "scalac-plugin.xml" + val pluginFile = "plugin" // 1. hack with absolute path for -Xplugin - // 2. copy `xmlFile` to destination + // 2. copy `pluginFile` to destination def compileFilesInDir(dir: String): CompilationTest = { val outDir = defaultOutputDir + "testPlugins/" val sourceDir = new JFile(dir) @@ -329,7 +329,7 @@ class CompilationTests extends ParallelTesting { val targets = dirs.map { dir => val compileDir = createOutputDirsForDir(dir, sourceDir, outDir) import java.nio.file.StandardCopyOption.REPLACE_EXISTING - Files.copy(dir.toPath.resolve(xmlFile), compileDir.toPath.resolve(xmlFile), REPLACE_EXISTING) + Files.copy(dir.toPath.resolve(pluginFile), compileDir.toPath.resolve(pluginFile), REPLACE_EXISTING) val flags = TestFlags(classPath, noCheckOptions) and ("-Xplugin:" + compileDir.getAbsolutePath) SeparateCompilationSource("testPlugins", dir, flags, compileDir) } diff --git a/sbt-dotty/sbt-test/sbt-dotty/compiler-plugin/plugin/src/main/resources/plugin b/sbt-dotty/sbt-test/sbt-dotty/compiler-plugin/plugin/src/main/resources/plugin new file mode 100644 index 000000000000..2dc979a0b4c7 --- /dev/null +++ b/sbt-dotty/sbt-test/sbt-dotty/compiler-plugin/plugin/src/main/resources/plugin @@ -0,0 +1 @@ +dividezero.DivideZero \ No newline at end of file diff --git a/sbt-dotty/sbt-test/sbt-dotty/compiler-plugin/plugin/src/main/resources/scalac-plugin.xml b/sbt-dotty/sbt-test/sbt-dotty/compiler-plugin/plugin/src/main/resources/scalac-plugin.xml deleted file mode 100644 index 56d664377389..000000000000 --- a/sbt-dotty/sbt-test/sbt-dotty/compiler-plugin/plugin/src/main/resources/scalac-plugin.xml +++ /dev/null @@ -1,4 +0,0 @@ - - divideZero - dividezero.DivideZero - diff --git a/tests/plugins/neg/divideZero-research/plugin b/tests/plugins/neg/divideZero-research/plugin new file mode 100644 index 000000000000..2fd9e8c2ba84 --- /dev/null +++ b/tests/plugins/neg/divideZero-research/plugin @@ -0,0 +1 @@ +DivideZero \ No newline at end of file diff --git a/tests/plugins/neg/divideZero-research/scalac-plugin.xml b/tests/plugins/neg/divideZero-research/scalac-plugin.xml deleted file mode 100644 index 67ac36ab0562..000000000000 --- a/tests/plugins/neg/divideZero-research/scalac-plugin.xml +++ /dev/null @@ -1,4 +0,0 @@ - - divideZero - DivideZero - diff --git a/tests/plugins/neg/divideZero/plugin b/tests/plugins/neg/divideZero/plugin new file mode 100644 index 000000000000..b7f582617b85 --- /dev/null +++ b/tests/plugins/neg/divideZero/plugin @@ -0,0 +1 @@ +DivideZero diff --git a/tests/plugins/neg/divideZero/scalac-plugin.xml b/tests/plugins/neg/divideZero/scalac-plugin.xml deleted file mode 100644 index 67ac36ab0562..000000000000 --- a/tests/plugins/neg/divideZero/scalac-plugin.xml +++ /dev/null @@ -1,4 +0,0 @@ - - divideZero - DivideZero - From 0e253ef2256c9d57cc82d16b0fb730fdf735c512 Mon Sep 17 00:00:00 2001 From: liu fengyun Date: Thu, 22 Mar 2018 18:03:16 +0100 Subject: [PATCH 26/27] use plugin.properties --- compiler/src/dotty/tools/dotc/plugins/Plugin.scala | 13 ++++++++----- .../test/dotty/tools/dotc/CompilationTests.scala | 2 +- .../plugin/src/main/resources/plugin | 1 - .../plugin/src/main/resources/plugin.properties | 1 + tests/plugins/neg/divideZero-research/plugin | 1 - .../neg/divideZero-research/plugin.properties | 1 + tests/plugins/neg/divideZero/plugin | 1 - tests/plugins/neg/divideZero/plugin.properties | 1 + 8 files changed, 12 insertions(+), 9 deletions(-) delete mode 100644 sbt-dotty/sbt-test/sbt-dotty/compiler-plugin/plugin/src/main/resources/plugin create mode 100644 sbt-dotty/sbt-test/sbt-dotty/compiler-plugin/plugin/src/main/resources/plugin.properties delete mode 100644 tests/plugins/neg/divideZero-research/plugin create mode 100644 tests/plugins/neg/divideZero-research/plugin.properties delete mode 100644 tests/plugins/neg/divideZero/plugin create mode 100644 tests/plugins/neg/divideZero/plugin.properties diff --git a/compiler/src/dotty/tools/dotc/plugins/Plugin.scala b/compiler/src/dotty/tools/dotc/plugins/Plugin.scala index 2b62c1b967ec..4d765c1e3d83 100644 --- a/compiler/src/dotty/tools/dotc/plugins/Plugin.scala +++ b/compiler/src/dotty/tools/dotc/plugins/Plugin.scala @@ -8,7 +8,7 @@ import dotty.tools.io._ import transform.MegaPhase.MiniPhase import java.io.InputStream -import java.util.Scanner +import java.util.Properties import scala.collection.mutable import scala.util.{ Try, Success, Failure } @@ -59,7 +59,7 @@ trait ResearchPlugin extends Plugin { object Plugin { - private val PluginFile = "plugin" + private val PluginFile = "plugin.properties" /** Create a class loader with the specified locations plus * the loader that loaded the Scala compiler. @@ -101,10 +101,13 @@ object Plugin { { def fromFile(inputStream: InputStream): String = { - val s = new Scanner(inputStream) + val props = new Properties + props.load(inputStream) - if (s.hasNext) s.nextLine.trim - else throw new RuntimeException("Bad plugin descriptor.") + val pluginClass = props.getProperty("pluginClass") + + if (pluginClass == null) throw new RuntimeException("Bad plugin descriptor.") + else pluginClass } def loadDescriptionFromDir(f: Path): Try[String] = diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index 065964d90081..ec6ba0fdb12a 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -314,7 +314,7 @@ class CompilationTests extends ParallelTesting { } @Test def testPlugins: Unit = { - val pluginFile = "plugin" + val pluginFile = "plugin.properties" // 1. hack with absolute path for -Xplugin // 2. copy `pluginFile` to destination diff --git a/sbt-dotty/sbt-test/sbt-dotty/compiler-plugin/plugin/src/main/resources/plugin b/sbt-dotty/sbt-test/sbt-dotty/compiler-plugin/plugin/src/main/resources/plugin deleted file mode 100644 index 2dc979a0b4c7..000000000000 --- a/sbt-dotty/sbt-test/sbt-dotty/compiler-plugin/plugin/src/main/resources/plugin +++ /dev/null @@ -1 +0,0 @@ -dividezero.DivideZero \ No newline at end of file diff --git a/sbt-dotty/sbt-test/sbt-dotty/compiler-plugin/plugin/src/main/resources/plugin.properties b/sbt-dotty/sbt-test/sbt-dotty/compiler-plugin/plugin/src/main/resources/plugin.properties new file mode 100644 index 000000000000..db215842cecc --- /dev/null +++ b/sbt-dotty/sbt-test/sbt-dotty/compiler-plugin/plugin/src/main/resources/plugin.properties @@ -0,0 +1 @@ +pluginClass=dividezero.DivideZero \ No newline at end of file diff --git a/tests/plugins/neg/divideZero-research/plugin b/tests/plugins/neg/divideZero-research/plugin deleted file mode 100644 index 2fd9e8c2ba84..000000000000 --- a/tests/plugins/neg/divideZero-research/plugin +++ /dev/null @@ -1 +0,0 @@ -DivideZero \ No newline at end of file diff --git a/tests/plugins/neg/divideZero-research/plugin.properties b/tests/plugins/neg/divideZero-research/plugin.properties new file mode 100644 index 000000000000..b50c037c53ac --- /dev/null +++ b/tests/plugins/neg/divideZero-research/plugin.properties @@ -0,0 +1 @@ +pluginClass=DivideZero \ No newline at end of file diff --git a/tests/plugins/neg/divideZero/plugin b/tests/plugins/neg/divideZero/plugin deleted file mode 100644 index b7f582617b85..000000000000 --- a/tests/plugins/neg/divideZero/plugin +++ /dev/null @@ -1 +0,0 @@ -DivideZero diff --git a/tests/plugins/neg/divideZero/plugin.properties b/tests/plugins/neg/divideZero/plugin.properties new file mode 100644 index 000000000000..dd7ab35bd1c1 --- /dev/null +++ b/tests/plugins/neg/divideZero/plugin.properties @@ -0,0 +1 @@ +pluginClass=DivideZero From f1a6923278c7852465fd51a5f18765d587b6c768 Mon Sep 17 00:00:00 2001 From: liu fengyun Date: Fri, 23 Mar 2018 09:29:43 +0100 Subject: [PATCH 27/27] small tweak --- .../src/dotty/tools/dotc/plugins/Plugin.scala | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/plugins/Plugin.scala b/compiler/src/dotty/tools/dotc/plugins/Plugin.scala index 4d765c1e3d83..f5ce30e2a4e2 100644 --- a/compiler/src/dotty/tools/dotc/plugins/Plugin.scala +++ b/compiler/src/dotty/tools/dotc/plugins/Plugin.scala @@ -100,24 +100,26 @@ object Plugin { ignoring: List[String]): List[Try[Plugin]] = { - def fromFile(inputStream: InputStream): String = { + def fromFile(inputStream: InputStream, path: Path): String = { val props = new Properties props.load(inputStream) val pluginClass = props.getProperty("pluginClass") - if (pluginClass == null) throw new RuntimeException("Bad plugin descriptor.") + if (pluginClass == null) throw new RuntimeException("Bad plugin descriptor: " + path) else pluginClass } - def loadDescriptionFromDir(f: Path): Try[String] = - Try(fromFile(new java.io.FileInputStream((f / PluginFile).jpath.toFile))) + def loadDescriptionFromDir(f: Path): Try[String] = { + val path = f / PluginFile + Try(fromFile(new java.io.FileInputStream(path.jpath.toFile), path)) + } def loadDescriptionFromJar(jarp: Path): Try[String] = { // XXX Return to this once we have more ARM support def read(is: InputStream) = if (is == null) throw new PluginLoadException(jarp.path, s"Missing $PluginFile in $jarp") - else fromFile(is) + else fromFile(is, jarp) val fileEntry = new java.util.jar.JarEntry(PluginFile) Try(read(new Jar(jarp.jpath.toFile).getEntryStream(fileEntry))) @@ -157,10 +159,9 @@ object Plugin { val seen = mutable.HashSet[String]() val enabled = (fromPaths ::: fromDirs) map(_.flatMap { case (classname, loader) => - // a nod to scala/bug#7494, take the plugin classes distinctly Plugin.load(classname, loader).flatMap { clazz => val plugin = instantiate(clazz) - if (seen(classname)) + if (seen(classname)) // a nod to scala/bug#7494, take the plugin classes distinctly Failure(new PluginLoadException(plugin.name, s"Ignoring duplicate plugin ${plugin.name} (${classname})")) else if (ignoring contains plugin.name) Failure(new PluginLoadException(plugin.name, s"Disabling plugin ${plugin.name}"))