Skip to content

Commit 910c59d

Browse files
authored
Merge pull request scala#10484 from lrytz/moreQuickFixes
Add quick fixes to more warnings and errors
2 parents fa1c9eb + 65fb732 commit 910c59d

File tree

106 files changed

+646
-438
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

106 files changed

+646
-438
lines changed

src/compiler/scala/tools/nsc/Reporting.scala

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ trait Reporting extends internal.Reporting { self: ast.Positions with Compilatio
6969
if (settings.quickfix.isSetByUser && settings.quickfix.value.isEmpty) {
7070
globalError(s"Missing message filter for `-quickfix`; see `-quickfix:help` or use `-quickfix:any` to apply all available quick fixes.")
7171
Nil
72+
} else if (settings.quickFixSilent) {
73+
Nil
7274
} else {
7375
val parsed = settings.quickfix.value.map(WConf.parseFilter(_, rootDirPrefix))
7476
val msgs = parsed.collect { case Left(msg) => msg }
@@ -167,7 +169,7 @@ trait Reporting extends internal.Reporting { self: ast.Positions with Compilatio
167169

168170
val quickfixed = {
169171
if (!skipRewriteAction(action) && registerTextEdit(warning)) s"[rewritten by -quickfix] ${warning.msg}"
170-
else if (warning.actions.exists(_.edits.nonEmpty)) s"[quick fix available] ${warning.msg}"
172+
else if (warning.actions.exists(_.edits.nonEmpty) && !settings.quickFixSilent) s"${warning.msg} [quickfixable]"
171173
else warning.msg
172174
}
173175

@@ -352,11 +354,14 @@ trait Reporting extends internal.Reporting { self: ast.Positions with Compilatio
352354
def warning(pos: Position, msg: String, category: WarningCategory, site: Symbol, origin: String): Unit =
353355
issueIfNotSuppressed(Message.Origin(pos, msg, category, siteName(site), origin, actions = Nil))
354356

357+
def codeAction(title: String, pos: Position, newText: String, desc: String, expected: Option[(String, CompilationUnit)] = None) =
358+
CodeAction(title, pos, newText, desc, expected.forall(e => e._1 == e._2.sourceAt(pos)))
359+
355360
// Remember CodeActions that match `-quickfix` and report the error through the reporter
356361
def error(pos: Position, msg: String, actions: List[CodeAction]): Unit = {
357362
val quickfixed = {
358363
if (registerErrorTextEdit(pos, msg, actions)) s"[rewritten by -quickfix] $msg"
359-
else if (actions.exists(_.edits.nonEmpty)) s"[quick fix available] $msg"
364+
else if (actions.exists(_.edits.nonEmpty) && !settings.quickFixSilent) s"$msg [quickfixable]"
360365
else msg
361366
}
362367
reporter.error(pos, quickfixed, actions)

src/compiler/scala/tools/nsc/ast/parser/Parsers.scala

Lines changed: 58 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,12 @@ package scala.tools.nsc
1717
package ast.parser
1818

1919
import scala.annotation.tailrec
20-
import scala.collection.mutable, mutable.ListBuffer
21-
import scala.reflect.internal.{ModifierFlags => Flags, Precedence}
22-
import scala.reflect.internal.util.{
23-
CodeAction,
24-
FreshNameCreator,
25-
ListOfNil,
26-
Position,
27-
SourceFile,
28-
TextEdit,
29-
}
30-
import Tokens._
20+
import scala.collection.mutable
21+
import scala.collection.mutable.ListBuffer
22+
import scala.reflect.internal.util.{CodeAction, FreshNameCreator, ListOfNil, Position, SourceFile}
23+
import scala.reflect.internal.{Precedence, ModifierFlags => Flags}
3124
import scala.tools.nsc.Reporting.WarningCategory
25+
import scala.tools.nsc.ast.parser.Tokens._
3226

3327
/** Historical note: JavaParsers started life as a direct copy of Parsers
3428
* but at a time when that Parsers had been replaced by a different one.
@@ -329,7 +323,7 @@ self =>
329323
def source = parser.source
330324
}
331325
val treeBuilder = new ParserTreeBuilder
332-
import treeBuilder.{global => _, unit => _, source => _, fresh => _, _}
326+
import treeBuilder.{fresh => _, global => _, source => _, unit => _, _}
333327

334328
implicit def fresh: FreshNameCreator = unit.fresh
335329

@@ -633,11 +627,11 @@ self =>
633627
else deprecationWarning(offset, msg, since, actions)
634628

635629
// deprecation or migration under -Xsource:3, with different messages
636-
def hardMigrationWarning(offset: Offset, depr: => String, migr: => String, since: String, actions: List[CodeAction]): Unit =
637-
if (currentRun.isScala3) warning(offset, migr, WarningCategory.Scala3Migration, actions)
638-
else deprecationWarning(offset, depr, since, actions)
630+
def hardMigrationWarning(offset: Offset, depr: => String, migr: => String, since: String, actions: String => List[CodeAction]): Unit =
631+
if (currentRun.isScala3) warning(offset, migr, WarningCategory.Scala3Migration, actions(migr))
632+
else deprecationWarning(offset, depr, since, actions(depr))
639633
def hardMigrationWarning(offset: Offset, depr: => String, migr: => String, since: String): Unit =
640-
hardMigrationWarning(offset, depr, migr, since, Nil)
634+
hardMigrationWarning(offset, depr, migr, since, _ => Nil)
641635

642636
def expectedMsgTemplate(exp: String, fnd: String) = s"$exp expected but $fnd found."
643637
def expectedMsg(token: Token): String =
@@ -748,12 +742,18 @@ self =>
748742
def isWildcardType = in.token == USCORE || isScala3WildcardType
749743
def isScala3WildcardType = isRawIdent && in.name == raw.QMARK
750744
def checkQMarkDefinition() =
751-
if (isScala3WildcardType)
752-
syntaxError(in.offset, "using `?` as a type name requires backticks.")
745+
if (isScala3WildcardType) {
746+
val msg = "using `?` as a type name requires backticks."
747+
syntaxError(in.offset, msg,
748+
runReporting.codeAction("add backticks", r2p(in.offset, in.offset, in.offset + 1), "`?`", msg, expected = Some(("?", unit))))
749+
}
750+
753751
def checkKeywordDefinition() =
754-
if (isRawIdent && scala3Keywords.contains(in.name))
755-
deprecationWarning(in.offset,
756-
s"Wrap `${in.name}` in backticks to use it as an identifier, it will become a keyword in Scala 3.", "2.13.7")
752+
if (isRawIdent && scala3Keywords.contains(in.name)) {
753+
val msg = s"Wrap `${in.name}` in backticks to use it as an identifier, it will become a keyword in Scala 3."
754+
deprecationWarning(in.offset, msg, "2.13.7",
755+
runReporting.codeAction("add backticks", r2p(in.offset, in.offset, in.offset + in.name.length), s"`${in.name}`", msg, expected = Some((in.name.toString, unit))))
756+
}
757757

758758
def isIdent = in.token == IDENTIFIER || in.token == BACKQUOTED_IDENT
759759
def isMacro = in.token == IDENTIFIER && in.name == nme.MACROkw
@@ -816,8 +816,7 @@ self =>
816816
val wrn = sm"""|$msg
817817
|Use '-Wconf:msg=lambda-parens:s' to silence this warning."""
818818
def actions =
819-
if (tree.pos.isRange)
820-
List(CodeAction("lambda parameter", Some(msg), List(TextEdit(tree.pos, s"(${unit.sourceAt(tree.pos)})"))))
819+
if (tree.pos.isRange) runReporting.codeAction("add parentheses", tree.pos, s"(${unit.sourceAt(tree.pos)})", msg)
821820
else Nil
822821
migrationWarning(tree.pos.point, wrn, "2.13.11", actions)
823822
List(convertToParam(tree))
@@ -1029,11 +1028,17 @@ self =>
10291028

10301029
def finishBinaryOp(isExpr: Boolean, opinfo: OpInfo, rhs: Tree): Tree = {
10311030
import opinfo._
1032-
if (targs.nonEmpty)
1033-
migrationWarning(offset, "type application is not allowed for infix operators", "2.13.11")
10341031
val operatorPos: Position = Position.range(rhs.pos.source, offset, offset, offset + operator.length)
10351032
val pos = lhs.pos.union(rhs.pos).union(operatorPos).withEnd(in.lastOffset).withPoint(offset)
10361033

1034+
if (targs.nonEmpty) {
1035+
val qual = unit.sourceAt(lhs.pos)
1036+
val fun = s"${CodeAction.maybeWrapInParens(qual)}.${unit.sourceAt(operatorPos.withEnd(rhs.pos.start))}".trim
1037+
val fix = s"$fun${CodeAction.wrapInParens(unit.sourceAt(rhs.pos))}"
1038+
val msg = "type application is not allowed for infix operators"
1039+
migrationWarning(offset, msg, "2.13.11",
1040+
runReporting.codeAction("use selection", pos, fix, msg))
1041+
}
10371042
atPos(pos)(makeBinop(isExpr, lhs, operator, rhs, operatorPos, targs))
10381043
}
10391044

@@ -1090,7 +1095,9 @@ self =>
10901095
if (in.token == ARROW)
10911096
atPos(start, in.skipToken()) { makeSafeFunctionType(ts, typ()) }
10921097
else if (ts.isEmpty) {
1093-
syntaxError(start, "Illegal literal type (), use Unit instead")
1098+
val msg = "Illegal literal type (), use Unit instead"
1099+
syntaxError(start, msg,
1100+
runReporting.codeAction("use `Unit`", r2p(start, start, start + 2), "Unit", msg, expected = Some(("()", unit))))
10941101
EmptyTree
10951102
}
10961103
else {
@@ -1174,7 +1181,9 @@ self =>
11741181
if (lookingAhead(in.token == RPAREN)) {
11751182
in.nextToken()
11761183
in.nextToken()
1177-
syntaxError(start, "Illegal literal type (), use Unit instead")
1184+
val msg = "Illegal literal type (), use Unit instead"
1185+
syntaxError(start, msg,
1186+
runReporting.codeAction("use `Unit`", r2p(start, start, start + 2), "Unit", msg, expected = Some(("()", unit))))
11781187
EmptyTree
11791188
}
11801189
else
@@ -1475,9 +1484,9 @@ self =>
14751484
else withPlaceholders(interpolatedString(inPattern), isAny = true) // interpolator params are Any* by definition
14761485
}
14771486
else if (in.token == SYMBOLLIT) {
1478-
def msg(what: String) =
1479-
s"""symbol literal is $what; use Symbol("${in.strVal}") instead"""
1480-
deprecationWarning(in.offset, msg("deprecated"), "2.13.0")
1487+
val msg = s"""symbol literal is deprecated; use Symbol("${in.strVal}") instead"""
1488+
deprecationWarning(in.offset, msg, "2.13.0",
1489+
runReporting.codeAction("replace symbol literal", r2p(in.offset, in.offset, in.offset + 1 + in.strVal.length), s"""Symbol("${in.strVal}")""", msg, expected = Some((s"'${in.strVal}", unit))))
14811490
Apply(scalaDot(nme.Symbol), List(finish(in.strVal)))
14821491
}
14831492
else finish(in.token match {
@@ -2095,17 +2104,15 @@ self =>
20952104
val hasEq = in.token == EQUALS
20962105

20972106
if (hasVal) {
2098-
def actions = {
2099-
val pos = r2p(valOffset, valOffset, valOffset + 4)
2100-
if (unit.sourceAt(pos) != "val ") Nil else
2101-
List(CodeAction("val in for comprehension", None, List(TextEdit(pos, ""))))
2102-
}
2107+
def actions(msg: String) = runReporting.codeAction("remove `val` keyword", r2p(valOffset, valOffset, valOffset + 4), "", msg, expected = Some(("val ", unit)))
21032108
def msg(what: String, instead: String): String = s"`val` keyword in for comprehension is $what: $instead"
21042109
if (hasEq) {
21052110
val without = "instead, bind the value without `val`"
21062111
hardMigrationWarning(in.offset, msg("deprecated", without), msg("unsupported", without), "2.10.0", actions)
2112+
} else {
2113+
val m = msg("unsupported", "just remove `val`")
2114+
syntaxError(in.offset, m, actions(m))
21072115
}
2108-
else syntaxError(in.offset, msg("unsupported", "just remove `val`"), actions)
21092116
}
21102117

21112118
if (hasEq && eqOK && !hasCase) in.nextToken()
@@ -2969,7 +2976,11 @@ self =>
29692976
def funDefOrDcl(start: Int, mods: Modifiers): Tree = {
29702977
in.nextToken()
29712978
if (in.token == THIS) {
2972-
def missingEquals() = hardMigrationWarning(in.lastOffset, "procedure syntax is deprecated for constructors: add `=`, as in method definition", "2.13.2")
2979+
def missingEquals() = {
2980+
val msg = "procedure syntax is deprecated for constructors: add `=`, as in method definition"
2981+
hardMigrationWarning(in.lastOffset, msg, "2.13.2",
2982+
runReporting.codeAction("replace procedure syntax", o2p(in.lastOffset), " =", msg))
2983+
}
29732984
atPos(start, in.skipToken()) {
29742985
val vparamss = paramClauses(nme.CONSTRUCTOR, classContextBounds map (_.duplicate), ofCaseClass = false)
29752986
newLineOptWhenFollowedBy(LBRACE)
@@ -3006,8 +3017,8 @@ self =>
30063017
var restype = fromWithinReturnType(typedOpt())
30073018
def msg(what: String, instead: String) =
30083019
s"procedure syntax is $what: instead, add `$instead` to explicitly declare `$name`'s return type"
3009-
def declActions = List(CodeAction("procedure syntax (decl)", None, List(TextEdit(o2p(in.lastOffset), ": Unit"))))
3010-
def defnActions = List(CodeAction("procedure syntax (defn)", None, List(TextEdit(o2p(in.lastOffset), ": Unit ="))))
3020+
def declActions(msg: String) = runReporting.codeAction("add result type", o2p(in.lastOffset), ": Unit", msg)
3021+
def defnActions(msg: String) = runReporting.codeAction("replace procedure syntax", o2p(in.lastOffset), ": Unit =", msg)
30113022
val rhs =
30123023
if (isStatSep || in.token == RBRACE) {
30133024
if (restype.isEmpty) {
@@ -3035,7 +3046,11 @@ self =>
30353046
if (nme.isEncodedUnary(name) && vparamss.nonEmpty) {
30363047
def instead = DefDef(newmods, name.toTermName.decodedName, tparams, vparamss.drop(1), restype, rhs)
30373048
def unaryMsg(what: String) = s"unary prefix operator definition with empty parameter list is $what: instead, remove () to declare as `$instead`"
3038-
def warnNilary() = hardMigrationWarning(nameOffset, unaryMsg("deprecated"), unaryMsg("unsupported"), "2.13.4")
3049+
def action(msg: String) = {
3050+
val o = nameOffset + name.decode.length
3051+
runReporting.codeAction("remove ()", r2p(o, o, o + 2), "", msg, expected = Some(("()", unit)))
3052+
}
3053+
def warnNilary() = hardMigrationWarning(nameOffset, unaryMsg("deprecated"), unaryMsg("unsupported"), "2.13.4", action)
30393054
vparamss match {
30403055
case List(List()) => warnNilary()
30413056
case List(List(), x :: xs) if x.mods.isImplicit => warnNilary()
@@ -3313,7 +3328,9 @@ self =>
33133328
*/
33143329
def templateOpt(mods: Modifiers, name: Name, constrMods: Modifiers, vparamss: List[List[ValDef]], tstart: Offset): Template = {
33153330
def deprecatedUsage(): Boolean = {
3316-
deprecationWarning(in.offset, "Using `<:` for `extends` is deprecated", since = "2.12.5")
3331+
val msg = "Using `<:` for `extends` is deprecated"
3332+
deprecationWarning(in.offset, msg, since = "2.12.5",
3333+
runReporting.codeAction("use `extends`", r2p(in.offset, in.offset, in.offset + 2), "extends", msg, expected = Some(("<:", unit))))
33173334
true
33183335
}
33193336
val (parents, self, body) =

src/compiler/scala/tools/nsc/ast/parser/Scanners.scala

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,8 @@ trait Scanners extends ScannersCommon {
167167
}
168168

169169
abstract class Scanner extends CharArrayReader with TokenData with ScannerData with ScannerCommon with DocScanner {
170+
def unit: CompilationUnit
171+
170172
/** A switch whether operators at the start of lines can be infix operators. */
171173
private var allowLeadingInfixOperators = true
172174

@@ -817,10 +819,14 @@ trait Scanners extends ScannersCommon {
817819
case _ =>
818820
def fetchOther() = {
819821
if (ch == '\u21D2') {
820-
deprecationWarning("The unicode arrow `⇒` is deprecated, use `=>` instead. If you still wish to display it as one character, consider using a font with programming ligatures such as Fira Code.", "2.13.0")
822+
val msg = "The unicode arrow `⇒` is deprecated, use `=>` instead. If you still wish to display it as one character, consider using a font with programming ligatures such as Fira Code."
823+
deprecationWarning(msg, "2.13.0",
824+
runReporting.codeAction("replace unicode arrow", unit.position(offset).withEnd(offset + 1), "=>", msg, expected = Some(("", unit))))
821825
nextChar(); token = ARROW
822826
} else if (ch == '\u2190') {
823-
deprecationWarning("The unicode arrow `←` is deprecated, use `<-` instead. If you still wish to display it as one character, consider using a font with programming ligatures such as Fira Code.", "2.13.0")
827+
val msg = "The unicode arrow `←` is deprecated, use `<-` instead. If you still wish to display it as one character, consider using a font with programming ligatures such as Fira Code."
828+
deprecationWarning(msg, "2.13.0",
829+
runReporting.codeAction("replace unicode arrow", unit.position(offset).withEnd(offset + 1), "<-", msg, expected = Some(("", unit))))
824830
nextChar(); token = LARROW
825831
} else if (isUnicodeIdentifierStart(ch)) {
826832
putChar(ch)
@@ -1370,7 +1376,9 @@ trait Scanners extends ScannersCommon {
13701376
// 1l is an acknowledged bad practice
13711377
def lintel(): Unit = {
13721378
val msg = "Lowercase el for long is not recommended because it is easy to confuse with numeral 1; use uppercase L instead"
1373-
if (ch == 'l') deprecationWarning(numberOffset + cbuf.length, msg, since="2.13.0")
1379+
val o = numberOffset + cbuf.length
1380+
if (ch == 'l') deprecationWarning(o, msg, since="2.13.0",
1381+
runReporting.codeAction("use uppercase L", unit.position(o).withEnd(o + 1), "L", msg, expected = Some(("l", unit))))
13741382
}
13751383
// after int: 5e7f, 42L, 42.toDouble but not 42b.
13761384
def restOfNumber(): Unit = {
@@ -1573,6 +1581,8 @@ trait Scanners extends ScannersCommon {
15731581
* Useful for looking inside source files that are not currently compiled to see what's there
15741582
*/
15751583
class SourceFileScanner(val source: SourceFile) extends Scanner {
1584+
def unit = global.currentUnit
1585+
15761586
val buf = source.content
15771587

15781588
// suppress warnings, throw exception on errors
@@ -1584,7 +1594,7 @@ trait Scanners extends ScannersCommon {
15841594

15851595
/** A scanner over a given compilation unit
15861596
*/
1587-
class UnitScanner(val unit: CompilationUnit, patches: List[BracePatch]) extends SourceFileScanner(unit.source) {
1597+
class UnitScanner(override val unit: CompilationUnit, patches: List[BracePatch]) extends SourceFileScanner(unit.source) {
15881598
def this(unit: CompilationUnit) = this(unit, List())
15891599

15901600
override def warning(off: Offset, msg: String, category: WarningCategory): Unit =

src/compiler/scala/tools/nsc/settings/StandardScalaSettings.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,11 @@ trait StandardScalaSettings { _: MutableSettings =>
6464
| -quickfix:msg=Auto-application apply quick fixes where the message contains "Auto-application"
6565
|
6666
|Use `-Wconf:any:warning-verbose` to display applicable message filters with each warning.
67+
|
68+
|Use `-quickfix:silent` to omit the `[quickfixable]` tag in compiler messages.
6769
|""".stripMargin),
6870
prepend = true)
71+
def quickFixSilent: Boolean = quickfix.value == List("silent")
6972
val release =
7073
ChoiceSetting("-release", "release", "Compile for a version of the Java API and target class file.", AllTargetVersions, normalizeTarget(javaSpecVersion))
7174
.withPostSetHook { setting =>

src/compiler/scala/tools/nsc/typechecker/Adaptations.scala

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -98,9 +98,13 @@ trait Adaptations {
9898
if (settings.lintArgDiscard && discardedArgs) context.warning(t.pos, adaptWarningMessage(
9999
s"adapted the argument list to expected Unit type: arguments will be discarded"),
100100
WarningCategory.LintAdaptedArgs)
101-
else if (settings.warnAdaptedArgs && !isInfix) context.warning(t.pos, adaptWarningMessage(
102-
s"adapted the argument list to the expected ${args.size}-tuple: add additional parens instead"),
103-
WarningCategory.LintAdaptedArgs)
101+
else if (settings.warnAdaptedArgs && !isInfix) {
102+
val msg = adaptWarningMessage(
103+
s"adapted the argument list to the expected ${args.size}-tuple: add additional parens instead")
104+
val pos = wrappingPos(args)
105+
context.warning(t.pos, msg, WarningCategory.LintAdaptedArgs,
106+
runReporting.codeAction("add wrapping parentheses", pos, s"(${currentUnit.sourceAt(pos)})", msg))
107+
}
104108
true // keep adaptation
105109
}
106110
if (args.nonEmpty)

0 commit comments

Comments
 (0)