Skip to content

Support -Wconf and @nowarn #12857

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 21 commits into from
Aug 25, 2021
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 60 additions & 2 deletions compiler/src/dotty/tools/dotc/Run.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ import io.{AbstractFile, PlainFile, VirtualFile}
import Phases.unfusedPhases

import util._
import reporting.Reporter
import reporting.{Reporter, Suppression, Action}
import reporting.Diagnostic
import reporting.Diagnostic.Warning
import rewrites.Rewrites

import profile.Profiler
Expand Down Expand Up @@ -96,6 +98,60 @@ class Run(comp: Compiler, ictx: Context) extends ImplicitRunInfo with Constraint
private var myUnitsCached: List[CompilationUnit] = _
private var myFiles: Set[AbstractFile] = _

// `@nowarn` annotations by source file, populated during typer
private val mySuppressions: mutable.LinkedHashMap[SourceFile, mutable.ListBuffer[Suppression]] = mutable.LinkedHashMap.empty
// source files whose `@nowarn` annotations are processed
private val mySuppressionsComplete: mutable.Set[SourceFile] = mutable.Set.empty
// warnings issued before a source file's `@nowarn` annotations are processed, suspended so that `@nowarn` can filter them
private val mySuspendedMessages: mutable.LinkedHashMap[SourceFile, mutable.LinkedHashSet[Warning]] = mutable.LinkedHashMap.empty

object suppressions:
// When the REPL creates a new run (ReplDriver.compile), parsing is already done in the old context, with the
// previous Run. Parser warnings were suspended in the old run and need to be copied over so they are not lost.
// Same as scala/scala/commit/79ca1408c7.
def initSuspendedMessages(oldRun: Run) = if oldRun != null then
mySuspendedMessages.clear()
mySuspendedMessages ++= oldRun.mySuspendedMessages

def suppressionsComplete(source: SourceFile) = source == NoSource || mySuppressionsComplete(source)

def addSuspendedMessage(warning: Warning) =
mySuspendedMessages.getOrElseUpdate(warning.pos.source, mutable.LinkedHashSet.empty) += warning

def nowarnAction(dia: Diagnostic): Action.Warning.type | Action.Verbose.type | Action.Silent.type =
mySuppressions.getOrElse(dia.pos.source, Nil).find(_.matches(dia)) match {
case Some(s) =>
s.markUsed()
if (s.verbose) Action.Verbose
else Action.Silent
case _ =>
Action.Warning
}

def addSuppression(sup: Suppression): Unit =
val source = sup.annotPos.source
mySuppressions.getOrElseUpdate(source, mutable.ListBuffer.empty) += sup

def reportSuspendedMessages(source: SourceFile)(using Context): Unit = {
// sort suppressions. they are not added in any particular order because of lazy type completion
for (sups <- mySuppressions.get(source))
mySuppressions(source) = sups.sortBy(sup => 0 - sup.start)
mySuppressionsComplete += source
mySuspendedMessages.remove(source).foreach(_.foreach(ctx.reporter.issueIfNotSuppressed))
}

def runFinished(hasErrors: Boolean): Unit =
// report suspended messages (in case the run finished before typer)
mySuspendedMessages.keysIterator.toList.foreach(reportSuspendedMessages)
// report unused nowarns only if all all phases are done
if !hasErrors && ctx.settings.WunusedHas.nowarn then
for {
source <- mySuppressions.keysIterator.toList
sups <- mySuppressions.remove(source)
sup <- sups.reverse
} if (!sup.used)
report.warning("@nowarn annotation does not suppress any warnings", sup.annotPos)

