diff --git a/compiler/src/dotty/tools/dotc/Run.scala b/compiler/src/dotty/tools/dotc/Run.scala index f3f948d1e0e6..d0fe07303e41 100644 --- a/compiler/src/dotty/tools/dotc/Run.scala +++ b/compiler/src/dotty/tools/dotc/Run.scala @@ -17,15 +17,15 @@ import Phases.{unfusedPhases, Phase} import sbt.interfaces.ProgressCallback import util.* -import reporting.{Suppression, Action, Profile, ActiveProfile, NoProfile} -import reporting.Diagnostic -import reporting.Diagnostic.Warning +import reporting.{Suppression, Action, Profile, ActiveProfile, MessageFilter, NoProfile, WConf} +import reporting.Diagnostic, Diagnostic.Warning import rewrites.Rewrites import profile.Profiler import printing.XprintMode import typer.ImplicitRunInfo import config.Feature import StdNames.nme +import Spans.Span import java.io.{BufferedWriter, OutputStreamWriter} import java.nio.charset.StandardCharsets @@ -38,6 +38,7 @@ import Run.Progress import scala.compiletime.uninitialized import dotty.tools.dotc.transform.MegaPhase import dotty.tools.dotc.transform.Pickler.AsyncTastyHolder +import dotty.tools.dotc.util.chaining.* import java.util.{Timer, TimerTask} /** A compiler run. Exports various methods to compile source files */ @@ -99,6 +100,26 @@ class Run(comp: Compiler, ictx: Context) extends ImplicitRunInfo with Constraint Action.Warning } + def registerNowarn(annotPos: SourcePosition, range: Span)(conf: String, pos: SrcPos)(using Context): Unit = + var verbose = false + val filters = conf match + case "" => + List(MessageFilter.Any) + case "none" => + List(MessageFilter.None) + case "verbose" | "v" => + verbose = true + List(MessageFilter.Any) + case conf => + WConf.parseFilters(conf).left.map: parseErrors => + report.warning(s"Invalid message filter\n${parseErrors.mkString("\n")}", pos) + List(MessageFilter.None) + .merge + addSuppression: + Suppression(annotPos, filters, range.start, range.end, verbose) + .tap: sup => + if filters == List(MessageFilter.None) then sup.markUsed() // invalid suppressions, don't report as unused + def addSuppression(sup: Suppression): Unit = val suppressions = mySuppressions.getOrElseUpdate(sup.annotPos.source, ListBuffer.empty) if sup.start != sup.end && suppressions.forall(x => x.start != sup.start || x.end != sup.end) then diff --git a/compiler/src/dotty/tools/dotc/inlines/Inliner.scala b/compiler/src/dotty/tools/dotc/inlines/Inliner.scala index 4e125f7dd649..92cae663352a 100644 --- a/compiler/src/dotty/tools/dotc/inlines/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/inlines/Inliner.scala @@ -98,7 +98,8 @@ object Inliner: // InlinerMap is a TreeTypeMap with special treatment for inlined arguments: // They are generally left alone (not mapped further, and if they wrap a type - // the type Inlined wrapper gets dropped + // the type Inlined wrapper gets dropped. + // As a side effect, register @nowarn annotations from annotated expressions. private class InlinerMap( typeMap: Type => Type, treeMap: Tree => Tree, @@ -115,6 +116,23 @@ object Inliner: ConservativeTreeCopier() ): + override def transform(tree: Tree)(using Context): Tree = + tree match + case Typed(expr, tpt) => + def loop(tpe: Type): Unit = + tpe match + case AnnotatedType(parent, annot) => + if annot.hasSymbol(defn.NowarnAnnot) then + val argPos = annot.argument(0).getOrElse(tree).sourcePos + val conf = annot.argumentConstantString(0).getOrElse("") + ctx.run.nn.suppressions.registerNowarn(tree.sourcePos, expr.span)(conf, argPos) + else + loop(parent) + case _ => + loop(tpt.tpe) + case _ => + super.transform(tree) + override def copy( typeMap: Type => Type, treeMap: Tree => Tree, diff --git a/compiler/src/dotty/tools/dotc/inlines/Inlines.scala b/compiler/src/dotty/tools/dotc/inlines/Inlines.scala index 85b1234461c8..85bea871b955 100644 --- a/compiler/src/dotty/tools/dotc/inlines/Inlines.scala +++ b/compiler/src/dotty/tools/dotc/inlines/Inlines.scala @@ -23,6 +23,7 @@ import collection.mutable import reporting.{NotConstant, trace} import util.Spans.Span import dotty.tools.dotc.core.Periods.PhaseId +import dotty.tools.dotc.util.chaining.* /** Support for querying inlineable methods and for inlining calls to such methods */ object Inlines: @@ -44,6 +45,11 @@ object Inlines: def bodyToInline(sym: SymDenotation)(using Context): Tree = if hasBodyToInline(sym) then sym.getAnnotation(defn.BodyAnnot).get.tree + .tap: body => + for annot <- sym.getAnnotation(defn.NowarnAnnot) do + val argPos = annot.argument(0).getOrElse(annot.tree).sourcePos + val conf = annot.argumentConstantString(0).getOrElse("") + ctx.run.nn.suppressions.registerNowarn(annot.tree.sourcePos, body.span)(conf, argPos) else EmptyTree diff --git a/compiler/src/dotty/tools/dotc/reporting/WConf.scala b/compiler/src/dotty/tools/dotc/reporting/WConf.scala index f29bef75959a..ac25f2f6cd30 100644 --- a/compiler/src/dotty/tools/dotc/reporting/WConf.scala +++ b/compiler/src/dotty/tools/dotc/reporting/WConf.scala @@ -139,8 +139,10 @@ object WConf: class Suppression(val annotPos: SourcePosition, filters: List[MessageFilter], val start: Int, val end: Int, val verbose: Boolean): private var _used = false def used: Boolean = _used - def markUsed(): Unit = { _used = true } - + def markUsed(): Unit = + _used = true def matches(dia: Diagnostic): Boolean = val pos = dia.pos pos.exists && start <= pos.start && pos.end <= end && filters.forall(_.matches(dia)) + + override def toString = s"Suppress in ${annotPos.source} $start..$end [${filters.mkString(", ")}]" diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 1372c0d5e27e..be3186720fa1 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2850,31 +2850,15 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer def registerNowarn(tree: Tree, mdef: untpd.Tree)(using Context): Unit = val annot = Annotations.Annotation(tree) - def argPos = annot.argument(0).getOrElse(tree).sourcePos - var verbose = false - val filters = annot.argumentConstantString(0) match - case None => annot.argument(0) match - case Some(t: Select) if t.name.is(DefaultGetterName) => - // default argument used for `@nowarn` and `@nowarn()` - List(MessageFilter.Any) - case _ => - report.warning(s"filter needs to be a compile-time constant string", argPos) - List(MessageFilter.None) - case Some("") => - List(MessageFilter.Any) - case Some("verbose") | Some("v") => - verbose = true - List(MessageFilter.Any) - case Some(s) => - WConf.parseFilters(s).left.map(parseErrors => - report.warning (s"Invalid message filter\n${parseErrors.mkString ("\n")}", argPos) - List(MessageFilter.None) - ).merge - val range = mdef.sourcePos - val sup = Suppression(tree.sourcePos, filters, range.start, range.end, verbose) - // invalid suppressions, don't report as unused - if filters == List(MessageFilter.None) then sup.markUsed() - ctx.run.nn.suppressions.addSuppression(sup) + val argPos = annot.argument(0).getOrElse(tree).sourcePos + val conf = annot.argumentConstantString(0).getOrElse: + annot.argument(0) match + case Some(t: Select) if t.name.is(DefaultGetterName) => + "" // default argument used for `@nowarn` and `@nowarn()` + case _ => + report.warning(s"filter needs to be a compile-time constant string", argPos) + "none" // not a -Wconf filter, mapped to MessageFilter.None by registerNowarn + ctx.run.nn.suppressions.registerNowarn(tree.sourcePos, mdef.span)(conf, argPos) /** Run `typed` on `rhs` except if `rhs` is the right hand side of a deferred given, * in which case the empty tree is returned. diff --git a/tests/warn/i22672/lib_1.scala b/tests/warn/i22672/lib_1.scala new file mode 100644 index 000000000000..722f2ae03899 --- /dev/null +++ b/tests/warn/i22672/lib_1.scala @@ -0,0 +1,16 @@ + +package p + +import annotation.{unchecked as _, *} + +@deprecated("old api", since="1.0") +def g = 42 + +//@deprecated("new api", since="1.0") +@nowarn("cat=deprecation") +inline def f = + g + +transparent inline def body = + g: @nowarn @unchecked + g: @unchecked @nowarn diff --git a/tests/warn/i22672/usage_2.scala b/tests/warn/i22672/usage_2.scala new file mode 100644 index 000000000000..39c0550b8fd4 --- /dev/null +++ b/tests/warn/i22672/usage_2.scala @@ -0,0 +1,13 @@ + +//> using options -deprecation + +package q + +def test = p.f // inline f is nowarn + +def bodily = p.body // transparent inline with annotated body + +@deprecated("do I even know how it works", since="0.1") +def huh = "hello" + +def failing = huh // warn