Skip to content

Commit 378928b

Browse files
committed
Demonstrate how xsbti.Problem#quickfix could be used
This requires https://github.com/smarter/sbt/tree/quickFix The following code should emit a Problem containing a quickfix replacing `f _` by `() => f`: ``` class Foo { def f: Int = { println("hi"); 1 } val g = f _ } ``` TODO: This broke -rewrite since I commented out the `patch` call, ideally they shouldn't be needed since -rewrite should be able to use the information stored in the `Message`.
1 parent d36cd2d commit 378928b

8 files changed

Lines changed: 78 additions & 8 deletions

File tree

compiler/src/dotty/tools/dotc/reporting/Message.scala

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import scala.language.unsafeNulls
1313

1414
import scala.annotation.threadUnsafe
1515

16+
import rewrites.Rewrites.Patch
17+
1618
/** ## Tips for error message generation
1719
*
1820
* - You can use the `em` interpolator for error messages. It's defined in core.Decorators.
@@ -384,6 +386,8 @@ abstract class Message(val errorId: ErrorMessageID)(using Context) { self =>
384386
*/
385387
def showAlways = false
386388

389+
def quickFix(using Context): java.util.List[Patch] = java.util.Collections.emptyList
390+
387391
override def toString = msg
388392
}
389393

compiler/src/dotty/tools/dotc/reporting/messages.scala

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ import transform.SymUtils._
2929
import scala.util.matching.Regex
3030
import java.util.regex.Matcher.quoteReplacement
3131
import cc.CaptureSet.IdentityCaptRefMap
32+
import util.Spans._
33+
import rewrites.Rewrites.Patch
3234

