Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 68d29fd

Browse files
committed
Show quick fixes in REPL
1 parent 906d814 commit 68d29fd

File tree

10 files changed

+102
-31
lines changed

10 files changed

+102
-31
lines changed

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

+16-13
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import scala.reflect.internal.util.{CodeAction, NoSourceFile, Position, ReplBatc
2626
import scala.tools.nsc.Reporting.Version.{NonParseableVersion, ParseableVersion}
2727
import scala.tools.nsc.Reporting._
2828
import scala.tools.nsc.settings.NoScalaVersion
29+
import scala.util.Using
2930
import scala.util.matching.Regex
3031

3132
/** Provides delegates to the reporter doing the actual work.
@@ -206,7 +207,8 @@ trait Reporting extends internal.Reporting { self: ast.Positions with Compilatio
206207
"\nScala 3 migration messages are errors under -Xsource:3. Use -Wconf / @nowarn to filter them or add -Xmigration to demote them to warnings."
207208
else ""
208209
def helpMsg(kind: String, isError: Boolean = false) =
209-
s"$quickfixed${scala3migration(isError)}\nApplicable -Wconf / @nowarn filters for this $kind: $filterHelp"
210+
sm"""|${quickfixed}${scala3migration(isError)}
211+
|Applicable -Wconf / @nowarn filters for this $kind: $filterHelp"""
210212

211213
action match {
212214
case Action.Error => reporter.error(warning.pos, helpMsg("fatal warning", isError = true), warning.actions)
@@ -444,15 +446,12 @@ trait Reporting extends internal.Reporting { self: ast.Positions with Compilatio
444446
Some(Paths.get(path))
445447
} else
446448
Option(source.file.file).map(_.toPath)
447-
val r = p.filter(Files.exists(_))
448-
if (r.isEmpty)
449-
issueWarning(Message.Plain(NoPosition, s"Failed to apply quick fixes, file does not exist: ${source.file}", WarningCategory.Other, "", Nil))
450-
r
449+
p.filter(Files.exists(_))
451450
}
452451

453452
val encoding = Charset.forName(settings.encoding.value)
454453

455-
def insertEdits(sourceChars: Array[Char], edits: List[TextEdit], file: Path): Array[Byte] = {
454+
def applyEdits(sourceChars: Array[Char], edits: List[TextEdit], file: Path): Array[Byte] = {
456455
val patchedChars = new Array[Char](sourceChars.length + edits.iterator.map(_.delta).sum)
457456
@tailrec def loop(edits: List[TextEdit], inIdx: Int, outIdx: Int): Unit = {
458457
def copy(upTo: Int): Int = {
@@ -471,22 +470,26 @@ trait Reporting extends internal.Reporting { self: ast.Positions with Compilatio
471470
issueWarning(Message.Plain(NoPosition, s"Unexpected content length when applying quick fixes; verify the changes to ${file.toFile.getAbsolutePath}", WarningCategory.Other, "", Nil))
472471
}
473472
}
474-
475473
loop(edits, 0, 0)
476474
new String(patchedChars).getBytes(encoding)
477475
}
478476

479477
def apply(edits: mutable.Set[TextEdit]): Unit = {
480-
for ((source, edits) <- edits.groupBy(_.position.source).view.mapValues(_.toList.sortBy(_.position.start))) {
481-
if (checkNoOverlap(edits, source)) {
482-
underlyingFile(source) foreach { file =>
483-
val sourceChars = new String(Files.readAllBytes(file), encoding).toCharArray
484-
try Files.write(file, insertEdits(sourceChars, edits, file))
478+
def editsOrdered = edits.groupBy(_.position.source).view.mapValues(_.toList.sortBy(_.position.start))
479+
for ((source, edits) <- editsOrdered if checkNoOverlap(edits, source)) {
480+
val sourceChars = source.content
481+
val amended = applyEdits(sourceChars, edits, Paths.get(source.file.path))
482+
underlyingFile(source) match {
483+
case Some(file) =>
484+
try Files.write(file, amended)
485485
catch {
486486
case e: IOException =>
487487
issueWarning(Message.Plain(NoPosition, s"Failed to apply quick fixes to ${file.toFile.getAbsolutePath}\n${e.getMessage}", WarningCategory.Other, "", Nil))
488488
}
489-
}
489+
case _ if source.file.isVirtual =>
490+
Using(source.file.output)(_.write(amended))
491+
case _ =>
492+
issueWarning(Message.Plain(NoPosition, s"Failed to apply quick fixes, file does not exist: ${source.file}", WarningCategory.Other, "", Nil))
490493
}
491494
}
492495
}

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

+4-4
Original file line numberDiff line numberDiff line change
@@ -976,12 +976,12 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
976976
// > // Add a () parameter section if this overrides some method with () parameters
977977
// > val vparamSymssOrEmptyParamsFromOverride =
978978
// This means an accessor that overrides a Java-defined method gets a MethodType instead of a NullaryMethodType, which breaks lots of assumptions about accessors)
979-
def checkCanAutoApply(): Boolean = {
979+
def checkCanAutoApply(): true = {
980980
if (!isPastTyper && !matchNullaryLoosely) {
981981
val msg =
982-
s"""Auto-application to `()` is deprecated. Supply the empty argument list `()` explicitly to invoke method ${meth.decodedName},
983-
|or remove the empty argument list from its definition (Java-defined methods are exempt).
984-
|In Scala 3, an unapplied method like this will be eta-expanded into a function.""".stripMargin
982+
sm"""|Auto-application to `()` is deprecated. Supply the empty argument list `()` explicitly to invoke method ${meth.decodedName},
983+
|or remove the empty argument list from its definition (Java-defined methods are exempt).
984+
|In Scala 3, an unapplied method like this will be eta-expanded into a function."""
985985
val action = runReporting.codeAction("add `()`", tree.pos.focusEnd, "()", msg)
986986
context.deprecationWarning(tree.pos, NoSymbol, msg, "2.13.3", action)
987987
}

src/repl-frontend/scala/tools/nsc/MainGenericRunner.scala

+2
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ class MainGenericRunner {
8484
settings.deprecation.value = true
8585
settings.feature.value = true
8686
}
87+
if (settings.quickfix.isDefault)
88+
settings.quickfix.value = List("any")
8789
val config = ShellConfig(settings)
8890
new ILoop(config).run(settings)
8991
None

src/repl-frontend/scala/tools/nsc/interpreter/shell/Reporter.scala

+34-14
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ package scala.tools.nsc.interpreter.shell
1414

1515
import java.io.PrintWriter
1616
import scala.reflect.internal
17-
import scala.reflect.internal.util.{CodeAction, NoSourceFile, Position, StringOps}
17+
import scala.reflect.internal.util.{CodeAction, NoSourceFile, Position, StringOps, TextEdit}
1818
import scala.tools.nsc.interpreter.{Naming, ReplReporter, ReplRequest}
1919
import scala.tools.nsc.reporters.FilteringReporter
2020
import scala.tools.nsc.{ConsoleWriter, NewLinePrintWriter, Settings}
@@ -87,20 +87,19 @@ class ReplReporterImpl(val config: ShellConfig, val settings: Settings = new Set
8787
}
8888

8989
/** The maximum length of toString to use when printing the result
90-
* of an evaluation. 0 means no maximum. If a printout requires
91-
* more than this number of characters, then the printout is
92-
* truncated.
93-
*/
90+
* of an evaluation. 0 means no maximum. If a printout requires
91+
* more than this number of characters, then the printout is truncated.
92+
*/
9493
var maxPrintString = config.maxPrintString.option getOrElse 800
9594

9695
/** Whether very long lines can be truncated. This exists so important
97-
* debugging information (like printing the classpath) is not rendered
98-
* invisible due to the max message length.
99-
*/
96+
* debugging information (like printing the classpath) is not rendered
97+
* invisible due to the max message length.
98+
*/
10099
var truncationOK: Boolean = !settings.verbose.value
101100

102101
def truncate(str: String): String =
103-
if (truncationOK && (maxPrintString != 0 && str.length > maxPrintString)) (str take maxPrintString - 3) + "..."
102+
if (truncationOK && maxPrintString != 0 && str.length > maxPrintString) s"${str.take(maxPrintString - 3)}..."
104103
else str
105104

106105
def withoutTruncating[T](body: => T): T = {
@@ -154,15 +153,32 @@ class ReplReporterImpl(val config: ShellConfig, val settings: Settings = new Set
154153
if (colorOk) severityColor(severity) + clabel(severity) + RESET
155154
else clabel(severity)
156155

157-
printMessageAt(pos, prefix + msg)
156+
val amended = {
157+
val line0 = pos.lineContent
158+
var line = line0
159+
if (line.nonEmpty && line.indexOf('\n') == -1 && actions.nonEmpty) {
160+
val comment = new StringBuilder
161+
for (CodeAction(title, description, edits) <- actions; TextEdit(editPos, editText) <- edits)
162+
if (pos.line == editPos.line) {
163+
line = line.patch(editPos.start, editText, editPos.end - editPos.start)
164+
if (comment.nonEmpty) comment.append("; ")
165+
comment.append(title)
166+
}
167+
if (line == line0) ""
168+
else if (comment.isEmpty) line
169+
else s"$line // $comment" // sb.append(s"$patched // ${description.getOrElse(title)}")
170+
}
171+
else ""
172+
}
173+
printMessageAt(pos, prefix + msg, amended)
158174
}
159175

160176
// indent errors, error message uses the caret to point at the line already on the screen instead of repeating it
161-
// TODO: can we splice the error into the code the user typed when multiple lines were entered?
177+
// can we splice the error into the code the user typed when multiple lines were entered?
162178
// (should also comment out the error to keep multi-line copy/pastable)
163-
// TODO: multiple errors are not very intuitive (should the second error for same line repeat the line?)
164-
// TODO: the console could be empty due to external changes (also, :reset? -- see unfortunate example in jvm/interpreter (plusOne))
165-
def printMessageAt(posIn: Position, msg: String): Unit = {
179+
// multiple errors are not very intuitive (should the second error for same line repeat the line?)
180+
// the console could be empty due to external changes (also, :reset? -- see unfortunate example in jvm/interpreter (plusOne))
181+
def printMessageAt(posIn: Position, msg: String, amended: String): Unit = {
166182
if ((posIn eq null) || (posIn.source eq NoSourceFile)) printMessage(msg)
167183
else if (posIn.source.file.name == "<console>" && posIn.line == 1) {
168184
// If there's only one line of input, and it's already printed on the console (as indicated by the position's source file name),
@@ -200,6 +216,10 @@ class ReplReporterImpl(val config: ShellConfig, val settings: Settings = new Set
200216

201217
if (isSynthetic) printMessage("\n(To diagnose errors in synthetic code, try adding `// show` to the end of your input.)")
202218
}
219+
amended match {
220+
case null | "" =>
221+
case fixup => printMessage(/*config.promptText*/ s"\nfixed> $fixup")
222+
}
203223
if (settings.prompt.value) displayPrompt()
204224
}
205225

test/files/jvm/interpreter.check

+2
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,8 @@ class Bar
8989
scala> implicit def foo2bar(foo: Foo) = Bar(foo.n)
9090
^
9191
warning: Implicit definition should have explicit type (inferred Bar) [quickfixable]
92+
93+
fixed> implicit def foo2bar(foo: Foo): Bar = Bar(foo.n) // insert explicit type
9294
warning: 1 feature warning; for details, enable `:setting -feature` or `:replay -feature`
9395
def foo2bar(foo: Foo): Bar
9496

test/files/run/repl-errors.check

+2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ scala> '\060'
66
scala> def foo() { }
77
^
88
warning: procedure syntax is deprecated: instead, add `: Unit =` to explicitly declare `foo`'s return type [quickfixable]
9+
10+
fixed> def foo(): Unit = { } // replace procedure syntax
911
def foo(): Unit
1012

1113
scala> @annotation.nowarn def sshhh() { }

test/files/run/repl-suspended-warnings.check

+2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ scala> def f { }
77
error: procedure syntax is deprecated: instead, add `: Unit =` to explicitly declare `f`'s return type [quickfixable]
88
Applicable -Wconf / @nowarn filters for this fatal warning: msg=<part of the message>, cat=deprecation, version=2.13.0
99

10+
fixed> def f: Unit = { } // replace procedure syntax
11+
1012
scala> @annotation.nowarn def f { }
1113
def f: Unit
1214

test/files/run/t11402.check

+2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ scala> def f = {
77
val x = 'abc
88
^
99
On line 2: warning: symbol literal is deprecated; use Symbol("abc") instead [quickfixable]
10+
11+
fixed> val x = 'abcSymbol("abc") // replace symbol literal
1012
def f: String
1113

1214
scala> :quit

test/files/run/t12844.check

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
2+
scala> def f() = 42
3+
def f(): Int
4+
5+
scala> f
6+
^
7+
warning: Auto-application to `()` is deprecated. Supply the empty argument list `()` explicitly to invoke method f,
8+
or remove the empty argument list from its definition (Java-defined methods are exempt).
9+
In Scala 3, an unapplied method like this will be eta-expanded into a function. [quickfixable]
10+
11+
fixed> f() // add `()`
12+
val res0: Int = 42
13+
14+
scala> def g { println }
15+
^
16+
warning: procedure syntax is deprecated: instead, add `: Unit =` to explicitly declare `g`'s return type [quickfixable]
17+
18+
fixed> def g: Unit = { println } // replace procedure syntax
19+
^
20+
warning: Auto-application to `()` is deprecated. Supply the empty argument list `()` explicitly to invoke method println,
21+
or remove the empty argument list from its definition (Java-defined methods are exempt).
22+
In Scala 3, an unapplied method like this will be eta-expanded into a function. [quickfixable]
23+
24+
fixed> def g { println() } // add `()`
25+
^
26+
warning: side-effecting nullary methods are discouraged: suggest defining as `def g()` instead [quickfixable]
27+
28+
fixed> def g() { println } // add empty parameter list
29+
def g: Unit
30+
31+
scala> :quit

test/files/run/t12844.scala

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
2+
import scala.tools.partest.SessionTest
3+
4+
// .check file is the session text to verify
5+
object Test extends SessionTest {
6+
override def extraSettings: String = "-usejavacp -Xlint"
7+
}

0 commit comments

Comments
 (0)