Skip to content

Commit

Permalink
Demonstrate how xsbti.Problem#quickfix could be used
Browse files Browse the repository at this point in the history
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`.
  • Loading branch information
smarter committed Mar 28, 2023
1 parent d36cd2d commit 378928b
Show file tree
Hide file tree
Showing 8 changed files with 78 additions and 8 deletions.
4 changes: 4 additions & 0 deletions compiler/src/dotty/tools/dotc/reporting/Message.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import scala.language.unsafeNulls

import scala.annotation.threadUnsafe

import rewrites.Rewrites.Patch

/** ## Tips for error message generation
*
* - You can use the `em` interpolator for error messages. It's defined in core.Decorators.
Expand Down Expand Up @@ -384,6 +386,8 @@ abstract class Message(val errorId: ErrorMessageID)(using Context) { self =>
*/
def showAlways = false

def quickFix(using Context): java.util.List[Patch] = java.util.Collections.emptyList

override def toString = msg
}

Expand Down
11 changes: 10 additions & 1 deletion compiler/src/dotty/tools/dotc/reporting/messages.scala
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ import transform.SymUtils._
import scala.util.matching.Regex
import java.util.regex.Matcher.quoteReplacement
import cc.CaptureSet.IdentityCaptRefMap
import util.Spans._
import rewrites.Rewrites.Patch

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

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

class MissingEmptyArgumentList(method: String)(using Context)
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/rewrites/Rewrites.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import java.nio.charset.StandardCharsets.UTF_8
object Rewrites {
private class PatchedFiles extends mutable.HashMap[SourceFile, Patches]

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

Expand Down
6 changes: 3 additions & 3 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2799,11 +2799,11 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
case closure(_, _, _) =>
case _ =>
val recovered = typed(qual)(using ctx.fresh.setExploreTyperState())
report.errorOrMigrationWarning(OnlyFunctionsCanBeFollowedByUnderscore(recovered.tpe.widen), tree.srcPos, from = `3.0`)
report.errorOrMigrationWarning(OnlyFunctionsCanBeFollowedByUnderscore(recovered.tpe.widen, tree), tree.srcPos, from = `3.0`)
if (migrateTo3) {
// Under -rewrite, patch `x _` to `(() => x)`
patch(Span(tree.span.start), "(() => ")
patch(Span(qual.span.end, tree.span.end), ")")
// patch(Span(tree.span.start), "(() => ")
// patch(Span(qual.span.end, tree.span.end), ")")
return typed(untpd.Function(Nil, qual), pt)
}
}
Expand Down
1 change: 1 addition & 0 deletions project/Build.scala
Original file line number Diff line number Diff line change
Expand Up @@ -549,6 +549,7 @@ object Build {
libraryDependencies ++= Seq(
"org.scala-lang.modules" % "scala-asm" % "9.4.0-scala-1", // used by the backend
Dependencies.oldCompilerInterface, // we stick to the old version to avoid deprecation warnings
"org.scala-sbt" % "util-interface" % "1.8.1-SNAPSHOT",
"org.jline" % "jline-reader" % "3.19.0", // used by the REPL
"org.jline" % "jline-terminal" % "3.19.0",
"org.jline" % "jline-terminal-jna" % "3.19.0", // needed for Windows
Expand Down
17 changes: 15 additions & 2 deletions sbt-bridge/src/dotty/tools/xsbt/DelegatingReporter.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,12 @@
import dotty.tools.dotc.reporting.Message;
import dotty.tools.dotc.util.SourceFile;
import dotty.tools.dotc.util.SourcePosition;
import dotty.tools.dotc.rewrites.Rewrites.Patch;
import xsbti.Position;
import xsbti.Severity;

import static java.util.stream.Collectors.toList;

final public class DelegatingReporter extends AbstractReporter {
private xsbti.Reporter delegate;

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

public void doReport(Diagnostic dia, Context ctx) {
Severity severity = severityOf(dia.level());
Position position = positionOf(dia.pos().nonInlined());
SourcePosition srcPosition = dia.pos().nonInlined();
Position position = positionOf(srcPosition);

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

delegate.log(new Problem(position, messageBuilder.toString(), severity, rendered.toString(), diagnosticCode));
delegate.log(new Problem(position, messageBuilder.toString(), severity, rendered.toString(), diagnosticCode, quickFix));
}

private static Severity severityOf(int level) {
Expand All @@ -71,6 +77,13 @@ private static Position positionOf(SourcePosition pos) {
}
}

// TODO: Replace Patch#span by Patch#SourcePosition to support patches in other files?
private static xsbti.TextEdit textEditOf(Patch patch, SourceFile source) {
SourcePosition srcPos = SourcePosition.apply(source, patch.span(), dotty.tools.dotc.util.NoSourcePosition$.MODULE$);
Position pos = positionOf(srcPos);
return new TextEditBridge(pos, patch.replacement());
}

@SuppressWarnings("unchecked")
// [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
// [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>
Expand Down
10 changes: 9 additions & 1 deletion sbt-bridge/src/dotty/tools/xsbt/Problem.java
Original file line number Diff line number Diff line change
@@ -1,23 +1,27 @@
package dotty.tools.xsbt;

import java.util.List;
import java.util.Optional;
import xsbti.Position;
import xsbti.Severity;
import xsbti.TextEdit;

final public class Problem implements xsbti.Problem {
private final Position _position;
private final String _message;
private final Severity _severity;
private final Optional<String> _rendered;
private final String _diagnosticCode;
private final List<TextEdit> _quickFix;

public Problem(Position position, String message, Severity severity, String rendered, String diagnosticCode) {
public Problem(Position position, String message, Severity severity, String rendered, String diagnosticCode, List<TextEdit> quickFix) {
super();
this._position = position;
this._message = message;
this._severity = severity;
this._rendered = Optional.of(rendered);
this._diagnosticCode = diagnosticCode;
this._quickFix = quickFix;
}

public String category() {
Expand All @@ -40,6 +44,10 @@ public Optional<String> rendered() {
return _rendered;
}

public List<TextEdit> quickFix() {
return _quickFix;
}

public Optional<xsbti.DiagnosticCode> diagnosticCode() {
// We don't forward the code if it's -1 since some tools will assume that this is actually
// the diagnostic code and show it or attempt to use it. This will ensure tools consuming
Expand Down
35 changes: 35 additions & 0 deletions sbt-bridge/src/dotty/tools/xsbt/TextEditBridge.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Zinc - The incremental compiler for Scala.
* Copyright Lightbend, Inc. and Mark Harrah
*/

package dotty.tools.xsbt;

import dotty.tools.dotc.util.SourceFile;
import dotty.tools.dotc.util.SourcePosition;
import dotty.tools.io.AbstractFile;
import xsbti.Position;
import xsbti.TextEdit;

import java.io.File;
import java.util.Optional;

public class TextEditBridge implements TextEdit {
private final Position position;
private final String newText;

public TextEditBridge(Position position, String newText) {
this.position = position;
this.newText = newText;
}

@Override
public Position position() {
return position;
}

@Override
public String newText() {
return newText;
}
}

0 comments on commit 378928b

Please sign in to comment.