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/compiler/src/dotty/tools/dotc/Run.scala b/compiler/src/dotty/tools/dotc/Run.scala index b6c401f55f22..bcab1c50db65 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.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/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/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/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/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 new file mode 100644 index 000000000000..f5ce30e2a4e2 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/plugins/Plugin.scala @@ -0,0 +1,189 @@ +package dotty.tools.dotc +package plugins + +import core._ +import Contexts._ +import Phases._ +import dotty.tools.io._ +import transform.MegaPhase.MiniPhase + +import java.io.InputStream +import java.util.Properties + +import scala.collection.mutable +import scala.util.{ Try, Success, Failure } + +trait PluginPhase extends MiniPhase { + def runsBefore: Set[String] = Set.empty +} + +sealed trait Plugin { + /** The name of this plugin */ + def name: String + + /** A one-line description of the plugin */ + def description: String + + /** 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 = 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] +} + +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]] +} + +object Plugin { + + private val PluginFile = "plugin.properties" + + /** 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) + } + + 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: ${e.getMessage}")) + 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[Plugin]] = + { + + 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: " + path) + else pluginClass + } + + 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, jarp) + + 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[(String, 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[String] = qs match { + case Nil => Failure(new MissingPluginException(ps)) + case p :: rest => + if (p.isDirectory) loadDescriptionFromDir(p.toDirectory) orElse loop(rest) + else if (p.isFile) loadDescriptionFromJar(p.toFile) orElse loop(rest) + else loop(rest) + } + loop(ps) + } + + 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 (classname, loader) => + Plugin.load(classname, loader).flatMap { clazz => + val plugin = instantiate(clazz) + 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}")) + else { + seen += classname + Success(plugin) + } + } + }) + 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/Plugins.scala b/compiler/src/dotty/tools/dotc/plugins/Plugins.scala new file mode 100644 index 000000000000..1f32b793ea11 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/plugins/Plugins.scala @@ -0,0 +1,272 @@ +package dotty.tools.dotc +package plugins + +import core._ +import Contexts._ +import config.PathResolver +import dotty.tools.io._ +import Phases._ +import config.Printers.plugins.{ println => debug } + +import scala.collection.mutable.ListBuffer + +/** 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) + }) + + goods map (_.get) + } + + 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, ctx.phasePlan.flatten.map(_.phaseName).toSet) + + // 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 + + /** 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 pluginPhases = plugins.collect { case p: StandardPlugin => p }.flatMap { plug => plug.init(options(plug)) } + val updatedPlan = Plugins.schedule(plan, pluginPhases) + + // add research plugins + plugins.collect { case p: ResearchPlugin => p }.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]], pluginPhases: List[PluginPhase]): List[List[Phase]] = { + import scala.collection.mutable.{ Map => MMap } + type OrderingReq = (Set[String], Set[String]) + + 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 + // 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(_.phaseName)).toSet + def isInserted(phase: String): Boolean = insertedPhase.contains(phase) + + var updatedPlan = plan + + def constraintConflict(phase: Phase): String = { + val (runsAfter, runsBefore) = orderRequirements(phase.phaseName) + s""" + |Ordering conflict for phase ${phase.phaseName} + |after: ${runsAfter.mkString("[", ", ", "]")} + |before: ${runsBefore.mkString("[", ", ", "]")} + """.stripMargin + } + + // init ordering map, no propagation + pluginPhases.foreach { phase => + val runsAfter = phase.runsAfter + val runsBefore = phase.runsBefore + + orderRequirements.update(phase.phaseName, (runsAfter, runsBefore)) + } + + // propagate ordering constraint : reflexivity + pluginPhases.foreach { phase => + + var (runsAfter, runsBefore) = orderRequirements(phase.phaseName) + + // propagate transitive constraints to related phases + runsAfter.filter(!isInserted(_)).foreach { phaseName => + val (runsAfter1, runsBefore1) = orderRequirements(phaseName) + orderRequirements.update(phaseName, (runsAfter1, runsBefore1 + phase.phaseName)) + } + + runsBefore.filter(!isInserted(_)).foreach { phaseName => + val (runsAfter1, runsBefore1) = orderRequirements(phaseName) + orderRequirements.update(phaseName, (runsAfter1 + phase.phaseName, runsBefore1)) + } + + } + + debug( + s""" reflexive constraints: + | ${orderRequirements.mkString("\n")} + """.stripMargin + ) + + // propagate constraints from related phases to current phase: transitivity + def propagate(phase: Phase): OrderingReq = { + def propagateRunsBefore(beforePhase: String): Set[String] = + if (beforePhase == phase.phaseName) + throw new Exception(constraintConflict(phase)) + else if (isInserted(beforePhase)) + Set(beforePhase) + else { + val (_, runsBefore) = orderRequirements(beforePhase) + runsBefore.flatMap(propagateRunsBefore) + beforePhase + } + + def propagateRunsAfter(afterPhase: String): Set[String] = + if (afterPhase == phase.phaseName) + throw new Exception(constraintConflict(phase)) + else if (isInserted(afterPhase)) + Set(afterPhase) + else { + val (runsAfter, _) = orderRequirements(afterPhase) + runsAfter.flatMap(propagateRunsAfter) + afterPhase + } + + var (runsAfter, runsBefore) = orderRequirements(phase.phaseName) + + runsAfter = runsAfter.flatMap(propagateRunsAfter) + runsBefore = runsBefore.flatMap(propagateRunsBefore) + + (runsAfter, runsBefore) + } + + pluginPhases.sortBy(_.phaseName).foreach { phase => + 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 + + // runsBefore met after the split + val (before, after) = updatedPlan.span { ps => + val phases = ps.map(_.phaseName) + val runsAfterSat = runsAfter.isEmpty + runsAfter = runsAfter -- phases + // Prefer the point immediately before the first runsBefore. + // If runsBefore not specified, insert at the point immediately + // after the last afterPhases. + !phases.exists(runsBefore.contains) && + !(runsBefore.isEmpty && runsAfterSat) + } + + // check runsAfter + // error can occur if: a < b, b < c, c < a + after.foreach { ps => + val phases = ps.map(_.phaseName) + if (phases.exists(runsAfter)) // afterReq satisfied + throw new Exception(s"Ordering conflict for phase ${phase.phaseName}") + } + + insertedPhase = insertedPhase + phase.phaseName + updatedPlan = before ++ (List(phase) :: after) + } + + updatedPlan + } +} 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/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 } diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index 1c5efcd57de7..ec6ba0fdb12a 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -313,6 +313,33 @@ class CompilationTests extends ParallelTesting { compileFilesInDir("tests/neg", defaultOptimised).checkExpectedErrors() } + @Test def testPlugins: Unit = { + val pluginFile = "plugin.properties" + + // 1. hack with absolute path for -Xplugin + // 2. copy `pluginFile` 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(pluginFile), compileDir.toPath.resolve(pluginFile), REPLACE_EXISTING) + val flags = TestFlags(classPath, noCheckOptions) and ("-Xplugin:" + compileDir.getAbsolutePath) + SeparateCompilationSource("testPlugins", dir, flags, compileDir) + } + + new CompilationTest(targets) + } + + compileFilesInDir("tests/plugins/neg").checkExpectedErrors() + } + 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/plugins/PluginsTest.scala b/compiler/test/dotty/tools/dotc/plugins/PluginsTest.scala new file mode 100644 index 000000000000..5438d998dac1 --- /dev/null +++ b/compiler/test/dotty/tools/dotc/plugins/PluginsTest.scala @@ -0,0 +1,228 @@ +package dotty.tools.dotc.plugins + +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 } + 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 + + 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) + ) + + implicit def clazzToName(cls: Class[_]): String = cls.getName + + def debugPlan(plan: List[List[Phase]]): Unit = { + println(plan.mkString("plan:\n- ", "\n- ", "")) + } + + @Test + def insertAfter = { + object M1 extends TestPhase { + override val runsAfter = Set(classOf[P3d]) + } + + val updatedPlan = Plugins.schedule(basicPlan, M1 :: Nil) + assert(updatedPlan(3)(0) eq M1) + } + + @Test + def insertBefore = { + object ConstFold extends TestPhase { + override val runsBefore = Set(classOf[P7]) + } + + val updatedPlan = Plugins.schedule(basicPlan, ConstFold :: Nil) + assert(updatedPlan(6)(0) eq ConstFold) + } + + @Test + def insertBeforeAfter = { + 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) + assert(updatedPlan(6)(0) eq ConstFold) + } + + @Test + def constraintUnsatisfiable = { + object ConstFold extends TestPhase { + override val runsAfter = Set(classOf[P6d]) + override val runsBefore = Set(classOf[P2], classOf[P8]) + } + + try { + Plugins.schedule(basicPlan, ConstFold :: Nil) + assert(false, "unsatisfiable constraint should throw exception, but not") + } catch { + case _: Exception => + } + } + + @Test + def orderingTwoPlugins1 = { + object M1 extends TestPhase { + override val runsAfter = Set(classOf[P3d]) + override val runsBefore = Set(M2.phaseName, classOf[P7], classOf[P8]) + } + 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) + 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(6)(0) eq M1) + assert(updatedPlan2(7)(0) eq M2) + } + + @Test + def orderingTwoPlugins2 = { + object M1 extends TestPhase { + override val runsAfter = Set(classOf[P3d], M2.phaseName) + } + 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) + 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) + 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.phaseName) + 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(4)(0) eq M2) + } + + @Test + def orderingTwoPlugins4 = { + object M1 extends TestPhase { + override val runsAfter = Set(classOf[P3d]) + 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]) + } + + // M1 inserted to plan first + val updatedPlan1 = Plugins.schedule(basicPlan, M1 :: M2 :: Nil) + 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) + assert(updatedPlan2(4)(0) eq M1) + assert(updatedPlan2(5)(0) eq M2) + } + + @Test + def orderingTransitive = { + object M1 extends TestPhase { + override val runsAfter = Set(classOf[P3d]) + 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(M2.phaseName, 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 = { + 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) + } +} diff --git a/compiler/test/dotty/tools/vulpix/ParallelTesting.scala b/compiler/test/dotty/tools/vulpix/ParallelTesting.scala index 7eba42f4b582..39cf3ed6d61c 100644 --- a/compiler/test/dotty/tools/vulpix/ParallelTesting.scala +++ b/compiler/test/dotty/tools/vulpix/ParallelTesting.scala @@ -48,7 +48,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 @@ -145,7 +145,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, @@ -919,10 +919,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` @@ -954,6 +954,8 @@ trait ParallelTesting extends RunnerOrchestration { self => def checkCompile()(implicit summaryReport: SummaryReporting): this.type = { val test = new PosTest(targets, times, threadLimit, shouldFail || shouldSuppressOutput).executeTestSuite() + cleanup() + if (!shouldFail && test.didFail) { fail(s"Expected no errors when compiling, failed for the following reason(s):\n${ reasonsForFailure(test) }") } @@ -961,7 +963,7 @@ 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 @@ -971,6 +973,8 @@ trait ParallelTesting extends RunnerOrchestration { self => def checkExpectedErrors()(implicit summaryReport: SummaryReporting): this.type = { val test = new NegTest(targets, times, threadLimit, shouldFail || shouldSuppressOutput).executeTestSuite() + cleanup() + if (!shouldFail && test.didFail) { fail(s"Neg test shouldn't have failed, but did. Reasons:\n${ reasonsForFailure(test) }") } @@ -978,7 +982,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 +993,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 +1002,7 @@ trait ParallelTesting extends RunnerOrchestration { self => fail("Run test should have failed, but did not") } - cleanup() + this } /** Deletes output directories and files */ @@ -1099,7 +1105,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/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..1dfe23cf5152 --- /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._ +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 StandardPlugin { + val name: String = "divideZero" + override val description: String = "divide zero check" + + val phaseName = name + + override val runsAfter = Set(Pickler.phaseName) + override val runsBefore = Set(LinkAll.phaseName) + + 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/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/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..133a8f197e36 --- /dev/null +++ b/sbt-dotty/sbt-test/sbt-dotty/compiler-plugin/project/build.properties @@ -0,0 +1 @@ +sbt.version=0.13.17 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 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.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-research/plugin_1.scala b/tests/plugins/neg/divideZero-research/plugin_1.scala new file mode 100644 index 000000000000..f7f4efbda87b --- /dev/null +++ b/tests/plugins/neg/divideZero-research/plugin_1.scala @@ -0,0 +1,38 @@ +import dotty.tools.dotc._ +import core._ +import Contexts.Context +import plugins._ +import Phases.Phase +import ast.tpd +import transform.MegaPhase.MiniPhase +import Decorators._ +import Symbols.Symbol +import Constants.Constant +import StdNames._ + +class DivideZero extends MiniPhase with ResearchPlugin { + val name: String = "divideZero" + override val description: String = "divide zero check" + + val phaseName = name + + 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) + } + + private def isNumericDivide(sym: Symbol)(implicit ctx: Context): Boolean = { + def test(tpe: String): Boolean = + (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") + } + + 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/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.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 diff --git a/tests/plugins/neg/divideZero/plugin_1.scala b/tests/plugins/neg/divideZero/plugin_1.scala new file mode 100644 index 000000000000..04e0bd9941e9 --- /dev/null +++ b/tests/plugins/neg/divideZero/plugin_1.scala @@ -0,0 +1,39 @@ +import dotty.tools.dotc._ +import core._ +import Contexts.Context +import plugins._ +import Phases.Phase +import ast.tpd +import transform.MegaPhase.MiniPhase +import Decorators._ +import Symbols.Symbol +import Constants.Constant +import transform.{LinkAll, Pickler} +import StdNames._ + +class DivideZero extends PluginPhase with StandardPlugin { + val name: String = "divideZero" + override val description: String = "divide zero check" + + val phaseName = name + + override val runsAfter = Set(Pickler.name) + override val runsBefore = Set(LinkAll.name) + + 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 == nme.DIV + + 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/pos/divideZero/plugin_1.scala b/tests/plugins/pos/divideZero/plugin_1.scala new file mode 100644 index 000000000000..eca2e6fdf53b --- /dev/null +++ b/tests/plugins/pos/divideZero/plugin_1.scala @@ -0,0 +1,41 @@ +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 +import StdNames._ + + +class DivideZero extends MiniPhase with Plugin { + val name: String = "divideZero" + override val description: String = "divide by zero check" + + val phaseName = name + + override val research = true + + 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) + } + + private def isNumericDivide(sym: Symbol)(implicit ctx: Context): Boolean = { + def test(tpe: String): Boolean = + (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") + } + + 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 + } +}