/** The compilation units currently being compiled, this may return different
* results over time.
*/
Expand Down Expand Up @@ -222,7 +278,9 @@ class Run(comp: Compiler, ictx: Context) extends ImplicitRunInfo with Constraint
runCtx.setProfiler(Profiler())
unfusedPhases.foreach(_.initContext(runCtx))
runPhases(using runCtx)
if (!ctx.reporter.hasErrors) Rewrites.writeBack()
if (!ctx.reporter.hasErrors)
Rewrites.writeBack()
suppressions.runFinished(hasErrors = ctx.reporter.hasErrors)
while (finalizeActions.nonEmpty) {
val action = finalizeActions.remove(0)
action()
Expand Down
19 changes: 11 additions & 8 deletions compiler/src/dotty/tools/dotc/config/CompilerCommand.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,17 @@ abstract class CompilerCommand extends CliCommand:
type ConcreteSettings = ScalaSettings

final def helpMsg(using settings: ScalaSettings)(using SettingsState, Context): String =
if (settings.help.value) usageMessage
else if (settings.Vhelp.value) vusageMessage
else if (settings.Whelp.value) wusageMessage
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 ""
settings.allSettings.find(isHelping) match
case Some(s) => s.description
case _ =>
if (settings.help.value) usageMessage
else if (settings.Vhelp.value) vusageMessage
else if (settings.Whelp.value) wusageMessage
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 ""

final def isHelpFlag(using settings: ScalaSettings)(using SettingsState): Boolean =
import settings._
Expand Down
70 changes: 65 additions & 5 deletions compiler/src/dotty/tools/dotc/config/ScalaSettings.scala
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package dotty.tools.dotc
package config

import dotty.tools.dotc.config.PathResolver.Defaults
import dotty.tools.dotc.config.Settings.{Setting, SettingGroup}
import dotty.tools.dotc.core.Contexts._
import dotty.tools.io.{ Directory, PlainDirectory, AbstractFile, JDK9Reflectors }
import PathResolver.Defaults
import rewrites.Rewrites
import Settings.{ Setting, SettingGroup }
import dotty.tools.dotc.rewrites.Rewrites
import dotty.tools.io.{AbstractFile, Directory, JDK9Reflectors, PlainDirectory}

import scala.util.chaining._

class ScalaSettings extends SettingGroup with AllScalaSettings

Expand Down Expand Up @@ -51,7 +53,7 @@ trait AllScalaSettings extends CommonScalaSettings, VerboseSettings, WarningSett
val explain: Setting[Boolean] = BooleanSetting("-explain", "Explain errors in more detail.", aliases = List("--explain"))
val feature: Setting[Boolean] = BooleanSetting("-feature", "Emit warning and location for usages of features that should be imported explicitly.", aliases = List("--feature"))
val source: Setting[String] = ChoiceSetting("-source", "source version", "source version", List("3.0", "future", "3.0-migration", "future-migration"), "3.0", aliases = List("--source"))
val unchecked: Setting[Boolean] = BooleanSetting("-unchecked", "Enable additional warnings where generated code depends on assumptions.", aliases = List("--unchecked"))
val unchecked: Setting[Boolean] = BooleanSetting("-unchecked", "Enable additional warnings where generated code depends on assumptions.", initialValue = true, aliases = List("--unchecked"))
val uniqid: Setting[Boolean] = BooleanSetting("-uniqid", "Uniquely tag all identifiers in debugging output.", aliases = List("--unique-id"))
val language: Setting[List[String]] = MultiStringSetting("-language", "feature", "Enable one or more language features.", aliases = List("--language"))
val rewrite: Setting[Option[Rewrites]] = OptionSetting[Rewrites]("-rewrite", "When used in conjunction with a `...-migration` source version, rewrites sources to migrate to new version.", aliases = List("--rewrite"))
Expand Down Expand Up @@ -133,6 +135,64 @@ private sealed trait WarningSettings:
val Whelp: Setting[Boolean] = BooleanSetting("-W", "Print a synopsis of warning options.")
val XfatalWarnings: Setting[Boolean] = BooleanSetting("-Werror", "Fail the compilation if there are any warnings.", aliases = List("-Xfatal-warnings"))

val Wunused: Setting[List[String]] = MultiChoiceSetting(
name = "-Wunused",
helpArg = "warning",
descr = "Enable or disable specific `unused` warnings",
choices = List("nowarn", "all"),
default = Nil
)
object WunusedHas:
def allOr(s: String)(using Context) = Wunused.value.pipe(us => us.contains("all") || us.contains(s))
def nowarn(using Context) = allOr("nowarn")

val Wconf: Setting[List[String]] = MultiStringSetting(
"-Wconf",
"patterns",
default = List(),
descr =
s"""Configure compiler warnings.
|Syntax: -Wconf:<filters>:<action>,<filters>:<action>,...
|multiple <filters> are combined with &, i.e., <filter>&...&<filter>
|
|<filter>
| - Any message: any
|
| - Message categories: cat=deprecation, cat=feature, cat=unchecked
|
| - Message content: msg=regex
| The regex need only match some part of the message, not all of it.
|
| - Message id: id=E129
| The message id is printed with the warning.
|
| - Message name: name=PureExpressionInStatementPosition
| The message name is printed with the warning in verbose warning mode.
|
|In verbose warning mode the compiler prints matching filters for warnings.
|Verbose mode can be enabled globally using `-Wconf:any:verbose`, or locally
|using the @nowarn annotation (example: `@nowarn("v") def test = try 1`).
|
|<action>
| - error / e
| - warning / w
| - verbose / v (emit warning, show additional help for writing `-Wconf` filters)
| - info / i (infos are not counted as warnings and not affected by `-Werror`)
| - silent / s
|
|The default configuration is empty.
|
|User-defined configurations are added to the left. The leftmost rule matching
|a warning message defines the action.
|
|Examples:
| - change every warning into an error: -Wconf:any:error
| - silence deprecations: -Wconf:cat=deprecation:s
|
|Note: on the command-line you might need to quote configurations containing `*` or `&`
|to prevent the shell from expanding patterns.""".stripMargin,
)

/** -X "Extended" or "Advanced" settings */
private sealed trait XSettings:
self: SettingGroup =>
Expand Down
17 changes: 13 additions & 4 deletions compiler/src/dotty/tools/dotc/config/Settings.scala
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ object Settings:
description: String,
default: T,
helpArg: String = "",
choices: Option[Seq[T]] = None,
choices: Option[Seq[?]] = None,
prefix: String = "",
aliases: List[String] = Nil,
depends: List[(Setting[?], Any)] = Nil,
Expand Down Expand Up @@ -115,7 +115,13 @@ object Settings:
update(Some(propertyClass.get.getConstructor().newInstance()), args)
case (ListTag, _) =>
if (argRest.isEmpty) missingArg
else update((argRest split ",").toList, args)
else
val strings = argRest.split(",").toList
choices match
case Some(valid) => strings.filterNot(valid.contains) match
case Nil => update(strings, args)
case invalid => fail(s"invalid choice(s) for $name: ${invalid.mkString(",")}", args)
case _ => update(strings, args)
case (StringTag, _) if argRest.nonEmpty || choices.exists(_.contains("")) =>
setString(argRest, args)
case (StringTag, arg2 :: args2) =>
Expand Down Expand Up @@ -251,14 +257,17 @@ object Settings:
def ChoiceSetting(name: String, helpArg: String, descr: String, choices: List[String], default: String, aliases: List[String] = Nil): Setting[String] =
publish(Setting(name, descr, default, helpArg, Some(choices), aliases = aliases))

def MultiChoiceSetting(name: String, helpArg: String, descr: String, choices: List[String], default: List[String], aliases: List[String] = Nil): Setting[List[String]] =
publish(Setting(name, descr, default, helpArg, Some(choices), aliases = aliases))

def IntSetting(name: String, descr: String, default: Int, aliases: List[String] = Nil): Setting[Int] =
publish(Setting(name, descr, default, aliases = aliases))

def IntChoiceSetting(name: String, descr: String, choices: Seq[Int], default: Int): Setting[Int] =
publish(Setting(name, descr, default, choices = Some(choices)))

def MultiStringSetting(name: String, helpArg: String, descr: String, aliases: List[String] = Nil): Setting[List[String]] =
publish(Setting(name, descr, Nil, helpArg, aliases = aliases))
def MultiStringSetting(name: String, helpArg: String, descr: String, default: List[String] = Nil, aliases: List[String] = Nil): Setting[List[String]] =
publish(Setting(name, descr, default, helpArg, aliases = aliases))

def OutputSetting(name: String, helpArg: String, descr: String, default: AbstractFile): Setting[AbstractFile] =
publish(Setting(name, descr, default, helpArg))
Expand Down
2 changes: 2 additions & 0 deletions compiler/src/dotty/tools/dotc/core/Contexts.scala
Original file line number Diff line number Diff line change
Expand Up @@ -959,6 +959,8 @@ object Contexts {

private[core] val reusableDataReader = ReusableInstance(new ReusableDataReader())

private[dotc] var wConfCache: (List[String], WConf) = _

def sharedCharArray(len: Int): Array[Char] =
while len > charArray.length do
charArray = new Array[Char](charArray.length * 2)
Expand Down
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -903,6 +903,7 @@ class Definitions {
@tu lazy val InvariantBetweenAnnot: ClassSymbol = requiredClass("scala.annotation.internal.InvariantBetween")
@tu lazy val MainAnnot: ClassSymbol = requiredClass("scala.main")
@tu lazy val MigrationAnnot: ClassSymbol = requiredClass("scala.annotation.migration")
@tu lazy val NowarnAnnot: ClassSymbol = requiredClass("scala.annotation.nowarn")
@tu lazy val TransparentTraitAnnot: ClassSymbol = requiredClass("scala.annotation.transparentTrait")
@tu lazy val NativeAnnot: ClassSymbol = requiredClass("scala.native")
@tu lazy val RepeatedAnnot: ClassSymbol = requiredClass("scala.annotation.internal.Repeated")
Expand Down
7 changes: 4 additions & 3 deletions compiler/src/dotty/tools/dotc/parsing/Parsers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1158,10 +1158,11 @@ object Parsers {
}
else
if !in.featureEnabled(Feature.symbolLiterals) then
val name = in.name // capture name (not `in`) in the warning message closure
report.errorOrMigrationWarning(
em"""symbol literal '${in.name} is no longer supported,
|use a string literal "${in.name}" or an application Symbol("${in.name}") instead,
|or enclose in braces '{${in.name}} if you want a quoted expression.
em"""symbol literal '$name is no longer supported,
|use a string literal "$name" or an application Symbol("$name") instead,
|or enclose in braces '{$name} if you want a quoted expression.
|For now, you can also `import language.deprecated.symbolLiterals` to accept
|the idiom, but this possibility might no longer be available in the future.""",
in.sourcePos())
Expand Down
10 changes: 1 addition & 9 deletions compiler/src/dotty/tools/dotc/report.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,7 @@ object report:
ctx.reporter.report(new Info(msg, pos.sourcePos))

private def issueWarning(warning: Warning)(using Context): Unit =
if (!ctx.settings.silentWarnings.value)
if (ctx.settings.XfatalWarnings.value)
warning match {
case warning: ConditionalWarning if !warning.enablingOption.value =>
ctx.reporter.report(warning) // conditional warnings that are not enabled are not fatal
case _ =>
ctx.reporter.report(warning.toError)
}
else ctx.reporter.report(warning)
ctx.reporter.report(warning)

def deprecationWarning(msg: Message, pos: SrcPos = NoSourcePosition)(using Context): Unit =
issueWarning(new DeprecationWarning(msg, pos.sourcePos))
Expand Down
15 changes: 5 additions & 10 deletions compiler/src/dotty/tools/dotc/reporting/ConsoleReporter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,21 +21,16 @@ class ConsoleReporter(

/** Prints the message with the given position indication. */
def doReport(dia: Diagnostic)(using Context): Unit = {
val didPrint = dia match {
dia match
case dia: Error =>
printMessage(messageAndPos(dia.msg, dia.pos, diagnosticLevel(dia)))
printMessage(messageAndPos(dia))
if (ctx.settings.Xprompt.value) Reporter.displayPrompt(reader, writer)
true
case dia: ConditionalWarning if !dia.enablingOption.value =>
false
case dia =>
printMessage(messageAndPos(dia.msg, dia.pos, diagnosticLevel(dia)))
true
}
printMessage(messageAndPos(dia))

if (didPrint && shouldExplain(dia))
if shouldExplain(dia) then
printMessage(explanation(dia.msg))
else if (didPrint && dia.msg.canExplain)
else if dia.msg.canExplain then
printMessage("\nlonger explanation available when compiling with `-explain`")
}

Expand Down
20 changes: 15 additions & 5 deletions compiler/src/dotty/tools/dotc/reporting/Diagnostic.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ package dotty.tools
package dotc
package reporting

import util.SourcePosition
import core.Contexts._
import config.Settings.Setting
import interfaces.Diagnostic.{ERROR, INFO, WARNING}
import dotty.tools.dotc.config.Settings.Setting
import dotty.tools.dotc.core.Contexts._
import dotty.tools.dotc.interfaces.Diagnostic.{ERROR, INFO, WARNING}
import dotty.tools.dotc.util.SourcePosition

import java.util.Optional
import scala.util.chaining._

object Diagnostic:

Expand Down Expand Up @@ -35,7 +36,9 @@ object Diagnostic:
msg: Message,
pos: SourcePosition
) extends Diagnostic(msg, pos, WARNING) {
def toError: Error = new Error(msg, pos)
def toError: Error = new Error(msg, pos).tap(e => if isVerbose then e.setVerbose())
def toInfo: Info = new Info(msg, pos).tap(e => if isVerbose then e.setVerbose())
def isSummarizedConditional(using Context): Boolean = false
}

class Info(
Expand All @@ -48,6 +51,7 @@ object Diagnostic:
pos: SourcePosition
) extends Warning(msg, pos) {
def enablingOption(using Context): Setting[Boolean]
override def isSummarizedConditional(using Context): Boolean = !enablingOption.value
}

class FeatureWarning(
Expand Down Expand Up @@ -81,6 +85,12 @@ class Diagnostic(
val pos: SourcePosition,
val level: Int
) extends Exception with interfaces.Diagnostic:
private var verbose: Boolean = false
def isVerbose: Boolean = verbose
def setVerbose(): this.type =
verbose = true
this

override def position: Optional[interfaces.SourcePosition] =
if (pos.exists && pos.source.exists) Optional.of(pos) else Optional.empty()
override def message: String =
Expand Down
6 changes: 4 additions & 2 deletions compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package dotty.tools.dotc.reporting

/** Unique IDs identifying the messages */
enum ErrorMessageID extends java.lang.Enum[ErrorMessageID] {
enum ErrorMessageID extends java.lang.Enum[ErrorMessageID]:

// IMPORTANT: Add new IDs only at the end and never remove IDs
case
Expand Down Expand Up @@ -177,4 +177,6 @@ enum ErrorMessageID extends java.lang.Enum[ErrorMessageID] {
CannotExtendFunctionID

def errorNumber = ordinal - 2
}

object ErrorMessageID:
def fromErrorNumber(n: Int) = fromOrdinal(n + 2)
Loading