Skip to content

Commit

Permalink
Add origin filter to WConf, DeprecationWarning (#21404)
Browse files Browse the repository at this point in the history
The `origin` parameter to `deprecationWarning` is optional; the
parameter to `DeprecationWarning` is not.

The `origin` of a deprecation is simply the deprecated symbol. The full
name is matched by the regex specified by `-Wconf`.

The other important use case is the `origin` of an unused element, in
particular, the selector which causes an "unused import" warning. That
use case will be supported in a follow-up PR, and should work as in
Scala 2.

Fixes #17538
  • Loading branch information
lrytz authored Aug 26, 2024
2 parents 5101daf + 929e7eb commit 1604e77
Show file tree
Hide file tree
Showing 12 changed files with 93 additions and 30 deletions.
3 changes: 2 additions & 1 deletion compiler/src/dotty/tools/dotc/config/CompilerCommand.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ abstract class CompilerCommand extends CliCommand:

final def helpMsg(using settings: ConcreteSettings)(using SettingsState, Context): String =
settings.allSettings.find(isHelping) match
case Some(s) => availableOptionsMsg(_ == s, showArgFileMsg = false)
case Some(s @ settings.language) => availableOptionsMsg(_ == s, showArgFileMsg = false)
case Some(s) => s.description
case _ =>
if (settings.help.value) usageMessage
else if (settings.Vhelp.value) vusageMessage
Expand Down
6 changes: 5 additions & 1 deletion compiler/src/dotty/tools/dotc/config/ScalaSettings.scala
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ private sealed trait WarningSettings:
"patterns",
default = List(),
descr =
s"""Configure compiler warnings.
raw"""Configure compiler warnings.
|Syntax: -Wconf:<filters>:<action>,<filters>:<action>,...
|multiple <filters> are combined with &, i.e., <filter>&...&<filter>
|
Expand All @@ -254,6 +254,9 @@ private sealed trait WarningSettings:
| - Source location: src=regex
| The regex is evaluated against the full source path.
|
| - Origin of warning: origin=regex
| The regex must match the full name (`package.Class.method`) of the deprecated entity.
|
|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`).
Expand All @@ -273,6 +276,7 @@ private sealed trait WarningSettings:
|Examples:
| - change every warning into an error: -Wconf:any:error
| - silence deprecations: -Wconf:cat=deprecation:s
| - silence a deprecation: -Wconf:origin=java\.lang\.Thread\.getId:s
| - silence warnings in src_managed directory: -Wconf:src=src_managed/.*:s
|
|Note: on the command-line you might need to quote configurations containing `*` or `&`
Expand Down
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/report.scala
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ object report:
private def issueWarning(warning: Warning)(using Context): Unit =
ctx.reporter.report(warning)

def deprecationWarning(msg: Message, pos: SrcPos)(using Context): Unit =
issueWarning(new DeprecationWarning(msg, pos.sourcePos))
def deprecationWarning(msg: Message, pos: SrcPos, origin: String = "")(using Context): Unit =
issueWarning(new DeprecationWarning(msg, pos.sourcePos, origin))

def migrationWarning(msg: Message, pos: SrcPos)(using Context): Unit =
issueWarning(new MigrationWarning(msg, pos.sourcePos))
Expand Down
3 changes: 2 additions & 1 deletion compiler/src/dotty/tools/dotc/reporting/Diagnostic.scala
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,8 @@ object Diagnostic:

class DeprecationWarning(
msg: Message,
pos: SourcePosition
pos: SourcePosition,
val origin: String
) extends ConditionalWarning(msg, pos) {
def enablingOption(using Context): Setting[Boolean] = ctx.settings.deprecation
}
Expand Down
39 changes: 22 additions & 17 deletions compiler/src/dotty/tools/dotc/reporting/MessageRendering.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import util.{ SourcePosition, NoSourcePosition }
import util.Chars.{ LF, CR, FF, SU }
import scala.annotation.switch

import scala.collection.mutable
import scala.collection.mutable.StringBuilder

trait MessageRendering {
import Highlight.*
Expand Down Expand Up @@ -209,22 +209,27 @@ trait MessageRendering {
sb.toString
}

private def appendFilterHelp(dia: Diagnostic, sb: mutable.StringBuilder): Unit =
import dia.*
private def appendFilterHelp(dia: Diagnostic, sb: StringBuilder): Unit =
import dia.msg
val hasId = msg.errorId.errorNumber >= 0
val category = dia match {
case _: UncheckedWarning => "unchecked"
case _: DeprecationWarning => "deprecation"
case _: FeatureWarning => "feature"
case _ => ""
}
if (hasId || category.nonEmpty)
sb.append(EOL).append("Matching filters for @nowarn or -Wconf:")
if (hasId)
sb.append(EOL).append(" - id=E").append(msg.errorId.errorNumber)
sb.append(EOL).append(" - name=").append(msg.errorId.productPrefix.stripSuffix("ID"))
if (category.nonEmpty)
sb.append(EOL).append(" - cat=").append(category)
val (category, origin) = dia match
case _: UncheckedWarning => ("unchecked", "")
case w: DeprecationWarning => ("deprecation", w.origin)
case _: FeatureWarning => ("feature", "")
case _ => ("", "")
var entitled = false
def addHelp(what: String)(value: String): Unit =
if !entitled then
sb.append(EOL).append("Matching filters for @nowarn or -Wconf:")
entitled = true
sb.append(EOL).append(" - ").append(what).append(value)
if hasId then
addHelp("id=E")(msg.errorId.errorNumber.toString)
addHelp("name=")(msg.errorId.productPrefix.stripSuffix("ID"))
if category.nonEmpty then
addHelp("cat=")(category)
if origin.nonEmpty then
addHelp("origin=")(origin)

/** The whole message rendered from `msg` */
def messageAndPos(dia: Diagnostic)(using Context): String = {
Expand All @@ -236,7 +241,7 @@ trait MessageRendering {
else 0
given Level = Level(level)
given Offset = Offset(maxLineNumber.toString.length + 2)
val sb = mutable.StringBuilder()
val sb = StringBuilder()
val posString = posStr(pos, msg, diagnosticLevel(dia))
if (posString.nonEmpty) sb.append(posString).append(EOL)
if (pos.exists) {
Expand Down
9 changes: 7 additions & 2 deletions compiler/src/dotty/tools/dotc/reporting/WConf.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,23 +19,27 @@ enum MessageFilter:
case Deprecated => message.isInstanceOf[Diagnostic.DeprecationWarning]
case Feature => message.isInstanceOf[Diagnostic.FeatureWarning]
case Unchecked => message.isInstanceOf[Diagnostic.UncheckedWarning]
case MessageID(errorId) => message.msg.errorId == errorId
case MessagePattern(pattern) =>
val noHighlight = message.msg.message.replaceAll("\\e\\[[\\d;]*[^\\d;]","")
pattern.findFirstIn(noHighlight).nonEmpty
case MessageID(errorId) => message.msg.errorId == errorId
case SourcePattern(pattern) =>
val source = message.position.orElse(NoSourcePosition).source()
val path = source.jfile()
.map(_.toPath.toAbsolutePath.toUri.normalize().getRawPath)
.orElse(source.path())
pattern.findFirstIn(path).nonEmpty

case Origin(pattern) =>
message match
case message: Diagnostic.DeprecationWarning => pattern.findFirstIn(message.origin).nonEmpty
case _ => false
case None => false

case Any, Deprecated, Feature, Unchecked, None
case MessagePattern(pattern: Regex)
case MessageID(errorId: ErrorMessageID)
case SourcePattern(pattern: Regex)
case Origin(pattern: Regex)

enum Action:
case Error, Warning, Verbose, Info, Silent
Expand Down Expand Up @@ -96,6 +100,7 @@ object WConf:
case _ => Left(s"unknown category: $conf")

case "src" => regex(conf).map(SourcePattern.apply)
case "origin" => regex(conf).map(Origin.apply)

case _ => Left(s"unknown filter: $filter")
case _ => Left(s"unknown filter: $s")
Expand Down
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/typer/CrossVersionChecks.scala
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ class CrossVersionChecks extends MiniPhase:
do
val msg = annot.argumentConstantString(0).map(msg => s": $msg").getOrElse("")
val since = annot.argumentConstantString(1).map(version => s" (since: $version)").getOrElse("")
report.deprecationWarning(em"inheritance from $psym is deprecated$since$msg", parent.srcPos)
report.deprecationWarning(em"inheritance from $psym is deprecated$since$msg", parent.srcPos, origin=psym.showFullName)
}

override def transformValDef(tree: ValDef)(using Context): ValDef =
Expand Down Expand Up @@ -171,7 +171,7 @@ object CrossVersionChecks:
def maybeWarn(annotee: Symbol, annot: Annotation) = if !skipWarning(sym) then
val message = annot.argumentConstantString(0).filter(!_.isEmpty).map(": " + _).getOrElse("")
val since = annot.argumentConstantString(1).filter(!_.isEmpty).map(" since " + _).getOrElse("")
report.deprecationWarning(em"${annotee.showLocated} is deprecated${since}${message}", pos)
report.deprecationWarning(em"${annotee.showLocated} is deprecated${since}${message}", pos, origin=annotee.showFullName)
sym.getAnnotation(defn.DeprecatedAnnot) match
case Some(annot) => maybeWarn(sym, annot)
case _ =>
Expand Down
4 changes: 3 additions & 1 deletion compiler/src/dotty/tools/dotc/typer/RefChecks.scala
Original file line number Diff line number Diff line change
Expand Up @@ -484,7 +484,9 @@ object RefChecks {
def overrideDeprecation(what: String, member: Symbol, other: Symbol, fix: String): Unit =
report.deprecationWarning(
em"overriding $what${infoStringWithLocation(other)} is deprecated;\n ${infoString(member)} should be $fix.",
if member.owner == clazz then member.srcPos else clazz.srcPos)
if member.owner == clazz then member.srcPos else clazz.srcPos,
origin = other.showFullName
)

def autoOverride(sym: Symbol) =
sym.is(Synthetic) && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ class ScalaSettingsTests:
val conf = sets.Wconf.valueIn(proc.sstate)
val sut = reporting.WConf.fromSettings(conf).getOrElse(???)
val msg = "There was a problem!".toMessage
val depr = new Diagnostic.DeprecationWarning(msg, util.NoSourcePosition)
val depr = new Diagnostic.DeprecationWarning(msg, util.NoSourcePosition, origin="")
assertEquals(Action.Silent, sut.action(depr))
val feat = new Diagnostic.FeatureWarning(msg, util.NoSourcePosition)
assertEquals(Action.Error, sut.action(feat))
Expand Down Expand Up @@ -197,7 +197,7 @@ class ScalaSettingsTests:
val proc = sets.processArguments(sumy, processAll = true, skipped = Nil)
val conf = sets.Wconf.valueIn(proc.sstate)
val msg = "Don't use that!".toMessage
val depr = new Diagnostic.DeprecationWarning(msg, util.NoSourcePosition)
val depr = new Diagnostic.DeprecationWarning(msg, util.NoSourcePosition, origin="")
val sut = reporting.WConf.fromSettings(conf).getOrElse(???)
assertEquals(Action.Silent, sut.action(depr))

Expand Down Expand Up @@ -293,7 +293,8 @@ class ScalaSettingsTests:
util.SourcePosition(
source = util.SourceFile.virtual(new URI("file:///some/path/file.scala"), ""),
span = util.Spans.Span(1L)
)
),
origin="",
)
)
assertEquals(result, Right(reporting.Action.Error))
Expand Down
14 changes: 14 additions & 0 deletions tests/warn/deprecated-origin-verbose.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
-- Deprecation Warning: tests/warn/deprecated-origin-verbose.scala:12:18 -----------------------------------------------
12 | class D extends C // warn
| ^
| class C in package p is deprecated since 1.0: Old style
Matching filters for @nowarn or -Wconf:
- cat=deprecation
- origin=p.C
-- Deprecation Warning: tests/warn/deprecated-origin-verbose.scala:13:20 -----------------------------------------------
13 | class Oil extends Crude // warn
| ^^^^^
| class Crude in package p is deprecated since 1.0: Bad style
Matching filters for @nowarn or -Wconf:
- cat=deprecation
- origin=p.Crude
15 changes: 15 additions & 0 deletions tests/warn/deprecated-origin-verbose.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//> using options -deprecation -Wconf:any:verbose

package p:
@deprecated("Old style", since="1.0")
class C
@deprecated("Bad style", since="1.0")
class Crude

package q:
import annotation.*
import p.*
class D extends C // warn
class Oil extends Crude // warn
@nowarn("""origin=p\.Crude""")
class Language extends Crude // nowarn obvs
15 changes: 15 additions & 0 deletions tests/warn/deprecated-origin.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//> using options -deprecation -Wconf:origin=p\.C$:s

package p:
@deprecated("Old style", since="1.0")
class C
@deprecated("Bad style", since="1.0")
class Crude

package q:
import annotation.*
import p.*
class D extends C // nowarn - C$ pattern avoids matching Crude
class Oil extends Crude // warn
@nowarn("""origin=p\.Crude""")
class Language extends Crude // nowarn obvs

0 comments on commit 1604e77

Please sign in to comment.