diff --git a/compiler/src/dotty/tools/dotc/Run.scala b/compiler/src/dotty/tools/dotc/Run.scala index a9f0c677e17f..01d3cb3bc9c8 100644 --- a/compiler/src/dotty/tools/dotc/Run.scala +++ b/compiler/src/dotty/tools/dotc/Run.scala @@ -12,7 +12,7 @@ import typer.Typer import typer.ImportInfo.withRootImports import Decorators.* import io.AbstractFile -import Phases.{unfusedPhases, Phase} +import Phases.{assemblePhases, unfusedPhases, Phase} import sbt.interfaces.ProgressCallback @@ -295,19 +295,12 @@ extends ImplicitRunInfo, ConstraintRunInfo, cc.CaptureRunInfo { report.echo(this.enrichErrorMessage(s"exception occurred while compiling ${files1.map(_.path)}")) throw ex - /** TODO: There's a fundamental design problem here: We assemble phases using `fusePhases` - * when we first build the compiler. But we modify them with -Yskip, -Ystop - * on each run. That modification needs to either transform the tree structure, - * or we need to assemble phases on each run, and take -Yskip, -Ystop into - * account. I think the latter would be preferable. - */ def compileSources(sources: List[SourceFile]): Unit = if (sources forall (_.exists)) { units = sources.map(CompilationUnit(_)) compileUnits() } - def compileUnits(us: List[CompilationUnit]): Unit = { units = us compileUnits() @@ -333,19 +326,9 @@ extends ImplicitRunInfo, ConstraintRunInfo, cc.CaptureRunInfo { then ActiveProfile(ctx.settings.VprofileDetails.value.max(0).min(1000)) else NoProfile - // If testing pickler, make sure to stop after pickling phase: - val stopAfter = - if (ctx.settings.YtestPickler.value) List("pickler") - else ctx.settings.YstopAfter.value - - val runCtx = ctx.fresh + val runCtx = assemblePhases() runCtx.setProfiler(Profiler()) - val pluginPlan = ctx.base.addPluginPhases(ctx.base.phasePlan) - val phases = ctx.base.fusePhases(pluginPlan, - ctx.settings.Yskip.value, ctx.settings.YstopBefore.value, stopAfter, ctx.settings.Ycheck.value) - ctx.base.usePhases(phases, runCtx) - if ctx.settings.YnoDoubleBindings.value then ctx.base.checkNoDoubleBindings = true @@ -355,7 +338,7 @@ extends ImplicitRunInfo, ConstraintRunInfo, cc.CaptureRunInfo { var phasesWereAdjusted = false var forceReachPhaseMaybe = - if (ctx.isBestEffort && phases.exists(_.phaseName == "typer")) Some("typer") + if (ctx.isBestEffort && allPhases.exists(_.phaseName == "typer")) Some("typer") else None for phase <- allPhases do @@ -367,7 +350,7 @@ extends ImplicitRunInfo, ConstraintRunInfo, cc.CaptureRunInfo { profiler.onPhase(phase): try units = phase.runOn(units) catch case _: InterruptedException => cancelInterrupted() - if (ctx.settings.Vprint.value.containsPhase(phase)) + for printAt <- ctx.settings.Vprint.userValue if printAt.containsPhase(phase) do for (unit <- units) def printCtx(unit: CompilationUnit) = phase.printingContext( ctx.fresh.setPhase(phase.next).setCompilationUnit(unit)) diff --git a/compiler/src/dotty/tools/dotc/config/CliCommand.scala b/compiler/src/dotty/tools/dotc/config/CliCommand.scala index 9c1b0871c144..62c31bc4e97d 100644 --- a/compiler/src/dotty/tools/dotc/config/CliCommand.scala +++ b/compiler/src/dotty/tools/dotc/config/CliCommand.scala @@ -3,7 +3,9 @@ package config import Settings.* import core.Contexts.* +import core.Phases.assemblePhases import printing.Highlighting +import transform.MegaPhase import dotty.tools.dotc.util.chaining.* import scala.PartialFunction.cond @@ -115,9 +117,17 @@ trait CliCommand: /** Used for the formatted output of -Xshow-phases */ protected def phasesMessage(using Context): String = - val phases = new Compiler().phases + val compiler = new Compiler() + ctx.initialize() + ctx.base.setPhasePlan(compiler.phases) + val runCtx = assemblePhases() + val phases = runCtx.base.allPhases + val texts = phases.iterator.map { + case mp: MegaPhase => mp.miniPhases.iterator.map(p => (p.phaseName, p.description)).toList + case p => (p.phaseName, p.description) :: Nil + }.toList val formatter = Columnator("phase name", "description", maxField = 25) - formatter(phases.map(mega => mega.map(p => (p.phaseName, p.description)))) + formatter(texts) /** Provide usage feedback on argument summary, assuming that all settings * are already applied in context. @@ -156,12 +166,15 @@ trait CliCommand: private def columnate(sb: StringBuilder, texts: List[List[(String, String)]])(using Context): Unit = import Highlighting.* val colors = Seq(Green(_), Yellow(_), Magenta(_), Cyan(_), Red(_)) + val bolds = Seq(GreenB(_), YellowB(_), MagentaB(_), CyanB(_), RedB(_)) val nocolor = texts.length == 1 def color(index: Int): String => Highlight = if nocolor then NoColor(_) else colors(index % colors.length) + def colorB(index: Int): String => Highlight = if nocolor then NoColor(_) else bolds(index % colors.length) val maxCol = ctx.settings.pageWidth.value val field1 = maxField.min(texts.flatten.map(_._1.length).filter(_ < maxField).max) // widest field under maxField val field2 = if field1 + separation + maxField < maxCol then maxCol - field1 - separation else 0 // skinny window -> terminal wrap - val separator = " " * separation + def toMark(name: String) = ctx.settings.Vphases.value.exists(s => name.toLowerCase.contains(s.toLowerCase)) + def separator(name: String) = if toMark(name) then "->" + " " * (separation - 2) else " " * separation val EOL = "\n" def formatField1(text: String): String = if text.length <= field1 then text.padLeft(field1) else text + EOL + "".padLeft(field1) def formatField2(text: String): String = @@ -170,15 +183,15 @@ trait CliCommand: else fld.lastIndexOf(" ", field2) match case -1 => List(fld) - case i => val (prefix, rest) = fld.splitAt(i) ; prefix :: loopOverField2(rest.trim) - text.split("\n").toList.flatMap(loopOverField2).filter(_.nonEmpty).mkString(EOL + "".padLeft(field1) + separator) + case i => val (prefix, rest) = fld.splitAt(i); prefix :: loopOverField2(rest.trim) + text.split("\n").toList.flatMap(loopOverField2).filter(_.nonEmpty).mkString(EOL + "".padLeft(field1) + separator("no-phase")) end formatField2 def format(first: String, second: String, index: Int, colorPicker: Int => String => Highlight) = sb.append(colorPicker(index)(formatField1(first)).show) - .append(separator) + .append(colorPicker(index)(separator(first)).show) .append(formatField2(second)) .append(EOL): Unit - def fancy(first: String, second: String, index: Int) = format(first, second, index, color) + def fancy(first: String, second: String, index: Int) = format(first, second, index, if toMark(first) then colorB else color) def plain(first: String, second: String) = format(first, second, 0, _ => NoColor(_)) if heading1.nonEmpty then diff --git a/compiler/src/dotty/tools/dotc/config/CompilerCommand.scala b/compiler/src/dotty/tools/dotc/config/CompilerCommand.scala index e90bbcc36878..12077291472c 100644 --- a/compiler/src/dotty/tools/dotc/config/CompilerCommand.scala +++ b/compiler/src/dotty/tools/dotc/config/CompilerCommand.scala @@ -18,10 +18,10 @@ abstract class CompilerCommand extends CliCommand: else if (settings.Xhelp.value) xusageMessage else if (settings.Yhelp.value) yusageMessage else if (settings.showPlugins.value) ctx.base.pluginDescriptions - else if (settings.XshowPhases.value) phasesMessage + else if settings.Vphases.isPresent then phasesMessage else "" - final def isHelpFlag(using settings: ConcreteSettings)(using SettingsState): Boolean = + final def isHelpFlag(using settings: ConcreteSettings)(using ss: SettingsState): Boolean = import settings.* - val flags = Set(help, Vhelp, Whelp, Xhelp, Yhelp, showPlugins, XshowPhases) - flags.exists(_.value) || allSettings.exists(isHelping) + val flags = Set(help, Vhelp, Whelp, Xhelp, Yhelp, showPlugins) + flags.exists(_.value) || Vphases.isPresentIn(ss) || allSettings.exists(isHelping) diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index 248eaf73931e..757330345baa 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -113,7 +113,7 @@ trait CommonScalaSettings: val explainTypes: Setting[Boolean] = BooleanSetting(RootSetting, "explain-types", "Explain type errors in more detail (deprecated, use -explain instead).", aliases = List("--explain-types", "-explaintypes")) val explainCyclic: Setting[Boolean] = BooleanSetting(RootSetting, "explain-cyclic", "Explain cyclic reference errors in more detail.", aliases = List("--explain-cyclic")) val unchecked: Setting[Boolean] = BooleanSetting(RootSetting, "unchecked", "Enable additional warnings where generated code depends on assumptions.", initialValue = true, aliases = List("--unchecked")) - val language: Setting[List[ChoiceWithHelp[String]]] = MultiChoiceHelpSetting(RootSetting, "language", "feature", "Enable one or more language features.", choices = ScalaSettingsProperties.supportedLanguageFeatures, legacyChoices = ScalaSettingsProperties.legacyLanguageFeatures, default = Nil, aliases = List("--language")) + val language: Setting[List[ChoiceWithHelp[String]]] = MultiChoiceHelpSetting(RootSetting, "language", "feature", "Enable one or more language features.", choices = ScalaSettingsProperties.supportedLanguageFeatures, legacyChoices = ScalaSettingsProperties.legacyLanguageFeatures, aliases = List("--language")) val experimental: Setting[Boolean] = BooleanSetting(RootSetting, "experimental", "Annotate all top-level definitions with @experimental. This enables the use of experimental features anywhere in the project.") val preview: Setting[Boolean] = BooleanSetting(RootSetting, "preview", "Enable the use of preview features anywhere in the project.") @@ -144,9 +144,8 @@ private sealed trait PluginSettings: private sealed trait VerboseSettings: self: SettingGroup => val Vhelp: Setting[Boolean] = BooleanSetting(VerboseSetting, "V", "Print a synopsis of verbose options.") - val Vprint: Setting[List[String]] = PhasesSetting(VerboseSetting, "Vprint", "Print out program after", aliases = List("-Xprint")) - val XshowPhases: Setting[Boolean] = BooleanSetting(VerboseSetting, "Vphases", "List compiler phases.", aliases = List("-Xshow-phases")) - + val Vprint: Setting[List[String]] = PhasesSetting(VerboseSetting, "Vprint", "Print out program after", default = "typer", aliases = List("-Xprint")) + val Vphases: Setting[List[String]] = PhasesSetting(VerboseSetting, "Vphases", "List compiler phases.", default = "none", aliases = List("-Xshow-phases")) val Vprofile: Setting[Boolean] = BooleanSetting(VerboseSetting, "Vprofile", "Show metrics about sources and internal representations to estimate compile-time complexity.") val VprofileSortedBy = ChoiceSetting(VerboseSetting, "Vprofile-sorted-by", "key", "Show metrics about sources and internal representations sorted by given column name", List("name", "path", "lines", "tokens", "tasty", "complexity"), "") val VprofileDetails = IntSetting(VerboseSetting, "Vprofile-details", "Show metrics about sources and internal representations of the most complex methods", 0) @@ -191,7 +190,6 @@ private sealed trait WarningSettings: ), ChoiceWithHelp("unsafe-warn-patvars", "Deprecated alias for `patvars`"), ), - default = Nil ) object WunusedHas: def isChoiceSet(s: String)(using Context) = Wunused.value.pipe(us => us.contains(s)) @@ -225,7 +223,6 @@ private sealed trait WarningSettings: WarningSetting, "Wconf", "patterns", - default = List(), descr = raw"""Configure compiler warnings. |Syntax: -Wconf::,:,... @@ -287,7 +284,6 @@ private sealed trait WarningSettings: ChoiceWithHelp("private-shadow", "Warn if a private field or class parameter shadows a superclass field"), ChoiceWithHelp("type-parameter-shadow", "Warn when a type parameter shadows a type already in the scope"), ), - default = Nil ) object WshadowHas: diff --git a/compiler/src/dotty/tools/dotc/config/Settings.scala b/compiler/src/dotty/tools/dotc/config/Settings.scala index 7842113b5e48..f28fd9ea7553 100644 --- a/compiler/src/dotty/tools/dotc/config/Settings.scala +++ b/compiler/src/dotty/tools/dotc/config/Settings.scala @@ -5,7 +5,6 @@ import core.Contexts.* import dotty.tools.io.{AbstractFile, Directory, JarArchive, PlainDirectory} -import annotation.tailrec import annotation.internal.unshared import collection.mutable.ArrayBuffer import collection.mutable @@ -15,6 +14,8 @@ import dotty.tools.dotc.config.Settings.Setting.ChoiceWithHelp object Settings: + private inline def classTag[T](using ctag: ClassTag[T]): ClassTag[T] = ctag + val BooleanTag: ClassTag[Boolean] = ClassTag.Boolean val IntTag: ClassTag[Int] = ClassTag.Int val StringTag: ClassTag[String] = ClassTag(classOf[String]) @@ -102,21 +103,24 @@ object Settings: assert(legacyArgs || !choices.exists(_.contains("")), s"Empty string is not supported as a choice for setting $name") // Without the following assertion, it would be easy to mistakenly try to pass a file to a setting that ignores invalid args. // Example: -opt Main.scala would be interpreted as -opt:Main.scala, and the source file would be ignored. - assert(!(summon[ClassTag[T]] == ListTag && ignoreInvalidArgs), s"Ignoring invalid args is not supported for multivalue settings: $name") + assert(!(classTag[T] == ListTag && ignoreInvalidArgs), s"Ignoring invalid args is not supported for multivalue settings: $name") val allFullNames: List[String] = s"$name" :: s"-$name" :: aliases + def isPresentIn(state: SettingsState): Boolean = state.wasChanged(idx) + def valueIn(state: SettingsState): T = state.value(idx).asInstanceOf[T] + def userValueIn(state: SettingsState): Option[T] = if isPresentIn(state) then Some(valueIn(state)) else None + def updateIn(state: SettingsState, x: Any): SettingsState = x match case _: T => state.update(idx, x) - case _ => throw IllegalArgumentException(s"found: $x of type ${x.getClass.getName}, required: ${summon[ClassTag[T]]}") + case null => throw IllegalArgumentException(s"attempt to set null ${classTag[T]}") + case _ => throw IllegalArgumentException(s"found: $x of type ${x.getClass.getName}, required: ${classTag[T]}") def isDefaultIn(state: SettingsState): Boolean = valueIn(state) == default - def isMultivalue: Boolean = summon[ClassTag[T]] == ListTag - - def acceptsNoArg: Boolean = summon[ClassTag[T]] == BooleanTag || summon[ClassTag[T]] == OptionTag || choices.exists(_.contains("")) + def isMultivalue: Boolean = classTag[T] == ListTag def legalChoices: String = choices match @@ -132,7 +136,7 @@ object Settings: * Updates the value in state * * @param getValue it is crucial that this argument is passed by name, as [setOutput] have side effects. - * @param argStringValue string value of currently proccessed argument that will be used to set deprecation replacement + * @param argStringValue string value of currently processed argument that will be used to set deprecation replacement * @param args remaining arguments to process * @return new argumment state */ @@ -168,11 +172,17 @@ object Settings: def missingArg = val msg = s"missing argument for option $name" - if ignoreInvalidArgs then state.warn(msg + ", the tag was ignored") else state.fail(msg) + if ignoreInvalidArgs then state.warn(s"$msg, the tag was ignored") else state.fail(msg) def invalidChoices(invalid: List[String]) = val msg = s"invalid choice(s) for $name: ${invalid.mkString(",")}" - if ignoreInvalidArgs then state.warn(msg + ", the tag was ignored") else state.fail(msg) + if ignoreInvalidArgs then state.warn(s"$msg, the tag was ignored") else state.fail(msg) + + def isEmptyDefault = default == null.asInstanceOf[T] || classTag[T].match + case ListTag => default.asInstanceOf[List[?]].isEmpty + case StringTag => default.asInstanceOf[String].isEmpty + case OptionTag => default.asInstanceOf[Option[?]].isEmpty + case _ => false def setBoolean(argValue: String, args: List[String]) = if argValue.equalsIgnoreCase("true") || argValue.isEmpty then update(true, argValue, args) @@ -201,11 +211,11 @@ object Settings: def setOutput(argValue: String, args: List[String]) = val path = Directory(argValue) val isJar = path.ext.isJar - if (!isJar && !path.isDirectory) then + if !isJar && !path.isDirectory then state.fail(s"'$argValue' does not exist or is not a directory or .jar file") else /* Side effect, do not change this method to evaluate eagerly */ - def output = if (isJar) JarArchive.create(path) else new PlainDirectory(path) + def output = if isJar then JarArchive.create(path) else new PlainDirectory(path) update(output, argValue, args) def setVersion(argValue: String, args: List[String]) = @@ -226,25 +236,37 @@ object Settings: case _ => update(strings, argValue, args) def doSet(argRest: String) = - ((summon[ClassTag[T]], args): @unchecked) match - case (BooleanTag, _) => + classTag[T] match + case BooleanTag => if sstate.wasChanged(idx) && preferPrevious then ignoreValue(args) else setBoolean(argRest, args) - case (OptionTag, _) => + case OptionTag => update(Some(propertyClass.get.getConstructor().newInstance()), "", args) - case (ct, args) => + case ct => val argInArgRest = !argRest.isEmpty || legacyArgs - val argAfterParam = !argInArgRest && args.nonEmpty && (ct == IntTag || !args.head.startsWith("-")) + inline def argAfterParam = args.nonEmpty && (ct == IntTag || !args.head.startsWith("-")) + inline def isMultivalueWithDefault = isMultivalue && !isEmptyDefault if argInArgRest then doSetArg(argRest, args) - else if argAfterParam then + else if argAfterParam && !isMultivalueWithDefault then doSetArg(args.head, args.tail) - else missingArg + else if isEmptyDefault then + missingArg + else + doSetArg(arg = null, args) // update with default - def doSetArg(arg: String, argsLeft: List[String]) = summon[ClassTag[T]] match + def doSetArg(arg: String | Null, argsLeft: List[String]) = + arg match + case null => + classTag[T] match + case ListTag => + update(default, argStringValue = "", argsLeft) + case _ => + missingArg + case arg => + classTag[T] match case ListTag => - val strings = arg.split(",").toList - appendList(strings, arg, argsLeft) + appendList(arg.split(",").toList, arg, argsLeft) case StringTag => setString(arg, argsLeft) case OutputTag => @@ -260,10 +282,10 @@ object Settings: missingArg def matches(argName: String): Boolean = - (allFullNames).exists(_ == argName.takeWhile(_ != ':')) || prefix.exists(arg.startsWith) + allFullNames.exists(_ == argName.takeWhile(_ != ':')) || prefix.exists(arg.startsWith) def argValRest: String = - if(prefix.isEmpty) arg.dropWhile(_ != ':').drop(1) else arg.drop(prefix.get.length) + if prefix.isEmpty then arg.dropWhile(_ != ':').drop(1) else arg.drop(prefix.get.length) if matches(arg) then deprecation match @@ -297,8 +319,10 @@ object Settings: object Setting: extension [T](setting: Setting[T]) def value(using Context): T = setting.valueIn(ctx.settingsState) + def userValue(using Context): Option[T] = setting.userValueIn(ctx.settingsState) def update(x: T)(using Context): SettingsState = setting.updateIn(ctx.settingsState, x) def isDefault(using Context): Boolean = setting.isDefaultIn(ctx.settingsState) + def isPresent(using Context): Boolean = setting.isPresentIn(ctx.settingsState) /** * A choice with help description. @@ -351,7 +375,6 @@ object Settings: * * to get their arguments. */ - @tailrec final def processArguments(state: ArgsSummary, processAll: Boolean, skipped: List[String]): ArgsSummary = def stateWithArgs(args: List[String]) = ArgsSummary(state.sstate, args, state.errors, state.warnings) state.arguments match @@ -360,11 +383,11 @@ object Settings: case "--" :: args => checkDependencies(stateWithArgs(skipped ++ args)) case x :: _ if x startsWith "-" => - @tailrec def loop(settings: List[Setting[?]]): ArgsSummary = settings match - case setting :: settings1 => + def loop(settings: List[Setting[?]]): ArgsSummary = settings match + case setting :: settings => val state1 = setting.tryToSet(state) if state1 ne state then state1 - else loop(settings1) + else loop(settings) case Nil => state.warn(s"bad option '$x' was ignored") processArguments(loop(allSettings.toList), processAll, skipped) @@ -374,7 +397,7 @@ object Settings: end processArguments def processArguments(arguments: List[String], processAll: Boolean, settingsState: SettingsState = defaultState): ArgsSummary = - processArguments(ArgsSummary(settingsState, arguments, Nil, Nil), processAll, Nil) + processArguments(ArgsSummary(settingsState, arguments, errors = Nil, warnings = Nil), processAll, skipped = Nil) def publish[T](settingf: Int => Setting[T]): Setting[T] = val setting = settingf(_allSettings.length) @@ -397,7 +420,7 @@ object Settings: def MultiChoiceSetting(category: SettingCategory, name: String, helpArg: String, descr: String, choices: List[String], default: List[String] = Nil, legacyChoices: List[String] = Nil, aliases: List[String] = Nil, deprecation: Option[Deprecation] = None): Setting[List[String]] = publish(Setting(category, prependName(name), descr, default, helpArg, Some(choices), legacyChoices = Some(legacyChoices), aliases = aliases, deprecation = deprecation)) - def MultiChoiceHelpSetting(category: SettingCategory, name: String, helpArg: String, descr: String, choices: List[ChoiceWithHelp[String]], default: List[ChoiceWithHelp[String]], legacyChoices: List[String] = Nil, aliases: List[String] = Nil, deprecation: Option[Deprecation] = None): Setting[List[ChoiceWithHelp[String]]] = + def MultiChoiceHelpSetting(category: SettingCategory, name: String, helpArg: String, descr: String, choices: List[ChoiceWithHelp[String]], default: List[ChoiceWithHelp[String]] = Nil, legacyChoices: List[String] = Nil, aliases: List[String] = Nil, deprecation: Option[Deprecation] = None): Setting[List[ChoiceWithHelp[String]]] = publish(Setting(category, prependName(name), descr, default, helpArg, Some(choices), legacyChoices = Some(legacyChoices), aliases = aliases, deprecation = deprecation)) def IntSetting(category: SettingCategory, name: String, descr: String, default: Int, aliases: List[String] = Nil, deprecation: Option[Deprecation] = None): Setting[Int] = @@ -427,7 +450,7 @@ object Settings: publish(Setting(category, prependName(name), descr, default, legacyArgs = legacyArgs, deprecation = deprecation)) def OptionSetting[T: ClassTag](category: SettingCategory, name: String, descr: String, aliases: List[String] = Nil, deprecation: Option[Deprecation] = None): Setting[Option[T]] = - publish(Setting(category, prependName(name), descr, None, propertyClass = Some(summon[ClassTag[T]].runtimeClass), aliases = aliases, deprecation = deprecation)) + publish(Setting(category, prependName(name), descr, None, propertyClass = Some(classTag[T].runtimeClass), aliases = aliases, deprecation = deprecation)) end SettingGroup end Settings diff --git a/compiler/src/dotty/tools/dotc/core/Decorators.scala b/compiler/src/dotty/tools/dotc/core/Decorators.scala index f8096dddeedd..fcdf519bae31 100644 --- a/compiler/src/dotty/tools/dotc/core/Decorators.scala +++ b/compiler/src/dotty/tools/dotc/core/Decorators.scala @@ -251,22 +251,19 @@ object Decorators { * a given phase. See [[config.CompilerCommand#explainAdvanced]] for the * exact meaning of "contains" here. */ - extension (names: List[String]) + extension (names: List[String]) def containsPhase(phase: Phase): Boolean = - names.nonEmpty && { - phase match { - case phase: MegaPhase => phase.miniPhases.exists(x => names.containsPhase(x)) - case _ => - names exists { name => - name == "all" || { - val strippedName = name.stripSuffix("+") - val logNextPhase = name != strippedName - phase.phaseName.startsWith(strippedName) || - (logNextPhase && phase.prev.phaseName.startsWith(strippedName)) - } - } - } - } + names.nonEmpty && + phase.match + case phase: MegaPhase => phase.miniPhases.exists(containsPhase) + case _ => + names.exists: + case "all" => true + case name => + val strippedName = name.stripSuffix("+") + val logNextPhase = name != strippedName + phase.phaseName.startsWith(strippedName) + || logNextPhase && phase.prev.phaseName.startsWith(strippedName) extension [T](x: T) def showing[U]( diff --git a/compiler/src/dotty/tools/dotc/core/Phases.scala b/compiler/src/dotty/tools/dotc/core/Phases.scala index 2cad765aa9ce..00303ceae33f 100644 --- a/compiler/src/dotty/tools/dotc/core/Phases.scala +++ b/compiler/src/dotty/tools/dotc/core/Phases.scala @@ -9,9 +9,9 @@ import DenotTransformers.* import Denotations.* import Decorators.* import config.Printers.config -import scala.collection.mutable.ListBuffer import dotty.tools.dotc.transform.MegaPhase.* import dotty.tools.dotc.transform.* +import dotty.tools.dotc.util.chaining.* import Periods.* import parsing.Parser import printing.XprintMode @@ -20,8 +20,9 @@ import cc.CheckCaptures import typer.ImportInfo.withRootImports import ast.{tpd, untpd} import scala.annotation.internal.sharable -import scala.util.control.NonFatal +import scala.collection.mutable.ListBuffer import scala.compiletime.uninitialized +import scala.util.control.NonFatal object Phases { @@ -58,15 +59,15 @@ object Phases { final def phasePlan: List[List[Phase]] = this.phasesPlan final def setPhasePlan(phasess: List[List[Phase]]): Unit = this.phasesPlan = phasess - /** Squash TreeTransform's beloning to same sublist to a single TreeTransformer - * Each TreeTransform gets own period, - * whereas a combined TreeTransformer gets period equal to union of periods of it's TreeTransforms - */ + /** Squash TreeTransforms belonging to same sublist to a single TreeTransformer. + * Each TreeTransform gets its own period, + * whereas a combined TreeTransformer gets period equal to union of periods of its TreeTransforms. + */ final def fusePhases(phasess: List[List[Phase]], phasesToSkip: List[String], stopBeforePhases: List[String], stopAfterPhases: List[String], - YCheckAfter: List[String])(using Context): List[Phase] = { + checkAfter: List[String])(using Context): List[Phase] = { val fusedPhases = ListBuffer[Phase]() var prevPhases: Set[String] = Set.empty @@ -110,7 +111,7 @@ object Phases { phase } fusedPhases += phaseToAdd - val shouldAddYCheck = filteredPhases(i).exists(_.isCheckable) && YCheckAfter.containsPhase(phaseToAdd) + val shouldAddYCheck = filteredPhases(i).exists(_.isCheckable) && checkAfter.containsPhase(phaseToAdd) if (shouldAddYCheck) { val checker = new TreeChecker fusedPhases += checker @@ -128,12 +129,11 @@ object Phases { */ final def usePhases(phasess: List[Phase], runCtx: FreshContext, fuse: Boolean = true): Unit = { - val flatPhases = collection.mutable.ListBuffer[Phase]() + val flatPhases = ListBuffer.empty[Phase] - phasess.foreach(p => p match { + phasess.foreach: case p: MegaPhase => flatPhases ++= p.miniPhases - case _ => flatPhases += p - }) + case p => flatPhases += p phases = (NoPhase :: flatPhases.toList ::: new TerminalPhase :: Nil).toArray setSpecificPhases() @@ -564,4 +564,27 @@ object Phases { private 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)) + + def assemblePhases()(using Context): FreshContext = ctx.fresh.tap: runCtx => + // If testing pickler, make sure to stop after pickling phase: + val stopAfter = + if ctx.settings.YtestPickler.value then List("pickler") + else ctx.settings.YstopAfter.value + + val pluginPlan = ctx.base.addPluginPhases(ctx.base.phasePlan) + val phases = ctx.base.fusePhases(pluginPlan, + phasesToSkip = ctx.settings.Yskip.value, + stopBeforePhases = ctx.settings.YstopBefore.value, + stopAfterPhases = stopAfter, + checkAfter = ctx.settings.Ycheck.value) + ctx.base.usePhases(phases, runCtx) + + val phasesSettings = + val ss = ctx.settings + import ss.* + List(Vprint) // no check for Vphases + for phasesSetting <- phasesSettings do + for case ps: List[String] <- phasesSetting.userValue; p <- ps do + if !phases.exists(List(p).containsPhase) then report.warning(s"'$p' specifies no phase") + end assemblePhases } diff --git a/compiler/src/dotty/tools/dotc/semanticdb/ExtractSemanticDB.scala b/compiler/src/dotty/tools/dotc/semanticdb/ExtractSemanticDB.scala index 3768ce6ce1e0..7245538902f0 100644 --- a/compiler/src/dotty/tools/dotc/semanticdb/ExtractSemanticDB.scala +++ b/compiler/src/dotty/tools/dotc/semanticdb/ExtractSemanticDB.scala @@ -49,7 +49,12 @@ import java.nio.file.Path */ class ExtractSemanticDB private (phaseMode: ExtractSemanticDB.PhaseMode) extends Phase: - override val phaseName: String = ExtractSemanticDB.phaseNamePrefix + phaseMode.toString() + override val phaseName: String = s"""${ExtractSemanticDB.phaseNamePrefix}${ + import ExtractSemanticDB.PhaseMode.* + phaseMode match + case ExtractSemanticInfo => "ExtractInfo" + case AppendDiagnostics => "AppendDiag" + }""" override val description: String = ExtractSemanticDB.description @@ -129,7 +134,7 @@ object ExtractSemanticDB: import java.nio.file.Files import java.nio.file.Paths - val phaseNamePrefix: String = "extractSemanticDB" + val phaseNamePrefix: String = "semanticDB" val description: String = "extract info into .semanticdb files" enum PhaseMode: diff --git a/compiler/src/dotty/tools/dotc/transform/CollectEntryPoints.scala b/compiler/src/dotty/tools/dotc/transform/CollectEntryPoints.scala index 5534947c6799..966b1732af8e 100644 --- a/compiler/src/dotty/tools/dotc/transform/CollectEntryPoints.scala +++ b/compiler/src/dotty/tools/dotc/transform/CollectEntryPoints.scala @@ -49,5 +49,5 @@ class CollectEntryPoints extends MiniPhase: } object CollectEntryPoints: - val name: String = "Collect entry points" + val name: String = "collectEntryPoints" val description: String = "collect all entry points and save them in the context" diff --git a/compiler/src/dotty/tools/dotc/transform/SetRootTree.scala b/compiler/src/dotty/tools/dotc/transform/SetRootTree.scala index f62b1f5f01f2..3f95c6567ca5 100644 --- a/compiler/src/dotty/tools/dotc/transform/SetRootTree.scala +++ b/compiler/src/dotty/tools/dotc/transform/SetRootTree.scala @@ -44,6 +44,6 @@ class SetRootTree extends Phase { } object SetRootTree { - val name: String = "SetRootTree" + val name: String = "setRootTree" val description: String = "set the rootTreeOrProvider on class symbols" } diff --git a/compiler/test/dotty/tools/dotc/SettingsTests.scala b/compiler/test/dotty/tools/dotc/SettingsTests.scala index 24549ade4d23..4949c697cd79 100644 --- a/compiler/test/dotty/tools/dotc/SettingsTests.scala +++ b/compiler/test/dotty/tools/dotc/SettingsTests.scala @@ -13,13 +13,15 @@ import dotty.tools.io.PlainDirectory import dotty.tools.io.Directory import dotty.tools.dotc.config.ScalaVersion -import java.nio.file._ +import java.nio.file.* import org.junit.Test -import org.junit.Assert._ +import org.junit.Assert.{assertEquals, assertFalse, assertNotEquals, assertTrue} import scala.util.Using -class SettingsTests { +class SettingsTests: + + private val TestCategory = new SettingCategory { def prefixLetter = "T" } @Test def missingOutputDir: Unit = val options = Array("-d", "not_here") @@ -110,7 +112,7 @@ class SettingsTests { false val default = Settings.defaultState - dotty.tools.assertThrows[IllegalArgumentException](checkMessage("found: not an option of type java.lang.String, required: Boolean")) { + assertThrows[IllegalArgumentException](checkMessage("found: not an option of type java.lang.String, required: Boolean")) { Settings.option.updateIn(default, "not an option") } @@ -170,11 +172,12 @@ class SettingsTests { ) assertEquals(expectedErrors, summary.errors) } + end validateChoices @Test def `Allow IntSetting's to be set with a colon`: Unit = object Settings extends SettingGroup: val foo = IntSetting(RootSetting, "foo", "foo", 80) - import Settings._ + import Settings.* val args = List("-foo:100") val summary = processArguments(args, processAll = true) @@ -189,7 +192,7 @@ class SettingsTests { val bar = BooleanSetting(RootSetting, "bar", "bar", true) val baz = BooleanSetting(RootSetting, "baz", "baz", false) val qux = BooleanSetting(RootSetting, "qux", "qux", false) - import Settings._ + import Settings.* val args = List("-foo:true", "-bar:false", "-baz", "-qux:true", "-qux:false") val summary = processArguments(args, processAll = true) @@ -208,7 +211,7 @@ class SettingsTests { val defaultDir = new PlainDirectory(Directory(".")) val testOutput = OutputSetting(RootSetting, "testOutput", "testOutput", "", defaultDir) - import Settings._ + import Settings.* Files.write(file, "test".getBytes()) val fileStateBefore = String(Files.readAllBytes(file)) @@ -228,7 +231,7 @@ class SettingsTests { val defaultDir = new PlainDirectory(Directory(".")) val testOutput = OutputSetting(RootSetting, "testOutput", "testOutput", "", defaultDir, preferPrevious = true) - import Settings._ + import Settings.* Files.write(file1, "test1".getBytes()) Files.write(file2, "test2".getBytes()) @@ -253,7 +256,7 @@ class SettingsTests { val defaultDir = new PlainDirectory(Directory(".")) val testOutput = OutputSetting(RootSetting, "testOutput", "testOutput", "", defaultDir, preferPrevious = true, deprecation = Deprecation.renamed("XtestOutput")) - import Settings._ + import Settings.* Files.write(file, "test".getBytes()) val fileStateBefore = String(Files.readAllBytes(file)) @@ -277,10 +280,10 @@ class SettingsTests { val multiStringSetting = MultiStringSetting(RootSetting, "multiStringSetting", "multiStringSetting", "", List("a", "b"), List()) val outputSetting = OutputSetting(RootSetting, "outputSetting", "outputSetting", "", new PlainDirectory(Directory("."))) val pathSetting = PathSetting(RootSetting, "pathSetting", "pathSetting", ".") - val phasesSetting = PhasesSetting(RootSetting, "phasesSetting", "phasesSetting", "all") + val phasesSetting = PhasesSetting(RootSetting, "phasesSetting", "phasesSetting") val versionSetting= VersionSetting(RootSetting, "versionSetting", "versionSetting") - import Settings._ + import Settings.* Using.resource(Files.createTempDirectory("testDir")) { dir => val args = List( @@ -321,8 +324,17 @@ class SettingsTests { }(Files.deleteIfExists(_)) + @Test def `Multi setting with empty default errors if missing`: Unit = + object Settings extends SettingGroup: + val answers = MultiStringSetting(TestCategory, name="Tanswer", helpArg="your reply", descr="answer me this") + import Settings.{answers, processArguments} + val summary = processArguments("-Tanswer" :: Nil, processAll = true) + assertTrue(summary.warnings.isEmpty) + assertFalse(summary.errors.isEmpty) + withProcessedArgs(summary): + assertTrue(answers.value.isEmpty) + private def withProcessedArgs(summary: ArgsSummary)(f: SettingsState ?=> Unit) = f(using summary.sstate) extension [T](setting: Setting[T]) private def value(using ss: SettingsState): T = setting.valueIn(ss) -} diff --git a/compiler/test/dotty/tools/dotc/config/ScalaSettingsTests.scala b/compiler/test/dotty/tools/dotc/config/ScalaSettingsTests.scala index c74be4901137..b0a5d227dc4d 100644 --- a/compiler/test/dotty/tools/dotc/config/ScalaSettingsTests.scala +++ b/compiler/test/dotty/tools/dotc/config/ScalaSettingsTests.scala @@ -306,4 +306,24 @@ class ScalaSettingsTests: assertEquals(0, result.warnings.length) assertEquals(1, result.errors.length) + @Test def `Vphases takes optional phase names`: Unit = + val settings = ScalaSettings + def process(args: List[String]) = + val summary = ArgsSummary(settings.defaultState, args, errors = Nil, warnings = Nil) + settings.processArguments(summary, processAll = true, skipped = Nil) + locally: + val summary = process("-Vphases:foo,bar,baz" :: Nil) + assertTrue(summary.errors.isEmpty && summary.warnings.isEmpty) + assertEquals(List("foo","bar","baz"), settings.Vphases.valueIn(summary.sstate)) + locally: + val summary = process(List("-Vphases","--","foo")) + assertTrue(summary.errors.isEmpty && summary.warnings.isEmpty) + assertEquals(List("none"), settings.Vphases.valueIn(summary.sstate)) // nonempty default required + assertEquals(Some(List("none")), settings.Vphases.userValueIn(summary.sstate)) // nonempty default required + locally: + val summary = process(Nil) + assertTrue(summary.errors.isEmpty && summary.warnings.isEmpty) + assertEquals(List("none"), settings.Vphases.valueIn(summary.sstate)) // nonempty default required + assertEquals(None, settings.Vphases.userValueIn(summary.sstate)) + end ScalaSettingsTests diff --git a/sbt-bridge/test/xsbt/CompileProgressSpecification.scala b/sbt-bridge/test/xsbt/CompileProgressSpecification.scala index dc3956ada0db..07ca1d092204 100644 --- a/sbt-bridge/test/xsbt/CompileProgressSpecification.scala +++ b/sbt-bridge/test/xsbt/CompileProgressSpecification.scala @@ -56,7 +56,7 @@ class CompileProgressSpecification { "sbt-deps", "posttyper", "sbt-api", - "SetRootTree", + "setRootTree", "pickler", "inlining", "postInlining", diff --git a/tests/printing/transformed/lazy-vals-legacy.check b/tests/printing/transformed/lazy-vals-legacy.check index da1ed360b7ea..ff54a8babc3d 100644 --- a/tests/printing/transformed/lazy-vals-legacy.check +++ b/tests/printing/transformed/lazy-vals-legacy.check @@ -1,4 +1,4 @@ -[[syntax trees at end of MegaPhase{dropOuterAccessors, dropParentRefinements, checkNoSuperThis, flatten, transformWildcards, moveStatic, expandPrivate, restoreScopes, selectStatic, Collect entry points, collectSuperCalls, repeatableAnnotations}]] // tests/printing/transformed/lazy-vals-legacy.scala +[[syntax trees at end of MegaPhase{dropOuterAccessors, dropParentRefinements, checkNoSuperThis, flatten, transformWildcards, moveStatic, expandPrivate, restoreScopes, selectStatic, collectEntryPoints, collectSuperCalls, repeatableAnnotations}]] // tests/printing/transformed/lazy-vals-legacy.scala package { @SourceFile("tests/printing/transformed/lazy-vals-legacy.scala") final module class A extends Object { diff --git a/tests/printing/transformed/lazy-vals-new.check b/tests/printing/transformed/lazy-vals-new.check index 75ae8885f68c..24df2bfcfe14 100644 --- a/tests/printing/transformed/lazy-vals-new.check +++ b/tests/printing/transformed/lazy-vals-new.check @@ -1,4 +1,4 @@ -[[syntax trees at end of MegaPhase{dropOuterAccessors, dropParentRefinements, checkNoSuperThis, flatten, transformWildcards, moveStatic, expandPrivate, restoreScopes, selectStatic, Collect entry points, collectSuperCalls, repeatableAnnotations}]] // tests/printing/transformed/lazy-vals-new.scala +[[syntax trees at end of MegaPhase{dropOuterAccessors, dropParentRefinements, checkNoSuperThis, flatten, transformWildcards, moveStatic, expandPrivate, restoreScopes, selectStatic, collectEntryPoints, collectSuperCalls, repeatableAnnotations}]] // tests/printing/transformed/lazy-vals-new.scala package { @SourceFile("tests/printing/transformed/lazy-vals-new.scala") final module class A extends Object {