Skip to content

Commit 5f46db7

Browse files
committed
Add quick fixes to more warnings and errors
1 parent 9ecf74d commit 5f46db7

File tree

84 files changed

+525
-338
lines changed

Some content is hidden

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

84 files changed

+525
-338
lines changed

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,9 @@ trait Reporting extends internal.Reporting { self: ast.Positions with Compilatio
352352
def warning(pos: Position, msg: String, category: WarningCategory, site: Symbol, origin: String): Unit =
353353
issueIfNotSuppressed(Message.Origin(pos, msg, category, siteName(site), origin, actions = Nil))
354354

355+
def codeAction(title: String, pos: Position, newText: String, desc: String, expected: Option[(String, CompilationUnit)] = None) =
356+
CodeAction(title, pos, newText, desc, expected.forall(e => e._1 == e._2.sourceAt(pos)))
357+
355358
// Remember CodeActions that match `-quickfix` and report the error through the reporter
356359
def error(pos: Position, msg: String, actions: List[CodeAction]): Unit = {
357360
val quickfixed = {

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/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)

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

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ trait ContextErrors extends splain.SplainErrors {
5959
def errPos = underlyingTree.pos
6060
}
6161

62-
case class NormalTypeError(underlyingTree: Tree, errMsg: String)
62+
case class NormalTypeError(underlyingTree: Tree, errMsg: String, override val actions: List[CodeAction] = Nil)
6363
extends TreeTypeError
6464

6565
case class AccessTypeError(underlyingTree: Tree, errMsg: String)
@@ -104,8 +104,8 @@ trait ContextErrors extends splain.SplainErrors {
104104
extends AbsTypeError
105105

106106
object ErrorUtils {
107-
def issueNormalTypeError(tree: Tree, msg: String)(implicit context: Context): Unit = {
108-
issueTypeError(NormalTypeError(tree, msg))
107+
def issueNormalTypeError(tree: Tree, msg: String, actions: List[CodeAction] = Nil)(implicit context: Context): Unit = {
108+
issueTypeError(NormalTypeError(tree, msg, actions))
109109
}
110110

111111
def issueSymbolTypeError(sym: Symbol, msg: String)(implicit context: Context): Unit = {
@@ -196,8 +196,14 @@ trait ContextErrors extends splain.SplainErrors {
196196
s"Implicit definition ${if (currentRun.isScala3) "must" else "should"} have explicit type${
197197
if (!inferred.isErroneous) s" (inferred $inferred)" else ""
198198
}"
199-
if (currentRun.isScala3) cx.warning(tree.pos, msg, WarningCategory.Scala3Migration)
200-
else cx.warning(tree.pos, msg, WarningCategory.OtherImplicitType)
199+
// Assumption: tree.pos.focus points to the beginning of the name.
200+
// DefTree doesn't give the name position; also it can be a synthetic accessor DefDef with only offset pos.
201+
val namePos = tree.pos.focus.withEnd(tree.pos.point + tree.symbol.decodedName.length)
202+
val action =
203+
if (currentUnit.sourceAt(namePos) == tree.symbol.decodedName) runReporting.codeAction("insert explicit type", namePos.focusEnd, s": $inferred", msg)
204+
else Nil
205+
if (currentRun.isScala3) cx.warning(tree.pos, msg, WarningCategory.Scala3Migration, action)
206+
else cx.warning(tree.pos, msg, WarningCategory.OtherImplicitType, action)
201207
}
202208
val sym = tree.symbol
203209
// Defer warning field of class until typing getter (which is marked implicit)
@@ -533,8 +539,8 @@ trait ContextErrors extends splain.SplainErrors {
533539
final val UnderscoreNullaryEtaWarnMsg = mkUnderscoreNullaryEtaMessage("no longer")
534540
final val UnderscoreNullaryEtaErrorMsg = mkUnderscoreNullaryEtaMessage("not")
535541

536-
def UnderscoreNullaryEtaError(tree: Tree) = {
537-
issueNormalTypeError(tree, UnderscoreNullaryEtaErrorMsg)
542+
def UnderscoreNullaryEtaError(tree: Tree, actions: List[CodeAction]) = {
543+
issueNormalTypeError(tree, UnderscoreNullaryEtaErrorMsg, actions)
538544
setError(tree)
539545
}
540546

0 commit comments

Comments
 (0)