diff --git a/project/MimaFilters.scala b/project/MimaFilters.scala index cd2cd01e208..9f75e4349ad 100644 --- a/project/MimaFilters.scala +++ b/project/MimaFilters.scala @@ -39,6 +39,8 @@ object MimaFilters extends AutoPlugin { ProblemFilters.exclude[DirectMissingMethodProblem]("scala.concurrent.impl.FutureConvertersImpl#P.accept"), ProblemFilters.exclude[IncompatibleMethTypeProblem]("scala.concurrent.impl.FutureConvertersImpl#P.andThen"), + // PR 10406 + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.reflect.runtime.JavaUniverse#PerRunReporting.deprecationWarning"), ) override val buildSettings = Seq( diff --git a/src/compiler/scala/reflect/macros/contexts/Parsers.scala b/src/compiler/scala/reflect/macros/contexts/Parsers.scala index 7ac893d1378..2dfa8322503 100644 --- a/src/compiler/scala/reflect/macros/contexts/Parsers.scala +++ b/src/compiler/scala/reflect/macros/contexts/Parsers.scala @@ -32,7 +32,7 @@ trait Parsers { }) val tree = gen.mkTreeOrBlock(parser.parseStatsOrPackages()) sreporter.infos.foreach { - case Info(pos, msg, Reporter.ERROR) => throw ParseException(pos, msg) + case Info(pos, msg, Reporter.ERROR, _) => throw ParseException(pos, msg) case _ => } tree diff --git a/src/compiler/scala/tools/nsc/CompilationUnits.scala b/src/compiler/scala/tools/nsc/CompilationUnits.scala index fe971938438..156d5e177b4 100644 --- a/src/compiler/scala/tools/nsc/CompilationUnits.scala +++ b/src/compiler/scala/tools/nsc/CompilationUnits.scala @@ -47,6 +47,9 @@ trait CompilationUnits { global: Global => def freshTermName(prefix: String = nme.FRESH_TERM_NAME_PREFIX) = global.freshTermName(prefix) def freshTypeName(prefix: String) = global.freshTypeName(prefix) + def sourceAt(pos: Position): String = + if (pos.start < pos.end) new String(source.content.slice(pos.start, pos.end)) else "" + /** the content of the compilation unit in tree form */ var body: Tree = EmptyTree diff --git a/src/compiler/scala/tools/nsc/Parsing.scala b/src/compiler/scala/tools/nsc/Parsing.scala index 7d48e27678d..b78f8feab1f 100644 --- a/src/compiler/scala/tools/nsc/Parsing.scala +++ b/src/compiler/scala/tools/nsc/Parsing.scala @@ -14,6 +14,7 @@ package scala package tools.nsc import scala.reflect.internal.Positions +import scala.reflect.internal.util.CodeAction /** Similar to Reporting: gather global functionality specific to parsing. */ @@ -35,8 +36,8 @@ trait Parsing { self : Positions with Reporting => } def incompleteHandled = incompleteHandler != null - def incompleteInputError(pos: Position, msg: String): Unit = + def incompleteInputError(pos: Position, msg: String, actions: List[CodeAction] = Nil): Unit = if (incompleteHandled) incompleteHandler(pos, msg) - else reporter.error(pos, msg) + else reporter.error(pos, msg, actions) } } diff --git a/src/compiler/scala/tools/nsc/Reporting.scala b/src/compiler/scala/tools/nsc/Reporting.scala index f5a96415803..7c0b33efc5c 100644 --- a/src/compiler/scala/tools/nsc/Reporting.scala +++ b/src/compiler/scala/tools/nsc/Reporting.scala @@ -19,7 +19,7 @@ import scala.annotation.nowarn import scala.collection.mutable import scala.reflect.internal import scala.reflect.internal.util.StringOps.countElementsAsString -import scala.reflect.internal.util.{NoSourceFile, Position, SourceFile} +import scala.reflect.internal.util.{CodeAction, NoSourceFile, Position, SourceFile} import scala.tools.nsc.Reporting.Version.{NonParseableVersion, ParseableVersion} import scala.tools.nsc.Reporting._ import scala.tools.nsc.settings.NoScalaVersion @@ -99,7 +99,7 @@ trait Reporting extends internal.Reporting { self: ast.Positions with Compilatio source <- suppressions.keysIterator.toList sups <- suppressions.remove(source) sup <- sups.reverse - } if (!sup.used && !sup.synthetic) issueWarning(Message.Plain(sup.annotPos, "@nowarn annotation does not suppress any warnings", WarningCategory.UnusedNowarn, "")) + } if (!sup.used && !sup.synthetic) issueWarning(Message.Plain(sup.annotPos, "@nowarn annotation does not suppress any warnings", WarningCategory.UnusedNowarn, "", Nil)) } def reportSuspendedMessages(unit: CompilationUnit): Unit = { @@ -120,16 +120,16 @@ trait Reporting extends internal.Reporting { self: ast.Positions with Compilatio private def issueWarning(warning: Message): Unit = { def verbose = warning match { - case Message.Deprecation(_, msg, site, origin, version) => s"[${warning.category.name} @ $site | origin=$origin | version=${version.filterString}] $msg" - case Message.Origin(_, msg, category, site, origin @ _) => s"[${category.name} @ $site] $msg" // origin text is obvious at caret - case Message.Plain(_, msg, category, site) => s"[${category.name} @ $site] $msg" + case Message.Deprecation(_, msg, site, origin, version, _) => s"[${warning.category.name} @ $site | origin=$origin | version=${version.filterString}] $msg" + case Message.Origin(_, msg, category, site, origin @ _, _) => s"[${category.name} @ $site] $msg" // origin text is obvious at caret + case Message.Plain(_, msg, category, site, _) => s"[${category.name} @ $site] $msg" } wconf.action(warning) match { - case Action.Error => reporter.error(warning.pos, warning.msg) - case Action.Warning => reporter.warning(warning.pos, warning.msg) - case Action.WarningVerbose => reporter.warning(warning.pos, verbose) - case Action.Info => reporter.echo(warning.pos, warning.msg) - case Action.InfoVerbose => reporter.echo(warning.pos, verbose) + case Action.Error => reporter.error(warning.pos, warning.msg, warning.actions) + case Action.Warning => reporter.warning(warning.pos, warning.msg, warning.actions) + case Action.WarningVerbose => reporter.warning(warning.pos, verbose, warning.actions) + case Action.Info => reporter.echo(warning.pos, warning.msg, warning.actions) + case Action.InfoVerbose => reporter.echo(warning.pos, verbose, warning.actions) case a @ (Action.WarningSummary | Action.InfoSummary) => val m = summaryMap(a, warning.category.summaryCategory) if (!m.contains(warning.pos)) m.addOne((warning.pos, warning)) @@ -206,9 +206,12 @@ trait Reporting extends internal.Reporting { self: ast.Positions with Compilatio impl(sym) } else "" - override def deprecationWarning(pos: Position, msg: String, since: String, site: String, origin: String): Unit = - issueIfNotSuppressed(Message.Deprecation(pos, msg, site, origin, Version.fromString(since))) + override def deprecationWarning(pos: Position, msg: String, since: String, site: String, origin: String, actions: List[CodeAction] = Nil): Unit = + issueIfNotSuppressed(Message.Deprecation(pos, msg, site, origin, Version.fromString(since), actions)) + // multiple overloads cannot use default args + def deprecationWarning(pos: Position, origin: Symbol, site: Symbol, msg: String, since: String, actions: List[CodeAction]): Unit = + deprecationWarning(pos, msg, since, siteName(site), siteName(origin), actions) def deprecationWarning(pos: Position, origin: Symbol, site: Symbol, msg: String, since: String): Unit = deprecationWarning(pos, msg, since, siteName(site), siteName(origin)) @@ -268,17 +271,19 @@ trait Reporting extends internal.Reporting { self: ast.Positions with Compilatio } else warning(pos, msg, featureCategory(featureTrait.nameString), site) } - // Used in the optimizer where we don't have no symbols, the site string is created from the class internal name and method name. + // Used in the optimizer where we don't have symbols, the site string is created from the class internal name and method name. + def warning(pos: Position, msg: String, category: WarningCategory, site: String, actions: List[CodeAction]): Unit = + issueIfNotSuppressed(Message.Plain(pos, msg, category, site, actions)) def warning(pos: Position, msg: String, category: WarningCategory, site: String): Unit = - issueIfNotSuppressed(Message.Plain(pos, msg, category, site)) + warning(pos, msg, category, site, Nil) // Preferred over the overload above whenever a site symbol is available - def warning(pos: Position, msg: String, category: WarningCategory, site: Symbol): Unit = - warning(pos, msg, category, siteName(site)) + def warning(pos: Position, msg: String, category: WarningCategory, site: Symbol, actions: List[CodeAction] = Nil): Unit = + warning(pos, msg, category, siteName(site), actions) // Provide an origin for the warning. def warning(pos: Position, msg: String, category: WarningCategory, site: Symbol, origin: String): Unit = - issueIfNotSuppressed(Message.Origin(pos, msg, category, siteName(site), origin)) + issueIfNotSuppressed(Message.Origin(pos, msg, category, siteName(site), origin, actions = Nil)) // used by Global.deprecationWarnings, which is used by sbt def deprecationWarnings: List[(Position, String)] = summaryMap(Action.WarningSummary, WarningCategory.Deprecation).toList.map(p => (p._1, p._2.msg)) @@ -320,17 +325,18 @@ object Reporting { def msg: String def category: WarningCategory def site: String // sym.FullName of the location where the warning is positioned, may be empty + def actions: List[CodeAction] } object Message { // an ordinary Message has a `category` for filtering and the `site` where it was issued - final case class Plain(pos: Position, msg: String, category: WarningCategory, site: String) extends Message + final case class Plain(pos: Position, msg: String, category: WarningCategory, site: String, actions: List[CodeAction]) extends Message // a Plain message with an `origin` which should not be empty. For example, the origin of an unused import is the fully-qualified selection - final case class Origin(pos: Position, msg: String, category: WarningCategory, site: String, origin: String) extends Message + final case class Origin(pos: Position, msg: String, category: WarningCategory, site: String, origin: String, actions: List[CodeAction]) extends Message // `site` and `origin` may be empty - final case class Deprecation(pos: Position, msg: String, site: String, origin: String, since: Version) extends Message { + final case class Deprecation(pos: Position, msg: String, site: String, origin: String, since: Version, actions: List[CodeAction]) extends Message { def category: WarningCategory = WarningCategory.Deprecation } } @@ -538,7 +544,7 @@ object Reporting { final case class DeprecatedSince(comp: Int, version: ParseableVersion) extends MessageFilter { def matches(message: Message): Boolean = message match { - case Message.Deprecation(_, _, _, _, mv: ParseableVersion) => + case Message.Deprecation(_, _, _, _, mv: ParseableVersion, _) => if (comp == -1) mv.smaller(version) else if (comp == 0) mv.same(version) else mv.greater(version) diff --git a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala index 5b97c782096..554a173d277 100644 --- a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala @@ -19,7 +19,14 @@ package ast.parser import scala.annotation.tailrec import scala.collection.mutable, mutable.ListBuffer import scala.reflect.internal.{ModifierFlags => Flags, Precedence} -import scala.reflect.internal.util.{FreshNameCreator, ListOfNil, Position, SourceFile} +import scala.reflect.internal.util.{ + CodeAction, + FreshNameCreator, + ListOfNil, + Position, + SourceFile, + TextEdit, +} import Tokens._ import scala.tools.nsc.Reporting.WarningCategory @@ -48,7 +55,7 @@ trait ParsersCommon extends ScannersCommon { */ abstract class ParserCommon { val in: ScannerCommon - def deprecationWarning(off: Offset, msg: String, since: String): Unit + def deprecationWarning(off: Offset, msg: String, since: String, actions: List[CodeAction] = Nil): Unit def accept(token: Token): Int /** Methods inParensOrError and similar take a second argument which, should @@ -175,11 +182,11 @@ self => def unit = global.currentUnit // suppress warnings; silent abort on errors - def warning(offset: Offset, msg: String, category: WarningCategory): Unit = () - def deprecationWarning(offset: Offset, msg: String, since: String): Unit = () + def warning(offset: Offset, msg: String, category: WarningCategory, actions: List[CodeAction]): Unit = () + def deprecationWarning(offset: Offset, msg: String, since: String, actions: List[CodeAction]): Unit = () - def syntaxError(offset: Offset, msg: String): Unit = throw new MalformedInput(offset, msg) - def incompleteInputError(msg: String): Unit = throw new MalformedInput(source.content.length - 1, msg) + def syntaxError(offset: Offset, msg: String, actions: List[CodeAction]): Unit = throw new MalformedInput(offset, msg) + def incompleteInputError(msg: String, actions: List[CodeAction]): Unit = throw new MalformedInput(source.content.length - 1, msg) object symbXMLBuilder extends SymbolicXMLBuilder(this, preserveWS = true) { // DEBUG choices val global: self.global.type = self.global @@ -225,12 +232,12 @@ self => override def newScanner() = new UnitScanner(unit, patches) - override def warning(offset: Offset, msg: String, category: WarningCategory): Unit = - runReporting.warning(o2p(offset), msg, category, site = "") + override def warning(offset: Offset, msg: String, category: WarningCategory, actions: List[CodeAction]): Unit = + runReporting.warning(o2p(offset), msg, category, site = "", actions) - override def deprecationWarning(offset: Offset, msg: String, since: String): Unit = + override def deprecationWarning(offset: Offset, msg: String, since: String, actions: List[CodeAction]): Unit = // we cannot provide a `site` in the parser, there's no context telling us where we are - runReporting.deprecationWarning(o2p(offset), msg, since, site = "", origin = "") + runReporting.deprecationWarning(o2p(offset), msg, since, site = "", origin = "", actions) private var smartParsing = false @inline private def withSmartParsing[T](body: => T): T = { @@ -241,20 +248,20 @@ self => } def withPatches(patches: List[BracePatch]): UnitParser = new UnitParser(unit, patches) - val syntaxErrors = new ListBuffer[(Int, String)] + val syntaxErrors = new ListBuffer[(Int, String, List[CodeAction])] def showSyntaxErrors() = - for ((offset, msg) <- syntaxErrors) - reporter.error(o2p(offset), msg) + for ((offset, msg, actions) <- syntaxErrors) + reporter.error(o2p(offset), msg, actions) - override def syntaxError(offset: Offset, msg: String): Unit = { - if (smartParsing) syntaxErrors += ((offset, msg)) - else reporter.error(o2p(offset), msg) + override def syntaxError(offset: Offset, msg: String, actions: List[CodeAction]): Unit = { + if (smartParsing) syntaxErrors += ((offset, msg, actions)) + else reporter.error(o2p(offset), msg, actions) } - override def incompleteInputError(msg: String): Unit = { + override def incompleteInputError(msg: String, actions: List[CodeAction]): Unit = { val offset = source.content.length - 1 - if (smartParsing) syntaxErrors += ((offset, msg)) - else currentRun.parsing.incompleteInputError(o2p(offset), msg) + if (smartParsing) syntaxErrors += ((offset, msg, actions)) + else currentRun.parsing.incompleteInputError(o2p(offset), msg, actions) } /** parse unit. If there are unbalanced braces, @@ -576,50 +583,61 @@ self => in.nextToken() } } - def warning(offset: Offset, msg: String, category: WarningCategory): Unit - def incompleteInputError(msg: String): Unit - def syntaxError(offset: Offset, msg: String): Unit - private def syntaxError(pos: Position, msg: String, skipIt: Boolean): Unit = - syntaxError(pos pointOrElse in.offset, msg, skipIt) - def syntaxError(msg: String, skipIt: Boolean): Unit = - syntaxError(in.offset, msg, skipIt) + def warning(offset: Offset, msg: String, category: WarningCategory, actions: List[CodeAction] = Nil): Unit + + def incompleteInputError(msg: String, actions: List[CodeAction] = Nil): Unit + + def syntaxError(offset: Offset, msg: String, actions: List[CodeAction] = Nil): Unit + + private def syntaxError(pos: Position, msg: String, skipIt: Boolean): Unit = syntaxError(pos, msg, skipIt, Nil) + private def syntaxError(pos: Position, msg: String, skipIt: Boolean, actions: List[CodeAction]): Unit = + syntaxError(pos pointOrElse in.offset, msg, skipIt, actions) - def syntaxError(offset: Offset, msg: String, skipIt: Boolean): Unit = { + def syntaxError(msg: String, skipIt: Boolean): Unit = syntaxError(msg, skipIt, Nil) + def syntaxError(msg: String, skipIt: Boolean, actions: List[CodeAction]): Unit = + syntaxError(in.offset, msg, skipIt, actions) + + def syntaxError(offset: Offset, msg: String, skipIt: Boolean): Unit = syntaxError(offset, msg, skipIt, Nil) + def syntaxError(offset: Offset, msg: String, skipIt: Boolean, actions: List[CodeAction]): Unit = { if (offset > lastErrorOffset) { - syntaxError(offset, msg) + syntaxError(offset, msg, actions) lastErrorOffset = in.offset // no more errors on this token. } if (skipIt) skip(UNDEF) } - def warning(msg: String, category: WarningCategory): Unit = warning(in.offset, msg, category) + def warning(msg: String, category: WarningCategory): Unit = warning(in.offset, msg, category, Nil) + def warning(msg: String, category: WarningCategory, actions: List[CodeAction]): Unit = warning(in.offset, msg, category, actions) - def syntaxErrorOrIncomplete(msg: String, skipIt: Boolean): Unit = { + def syntaxErrorOrIncomplete(msg: String, skipIt: Boolean, actions: List[CodeAction] = Nil): Unit = { if (in.token == EOF) - incompleteInputError(msg) + incompleteInputError(msg, actions) else - syntaxError(in.offset, msg, skipIt) + syntaxError(in.offset, msg, skipIt, actions) } - def syntaxErrorOrIncompleteAnd[T](msg: String, skipIt: Boolean)(and: T): T = { - syntaxErrorOrIncomplete(msg, skipIt) + def syntaxErrorOrIncompleteAnd[T](msg: String, skipIt: Boolean, actions: List[CodeAction] = Nil)(and: T): T = { + syntaxErrorOrIncomplete(msg, skipIt, actions) and } // warn under -Xsource:3 - def migrationWarning(offset: Offset, msg: String, since: String): Unit = - if (currentRun.isScala3) warning(offset, msg, WarningCategory.Scala3Migration) + def migrationWarning(offset: Offset, msg: String, since: String, actions: List[CodeAction] = Nil): Unit = + if (currentRun.isScala3) + warning(offset, msg, WarningCategory.Scala3Migration, actions) // warn under -Xsource:3, otherwise deprecation - def hardMigrationWarning(offset: Offset, msg: String, since: String): Unit = - if (currentRun.isScala3) warning(offset, msg, WarningCategory.Scala3Migration) - else deprecationWarning(offset, msg, since) + def hardMigrationWarning(offset: Offset, msg: String, since: String, actions: List[CodeAction] = Nil): Unit = + if (currentRun.isScala3) warning(offset, msg, WarningCategory.Scala3Migration, actions) + else deprecationWarning(offset, msg, since, actions) // deprecation or migration under -Xsource:3, with different messages + def hardMigrationWarning(offset: Offset, depr: => String, migr: => String, since: String, actions: List[CodeAction]): Unit = + if (currentRun.isScala3) warning(offset, migr, WarningCategory.Scala3Migration, actions) + else deprecationWarning(offset, depr, since, actions) def hardMigrationWarning(offset: Offset, depr: => String, migr: => String, since: String): Unit = - if (currentRun.isScala3) warning(offset, migr, WarningCategory.Scala3Migration) - else deprecationWarning(offset, depr, since) + hardMigrationWarning(offset, depr, migr, since, Nil) def expectedMsgTemplate(exp: String, fnd: String) = s"$exp expected but $fnd found." def expectedMsg(token: Token): String = @@ -797,7 +815,11 @@ self => val msg = "parentheses are required around the parameter of a lambda" val wrn = sm"""|$msg |Use '-Wconf:msg=lambda-parens:s' to silence this warning.""" - migrationWarning(tree.pos.point, wrn, "2.13.11") + def actions = + if (tree.pos.isRange) + List(CodeAction("lambda parameter", Some(msg), List(TextEdit(tree.pos, s"(${unit.sourceAt(tree.pos)})")))) + else Nil + migrationWarning(tree.pos.point, wrn, "2.13.11", actions) List(convertToParam(tree)) case _ => List(convertToParam(tree)) } @@ -2064,6 +2086,7 @@ self => in.skipCASE() val hasVal = in.token == VAL + val valOffset = in.offset if (hasVal) in.nextToken() @@ -2072,12 +2095,17 @@ self => val hasEq = in.token == EQUALS if (hasVal) { + def actions = { + val pos = r2p(valOffset, valOffset, valOffset + 4) + if (unit.sourceAt(pos) != "val ") Nil else + List(CodeAction("val in for comprehension", None, List(TextEdit(pos, "")))) + } def msg(what: String, instead: String): String = s"`val` keyword in for comprehension is $what: $instead" if (hasEq) { val without = "instead, bind the value without `val`" - hardMigrationWarning(in.offset, msg("deprecated", without), msg("unsupported", without), "2.10.0") + hardMigrationWarning(in.offset, msg("deprecated", without), msg("unsupported", without), "2.10.0", actions) } - else syntaxError(in.offset, msg("unsupported", "just remove `val`")) + else syntaxError(in.offset, msg("unsupported", "just remove `val`"), actions) } if (hasEq && eqOK && !hasCase) in.nextToken() @@ -2978,16 +3006,18 @@ self => var restype = fromWithinReturnType(typedOpt()) def msg(what: String, instead: String) = s"procedure syntax is $what: instead, add `$instead` to explicitly declare `$name`'s return type" + def declActions = List(CodeAction("procedure syntax (decl)", None, List(TextEdit(o2p(in.lastOffset), ": Unit")))) + def defnActions = List(CodeAction("procedure syntax (defn)", None, List(TextEdit(o2p(in.lastOffset), ": Unit =")))) val rhs = if (isStatSep || in.token == RBRACE) { if (restype.isEmpty) { - hardMigrationWarning(in.lastOffset, msg("deprecated", ": Unit"), msg("unsupported", ": Unit"), "2.13.0") + hardMigrationWarning(in.lastOffset, msg("deprecated", ": Unit"), msg("unsupported", ": Unit"), "2.13.0", declActions) restype = scalaUnitConstr } newmods |= Flags.DEFERRED EmptyTree } else if (restype.isEmpty && in.token == LBRACE) { - hardMigrationWarning(in.offset, msg("deprecated", ": Unit ="), msg("unsupported", ": Unit ="), "2.13.0") + hardMigrationWarning(in.offset, msg("deprecated", ": Unit ="), msg("unsupported", ": Unit ="), "2.13.0", defnActions) restype = scalaUnitConstr blockExpr() } else { diff --git a/src/compiler/scala/tools/nsc/ast/parser/Scanners.scala b/src/compiler/scala/tools/nsc/ast/parser/Scanners.scala index 51d1c0a4be1..3a7d180416c 100644 --- a/src/compiler/scala/tools/nsc/ast/parser/Scanners.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/Scanners.scala @@ -74,7 +74,7 @@ trait ScannersCommon { def error(off: Offset, msg: String): Unit def incompleteInputError(off: Offset, msg: String): Unit def warning(off: Offset, msg: String, category: WarningCategory): Unit - def deprecationWarning(off: Offset, msg: String, since: String): Unit + def deprecationWarning(off: Offset, msg: String, since: String, actions: List[CodeAction] = Nil): Unit // advance past COMMA NEWLINE RBRACE (to whichever token is the matching close bracket) def skipTrailingComma(right: Token): Boolean = false @@ -1402,7 +1402,8 @@ trait Scanners extends ScannersCommon { /** generate an error at the current token offset */ def syntaxError(msg: String): Unit = syntaxError(offset, msg) - def deprecationWarning(msg: String, since: String): Unit = deprecationWarning(offset, msg, since) + def deprecationWarning(msg: String, since: String): Unit = deprecationWarning(msg, since, Nil) + def deprecationWarning(msg: String, since: String, actions: List[CodeAction]): Unit = deprecationWarning(offset, msg, since, actions) /** signal an error where the input ended in the middle of a token */ def incompleteInputError(msg: String): Unit = { @@ -1576,7 +1577,7 @@ trait Scanners extends ScannersCommon { // suppress warnings, throw exception on errors def warning(off: Offset, msg: String, category: WarningCategory): Unit = () - def deprecationWarning(off: Offset, msg: String, since: String): Unit = () + def deprecationWarning(off: Offset, msg: String, since: String, actions: List[CodeAction]): Unit = () def error(off: Offset, msg: String): Unit = throw new MalformedInput(off, msg) def incompleteInputError(off: Offset, msg: String): Unit = throw new MalformedInput(off, msg) } @@ -1586,10 +1587,14 @@ trait Scanners extends ScannersCommon { class UnitScanner(val unit: CompilationUnit, patches: List[BracePatch]) extends SourceFileScanner(unit.source) { def this(unit: CompilationUnit) = this(unit, List()) - override def warning(off: Offset, msg: String, category: WarningCategory): Unit = runReporting.warning(unit.position(off), msg, category, site = "") - override def deprecationWarning(off: Offset, msg: String, since: String) = runReporting.deprecationWarning(unit.position(off), msg, since, site = "", origin = "") - override def error(off: Offset, msg: String) = reporter.error(unit.position(off), msg) - override def incompleteInputError(off: Offset, msg: String) = currentRun.parsing.incompleteInputError(unit.position(off), msg) + override def warning(off: Offset, msg: String, category: WarningCategory): Unit = + runReporting.warning(unit.position(off), msg, category, site = "") + override def deprecationWarning(off: Offset, msg: String, since: String, actions: List[CodeAction]) = + runReporting.deprecationWarning(unit.position(off), msg, since, site = "", origin = "", actions) + override def error(off: Offset, msg: String) = + reporter.error(unit.position(off), msg) + override def incompleteInputError(off: Offset, msg: String) = + currentRun.parsing.incompleteInputError(unit.position(off), msg) private var bracePatches: List[BracePatch] = patches diff --git a/src/compiler/scala/tools/nsc/javac/JavaParsers.scala b/src/compiler/scala/tools/nsc/javac/JavaParsers.scala index ffe71142df0..f54ce5feca9 100644 --- a/src/compiler/scala/tools/nsc/javac/JavaParsers.scala +++ b/src/compiler/scala/tools/nsc/javac/JavaParsers.scala @@ -22,7 +22,7 @@ import scala.annotation._ import scala.collection.mutable import scala.collection.mutable.ListBuffer import scala.language.implicitConversions -import scala.reflect.internal.util.{ListOfNil, Position} +import scala.reflect.internal.util.{CodeAction, ListOfNil, Position} import scala.tools.nsc.Reporting.WarningCategory import scala.util.chaining._ @@ -38,7 +38,7 @@ trait JavaParsers extends ast.parser.ParsersCommon with JavaScanners { def freshName(prefix: String): Name = freshTermName(prefix) def freshTermName(prefix: String): TermName = unit.freshTermName(prefix) def freshTypeName(prefix: String): TypeName = unit.freshTypeName(prefix) - def deprecationWarning(off: Int, msg: String, since: String) = runReporting.deprecationWarning(off, msg, since, site = "", origin = "") + def deprecationWarning(off: Int, msg: String, since: String, actions: List[CodeAction]) = runReporting.deprecationWarning(off, msg, since, site = "", origin = "", actions) implicit def i2p(offset : Int) : Position = Position.offset(unit.source, offset) def warning(pos : Int, msg : String) : Unit = runReporting.warning(pos, msg, WarningCategory.JavaSource, site = "") def syntaxError(pos: Int, msg: String) : Unit = reporter.error(pos, msg) diff --git a/src/compiler/scala/tools/nsc/javac/JavaScanners.scala b/src/compiler/scala/tools/nsc/javac/JavaScanners.scala index 0733376c029..8e80da83af3 100644 --- a/src/compiler/scala/tools/nsc/javac/JavaScanners.scala +++ b/src/compiler/scala/tools/nsc/javac/JavaScanners.scala @@ -1031,7 +1031,7 @@ trait JavaScanners extends ast.parser.ScannersCommon { def error(pos: Int, msg: String) = reporter.error(pos, msg) def incompleteInputError(pos: Int, msg: String) = currentRun.parsing.incompleteInputError(pos, msg) def warning(pos: Int, msg: String, category: WarningCategory) = runReporting.warning(pos, msg, category, site = "") - def deprecationWarning(pos: Int, msg: String, since: String) = runReporting.deprecationWarning(pos, msg, since, site = "", origin = "") + def deprecationWarning(pos: Int, msg: String, since: String, actions: List[CodeAction]) = runReporting.deprecationWarning(pos, msg, since, site = "", origin = "", actions) implicit def g2p(pos: Int): Position = Position.offset(unit.source, pos) } } diff --git a/src/compiler/scala/tools/nsc/reporters/ConsoleReporter.scala b/src/compiler/scala/tools/nsc/reporters/ConsoleReporter.scala index 9dafc2426d7..ce0bd318638 100644 --- a/src/compiler/scala/tools/nsc/reporters/ConsoleReporter.scala +++ b/src/compiler/scala/tools/nsc/reporters/ConsoleReporter.scala @@ -15,14 +15,14 @@ package tools.nsc package reporters import java.io.{BufferedReader, PrintWriter} -import scala.reflect.internal.util.Position +import scala.reflect.internal.util.{CodeAction, Position} /** This class implements a Reporter that displays messages on a text console. */ class ConsoleReporter(val settings: Settings, val reader: BufferedReader, val writer: PrintWriter, val echoWriter: PrintWriter) extends FilteringReporter with PrintReporter { def this(settings: Settings) = this(settings, Console.in, new PrintWriter(Console.err, true), new PrintWriter(Console.out, true)) def this(settings: Settings, reader: BufferedReader, writer: PrintWriter) = this(settings, reader, writer, writer) - def doReport(pos: Position, msg: String, severity: Severity): Unit = display(pos, msg, severity) + override def doReport(pos: Position, msg: String, severity: Severity, actions: List[CodeAction]): Unit = display(pos, msg, severity) override def finish(): Unit = { import reflect.internal.util.StringOps.countElementsAsString diff --git a/src/compiler/scala/tools/nsc/reporters/ForwardingReporter.scala b/src/compiler/scala/tools/nsc/reporters/ForwardingReporter.scala index bca541105a5..61a7751c737 100644 --- a/src/compiler/scala/tools/nsc/reporters/ForwardingReporter.scala +++ b/src/compiler/scala/tools/nsc/reporters/ForwardingReporter.scala @@ -12,7 +12,7 @@ package scala.tools.nsc.reporters import scala.reflect.internal.settings.MutableSettings -import scala.reflect.internal.util.Position +import scala.reflect.internal.util.{CodeAction, Position} import scala.tools.nsc.Settings @@ -20,7 +20,7 @@ import scala.tools.nsc.Settings * customize error reporting. * {{{ * val myReporter = new ForwardingReporter(global.reporter) { - * override def doReport(pos: Position, msg: String, severity: Severity): Unit = { ... } + * override def doReport(pos: Position, msg: String, severity: Severity, actions: List[Action]): Unit = { ... } * } * global.reporter = myReporter * }}} @@ -28,7 +28,8 @@ import scala.tools.nsc.Settings class ForwardingReporter(delegate: FilteringReporter) extends FilteringReporter { def settings: Settings = delegate.settings - def doReport(pos: Position, msg: String, severity: Severity): Unit = delegate.doReport(pos, msg, severity) + override def doReport(pos: Position, msg: String, severity: Severity, actions: List[CodeAction]): Unit = + delegate.doReport(pos, msg, severity, actions) override def filter(pos: Position, msg: String, severity: Severity): Int = delegate.filter(pos, msg, severity) @@ -56,7 +57,8 @@ class ForwardingReporter(delegate: FilteringReporter) extends FilteringReporter * maxerrs and do position filtering. */ class MakeFilteringForwardingReporter(delegate: Reporter, val settings: Settings) extends FilteringReporter { - def doReport(pos: Position, msg: String, severity: Severity): Unit = delegate.nonProtectedInfo0(pos, msg, severity) + override def doReport(pos: Position, msg: String, severity: Severity, actions: List[CodeAction]): Unit = + delegate.doReport(pos, msg, severity, actions) override def increment(severity: Severity): Unit = delegate.increment(severity) diff --git a/src/compiler/scala/tools/nsc/reporters/NoReporter.scala b/src/compiler/scala/tools/nsc/reporters/NoReporter.scala index b59a0444d13..95bec3701ef 100644 --- a/src/compiler/scala/tools/nsc/reporters/NoReporter.scala +++ b/src/compiler/scala/tools/nsc/reporters/NoReporter.scala @@ -12,11 +12,11 @@ package scala.tools.nsc.reporters -import scala.reflect.internal.util.Position +import scala.reflect.internal.util.{CodeAction, Position} import scala.tools.nsc.Settings /** A reporter that ignores reports. */ class NoReporter(val settings: Settings) extends FilteringReporter { - def doReport(pos: Position, msg: String, severity: Severity): Unit = () + override def doReport(pos: Position, msg: String, severity: Severity, actions: List[CodeAction]): Unit = () } diff --git a/src/compiler/scala/tools/nsc/reporters/Reporter.scala b/src/compiler/scala/tools/nsc/reporters/Reporter.scala index 5dde7649bbe..9510d878026 100644 --- a/src/compiler/scala/tools/nsc/reporters/Reporter.scala +++ b/src/compiler/scala/tools/nsc/reporters/Reporter.scala @@ -13,7 +13,7 @@ package scala.tools.nsc package reporters -import scala.annotation.unused +import scala.annotation.{nowarn, unused} import scala.collection.mutable import scala.reflect.internal import scala.reflect.internal.util.{Position, ScalaClassLoader} @@ -27,10 +27,6 @@ abstract class Reporter extends internal.Reporter { @deprecated("Use echo, as internal.Reporter does not support unforced info", since="2.13.0") final def info(pos: Position, msg: String, @unused force: Boolean): Unit = info0(pos, msg, INFO, force = true) - // allow calling info0 in MakeFilteringForwardingReporter - private[reporters] final def nonProtectedInfo0(pos: Position, msg: String, severity: Severity): Unit = - info0(pos, msg, severity, force = true) - // overridden by sbt, IDE -- should not be in the reporting interface // (IDE receives comments from ScaladocAnalyzer using this hook method) // TODO: IDE should override a hook method in the parser instead @@ -97,11 +93,19 @@ object Reporter { abstract class FilteringReporter extends Reporter { def settings: Settings + @deprecatedOverriding("override the `doReport` overload (defined in reflect.internal.Reporter) instead", "2.13.12") + @deprecated("use the `doReport` overload instead", "2.13.12") + def doReport(pos: Position, msg: String, severity: Severity): Unit = doReport(pos, msg, severity, Nil) + // this should be the abstract method all the way up in reflect.internal.Reporter, but sbt compat - def doReport(pos: Position, msg: String, severity: Severity): Unit + // the abstract override is commented-out to maintain binary compatibility for FilteringReporter subclasses + // override def doReport(pos: Position, msg: String, severity: Severity, actions: List[CodeAction]): Unit - @deprecatedOverriding("override doReport instead", "2.13.1") // overridden in scalameta for example - protected def info0(pos: Position, msg: String, severity: Severity, force: Boolean): Unit = doReport(pos, msg, severity) + @deprecatedOverriding("override `doReport` instead", "2.13.1") // overridden in scalameta for example + @nowarn("cat=deprecation") + protected def info0(pos: Position, msg: String, severity: Severity, force: Boolean): Unit = + // call the deprecated overload to support existing FilteringReporter subclasses (they override that overload) + doReport(pos, msg, severity) private lazy val positions = mutable.Map[Position, Severity]() withDefaultValue INFO private lazy val messages = mutable.Map[Position, List[String]]() withDefaultValue Nil @@ -118,8 +122,8 @@ abstract class FilteringReporter extends Reporter { } // Invoked when an error or warning is filtered by position. @inline def suppress = { - if (settings.prompt.value) doReport(pos, msg, severity) - else if (settings.isDebug) doReport(pos, s"[ suppressed ] $msg", severity) + if (settings.prompt.value) doReport(pos, msg, severity, Nil) + else if (settings.isDebug) doReport(pos, s"[ suppressed ] $msg", severity, Nil) Suppress } if (!duplicateOk(pos, severity, msg)) suppress else if (!maxOk) Count else Display diff --git a/src/compiler/scala/tools/nsc/reporters/StoreReporter.scala b/src/compiler/scala/tools/nsc/reporters/StoreReporter.scala index f5e5c6b414f..07d3545e3c2 100644 --- a/src/compiler/scala/tools/nsc/reporters/StoreReporter.scala +++ b/src/compiler/scala/tools/nsc/reporters/StoreReporter.scala @@ -16,7 +16,7 @@ package reporters import scala.annotation.unchecked.uncheckedStable import scala.collection.mutable import scala.reflect.internal.Reporter.Severity -import scala.reflect.internal.util.Position +import scala.reflect.internal.util.{CodeAction, Position} /** This class implements a Reporter that stores its reports in the set `infos`. */ class StoreReporter(val settings: Settings) extends FilteringReporter { @@ -31,8 +31,10 @@ class StoreReporter(val settings: Settings) extends FilteringReporter { val infos = new mutable.LinkedHashSet[StoreReporter.Info] - def doReport(pos: Position, msg: String, severity: Severity): Unit = - infos += StoreReporter.Info(pos, msg, severity) + override def doReport(pos: Position, msg: String, severity: Severity, actions: List[CodeAction]): Unit = { + val info = StoreReporter.Info(pos, msg, severity, actions) + infos += info + } override def reset(): Unit = { super.reset() @@ -40,7 +42,7 @@ class StoreReporter(val settings: Settings) extends FilteringReporter { } } object StoreReporter { - case class Info(pos: Position, msg: String, severity: Severity) { - override def toString: String = s"pos: $pos $msg $severity" + case class Info(pos: Position, msg: String, severity: Severity, actions: List[CodeAction]) { + override def toString: String = s"pos: $pos $msg $severity${if (actions.isEmpty) "" else " " + actions}" } } diff --git a/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala b/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala index 8d7a2581407..c6051f7a41a 100644 --- a/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala +++ b/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala @@ -15,7 +15,6 @@ package typechecker import scala.reflect.internal.util.StringOps.{countAsString, countElementsAsString} import java.lang.System.{lineSeparator => EOL} - import scala.PartialFunction.cond import scala.annotation.tailrec import scala.reflect.runtime.ReflectionUtils @@ -24,7 +23,7 @@ import scala.util.control.{ControlThrowable, NonFatal} import scala.tools.nsc.Reporting.WarningCategory import scala.tools.nsc.util.stackTraceString import scala.reflect.io.NoAbstractFile -import scala.reflect.internal.util.NoSourceFile +import scala.reflect.internal.util.{CodeAction, NoSourceFile} trait ContextErrors extends splain.SplainErrors { self: Analyzer => @@ -32,10 +31,17 @@ trait ContextErrors extends splain.SplainErrors { import global._ import definitions._ + final case class ContextWarning(pos: Position, msg: String, cat: WarningCategory, sym: Symbol, actions: List[CodeAction]) + sealed abstract class AbsTypeError { def errPos: Position def errMsg: String override def toString() = "[Type error at:" + errPos + "] " + errMsg + + // To include code actions in type errors, add a field to the corresponding case class + // override val actions: List[CodeAction] = Nil + // See TypeErrorWrapper for example + def actions: List[CodeAction] = Nil } abstract class AbsAmbiguousTypeError extends AbsTypeError @@ -65,7 +71,7 @@ trait ContextErrors extends splain.SplainErrors { def errPos = underlyingSym.pos } - case class TypeErrorWrapper(ex: TypeError) + case class TypeErrorWrapper(ex: TypeError, override val actions: List[CodeAction] = Nil) extends AbsTypeError { def errMsg = ex.msg def errPos = ex.pos diff --git a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala index b76149843e6..a7936511be5 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala @@ -14,8 +14,8 @@ package scala.tools.nsc package typechecker import scala.annotation.tailrec -import scala.collection.{immutable, mutable} -import scala.reflect.internal.util.{ReusableInstance, shortClassOfInstance, ListOfNil, SomeOfNil} +import scala.collection.mutable +import scala.reflect.internal.util.{CodeAction, ReusableInstance, shortClassOfInstance, ListOfNil, SomeOfNil} import scala.tools.nsc.Reporting.WarningCategory import scala.util.chaining._ @@ -812,15 +812,25 @@ trait Contexts { self: Analyzer => // /** Issue/buffer/throw the given type error according to the current mode for error reporting. */ - private[typechecker] def issue(err: AbsTypeError) = reporter.issue(err)(this) + private[typechecker] def issue(err: AbsTypeError) = reporter.issue(err)(this) + /** Issue/buffer/throw the given implicit ambiguity error according to the current mode for error reporting. */ private[typechecker] def issueAmbiguousError(err: AbsAmbiguousTypeError) = reporter.issueAmbiguousError(err)(this) + /** Issue/throw the given error message according to the current mode for error reporting. */ - def error(pos: Position, msg: String) = reporter.errorAndDumpIfDebug(fixPosition(pos), msg) + def error(pos: Position, msg: String, actions: List[CodeAction] = Nil) = + reporter.errorAndDumpIfDebug(fixPosition(pos), msg, actions) + /** Issue/throw the given error message according to the current mode for error reporting. */ - def warning(pos: Position, msg: String, category: WarningCategory) = reporter.warning(fixPosition(pos), msg, category, owner) - def warning(pos: Position, msg: String, category: WarningCategory, site: Symbol) = reporter.warning(fixPosition(pos), msg, category, site) - def echo(pos: Position, msg: String) = reporter.echo(fixPosition(pos), msg) + def warning(pos: Position, msg: String, category: WarningCategory, actions: List[CodeAction] = Nil): Unit = + reporter.warning(fixPosition(pos), msg, category, owner, actions) + def warning(pos: Position, msg: String, category: WarningCategory, site: Symbol, actions: List[CodeAction]): Unit = + reporter.warning(fixPosition(pos), msg, category, site, actions) + def warning(pos: Position, msg: String, category: WarningCategory, site: Symbol): Unit = + warning(pos, msg, category, site, Nil) + + def echo(pos: Position, msg: String) = reporter.echo(fixPosition(pos), msg) + def fixPosition(pos: Position): Position = pos match { case NoPosition => nextEnclosing(_.tree.pos != NoPosition).tree.pos case _ => pos @@ -828,9 +838,8 @@ trait Contexts { self: Analyzer => // TODO: buffer deprecations under silent (route through ContextReporter, store in BufferingReporter) - def deprecationWarning(pos: Position, sym: Symbol, msg: String, since: String): Unit = - runReporting.deprecationWarning(fixPosition(pos), sym, owner, msg, since) - + def deprecationWarning(pos: Position, sym: Symbol, msg: String, since: String, actions: List[CodeAction] = Nil): Unit = + runReporting.deprecationWarning(fixPosition(pos), sym, owner, msg, since, actions) def deprecationWarning(pos: Position, sym: Symbol): Unit = runReporting.deprecationWarning(fixPosition(pos), sym, owner) @@ -1712,24 +1721,19 @@ trait Contexts { self: Analyzer => * * To handle nested contexts, reporters share buffers. TODO: only buffer in BufferingReporter, emit immediately in ImmediateReporter */ - abstract class ContextReporter(private[this] var _errorBuffer: mutable.LinkedHashSet[AbsTypeError] = null, private[this] var _warningBuffer: mutable.LinkedHashSet[(Position, String, WarningCategory, Symbol)] = null) { - type Error = AbsTypeError - type Warning = (Position, String, WarningCategory, Symbol) - - def issue(err: AbsTypeError)(implicit context: Context): Unit = errorAndDumpIfDebug(context.fixPosition(err.errPos), addDiagString(err.errMsg)) + abstract class ContextReporter(private[this] var _errorBuffer: mutable.LinkedHashSet[AbsTypeError] = null, private[this] var _warningBuffer: mutable.LinkedHashSet[ContextWarning] = null) { + def issue(err: AbsTypeError)(implicit context: Context): Unit = errorAndDumpIfDebug(context.fixPosition(err.errPos), addDiagString(err.errMsg), err.actions) def echo(msg: String): Unit = echo(NoPosition, msg) + def echo(pos: Position, msg: String): Unit = reporter.echo(pos, msg) - def echo(pos: Position, msg: String): Unit = - reporter.echo(pos, msg) - - def warning(pos: Position, msg: String, category: WarningCategory, site: Symbol): Unit = - runReporting.warning(pos, msg, category, site) + def warning(pos: Position, msg: String, category: WarningCategory, site: Symbol, actions: List[CodeAction] = Nil): Unit = + runReporting.warning(pos, msg, category, site, actions) - def error(pos: Position, msg: String): Unit + def error(pos: Position, msg: String, actions: List[CodeAction]): Unit - final def errorAndDumpIfDebug(pos: Position, msg: String): Unit = { - error(pos, msg) + final def errorAndDumpIfDebug(pos: Position, msg: String, actions: List[CodeAction]): Unit = { + error(pos, msg, actions) if (settings.VdebugTypeError.value) { Thread.dumpStack() } @@ -1748,7 +1752,7 @@ trait Contexts { self: Analyzer => * - else, let this context reporter decide */ final def issueAmbiguousError(err: AbsAmbiguousTypeError)(implicit context: Context): Unit = - if (context.ambiguousErrors) reporter.error(context.fixPosition(err.errPos), addDiagString(err.errMsg)) // force reporting... see TODO above + if (context.ambiguousErrors) reporter.error(context.fixPosition(err.errPos), addDiagString(err.errMsg), err.actions) // force reporting... see TODO above else handleSuppressedAmbiguous(err) @inline final def withFreshErrorBuffer[T](expr: => T): T = { @@ -1766,7 +1770,7 @@ trait Contexts { self: Analyzer => if (target.isBuffering) { target ++= errors } else { - errors.foreach(e => target.errorAndDumpIfDebug(e.errPos, e.errMsg)) + errors.foreach(e => target.errorAndDumpIfDebug(e.errPos, e.errMsg, e.actions)) } // TODO: is clearAllErrors necessary? (no tests failed when dropping it) // NOTE: even though `this ne target`, it may still be that `target.errorBuffer eq _errorBuffer`, @@ -1826,19 +1830,19 @@ trait Contexts { self: Analyzer => final def emitWarnings() = if (_warningBuffer != null) { _warningBuffer foreach { - case (pos, msg, category, site) => runReporting.warning(pos, msg, category, site) + case ContextWarning(pos, msg, category, site, actions) => runReporting.warning(pos, msg, category, site, actions) } _warningBuffer = null } // [JZ] Contexts, pre- the scala/bug#7345 refactor, avoided allocating the buffers until needed. This // is replicated here out of conservatism. - private def newBuffer[A] = mutable.LinkedHashSet.empty[A] // Important to use LinkedHS for stable results. - final protected def errorBuffer = { if (_errorBuffer == null) _errorBuffer = newBuffer; _errorBuffer } + private def newBuffer[A] = mutable.LinkedHashSet.empty[A] // Important to use LinkedHS for stable results. + final protected def errorBuffer = { if (_errorBuffer == null) _errorBuffer = newBuffer; _errorBuffer } final protected def warningBuffer = { if (_warningBuffer == null) _warningBuffer = newBuffer; _warningBuffer } - final def errors: immutable.Seq[Error] = errorBuffer.toVector - final def warnings: immutable.Seq[Warning] = warningBuffer.toVector + final def errors: Seq[AbsTypeError] = errorBuffer.toVector + final def warnings: Seq[ContextWarning] = warningBuffer.toVector final def firstError: Option[AbsTypeError] = errorBuffer.headOption // TODO: remove ++= and clearAll* entirely in favor of more high-level combinators like withFreshErrorBuffer @@ -1850,22 +1854,22 @@ trait Contexts { self: Analyzer => final def clearAllErrors(): Unit = { _errorBuffer = null } } - private[typechecker] class ImmediateReporter(_errorBuffer: mutable.LinkedHashSet[AbsTypeError] = null, _warningBuffer: mutable.LinkedHashSet[(Position, String, WarningCategory, Symbol)] = null) extends ContextReporter(_errorBuffer, _warningBuffer) { + private[typechecker] class ImmediateReporter(_errorBuffer: mutable.LinkedHashSet[AbsTypeError] = null, _warningBuffer: mutable.LinkedHashSet[ContextWarning] = null) extends ContextReporter(_errorBuffer, _warningBuffer) { override def makeBuffering: ContextReporter = new BufferingReporter(errorBuffer, warningBuffer) - def error(pos: Position, msg: String): Unit = reporter.error(pos, msg) + def error(pos: Position, msg: String, actions: List[CodeAction]): Unit = reporter.error(pos, msg, actions) } - private[typechecker] class BufferingReporter(_errorBuffer: mutable.LinkedHashSet[AbsTypeError] = null, _warningBuffer: mutable.LinkedHashSet[(Position, String, WarningCategory, Symbol)] = null) extends ContextReporter(_errorBuffer, _warningBuffer) { + private[typechecker] class BufferingReporter(_errorBuffer: mutable.LinkedHashSet[AbsTypeError] = null, _warningBuffer: mutable.LinkedHashSet[ContextWarning] = null) extends ContextReporter(_errorBuffer, _warningBuffer) { override def isBuffering = true override def issue(err: AbsTypeError)(implicit context: Context): Unit = errorBuffer += err // this used to throw new TypeError(pos, msg) -- buffering lets us report more errors (test/files/neg/macro-basic-mamdmi) // the old throwing behavior was relied on by diagnostics in manifestOfType - def error(pos: Position, msg: String): Unit = errorBuffer += TypeErrorWrapper(new TypeError(pos, msg)) + def error(pos: Position, msg: String, actions: List[CodeAction]): Unit = errorBuffer += TypeErrorWrapper(new TypeError(pos, msg), actions) - override def warning(pos: Position, msg: String, category: WarningCategory, site: Symbol): Unit = - warningBuffer += ((pos, msg, category, site)) + override def warning(pos: Position, msg: String, category: WarningCategory, site: Symbol, actions: List[CodeAction]): Unit = + warningBuffer += ContextWarning(pos, msg, category, site, actions) override protected def handleSuppressedAmbiguous(err: AbsAmbiguousTypeError): Unit = errorBuffer += err @@ -1879,12 +1883,12 @@ trait Contexts { self: Analyzer => */ private[typechecker] class ThrowingReporter extends ContextReporter { override def isThrowing = true - def error(pos: Position, msg: String): Unit = throw new TypeError(pos, msg) + def error(pos: Position, msg: String, actions: List[CodeAction]): Unit = throw new TypeError(pos, msg) } /** Used during a run of [[scala.tools.nsc.typechecker.TreeCheckers]]? */ private[typechecker] class CheckingReporter extends ContextReporter { - def error(pos: Position, msg: String): Unit = onTreeCheckerError(pos, msg) + def error(pos: Position, msg: String, actions: List[CodeAction]): Unit = onTreeCheckerError(pos, msg) } class ImportInfo(val tree: Import, val depth: Int, val isRootImport: Boolean) { diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index d897a8349ff..ea95a6c0660 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -17,7 +17,14 @@ package typechecker import scala.annotation.{tailrec, unused} import scala.collection.mutable, mutable.ListBuffer import scala.reflect.internal.{Chars, TypesStats} -import scala.reflect.internal.util.{FreshNameCreator, ListOfNil, Statistics, StringContextStripMarginOps} +import scala.reflect.internal.util.{ + CodeAction, + FreshNameCreator, + ListOfNil, + Statistics, + StringContextStripMarginOps, + TextEdit, +} import scala.tools.nsc.Reporting.{MessageFilter, Suppression, WConf, WarningCategory} import scala.util.chaining._ import symtab.Flags._ @@ -85,7 +92,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper case s : SilentTypeError => f(s.reportableErrors) } } - class SilentTypeError private(val errors: List[AbsTypeError], val warnings: List[(Position, String, WarningCategory, Symbol)]) extends SilentResult[Nothing] { + class SilentTypeError private(val errors: List[AbsTypeError], val warnings: List[ContextWarning]) extends SilentResult[Nothing] { override def isEmpty = true def err: AbsTypeError = errors.head def reportableErrors = errors match { @@ -97,7 +104,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper } object SilentTypeError { def apply(errors: AbsTypeError*): SilentTypeError = apply(errors.toList, Nil) - def apply(errors: List[AbsTypeError], warnings: List[(Position, String, WarningCategory, Symbol)]): SilentTypeError = new SilentTypeError(errors, warnings) + def apply(errors: List[AbsTypeError], warnings: List[ContextWarning]): SilentTypeError = new SilentTypeError(errors, warnings) // todo: this extracts only one error, should be a separate extractor. def unapply(error: SilentTypeError): Option[AbsTypeError] = error.errors.headOption } @@ -964,9 +971,15 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper // This means an accessor that overrides a Java-defined method gets a MethodType instead of a NullaryMethodType, which breaks lots of assumptions about accessors) def checkCanAutoApply(): Boolean = { if (!isPastTyper && !matchNullaryLoosely) { - context.deprecationWarning(tree.pos, NoSymbol, s"Auto-application to `()` is deprecated. Supply the empty argument list `()` explicitly to invoke method ${meth.decodedName},\n" + - s"or remove the empty argument list from its definition (Java-defined methods are exempt).\n"+ - s"In Scala 3, an unapplied method like this will be eta-expanded into a function.", "2.13.3") + val description = + s"""Auto-application to `()` is deprecated. Supply the empty argument list `()` explicitly to invoke method ${meth.decodedName}, + |or remove the empty argument list from its definition (Java-defined methods are exempt). + |In Scala 3, an unapplied method like this will be eta-expanded into a function.""".stripMargin + val actions = if (tree.pos.isRange) + List(CodeAction("auto-application of empty-paren methods", Some(description), + List(TextEdit(tree.pos.focusEnd, "()")))) + else Nil + context.deprecationWarning(tree.pos, NoSymbol, description, "2.13.3", actions) } true } @@ -4991,7 +5004,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper def tryTypedApply(fun: Tree, args: List[Tree]): Tree = { val start = if (settings.areStatisticsEnabled) statistics.startTimer(failedApplyNanos) else null - def onError(typeErrors: Seq[AbsTypeError], warnings: Seq[(Position, String, WarningCategory, Symbol)]): Tree = { + def onError(typeErrors: Seq[AbsTypeError], warnings: Seq[ContextWarning]): Tree = { if (settings.areStatisticsEnabled) statistics.stopTimer(failedApplyNanos, start) // If the problem is with raw types, convert to existentials and try again. @@ -5059,7 +5072,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper } else err typeErrors.foreach(err => context.issue(adjust(err))) - warnings.foreach { case (p, m, c, s) => context.warning(p, m, c, s) } + warnings.foreach { case ContextWarning(p, m, c, s, as) => context.warning(p, m, c, s, as) } setError(treeCopy.Apply(tree, fun, args)) } @@ -5081,7 +5094,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper def reportError(error: SilentTypeError): Tree = { error.reportableErrors.foreach(context.issue) - error.warnings.foreach { case (p, m, c, s) => context.warning(p, m, c, s) } + error.warnings.foreach { case ContextWarning(p, m, c, s, as) => context.warning(p, m, c, s, as) } args.foreach(typed(_, mode, ErrorType)) setError(tree) } diff --git a/src/compiler/scala/tools/reflect/package.scala b/src/compiler/scala/tools/reflect/package.scala index 3f592e3ae57..a9ee6324a38 100644 --- a/src/compiler/scala/tools/reflect/package.scala +++ b/src/compiler/scala/tools/reflect/package.scala @@ -15,7 +15,7 @@ package scala.tools import scala.language.implicitConversions import scala.reflect.api.JavaUniverse import scala.reflect.internal.Reporter -import scala.reflect.internal.util.Position +import scala.reflect.internal.util.{CodeAction, Position} import scala.tools.nsc.Settings import scala.tools.nsc.reporters.{ConsoleReporter, FilteringReporter} @@ -88,7 +88,7 @@ package object reflect { val NSC_WARNING = Reporter.WARNING val NSC_ERROR = Reporter.ERROR - def doReport(pos: Position, msg: String, nscSeverity: NscSeverity): Unit = + override def doReport(pos: Position, msg: String, nscSeverity: NscSeverity, actions: List[CodeAction]): Unit = frontEnd.log(pos, msg, (nscSeverity: @unchecked) match { case NSC_INFO => API_INFO case NSC_WARNING => API_WARNING diff --git a/src/interactive/scala/tools/nsc/interactive/InteractiveReporter.scala b/src/interactive/scala/tools/nsc/interactive/InteractiveReporter.scala index de93a2974f9..1bbc308cd56 100644 --- a/src/interactive/scala/tools/nsc/interactive/InteractiveReporter.scala +++ b/src/interactive/scala/tools/nsc/interactive/InteractiveReporter.scala @@ -14,10 +14,10 @@ package scala.tools.nsc package interactive import scala.collection.mutable.ArrayBuffer -import scala.reflect.internal.util.Position +import scala.reflect.internal.util.{CodeAction, Position} import scala.tools.nsc.reporters.FilteringReporter -case class Problem(pos: Position, msg: String, severityLevel: Int) +case class Problem(pos: Position, msg: String, severityLevel: Int, actions: List[CodeAction]) abstract class InteractiveReporter extends FilteringReporter { @@ -27,7 +27,7 @@ abstract class InteractiveReporter extends FilteringReporter { val otherProblems = new ArrayBuffer[Problem] - override def doReport(pos: Position, msg: String, severity: Severity): Unit = try { + override def doReport(pos: Position, msg: String, severity: Severity, actions: List[CodeAction]): Unit = try { val problems = if (compiler eq null) { otherProblems @@ -44,7 +44,7 @@ abstract class InteractiveReporter extends FilteringReporter { compiler.debugLog("[no position] :" + msg) otherProblems } - problems += Problem(pos, msg, severity.id) + problems += Problem(pos, msg, severity.id, actions) } catch { case ex: UnsupportedOperationException => } diff --git a/src/partest/scala/tools/partest/nest/DirectCompiler.scala b/src/partest/scala/tools/partest/nest/DirectCompiler.scala index dd647837454..9d1a379693d 100644 --- a/src/partest/scala/tools/partest/nest/DirectCompiler.scala +++ b/src/partest/scala/tools/partest/nest/DirectCompiler.scala @@ -14,9 +14,8 @@ package scala.tools.partest package nest import java.io.{BufferedReader, FileWriter, PrintWriter} - import scala.collection.mutable.ListBuffer -import scala.reflect.internal.util.{NoPosition, Position, ScalaClassLoader} +import scala.reflect.internal.util.{CodeAction, NoPosition, Position, ScalaClassLoader} import scala.reflect.io.AbstractFile import scala.tools.nsc.reporters.{ConsoleReporter, Reporter} import scala.tools.nsc.{CompilerCommand, Global, Settings} @@ -30,7 +29,7 @@ object ExtConsoleReporter { } } class PlainReporter(settings: Settings, reader: BufferedReader, writer: PrintWriter, echo: PrintWriter) extends ConsoleReporter(settings, reader, writer, echo) { - override def doReport(pos: Position, msg: String, severity: Severity): Unit = writer.println(s"[$severity] [$pos]: $msg") + override def doReport(pos: Position, msg: String, severity: Severity, actions: List[CodeAction]): Unit = writer.println(s"[$severity] [$pos]: $msg") } class TestSettings(cp: String, error: String => Unit) extends Settings(error) { diff --git a/src/reflect/scala/reflect/internal/Reporting.scala b/src/reflect/scala/reflect/internal/Reporting.scala index 0d97927be93..ebb31e712bc 100644 --- a/src/reflect/scala/reflect/internal/Reporting.scala +++ b/src/reflect/scala/reflect/internal/Reporting.scala @@ -15,6 +15,7 @@ package reflect package internal import scala.annotation.unchecked.uncheckedStable +import scala.reflect.internal.util.CodeAction import settings.MutableSettings /** Provides delegates to the reporter doing the actual work. @@ -35,7 +36,7 @@ trait Reporting { self : Positions => type PerRunReporting <: PerRunReportingBase protected def PerRunReporting: PerRunReporting abstract class PerRunReportingBase { - def deprecationWarning(pos: Position, msg: String, since: String, site: String, origin: String): Unit + def deprecationWarning(pos: Position, msg: String, since: String, site: String, origin: String, actions: List[CodeAction] = Nil): Unit /** Have we already supplemented the error message of a compiler crash? */ private[this] var supplementedError = false @@ -96,24 +97,33 @@ abstract class Reporter { @uncheckedStable final def WARNING: Severity = Reporter.WARNING @uncheckedStable final def ERROR: Severity = Reporter.ERROR - // TODO: rename to `doReport`, remove the `force` parameter. + // TODO: rename to `doReport`, remove the `force` parameter (but sbt compat). // Note: `force` is ignored. It used to mean: if `!force`, the reporter may skip INFO messages. // If `force`, INFO messages were always printed. Now, INFO messages are always printed. + @deprecatedOverriding("extend scala.tools.nsc.reporters.FilteringReporter, and override doReport instead", "2.13.12") protected def info0(pos: Position, msg: String, severity: Severity, force: Boolean): Unit + def doReport(pos: Position, msg: String, severity: Severity, actions: List[CodeAction]): Unit = + info0(pos, msg, severity, force = false) + /** @return Reporter.Display, or override for Count, Suppress */ def filter(pos: Position, msg: String, severity: Severity): Int = Reporter.Display - final def echo(msg: String): Unit = echo(util.NoPosition, msg) - final def echo(pos: Position, msg: String): Unit = if (filter(pos, msg, INFO) == 0) info0(pos, msg, INFO, force = true) - final def warning(pos: Position, msg: String): Unit = filteredInfo(pos, msg, WARNING) - final def error(pos: Position, msg: String): Unit = filteredInfo(pos, msg, ERROR) + final def echo(msg: String): Unit = echo(util.NoPosition, msg) + final def echo(pos: Position, msg: String, actions: List[CodeAction] = Nil): Unit = + if (filter(pos, msg, INFO) == 0) doReport(pos, msg, INFO, actions) + + final def warning(pos: Position, msg: String, actions: List[CodeAction] = Nil): Unit = + filteredInfo(pos, msg, WARNING, actions) + + final def error(pos: Position, msg: String, actions: List[CodeAction] = Nil): Unit = + filteredInfo(pos, msg, ERROR, actions) - private def filteredInfo(pos: Position, msg: String, severity: Severity): Unit = { + private def filteredInfo(pos: Position, msg: String, severity: Severity, actions: List[CodeAction]): Unit = { val f = filter(pos, msg, severity) if (f <= 1) increment(severity) - if (f == 0) info0(pos, msg, severity, force = false) + if (f == 0) doReport(pos, msg, severity, actions) } def increment(severity: Severity): Unit = severity match { diff --git a/src/reflect/scala/reflect/internal/util/CodeAction.scala b/src/reflect/scala/reflect/internal/util/CodeAction.scala new file mode 100644 index 00000000000..b8525ebbff2 --- /dev/null +++ b/src/reflect/scala/reflect/internal/util/CodeAction.scala @@ -0,0 +1,39 @@ +/* + * Scala (https://www.scala-lang.org) + * + * Copyright EPFL and Lightbend, Inc. + * + * Licensed under Apache License 2.0 + * (http://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package scala +package reflect +package internal +package util + +/** + * EXPERIMENTAL + * + * CodeAction is used to communicate code edit suggestion to tooling in + * a structured manner. + * + * @see `CodeAction` + * + * @groupname Common Commonly used methods + * @group ReflectionAPI + */ +case class CodeAction(title: String, description: Option[String], edits: List[TextEdit]) + +/** + * EXPERIMENTAL + * + * + * @groupname Common Commonly used methods + * @group ReflectionAPI + */ +case class TextEdit(position: Position, newText: String) diff --git a/src/reflect/scala/reflect/runtime/JavaUniverse.scala b/src/reflect/scala/reflect/runtime/JavaUniverse.scala index 6d17d6be3fd..7f9dd853d14 100644 --- a/src/reflect/scala/reflect/runtime/JavaUniverse.scala +++ b/src/reflect/scala/reflect/runtime/JavaUniverse.scala @@ -19,7 +19,7 @@ import scala.reflect.internal.{SomePhase, TreeInfo} import scala.reflect.internal.{SymbolTable => InternalSymbolTable} import scala.reflect.runtime.{SymbolTable => RuntimeSymbolTable} import scala.reflect.api.{TypeCreator, Universe} -import scala.reflect.internal.util.Statistics +import scala.reflect.internal.util.{CodeAction, Statistics} /** An implementation of [[scala.reflect.api.Universe]] for runtime reflection using JVM classloaders. * @@ -39,13 +39,15 @@ class JavaUniverse extends InternalSymbolTable with JavaUniverseForce with Refle // TODO: why put output under isLogging? Calls to inform are already conditional on debug/verbose/... import scala.reflect.internal.Reporter override def reporter: Reporter = new Reporter { + @nowarn("msg=overriding method info0") protected def info0(pos: Position, msg: String, severity: Severity, force: Boolean): Unit = log(msg) } // minimal Run to get Reporting wired def currentRun = new RunReporting {} class PerRunReporting extends PerRunReportingBase { - def deprecationWarning(pos: Position, msg: String, since: String, site: String, origin: String): Unit = reporter.warning(pos, msg) + def deprecationWarning(pos: Position, msg: String, since: String, site: String, origin: String, actions: List[CodeAction]): Unit = + reporter.warning(pos, msg) } protected def PerRunReporting = new PerRunReporting diff --git a/src/repl-frontend/scala/tools/nsc/interpreter/shell/Reporter.scala b/src/repl-frontend/scala/tools/nsc/interpreter/shell/Reporter.scala index 538ed284c8e..e8069b64271 100644 --- a/src/repl-frontend/scala/tools/nsc/interpreter/shell/Reporter.scala +++ b/src/repl-frontend/scala/tools/nsc/interpreter/shell/Reporter.scala @@ -13,9 +13,8 @@ package scala.tools.nsc.interpreter.shell import java.io.PrintWriter - import scala.reflect.internal -import scala.reflect.internal.util.{NoSourceFile, Position, StringOps} +import scala.reflect.internal.util.{CodeAction, NoSourceFile, Position, StringOps} import scala.tools.nsc.interpreter.{Naming, ReplReporter, ReplRequest} import scala.tools.nsc.reporters.{FilteringReporter, Reporter} import scala.tools.nsc.{ConsoleWriter, NewLinePrintWriter, Settings} @@ -150,7 +149,7 @@ class ReplReporterImpl(val config: ShellConfig, val settings: Settings = new Set case internal.Reporter.INFO => RESET } - def doReport(pos: Position, msg: String, severity: Severity): Unit = withoutTruncating { + override def doReport(pos: Position, msg: String, severity: Severity, actions: List[CodeAction]): Unit = withoutTruncating { val prefix = if (colorOk) severityColor(severity) + clabel(severity) + RESET else clabel(severity) diff --git a/src/repl-frontend/scala/tools/nsc/interpreter/shell/Scripted.scala b/src/repl-frontend/scala/tools/nsc/interpreter/shell/Scripted.scala index 5587c8333c9..93d33873570 100644 --- a/src/repl-frontend/scala/tools/nsc/interpreter/shell/Scripted.scala +++ b/src/repl-frontend/scala/tools/nsc/interpreter/shell/Scripted.scala @@ -14,12 +14,10 @@ package scala.tools.nsc.interpreter.shell import java.io.{Closeable, OutputStream, PrintWriter, Reader} import java.util.Arrays.asList - import javax.script._ - import scala.beans.BeanProperty import scala.jdk.CollectionConverters._ -import scala.reflect.internal.util.Position +import scala.reflect.internal.util.{CodeAction, Position} import scala.tools.nsc.Settings import scala.tools.nsc.interpreter.Results.Incomplete import scala.tools.nsc.interpreter.{ImportContextPreamble, ScriptedInterpreter, ScriptedRepl} @@ -317,7 +315,7 @@ private class SaveFirstErrorReporter(settings: Settings, out: PrintWriter) exten private var _firstError: Option[(Position, String)] = None def firstError = _firstError - override def doReport(pos: Position, msg: String, severity: Severity): Unit = + override def doReport(pos: Position, msg: String, severity: Severity, actions: List[CodeAction]): Unit = if (severity == ERROR && _firstError.isEmpty) _firstError = Some((pos, msg)) override def reset() = { super.reset(); _firstError = None } diff --git a/test/files/presentation/t12308.check b/test/files/presentation/t12308.check index 80792e4a7f2..51b1da10bf8 100644 --- a/test/files/presentation/t12308.check +++ b/test/files/presentation/t12308.check @@ -1,11 +1,11 @@ reload: Foo.scala askLoadedTyped 1 -Problem(RangePosition(t12308/src/Foo.scala, 67, 67, 72),A try without a catch or finally is equivalent to putting its body in a block; no exceptions are handled.,1) +Problem(RangePosition(t12308/src/Foo.scala, 67, 67, 72),A try without a catch or finally is equivalent to putting its body in a block; no exceptions are handled.,1,List()) askLoadedTyped 2 -Problem(RangePosition(t12308/src/Foo.scala, 67, 67, 72),A try without a catch or finally is equivalent to putting its body in a block; no exceptions are handled.,1) +Problem(RangePosition(t12308/src/Foo.scala, 67, 67, 72),A try without a catch or finally is equivalent to putting its body in a block; no exceptions are handled.,1,List()) reload: Foo.scala askLoadedTyped 3 -Problem(RangePosition(t12308/src/Foo.scala, 67, 67, 72),A try without a catch or finally is equivalent to putting its body in a block; no exceptions are handled.,1) +Problem(RangePosition(t12308/src/Foo.scala, 67, 67, 72),A try without a catch or finally is equivalent to putting its body in a block; no exceptions are handled.,1,List()) targeted 1 askType at Foo.scala(2,37) @@ -25,7 +25,7 @@ askType at Foo.scala(4,37) [response] askTypeAt (4,37) 1 ================================================================================ -Problem(RangePosition(t12308/src/Foo.scala, 67, 67, 72),A try without a catch or finally is equivalent to putting its body in a block; no exceptions are handled.,1) +Problem(RangePosition(t12308/src/Foo.scala, 67, 67, 72),A try without a catch or finally is equivalent to putting its body in a block; no exceptions are handled.,1,List()) reload: Foo.scala targeted 2 - doesn't handle nowarn correctly @@ -46,5 +46,5 @@ askType at Foo.scala(4,37) [response] askTypeAt (4,37) 1 ================================================================================ -Problem(RangePosition(t12308/src/Foo.scala, 67, 67, 72),A try without a catch or finally is equivalent to putting its body in a block; no exceptions are handled.,1) -Problem(RangePosition(t12308/src/Foo.scala, 109, 109, 114),A try without a catch or finally is equivalent to putting its body in a block; no exceptions are handled.,1) +Problem(RangePosition(t12308/src/Foo.scala, 67, 67, 72),A try without a catch or finally is equivalent to putting its body in a block; no exceptions are handled.,1,List()) +Problem(RangePosition(t12308/src/Foo.scala, 109, 109, 114),A try without a catch or finally is equivalent to putting its body in a block; no exceptions are handled.,1,List()) diff --git a/test/files/run/maxerrs.scala b/test/files/run/maxerrs.scala index fa2768ec668..4bb2f8f3baf 100644 --- a/test/files/run/maxerrs.scala +++ b/test/files/run/maxerrs.scala @@ -1,7 +1,8 @@ import scala.tools.partest._ import scala.tools.nsc.Settings -import scala.tools.nsc.reporters.Reporter +import scala.tools.nsc.reporters.FilteringReporter +import scala.reflect.internal.util.CodeAction /** Test that compiler enforces maxerrs when given a plain Reporter. */ object Test extends DirectTest { @@ -14,8 +15,9 @@ object Test extends DirectTest { } """.trim + var store0: UnfilteredStoreReporter = _ // a reporter that ignores all limits - lazy val store = new UnfilteredStoreReporter + def store = store0 final val limit = 3 @@ -28,17 +30,25 @@ object Test extends DirectTest { s.maxerrs.value = limit s } - override def reporter(s: Settings) = store + override def reporter(s: Settings) = + if (store0 ne null) store0 + else { + store0 = new UnfilteredStoreReporter(s) + store0 + } } -class UnfilteredStoreReporter extends Reporter { +class UnfilteredStoreReporter(s: Settings) extends FilteringReporter { import scala.tools.nsc.reporters.StoreReporter._ import scala.collection.mutable import scala.reflect.internal.util.Position val infos = new mutable.LinkedHashSet[Info] - override def info0(pos: Position, msg: String, severity: Severity, force: Boolean) = infos += Info(pos, msg, severity) + override def settings: Settings = s + + override def doReport(pos: Position, msg: String, severity: Severity, actions: List[CodeAction]): Unit = + infos += Info(pos, msg, severity, actions) override def reset(): Unit = { super.reset() diff --git a/test/junit/scala/tools/nsc/async/AnnotationDrivenAsyncTest.scala b/test/junit/scala/tools/nsc/async/AnnotationDrivenAsyncTest.scala index 905a90ba6f2..e98e70f1f8d 100644 --- a/test/junit/scala/tools/nsc/async/AnnotationDrivenAsyncTest.scala +++ b/test/junit/scala/tools/nsc/async/AnnotationDrivenAsyncTest.scala @@ -11,7 +11,7 @@ import org.junit.{Assert, Ignore, Test} import scala.annotation.{StaticAnnotation, nowarn, unused} import scala.collection.mutable import scala.concurrent.duration.Duration -import scala.reflect.internal.util.Position +import scala.reflect.internal.util.{CodeAction, Position} import scala.reflect.internal.util.ScalaClassLoader.URLClassLoader import scala.tools.nsc.backend.jvm.AsmUtils import scala.tools.nsc.plugins.{Plugin, PluginComponent} @@ -515,9 +515,9 @@ class AnnotationDrivenAsyncTest { val out = createTempDir() val reporter = new StoreReporter(new Settings) { - override def doReport(pos: Position, msg: String, severity: Severity): Unit = + override def doReport(pos: Position, msg: String, severity: Severity, actions: List[CodeAction]): Unit = if (severity == INFO) println(msg) - else super.doReport(pos, msg, severity) + else super.doReport(pos, msg, severity, actions) } val settings = new Settings(println(_)) settings.async.value = true diff --git a/test/junit/scala/tools/nsc/reporters/AbstractCodeActionTest.scala b/test/junit/scala/tools/nsc/reporters/AbstractCodeActionTest.scala new file mode 100644 index 00000000000..3211c766433 --- /dev/null +++ b/test/junit/scala/tools/nsc/reporters/AbstractCodeActionTest.scala @@ -0,0 +1,97 @@ +package scala.tools.nsc.reporters + +import org.junit.Test +import org.junit.Assert._ + +import scala.reflect.internal.util.CodeAction +import scala.tools.testkit.BytecodeTesting + +abstract class AbstractCodeActionTest extends BytecodeTesting { + override def compilerArgs: String = "-Ystop-after:typer -Yvalidate-pos:typer -Yrangepos -deprecation -Xsource:3" + protected def reporter = compiler.global.reporter.asInstanceOf[StoreReporter] + + @Test + def testProcedureSyntaxDecl(): Unit = + assertCodeSuggestion( + """trait Test { + | def foo + | def bar; + | def pub { print() } + | def club{ print() } + | def saloon = { print() } + |}""".stripMargin, + """trait Test { + | def foo: Unit + | def bar: Unit; + | def pub: Unit = { print() } + | def club: Unit ={ print() } + | def saloon = { print() } + |}""".stripMargin, + ) + + @Test + def testGreatParenInsert(): Unit = { + assertCodeSuggestion( + """trait Test { + | def foo = { + | println + | Predef.println + | toString + this.toString + | } + | def bar: Unit = Predef println() + |} + """.stripMargin, + """trait Test { + | def foo = { + | println() + | Predef.println() + | toString + this.toString + | } + | def bar: Unit = Predef println() + |} + """.stripMargin, + ) + } + + @Test + def testValInFor(): Unit = + assertCodeSuggestion( + """trait Test { + | def foo: Unit = { + | for { + | val x <- 1 to 5 + | val y = x + | } yield x+y + | } + |} + """.stripMargin, + """trait Test { + | def foo: Unit = { + | for { + | x <- 1 to 5 + | y = x + | } yield x+y + | } + |} + """.stripMargin, + ) + + def assertCodeSuggestion(original: String, expected: String): Unit = { + val run = compiler.newRun() + run.compileSources(compiler.global.newSourceFile(original) :: Nil) + val actions = reporter.infos.flatMap(_.actions).toList + val newCode = applyChanges(original, actions) + assertEquals(s"\n$newCode", expected, newCode) + } + + def applyChanges(code: String, as: List[CodeAction]): String = { + var offset = 0 + var res = code.toVector + for (change <- as.flatMap(_.edits).sortBy(_.position.start)) { + // not the most efficient but it's just for tests + res = res.take(change.position.start + offset) ++ change.newText.toVector ++ res.drop(change.position.end + offset) + offset = offset - (change.position.end - change.position.start) + change.newText.length + } + new String(res.toArray) + } +} diff --git a/test/junit/scala/tools/nsc/reporters/CodeActionTest.scala b/test/junit/scala/tools/nsc/reporters/CodeActionTest.scala new file mode 100644 index 00000000000..6aaad689c9a --- /dev/null +++ b/test/junit/scala/tools/nsc/reporters/CodeActionTest.scala @@ -0,0 +1,9 @@ +package scala.tools.nsc.reporters + +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +@RunWith(classOf[JUnit4]) +class CodeActionTest extends AbstractCodeActionTest { + override def compilerArgs: String = "-Ystop-after:typer -Yvalidate-pos:typer -Yrangepos -deprecation" +} diff --git a/test/junit/scala/tools/nsc/reporters/CodeActionXsource3Test.scala b/test/junit/scala/tools/nsc/reporters/CodeActionXsource3Test.scala new file mode 100644 index 00000000000..718bb2f1489 --- /dev/null +++ b/test/junit/scala/tools/nsc/reporters/CodeActionXsource3Test.scala @@ -0,0 +1,39 @@ +package scala.tools.nsc.reporters + +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +@RunWith(classOf[JUnit4]) +class CodeActionXsource3Test extends AbstractCodeActionTest { + override def compilerArgs: String = "-Ystop-after:typer -Yvalidate-pos:typer -Yrangepos -deprecation -Xsource:3" + + @Test + def testLambdaParameterList(): Unit = + assertCodeSuggestion( + """trait Test { + | def foo: Any = { + | x: Int => x * 2 + | } + | def bar: Any = { + | x: Int=> x * 2 + | } + | def tavern: Any = { x: Int => + | x * 2 + | } + |} + """.stripMargin, + """trait Test { + | def foo: Any = { + | (x: Int) => x * 2 + | } + | def bar: Any = { + | (x: Int)=> x * 2 + | } + | def tavern: Any = { (x: Int) => + | x * 2 + | } + |} + """.stripMargin, + ) +} diff --git a/test/junit/scala/tools/nsc/reporters/ConsoleReporterTest.scala b/test/junit/scala/tools/nsc/reporters/ConsoleReporterTest.scala index 13948f678e2..72bc58726fb 100644 --- a/test/junit/scala/tools/nsc/reporters/ConsoleReporterTest.scala +++ b/test/junit/scala/tools/nsc/reporters/ConsoleReporterTest.scala @@ -88,8 +88,8 @@ class ConsoleReporterTest { reporter.settings.maxwarns.value = 1 // counting happens in .error/.warning, doReport doesn't count - testHelper(msg = "Testing display", severity = "warning: ")(reporter.doReport(_, "Testing display", reporter.WARNING)) - testHelper(msg = "Testing display", severity = "error: ")(reporter.doReport(_, "Testing display", reporter.ERROR)) + testHelper(msg = "Testing display", severity = "warning: ")(reporter.doReport(_, "Testing display", reporter.WARNING, Nil)) + testHelper(msg = "Testing display", severity = "error: ")(reporter.doReport(_, "Testing display", reporter.ERROR, Nil)) testHelper(msg = "Testing display")(reporter.echo(_, "Testing display")) testHelper(msg = "Testing display", severity = "warning: ")(reporter.warning(_, "Testing display")) @@ -100,8 +100,8 @@ class ConsoleReporterTest { testHelper(msg = "")(reporter.error(_, "Test maxwarns")) // the filter happens in .error/.warning, doReport always reports - testHelper(posWithSource, msg = "Testing display", severity = "warning: ")(reporter.doReport(_, "Testing display", reporter.WARNING)) - testHelper(posWithSource, msg = "Testing display", severity = "error: ")(reporter.doReport(_, "Testing display", reporter.ERROR)) + testHelper(posWithSource, msg = "Testing display", severity = "warning: ")(reporter.doReport(_, "Testing display", reporter.WARNING, Nil)) + testHelper(posWithSource, msg = "Testing display", severity = "error: ")(reporter.doReport(_, "Testing display", reporter.ERROR, Nil)) reporter.reset() @@ -178,7 +178,8 @@ class ConsoleReporterTest { new FilteringReporter { def settings: Settings = conf - def doReport(pos: Position, msg: String, severity: Severity): Unit = reporter.doReport(pos, msg, severity) + override def doReport(pos: Position, msg: String, severity: Severity, actions: List[CodeAction]): Unit = + reporter.doReport(pos, msg, severity, actions) } } @@ -213,7 +214,7 @@ class ConsoleReporterTest { def filteredInfoTest(): Unit = { val reporter = new FilteringReporter { val settings: Settings = new Settings - def doReport(pos: Position, msg: String, severity: Severity): Unit = () + override def doReport(pos: Position, msg: String, severity: Severity, actions: List[CodeAction]): Unit = () } // test obsolete API, make sure it doesn't throw reporter.info(NoPosition, "goodbye, cruel world", force = false) @@ -224,7 +225,8 @@ class ConsoleReporterTest { val reporter = createConsoleReporter("r", writerOut) val adapted = new FilteringReporter { def settings: Settings = reporter.settings - def doReport(pos: Position, msg: String, severity: Severity): Unit = reporter.doReport(pos, msg, severity) + override def doReport(pos: Position, msg: String, severity: Severity, actions: List[CodeAction]): Unit = + reporter.doReport(pos, msg, severity, actions) } // pass one message diff --git a/test/junit/scala/tools/nsc/reporters/PositionFilterTest.scala b/test/junit/scala/tools/nsc/reporters/PositionFilterTest.scala index a02c83b8e55..1eaaaa501d8 100644 --- a/test/junit/scala/tools/nsc/reporters/PositionFilterTest.scala +++ b/test/junit/scala/tools/nsc/reporters/PositionFilterTest.scala @@ -19,7 +19,8 @@ class PositionFilterTest { def createFilter: FilteringReporter = new FilteringReporter { def settings: Settings = store.settings - def doReport(pos: Position, msg: String, severity: Severity): Unit = store.doReport(pos, msg, severity) + override def doReport(pos: Position, msg: String, severity: Severity, actions: List[CodeAction]): Unit = + store.doReport(pos, msg, severity, actions) } @Test diff --git a/test/junit/scala/tools/nsc/reporters/WConfTest.scala b/test/junit/scala/tools/nsc/reporters/WConfTest.scala index f83a005457f..3fba15a03ed 100644 --- a/test/junit/scala/tools/nsc/reporters/WConfTest.scala +++ b/test/junit/scala/tools/nsc/reporters/WConfTest.scala @@ -338,7 +338,8 @@ class WConfTest extends BytecodeTesting { }, Array().toIndexedSeq), 0), msg = "", WarningCategory.Other, - site = "") + site = "", + actions = Nil) val aTest = Reporting.WConf.parseFilter("src=a/.*Test.scala", rootDir = "").getOrElse(null) assertTrue(aTest.matches(m("/a/FooTest.scala"))) diff --git a/test/junit/scala/tools/nsc/symtab/SymbolTableForUnitTesting.scala b/test/junit/scala/tools/nsc/symtab/SymbolTableForUnitTesting.scala index 71eec93127f..d54287e2c4e 100644 --- a/test/junit/scala/tools/nsc/symtab/SymbolTableForUnitTesting.scala +++ b/test/junit/scala/tools/nsc/symtab/SymbolTableForUnitTesting.scala @@ -1,9 +1,10 @@ package scala.tools.nsc package symtab +import scala.annotation.nowarn import scala.reflect.ClassTag import scala.reflect.internal.{NoPhase, Phase, Reporter, SomePhase} -import scala.reflect.internal.util.Statistics +import scala.reflect.internal.util.{CodeAction, Statistics} import scala.tools.util.PathResolver import util.ClassPath import io.AbstractFile @@ -85,13 +86,14 @@ class SymbolTableForUnitTesting extends SymbolTable { // Members declared in scala.reflect.internal.Reporting def reporter = new Reporter { + @nowarn("msg=overriding method info0") protected def info0(pos: Position, msg: String, severity: Severity, force: Boolean): Unit = println(msg) } // minimal Run to get Reporting wired def currentRun = new RunReporting {} class PerRunReporting extends PerRunReportingBase { - def deprecationWarning(pos: Position, msg: String, since: String, site: String, origin: String): Unit = reporter.warning(pos, msg) + def deprecationWarning(pos: Position, msg: String, since: String, site: String, origin: String, actions: List[CodeAction]): Unit = reporter.warning(pos, msg) } protected def PerRunReporting = new PerRunReporting