diff --git a/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala b/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala index 96942f913934..e639f4499f70 100644 --- a/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala +++ b/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala @@ -229,6 +229,7 @@ enum ErrorMessageID(val isActive: Boolean = true) extends java.lang.Enum[ErrorMe case PointlessAppliedConstructorTypeID // errorNumber: 213 case IllegalContextBoundsID // errorNumber: 214 case NamedPatternNotApplicableID // errorNumber: 215 + case UnnecessaryNN // errorNumber: 216 def errorNumber = ordinal - 1 diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index f69dfea0007a..7d0de344a44f 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -3549,3 +3549,24 @@ final class NamedPatternNotApplicable(selectorType: Type)(using Context) extends i"Named patterns cannot be used with $selectorType, because it is not a named tuple or case class" override protected def explain(using Context): String = "" + +/** @param reason The reason for the unnecessary null. The warning given to the user will be i""""Unncessary .nn: $reason""" + * @param sourcePosition The sourcePosition of the qualifier + */ +class UnnecessaryNN(reason: String, sourcePosition: SourcePosition)(using Context) extends SyntaxMsg(UnnecessaryNN) { + override def msg(using Context) = i"""Unnecessary .nn: $reason""" + + override def explain(using Context) = "" + + private val nnSourcePosition = SourcePosition(sourcePosition.source, Span(sourcePosition.span.end, sourcePosition.span.end + 3, sourcePosition.span.end), sourcePosition.outer) + + override def actions(using Context) = + List( + CodeAction(title = """Remove unnecessary .nn""", + description = None, + patches = List( + ActionPatch(nnSourcePosition, "") + ) + ) + ) +} diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 915bfb8ee1e1..50a791355041 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1093,8 +1093,8 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer if symbol.exists && symbol.owner == defn.ScalaPredefModuleClass && symbol.name == nme.nn then tree match case Apply(_, args) => - if(args.head.tpe.isNotNull) then report.warning("Unnecessary .nn: qualifier is already not null", tree) - if pt.admitsNull then report.warning("Unnecessary .nn: expected type admits null", tree) + if(args.head.tpe.isNotNull) then report.warning(UnnecessaryNN("qualifier is already not null", args.head.sourcePos), tree) + if pt.admitsNull then report.warning(UnnecessaryNN("expected type admits null", args.head.sourcePos), tree) case _ => } } diff --git a/compiler/test/dotty/tools/dotc/reporting/CodeActionTest.scala b/compiler/test/dotty/tools/dotc/reporting/CodeActionTest.scala index ffc6762cc8c7..91074110389e 100644 --- a/compiler/test/dotty/tools/dotc/reporting/CodeActionTest.scala +++ b/compiler/test/dotty/tools/dotc/reporting/CodeActionTest.scala @@ -4,6 +4,7 @@ import dotty.tools.DottyTest import dotty.tools.dotc.rewrites.Rewrites import dotty.tools.dotc.rewrites.Rewrites.ActionPatch import dotty.tools.dotc.util.SourceFile +import dotty.tools.dotc.core.Contexts._ import scala.annotation.tailrec import scala.jdk.CollectionConverters.* @@ -149,14 +150,43 @@ class CodeActionTest extends DottyTest: afterPhase = "patternMatcher" ) + @Test def removeNN = + val ctxx = newContext + ctxx.setSetting(ctxx.settings.YexplicitNulls, true) + checkCodeAction( + code = + """|val s: String|Null = "foo".nn + |""".stripMargin, + title = "Remove unnecessary .nn", + expected = + """|val s: String|Null = "foo" + |""".stripMargin, + ctxx = ctxx + ) + + + @Test def removeNN2 = + val ctxx = newContext + ctxx.setSetting(ctxx.settings.YexplicitNulls, true) + checkCodeAction( + code = + """val s: String|Null = null.nn + |""".stripMargin, + title = "Remove unnecessary .nn", + expected = + """val s: String|Null = null + |""".stripMargin, + ctxx = ctxx + ) + // Make sure we're not using the default reporter, which is the ConsoleReporter, // meaning they will get reported in the test run and that's it. private def newContext = val rep = new StoreReporter(null) with UniqueMessagePositions with HideNonSensicalMessages initialCtx.setReporter(rep).withoutColors - private def checkCodeAction(code: String, title: String, expected: String, afterPhase: String = "typer") = - ctx = newContext + private def checkCodeAction(code: String, title: String, expected: String, afterPhase: String = "typer", ctxx: Context = newContext) = + ctx = ctxx val source = SourceFile.virtual("test", code).content val runCtx = checkCompile(afterPhase, code) { (_, _) => () } val diagnostics = runCtx.reporter.removeBufferedMessages