From 5a05c0ac83f8c8b6db6ea3392e5d65f43463e408 Mon Sep 17 00:00:00 2001 From: Tomasz Godzik Date: Fri, 25 Jul 2025 09:11:02 +0200 Subject: [PATCH 1/2] bugfix: Also save infos in semanticdb (#23587) Previously, when user changed a warning to info, we would not save it and miss information if that warning was about unused. Now, we also save infos. https://github.com/scalacenter/scalafix/issues/2269 [Cherry-picked 00d19dfd0906d2b4a8c076ded8af15cc3a6f2dc9] --- .../dotty/tools/dotc/reporting/Reporter.scala | 10 ++- .../dotc/semanticdb/ExtractSemanticDB.scala | 10 +-- .../semanticdb/expect/InfoMacro.expect.scala | 14 ++++ tests/semanticdb/expect/InfoMacro.scala | 14 ++++ .../expect/InfoMacroTest.expect.scala | 7 ++ tests/semanticdb/expect/InfoMacroTest.scala | 7 ++ tests/semanticdb/metac.expect | 83 +++++++++++++++++++ 7 files changed, 139 insertions(+), 6 deletions(-) create mode 100644 tests/semanticdb/expect/InfoMacro.expect.scala create mode 100644 tests/semanticdb/expect/InfoMacro.scala create mode 100644 tests/semanticdb/expect/InfoMacroTest.expect.scala create mode 100644 tests/semanticdb/expect/InfoMacroTest.scala diff --git a/compiler/src/dotty/tools/dotc/reporting/Reporter.scala b/compiler/src/dotty/tools/dotc/reporting/Reporter.scala index eb9756028c27..a0f454f1818c 100644 --- a/compiler/src/dotty/tools/dotc/reporting/Reporter.scala +++ b/compiler/src/dotty/tools/dotc/reporting/Reporter.scala @@ -95,6 +95,7 @@ abstract class Reporter extends interfaces.ReporterResult { private var _errorCount = 0 private var _warningCount = 0 + private var _infoCount = 0 /** The number of errors reported by this reporter (ignoring outer reporters) */ def errorCount: Int = _errorCount @@ -112,12 +113,17 @@ abstract class Reporter extends interfaces.ReporterResult { private var warnings: List[Warning] = Nil + private var infos: List[Info] = Nil + /** All errors reported by this reporter (ignoring outer reporters) */ def allErrors: List[Error] = errors /** All warnings reported by this reporter (ignoring outer reporters) */ def allWarnings: List[Warning] = warnings + /** All infos reported by this reporter (ignoring outer reporters) */ + def allInfos: List[Info] = infos + /** Were sticky errors reported? Overridden in StoreReporter. */ def hasStickyErrors: Boolean = false @@ -171,7 +177,9 @@ abstract class Reporter extends interfaces.ReporterResult { _errorCount += 1 if ctx.typerState.isGlobalCommittable then ctx.base.errorsToBeReported = true - case _: Info => // nothing to do here + case i: Info => + infos = i :: infos + _infoCount += 1 // match error if d is something else } markReported(dia) diff --git a/compiler/src/dotty/tools/dotc/semanticdb/ExtractSemanticDB.scala b/compiler/src/dotty/tools/dotc/semanticdb/ExtractSemanticDB.scala index 076a7deda4b1..d39d3d095bb7 100644 --- a/compiler/src/dotty/tools/dotc/semanticdb/ExtractSemanticDB.scala +++ b/compiler/src/dotty/tools/dotc/semanticdb/ExtractSemanticDB.scala @@ -63,7 +63,7 @@ class ExtractSemanticDB private (phaseMode: ExtractSemanticDB.PhaseMode) extends private def computeDiagnostics( sourceRoot: String, - warnings: Map[SourceFile, List[Warning]], + warnings: Map[SourceFile, List[dotty.tools.dotc.reporting.Diagnostic]], append: ((Path, List[Diagnostic])) => Unit)(using Context): Boolean = monitor(phaseName) { val unit = ctx.compilationUnit warnings.get(unit.source).foreach { ws => @@ -104,14 +104,14 @@ class ExtractSemanticDB private (phaseMode: ExtractSemanticDB.PhaseMode) extends val appendDiagnostics = phaseMode == ExtractSemanticDB.PhaseMode.AppendDiagnostics val unitContexts = units.map(ctx.fresh.setCompilationUnit(_).withRootImports) if (appendDiagnostics) - val warnings = ctx.reporter.allWarnings.groupBy(w => w.pos.source) + val warningsAndInfos = (ctx.reporter.allWarnings ++ ctx.reporter.allInfos).groupBy(w => w.pos.source) val buf = mutable.ListBuffer.empty[(Path, Seq[Diagnostic])] val units0 = - for unitCtx <- unitContexts if computeDiagnostics(sourceRoot, warnings, buf += _)(using unitCtx) + for unitCtx <- unitContexts if computeDiagnostics(sourceRoot, warningsAndInfos, buf += _)(using unitCtx) yield unitCtx.compilationUnit cancellable { - buf.toList.asJava.parallelStream().forEach { case (out, warnings) => - ExtractSemanticDB.appendDiagnostics(warnings, out) + buf.toList.asJava.parallelStream().forEach { case (out, diagnostics) => + ExtractSemanticDB.appendDiagnostics(diagnostics, out) } } units0 diff --git a/tests/semanticdb/expect/InfoMacro.expect.scala b/tests/semanticdb/expect/InfoMacro.expect.scala new file mode 100644 index 000000000000..67122d6cbf37 --- /dev/null +++ b/tests/semanticdb/expect/InfoMacro.expect.scala @@ -0,0 +1,14 @@ +import scala.quoted.* + +object InfoMacro/*<-_empty_::InfoMacro.*/ { + inline def reportInfo/*<-_empty_::InfoMacro.reportInfo().*/(msg/*<-_empty_::InfoMacro.reportInfo().(msg)*/: String/*->scala::Predef.String#*/): Unit/*->scala::Unit#*/ = ${ reportInfoMacro/*->_empty_::InfoMacro.reportInfoMacro().*/('msg) } + + def reportInfoMacro/*<-_empty_::InfoMacro.reportInfoMacro().*/(msg/*<-_empty_::InfoMacro.reportInfoMacro().(msg)*/: Expr/*->scala::quoted::Expr#*/[String/*->scala::Predef.String#*/])(using Quotes/*->scala::quoted::Quotes#*/): Expr/*->scala::quoted::Expr#*/[Unit/*->scala::Unit#*/] = { + import quotes/*->scala::quoted::Quotes$package.quotes().*/.reflect/*->scala::quoted::Quotes#reflect.*/.report/*->scala::quoted::Quotes#reflectModule#report.*/ + + // Report an info diagnostic + report/*->scala::quoted::Quotes#reflectModule#report.*/.info/*->scala::quoted::Quotes#reflectModule#reportModule#info().*/(s/*->scala::StringContext#s().*/"Info from macro: ${msg/*->_empty_::InfoMacro.reportInfoMacro().(msg)*/.valueOrAbort/*->scala::quoted::Quotes#valueOrAbort().*/}") + + '{ () } + } +} diff --git a/tests/semanticdb/expect/InfoMacro.scala b/tests/semanticdb/expect/InfoMacro.scala new file mode 100644 index 000000000000..9248d51dd03a --- /dev/null +++ b/tests/semanticdb/expect/InfoMacro.scala @@ -0,0 +1,14 @@ +import scala.quoted.* + +object InfoMacro { + inline def reportInfo(msg: String): Unit = ${ reportInfoMacro('msg) } + + def reportInfoMacro(msg: Expr[String])(using Quotes): Expr[Unit] = { + import quotes.reflect.report + + // Report an info diagnostic + report.info(s"Info from macro: ${msg.valueOrAbort}") + + '{ () } + } +} diff --git a/tests/semanticdb/expect/InfoMacroTest.expect.scala b/tests/semanticdb/expect/InfoMacroTest.expect.scala new file mode 100644 index 000000000000..19977da0c79f --- /dev/null +++ b/tests/semanticdb/expect/InfoMacroTest.expect.scala @@ -0,0 +1,7 @@ + +object InfoMacroTest/*<-_empty_::InfoMacroTest.*/ { + def main/*<-_empty_::InfoMacroTest.main().*/(): Unit/*->scala::Unit#*/ = { + InfoMacro/*->_empty_::InfoMacro.*/.reportInfo/*->_empty_::InfoMacro.reportInfo().*/("This is a test info message") + InfoMacro/*->_empty_::InfoMacro.*/.reportInfo/*->_empty_::InfoMacro.reportInfo().*/("Another info message") + } +} diff --git a/tests/semanticdb/expect/InfoMacroTest.scala b/tests/semanticdb/expect/InfoMacroTest.scala new file mode 100644 index 000000000000..e53a840efc8a --- /dev/null +++ b/tests/semanticdb/expect/InfoMacroTest.scala @@ -0,0 +1,7 @@ + +object InfoMacroTest { + def main(): Unit = { + InfoMacro.reportInfo("This is a test info message") + InfoMacro.reportInfo("Another info message") + } +} diff --git a/tests/semanticdb/metac.expect b/tests/semanticdb/metac.expect index 80cfc754c057..592bf586fcd9 100644 --- a/tests/semanticdb/metac.expect +++ b/tests/semanticdb/metac.expect @@ -1980,6 +1980,89 @@ Occurrences: Diagnostics: [0:26..0:34): [warning] unused import +expect/InfoMacro.scala +---------------------- + +Summary: +Schema => SemanticDB v4 +Uri => InfoMacro.scala +Text => empty +Language => Scala +Symbols => 7 entries +Occurrences => 23 entries +Synthetics => 6 entries + +Symbols: +_empty_/InfoMacro. => final object InfoMacro extends Object { self: InfoMacro.type => +3 decls } +_empty_/InfoMacro.reportInfo(). => inline macro reportInfo (param msg: String): Unit +_empty_/InfoMacro.reportInfo().(msg) => param msg: String +_empty_/InfoMacro.reportInfoMacro(). => method reportInfoMacro (param msg: Expr[String])(implicit given param x$2: Quotes): Expr[Unit] +_empty_/InfoMacro.reportInfoMacro().(msg) => param msg: Expr[String] +_empty_/InfoMacro.reportInfoMacro().(x$2) => implicit given param x$2: Quotes +local0 => implicit given param contextual$1: Quotes + +Occurrences: +[0:7..0:12): scala -> scala/ +[0:13..0:19): quoted -> scala/quoted/ +[2:7..2:16): InfoMacro <- _empty_/InfoMacro. +[3:13..3:23): reportInfo <- _empty_/InfoMacro.reportInfo(). +[3:24..3:27): msg <- _empty_/InfoMacro.reportInfo().(msg) +[3:29..3:35): String -> scala/Predef.String# +[3:38..3:42): Unit -> scala/Unit# +[3:48..3:63): reportInfoMacro -> _empty_/InfoMacro.reportInfoMacro(). +[5:6..5:21): reportInfoMacro <- _empty_/InfoMacro.reportInfoMacro(). +[5:22..5:25): msg <- _empty_/InfoMacro.reportInfoMacro().(msg) +[5:27..5:31): Expr -> scala/quoted/Expr# +[5:32..5:38): String -> scala/Predef.String# +[5:47..5:53): Quotes -> scala/quoted/Quotes# +[5:56..5:60): Expr -> scala/quoted/Expr# +[5:61..5:65): Unit -> scala/Unit# +[6:11..6:17): quotes -> scala/quoted/Quotes$package.quotes(). +[6:18..6:25): reflect -> scala/quoted/Quotes#reflect. +[6:26..6:32): report -> scala/quoted/Quotes#reflectModule#report. +[9:4..9:10): report -> scala/quoted/Quotes#reflectModule#report. +[9:11..9:15): info -> scala/quoted/Quotes#reflectModule#reportModule#info(). +[9:16..9:17): s -> scala/StringContext#s(). +[9:37..9:40): msg -> _empty_/InfoMacro.reportInfoMacro().(msg) +[9:41..9:53): valueOrAbort -> scala/quoted/Quotes#valueOrAbort(). + +Synthetics: +[3:48..3:69):reportInfoMacro('msg) => *(contextual$1) +[3:64..3:68):'msg => orig()(contextual$1) +[6:11..6:17):quotes => *(x$2) +[9:37..9:53):msg.valueOrAbort => *(StringFromExpr[String]) +[9:41..9:53):valueOrAbort => *[String] +[11:4..11:11):'{ () } => orig(())(x$2) + +expect/InfoMacroTest.scala +-------------------------- + +Summary: +Schema => SemanticDB v4 +Uri => InfoMacroTest.scala +Text => empty +Language => Scala +Symbols => 2 entries +Occurrences => 7 entries +Diagnostics => 2 entries + +Symbols: +_empty_/InfoMacroTest. => final object InfoMacroTest extends Object { self: InfoMacroTest.type => +2 decls } +_empty_/InfoMacroTest.main(). => method main (): Unit + +Occurrences: +[1:7..1:20): InfoMacroTest <- _empty_/InfoMacroTest. +[2:6..2:10): main <- _empty_/InfoMacroTest.main(). +[2:14..2:18): Unit -> scala/Unit# +[3:4..3:13): InfoMacro -> _empty_/InfoMacro. +[3:14..3:24): reportInfo -> _empty_/InfoMacro.reportInfo(). +[4:4..4:13): InfoMacro -> _empty_/InfoMacro. +[4:14..4:24): reportInfo -> _empty_/InfoMacro.reportInfo(). + +Diagnostics: +[3:4..3:55): [info] Info from macro: This is a test info message +[4:4..4:48): [info] Info from macro: Another info message + expect/InstrumentTyper.scala ---------------------------- From c0929ded030e754a806398222a9d6d7cda73b47a Mon Sep 17 00:00:00 2001 From: Tomasz Godzik Date: Mon, 28 Jul 2025 16:58:05 +0200 Subject: [PATCH 2/2] bugfix: Fix tests after backporting changes --- .../tools/dotc/fromtasty/TastyFileUtil.scala | 1 + .../tools/pc/InferredMethodProvider.scala | 92 +++++++++---------- tests/semanticdb/metac.expect | 6 +- tests/warn/tostring-interpolated.scala | 2 +- 4 files changed, 51 insertions(+), 50 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/fromtasty/TastyFileUtil.scala b/compiler/src/dotty/tools/dotc/fromtasty/TastyFileUtil.scala index 9bd3016de1f5..e6baafe9bdb7 100644 --- a/compiler/src/dotty/tools/dotc/fromtasty/TastyFileUtil.scala +++ b/compiler/src/dotty/tools/dotc/fromtasty/TastyFileUtil.scala @@ -42,6 +42,7 @@ object TastyFileUtil { s"${className.lastPart.encode}" else s"${packageName.encode}.${className.lastPart.encode}" + } } diff --git a/presentation-compiler/src/main/dotty/tools/pc/InferredMethodProvider.scala b/presentation-compiler/src/main/dotty/tools/pc/InferredMethodProvider.scala index e6f27781bc64..d4299f6b1f95 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/InferredMethodProvider.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/InferredMethodProvider.scala @@ -101,8 +101,8 @@ final class InferredMethodProvider( .mkString(", ") def printSignature( - methodName: Name, - params: List[List[Type]], + methodName: Name, + params: List[List[Type]], retTypeOpt: Option[Type] ): String = val retTypeString = retTypeOpt match @@ -129,7 +129,7 @@ final class InferredMethodProvider( if pos > 0 then val isSpace = text(pos) == ' ' val isTab = text(pos) == '\t' - val indent = countIndent(params.text(), pos, 0) + val indent = countIndent(params.text().nn, pos, 0) if isSpace then " " * indent else if isTab then "\t" * indent else "" else "" @@ -145,8 +145,8 @@ final class InferredMethodProvider( /** * Returns the position to insert the method signature for a container. * If the container has an empty body, the position is the end of the container. - * If the container has a non-empty body, the position is the end of the last element in the body. - * + * If the container has a non-empty body, the position is the end of the last element in the body. + * * @param container the container to insert the method signature for * @return the position to insert the method signature for the container and a boolean indicating if the container has an empty body */ @@ -170,9 +170,9 @@ final class InferredMethodProvider( /** * Extracts type information for a specific parameter in a method signature. - * If the parameter is a function type, extracts both the function's argument types + * If the parameter is a function type, extracts both the function's argument types * and return type. Otherwise, extracts just the parameter type. - * + * * @param methodType the method type to analyze * @param argIndex the index of the parameter to extract information for * @return a tuple of (argument types, return type) where: @@ -192,10 +192,10 @@ final class InferredMethodProvider( else (None, Some(m.paramInfos(argIndex))) case _ => (None, None) - + def signatureEdits(signature: String): List[TextEdit] = val pos = insertPosition() - val indent = indentation(params.text(), pos.start - 1) + val indent = indentation(params.text().nn, pos.start - 1) val lspPos = pos.toLsp lspPos.setEnd(lspPos.getStart()) @@ -211,7 +211,7 @@ final class InferredMethodProvider( case Some((pos, hasEmptyBody)) => val lspPos = pos.toLsp lspPos.setStart(lspPos.getEnd()) - val indent = indentation(params.text(), pos.start - 1) + val indent = indentation(params.text().nn, pos.start - 1) if hasEmptyBody then List( @@ -234,17 +234,17 @@ final class InferredMethodProvider( * outerArgs * --------------------------- * method(..., errorMethod(args), ...) - * + * */ - case (id @ Ident(errorMethod)) :: - (apply @ Apply(func, args)) :: - Apply(method, outerArgs) :: + case (id @ Ident(errorMethod)) :: + (apply @ Apply(func, args)) :: + Apply(method, outerArgs) :: _ if id.symbol == NoSymbol && func == id && method != apply => - + val argTypes = args.map(_.typeOpt.widenDealias) val argIndex = outerArgs.indexOf(apply) - val (allArgTypes, retTypeOpt) = + val (allArgTypes, retTypeOpt) = extractParameterTypeInfo(method.tpe.widenDealias, argIndex) match case (Some(argTypes2), retTypeOpt) => (List(argTypes, argTypes2), retTypeOpt) case (None, retTypeOpt) => (List(argTypes), retTypeOpt) @@ -257,12 +257,12 @@ final class InferredMethodProvider( * outerArgs * --------------------- * method(..., errorMethod, ...) - * + * */ - case (id @ Ident(errorMethod)) :: - Apply(method, outerArgs) :: + case (id @ Ident(errorMethod)) :: + Apply(method, outerArgs) :: _ if id.symbol == NoSymbol && method != id => - + val argIndex = outerArgs.indexOf(id) val (argTypes, retTypeOpt) = extractParameterTypeInfo(method.tpe.widenDealias, argIndex) @@ -272,20 +272,20 @@ final class InferredMethodProvider( case None => Nil val signature = printSignature(errorMethod, allArgTypes, retTypeOpt) - + signatureEdits(signature) /** * tpt body * ----------- ---------------- * val value: DefinedType = errorMethod(args) - * + * */ - case (id @ Ident(errorMethod)) :: - (apply @ Apply(func, args)) :: - ValDef(_, tpt, body) :: + case (id @ Ident(errorMethod)) :: + (apply @ Apply(func, args)) :: + ValDef(_, tpt, body) :: _ if id.symbol == NoSymbol && func == id && apply == body => - + val retType = tpt.tpe.widenDealias val argTypes = args.map(_.typeOpt.widenDealias) @@ -296,24 +296,24 @@ final class InferredMethodProvider( * tpt body * ----------- ----------- * val value: DefinedType = errorMethod - * + * */ - case (id @ Ident(errorMethod)) :: - ValDef(_, tpt, body) :: + case (id @ Ident(errorMethod)) :: + ValDef(_, tpt, body) :: _ if id.symbol == NoSymbol && id == body => - + val retType = tpt.tpe.widenDealias val signature = printSignature(errorMethod, Nil, Some(retType)) signatureEdits(signature) /** - * + * * errorMethod(args) - * + * */ - case (id @ Ident(errorMethod)) :: - (apply @ Apply(func, args)) :: + case (id @ Ident(errorMethod)) :: + (apply @ Apply(func, args)) :: _ if id.symbol == NoSymbol && func == id => val argTypes = args.map(_.typeOpt.widenDealias) @@ -322,37 +322,37 @@ final class InferredMethodProvider( signatureEdits(signature) /** - * + * * errorMethod - * + * */ - case (id @ Ident(errorMethod)) :: + case (id @ Ident(errorMethod)) :: _ if id.symbol == NoSymbol => val signature = printSignature(errorMethod, Nil, None) signatureEdits(signature) /** - * + * * container.errorMethod(args) - * + * */ - case (select @ Select(container, errorMethod)) :: - (apply @ Apply(func, args)) :: + case (select @ Select(container, errorMethod)) :: + (apply @ Apply(func, args)) :: _ if select.symbol == NoSymbol && func == select => - + val argTypes = args.map(_.typeOpt.widenDealias) val signature = printSignature(errorMethod, List(argTypes), None) signatureEditsForContainer(signature, container) /** - * + * * container.errorMethod - * + * */ - case (select @ Select(container, errorMethod)) :: + case (select @ Select(container, errorMethod)) :: _ if select.symbol == NoSymbol => - + val signature = printSignature(errorMethod, Nil, None) signatureEditsForContainer(signature, container) diff --git a/tests/semanticdb/metac.expect b/tests/semanticdb/metac.expect index 592bf586fcd9..712f6ef99b3c 100644 --- a/tests/semanticdb/metac.expect +++ b/tests/semanticdb/metac.expect @@ -1999,7 +1999,7 @@ _empty_/InfoMacro.reportInfo().(msg) => param msg: String _empty_/InfoMacro.reportInfoMacro(). => method reportInfoMacro (param msg: Expr[String])(implicit given param x$2: Quotes): Expr[Unit] _empty_/InfoMacro.reportInfoMacro().(msg) => param msg: Expr[String] _empty_/InfoMacro.reportInfoMacro().(x$2) => implicit given param x$2: Quotes -local0 => implicit given param contextual$1: Quotes +local0 => implicit given param evidence$1: Quotes Occurrences: [0:7..0:12): scala -> scala/ @@ -2027,8 +2027,8 @@ Occurrences: [9:41..9:53): valueOrAbort -> scala/quoted/Quotes#valueOrAbort(). Synthetics: -[3:48..3:69):reportInfoMacro('msg) => *(contextual$1) -[3:64..3:68):'msg => orig()(contextual$1) +[3:48..3:69):reportInfoMacro('msg) => *(evidence$1) +[3:64..3:68):'msg => orig()(evidence$1) [6:11..6:17):quotes => *(x$2) [9:37..9:53):msg.valueOrAbort => *(StringFromExpr[String]) [9:41..9:53):valueOrAbort => *[String] diff --git a/tests/warn/tostring-interpolated.scala b/tests/warn/tostring-interpolated.scala index 165bc374b5ef..a7e9129b162d 100644 --- a/tests/warn/tostring-interpolated.scala +++ b/tests/warn/tostring-interpolated.scala @@ -29,7 +29,7 @@ class Mitigations { def ok = s"$s is ok" def jersey = s"number $i" - def unitized = s"unfortunately $shown" // maybe tell them about unintended ()? + def unitized = s"unfortunately $shown" // warn def nopct = f"$s is ok" def nofmt = f"number $i"