Skip to content

Commit

Permalink
-Vphases takes names to mark, -Vprint is typer
Browse files Browse the repository at this point in the history
Distinguish whether a setting was "set" by the user.
-Vphases takes names to append an asterisk for easy scanning,
and defaults to "none" to mark; -Vprint defaults to "typer".
Both options are used if the user used them: setting.isPresent.
If the setting value is not supplied, use a nonempty default.
An "empty" default, such as an empty string or list, would
require an explicit arg from the user.
  • Loading branch information
som-snytt committed May 10, 2024
1 parent 4bd3369 commit dffc44c
Show file tree
Hide file tree
Showing 5 changed files with 46 additions and 26 deletions.
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/Run.scala
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,7 @@ class Run(comp: Compiler, ictx: Context) extends ImplicitRunInfo with Constraint
try units = phase.runOn(units)
catch case _: InterruptedException => cancelInterrupted()
profiler.afterPhase(phase, profileBefore)
if (ctx.settings.Xprint.value.containsPhase(phase))
for (printAt <- ctx.settings.Xprint.userValue if printAt.containsPhase(phase))
for (unit <- units)
def printCtx(unit: CompilationUnit) = phase.printingContext(
ctx.fresh.setPhase(phase.next).setCompilationUnit(unit))
Expand Down
7 changes: 4 additions & 3 deletions compiler/src/dotty/tools/dotc/config/CliCommand.scala
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,8 @@ trait CliCommand:
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
val toMark = ctx.settings.XshowPhases.value.toSet
def separator(name: String) = if toMark(name) then "*" + " " * (separation - 1) 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 =
Expand All @@ -170,11 +171,11 @@ trait CliCommand:
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)
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)
Expand Down
6 changes: 3 additions & 3 deletions compiler/src/dotty/tools/dotc/config/CompilerCommand.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,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.XshowPhases.isPresentIn(summon[SettingsState]) then phasesMessage
else ""

final def isHelpFlag(using settings: ConcreteSettings)(using 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) || XshowPhases.isPresentIn(summon[SettingsState]) || allSettings.exists(isHelping)
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/config/ScalaSettings.scala
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,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 Xprint: 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 Xprint: Setting[List[String]] = PhasesSetting(VerboseSetting, "Vprint", "Print out program after", default = "typer", aliases = List("-Xprint"))
val XshowPhases: 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"), "")
Expand Down
53 changes: 36 additions & 17 deletions compiler/src/dotty/tools/dotc/config/Settings.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,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])
Expand Down Expand Up @@ -93,21 +95,25 @@ 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 _ => 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 isMultivalue: Boolean = classTag[T] == ListTag

def acceptsNoArg: Boolean = summon[ClassTag[T]] == BooleanTag || summon[ClassTag[T]] == OptionTag || choices.exists(_.contains(""))
def acceptsNoArg: Boolean = classTag[T] == BooleanTag || classTag[T] == OptionTag || choices.exists(_.contains(""))

def legalChoices: String =
choices match
Expand All @@ -123,7 +129,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
*/
Expand Down Expand Up @@ -159,11 +165,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)
Expand Down Expand Up @@ -192,11 +204,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]) =
Expand All @@ -212,22 +224,28 @@ 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 = !argInArgRest && args.nonEmpty && (ct == IntTag || !args.head.startsWith("-"))
if argInArgRest then
doSetArg(argRest, args)
else if argAfterParam then
doSetArg(args.head, args.tail)
else missingArg
else if isEmptyDefault then
missingArg
else
doSetArg(arg = null, args)

def doSetArg(arg: String, argsLeft: List[String]) = summon[ClassTag[T]] match
def doSetArg(arg: String, argsLeft: List[String]) =
classTag[T] match
case ListTag if arg == null =>
update(List(default), arg, argsLeft)
case ListTag =>
val strings = arg.split(",").toList
appendList(strings, arg, argsLeft)
Expand Down Expand Up @@ -283,6 +301,7 @@ 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)

Expand Down Expand Up @@ -412,7 +431,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

0 comments on commit dffc44c

Please sign in to comment.