From e33b637e1ecfebfa1674952c5bbc4df20284f2e6 Mon Sep 17 00:00:00 2001 From: Seyon Sivatharan Date: Tue, 1 Jul 2025 20:17:24 -0400 Subject: [PATCH 1/3] Add quick fix to remove unnecessary .nn --- .../tools/dotc/reporting/ErrorMessageID.scala | 1 + .../dotty/tools/dotc/reporting/messages.scala | 35 +++++++++++++++++++ .../src/dotty/tools/dotc/typer/Typer.scala | 4 +-- 3 files changed, 38 insertions(+), 2 deletions(-) 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..01dd4887ce33 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -3549,3 +3549,38 @@ 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) = { + val code1 = """val a: String = "foo".nn""" + val code2 = """val a: String = "foo"""" + i"""With -Yexplicit-nulls, this happens when use apply .nn to a term that is already non-null. + | + |Example: + | + |$code1 + | + |instead of + | + |$code2 + | + |""" + } + + private val nnSourcePosition = SourcePosition(sourcePosition.source, Span(sourcePosition.span.end, sourcePosition.span.end + 2, 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 _ => } } From db477ba5222d6061c1bb695947bb74262846659e Mon Sep 17 00:00:00 2001 From: Seyon Sivatharan Date: Wed, 2 Jul 2025 18:13:12 -0400 Subject: [PATCH 2/3] Remove explain message --- .../dotty/tools/dotc/reporting/messages.scala | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index 01dd4887ce33..f830951ed69b 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -3556,21 +3556,7 @@ final class NamedPatternNotApplicable(selectorType: Type)(using Context) extends 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) = { - val code1 = """val a: String = "foo".nn""" - val code2 = """val a: String = "foo"""" - i"""With -Yexplicit-nulls, this happens when use apply .nn to a term that is already non-null. - | - |Example: - | - |$code1 - | - |instead of - | - |$code2 - | - |""" - } + override def explain(using Context) = "" private val nnSourcePosition = SourcePosition(sourcePosition.source, Span(sourcePosition.span.end, sourcePosition.span.end + 2, sourcePosition.span.end), sourcePosition.outer) From 43230191c890e90c2f58b83a9c15f71f02e2ab1d Mon Sep 17 00:00:00 2001 From: HarrisL2 Date: Fri, 4 Jul 2025 13:22:34 -0400 Subject: [PATCH 3/3] Fix off-by-one and add tests --- .../dotty/tools/dotc/reporting/messages.scala | 2 +- .../tools/dotc/reporting/CodeActionTest.scala | 34 +++++++++++++++++-- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index f830951ed69b..7d0de344a44f 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -3558,7 +3558,7 @@ class UnnecessaryNN(reason: String, sourcePosition: SourcePosition)(using Contex override def explain(using Context) = "" - private val nnSourcePosition = SourcePosition(sourcePosition.source, Span(sourcePosition.span.end, sourcePosition.span.end + 2, sourcePosition.span.end), sourcePosition.outer) + 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( 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