3335
/** Messages
3436
* ========
@@ -1796,12 +1798,19 @@ class FailureToEliminateExistential(tp: Type, tp1: Type, tp2: Type, boundSyms: L
17961798
|are only approximated in a best-effort way."""
17971799
}
17981800

1799-
class OnlyFunctionsCanBeFollowedByUnderscore(tp: Type)(using Context)
1801+
class OnlyFunctionsCanBeFollowedByUnderscore(tp: Type, tree: untpd.PostfixOp)(using Context)
18001802
extends SyntaxMsg(OnlyFunctionsCanBeFollowedByUnderscoreID) {
18011803
def msg(using Context) = i"Only function types can be followed by ${hl("_")} but the current expression has type $tp"
18021804
def explain(using Context) =
18031805
i"""The syntax ${hl("x _")} is no longer supported if ${hl("x")} is not a function.
18041806
|To convert to a function value, you need to explicitly write ${hl("() => x")}"""
1807+
override def quickFix(using Context) =
1808+
val untpd.PostfixOp(qual, Ident(nme.WILDCARD)) = tree: @unchecked
1809+
import scala.jdk.CollectionConverters.*
1810+
import scala.language.unsafeNulls
1811+
List(
1812+
Patch(Span(tree.span.start), "(() => "),
1813+
Patch(Span(qual.span.end, tree.span.end), ")")).asJava
18051814
}
18061815

18071816
class MissingEmptyArgumentList(method: String)(using Context)

compiler/src/dotty/tools/dotc/rewrites/Rewrites.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import java.nio.charset.StandardCharsets.UTF_8
1515
object Rewrites {
1616
private class PatchedFiles extends mutable.HashMap[SourceFile, Patches]
1717

18-
private case class Patch(span: Span, replacement: String) {
18+
case class Patch(span: Span, replacement: String) {
1919
def delta = replacement.length - (span.end - span.start)
2020
}
2121

compiler/src/dotty/tools/dotc/typer/Typer.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2799,11 +2799,11 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
27992799
case closure(_, _, _) =>
28002800
case _ =>
28012801
val recovered = typed(qual)(using ctx.fresh.setExploreTyperState())
2802-
report.errorOrMigrationWarning(OnlyFunctionsCanBeFollowedByUnderscore(recovered.tpe.widen), tree.srcPos, from = `3.0`)
2802+
report.errorOrMigrationWarning(OnlyFunctionsCanBeFollowedByUnderscore(recovered.tpe.widen, tree), tree.srcPos, from = `3.0`)
28032803
if (migrateTo3) {
28042804
// Under -rewrite, patch `x _` to `(() => x)`
2805-
patch(Span(tree.span.start), "(() => ")
2806-
patch(Span(qual.span.end, tree.span.end), ")")
2805+
// patch(Span(tree.span.start), "(() => ")
2806+
// patch(Span(qual.span.end, tree.span.end), ")")
28072807
return typed(untpd.Function(Nil, qual), pt)
28082808
}
28092809
}

project/Build.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -549,6 +549,7 @@ object Build {
549549
libraryDependencies ++= Seq(
550550
"org.scala-lang.modules" % "scala-asm" % "9.4.0-scala-1", // used by the backend
551551
Dependencies.oldCompilerInterface, // we stick to the old version to avoid deprecation warnings
552+
"org.scala-sbt" % "util-interface" % "1.8.1-SNAPSHOT",
552553
"org.jline" % "jline-reader" % "3.19.0", // used by the REPL
553554
"org.jline" % "jline-terminal" % "3.19.0",
554555
"org.jline" % "jline-terminal-jna" % "3.19.0", // needed for Windows

sbt-bridge/src/dotty/tools/xsbt/DelegatingReporter.java

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,12 @@
1212
import dotty.tools.dotc.reporting.Message;
1313
import dotty.tools.dotc.util.SourceFile;
1414
import dotty.tools.dotc.util.SourcePosition;
15+
import dotty.tools.dotc.rewrites.Rewrites.Patch;
1516
import xsbti.Position;
1617
import xsbti.Severity;
1718

19+
import static java.util.stream.Collectors.toList;
20+
1821
final public class DelegatingReporter extends AbstractReporter {
1922
private xsbti.Reporter delegate;
2023

@@ -34,7 +37,8 @@ public void printSummary(Context ctx) {
3437

3538
public void doReport(Diagnostic dia, Context ctx) {
3639
Severity severity = severityOf(dia.level());
37-
Position position = positionOf(dia.pos().nonInlined());
40+
SourcePosition srcPosition = dia.pos().nonInlined();
41+
Position position = positionOf(srcPosition);
3842

3943
StringBuilder rendered = new StringBuilder();
4044
rendered.append(messageAndPos(dia, ctx));
@@ -47,8 +51,10 @@ public void doReport(Diagnostic dia, Context ctx) {
4751
rendered.append(explanation(message, ctx));
4852
messageBuilder.append(System.lineSeparator()).append(explanation(message, ctx));
4953
}
54+
java.util.List<xsbti.TextEdit> quickFix =
55+
message.quickFix(ctx).stream().map(patch -> textEditOf(patch, srcPosition.source())).collect(toList());
5056

51-
delegate.log(new Problem(position, messageBuilder.toString(), severity, rendered.toString(), diagnosticCode));
57+
delegate.log(new Problem(position, messageBuilder.toString(), severity, rendered.toString(), diagnosticCode, quickFix));
5258
}
5359

5460
private static Severity severityOf(int level) {
@@ -71,6 +77,13 @@ private static Position positionOf(SourcePosition pos) {
7177
}
7278
}
7379

80+
// TODO: Replace Patch#span by Patch#SourcePosition to support patches in other files?
81+
private static xsbti.TextEdit textEditOf(Patch patch, SourceFile source) {
82+
SourcePosition srcPos = SourcePosition.apply(source, patch.span(), dotty.tools.dotc.util.NoSourcePosition$.MODULE$);
83+
Position pos = positionOf(srcPos);
84+
return new TextEditBridge(pos, patch.replacement());
85+
}
86+
7487
@SuppressWarnings("unchecked")
7588
// [warn] sbt-bridge/src/dotty/tools/xsbt/DelegatingReporter.java:18:1: dotty$tools$dotc$reporting$UniqueMessagePositions$$positions() in dotty.tools.dotc.reporting.AbstractReporter implements dotty$tools$dotc$reporting$UniqueMessagePositions$$positions() in dotty.tools.dotc.reporting.UniqueMessagePositions
7689
// [warn] return type requires unchecked conversion from scala.collection.mutable.HashMap to scala.collection.mutable.HashMap<scala.Tuple2<dotty.tools.dotc.util.SourceFile,java.lang.Integer>,dotty.tools.dotc.reporting.Diagnostic>

sbt-bridge/src/dotty/tools/xsbt/Problem.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,27 @@
11
package dotty.tools.xsbt;
22

3+
import java.util.List;
34
import java.util.Optional;
45
import xsbti.Position;
56
import xsbti.Severity;
7+
import xsbti.TextEdit;
68

79
final public class Problem implements xsbti.Problem {
810
private final Position _position;
911
private final String _message;
1012
private final Severity _severity;
1113
private final Optional<String> _rendered;
1214
private final String _diagnosticCode;
15+
private final List<TextEdit> _quickFix;
1316

14-
public Problem(Position position, String message, Severity severity, String rendered, String diagnosticCode) {
17+
public Problem(Position position, String message, Severity severity, String rendered, String diagnosticCode, List<TextEdit> quickFix) {
1518
super();
1619
this._position = position;
1720
this._message = message;
1821
this._severity = severity;
1922
this._rendered = Optional.of(rendered);
2023
this._diagnosticCode = diagnosticCode;
24+
this._quickFix = quickFix;
2125
}
2226

2327
public String category() {
@@ -40,6 +44,10 @@ public Optional<String> rendered() {
4044
return _rendered;
4145
}
4246

47+
public List<TextEdit> quickFix() {
48+
return _quickFix;
49+
}
50+
4351
public Optional<xsbti.DiagnosticCode> diagnosticCode() {
4452
// We don't forward the code if it's -1 since some tools will assume that this is actually
4553
// the diagnostic code and show it or attempt to use it. This will ensure tools consuming
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* Zinc - The incremental compiler for Scala.
3+
* Copyright Lightbend, Inc. and Mark Harrah
4+
*/
5+
6+
package dotty.tools.xsbt;
7+
8+
import dotty.tools.dotc.util.SourceFile;
9+
import dotty.tools.dotc.util.SourcePosition;
10+
import dotty.tools.io.AbstractFile;
11+
import xsbti.Position;
12+
import xsbti.TextEdit;
13+
14+
import java.io.File;
15+
import java.util.Optional;
16+
17+
public class TextEditBridge implements TextEdit {
18+
private final Position position;
19+
private final String newText;
20+
21+
public TextEditBridge(Position position, String newText) {
22+
this.position = position;
23+
this.newText = newText;
24+
}
25+
26+
@Override
27+
public Position position() {
28+
return position;
29+
}
30+
31+
@Override
32+
public String newText() {
33+
return newText;
34+
}
35+
}

0 commit comments

Comments
 (0)