Skip to content

Commit 0bac3a9

Browse files
committed
Add error code to unused diagnostics
1 parent a6c40b1 commit 0bac3a9

File tree

6 files changed

+103
-33
lines changed

6 files changed

+103
-33
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,7 @@ enum ErrorMessageID(val isActive: Boolean = true) extends java.lang.Enum[ErrorMe
207207
case MatchTypeLegacyPatternID // errorNumber: 191
208208
case UnstableInlineAccessorID // errorNumber: 192
209209
case VolatileOnValID // errorNumber: 193
210+
case UnusedSymbolID // errorNumber: 194
210211

211212
def errorNumber = ordinal - 1
212213

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ enum MessageKind:
2121
case MatchCaseUnreachable
2222
case Compatibility
2323
case PotentialIssue
24+
case UnusedSymbol
2425

2526
/** Human readable message that will end up being shown to the user.
2627
* NOTE: This is only used in the situation where you have multiple words
@@ -37,5 +38,6 @@ enum MessageKind:
3738
case PatternMatchExhaustivity => "Pattern Match Exhaustivity"
3839
case MatchCaseUnreachable => "Match case Unreachable"
3940
case PotentialIssue => "Potential Issue"
41+
case UnusedSymbol => "Unused symbol"
4042
case kind => kind.toString
4143
end MessageKind

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

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3159,3 +3159,42 @@ class VolatileOnVal()(using Context)
31593159
extends SyntaxMsg(VolatileOnValID):
31603160
protected def msg(using Context): String = "values cannot be volatile"
31613161
protected def explain(using Context): String = ""
3162+
3163+
class UnusedSymbol(pos: SourcePosition,_msg: String, _actions: List[CodeAction])(using Context)
3164+
extends Message(UnusedSymbolID) {
3165+
def kind = MessageKind.UnusedSymbol
3166+
3167+
override def actions(using Context) = _actions
3168+
override def msg(using Context) = _msg
3169+
override def explain(using Context) = i""
3170+
}
3171+
3172+
object UnusedSymbol {
3173+
private def createRemoveLocalDefAction(tree: tpd.DefTree)(using Context): List[CodeAction] = {
3174+
import scala.language.unsafeNulls
3175+
3176+
val source = tree.sourcePos.source
3177+
val endLine = source.offsetToLine(tree.sourcePos.end - 1)
3178+
val nextLineOffset = source.lineToOffset(endLine + 1)
3179+
val startOffset = source.lineToOffset(tree.sourcePos.startLine)
3180+
3181+
val pathes = List(
3182+
ActionPatch(
3183+
srcPos = tree.sourcePos.withSpan(tree.sourcePos.span.withStart(startOffset).withEnd(nextLineOffset)),
3184+
replacement = ""
3185+
),
3186+
)
3187+
List(
3188+
CodeAction(title = s"Remove unused code",
3189+
description = None,
3190+
patches = pathes
3191+
)
3192+
)
3193+
}
3194+
def imports(pos: SourcePosition)(using Context): UnusedSymbol= new UnusedSymbol(pos, i"unused import", List.empty)
3195+
def localDefs(tree: tpd.NamedDefTree)(using Context): UnusedSymbol = new UnusedSymbol(tree.namePos, i"unused local definition", createRemoveLocalDefAction(tree))
3196+
def explicitParams(tree: tpd.NamedDefTree)(using Context): UnusedSymbol = new UnusedSymbol(tree.namePos, i"unused explicit parameter", List.empty)
3197+
def implicitParams(tree: tpd.NamedDefTree)(using Context): UnusedSymbol = new UnusedSymbol(tree.namePos, i"unused implicit parameter", List.empty)
3198+
def privateMembers(tree: tpd.NamedDefTree)(using Context): UnusedSymbol = new UnusedSymbol(tree.namePos, i"unused private member", List.empty)
3199+
def patVars(tree: tpd.NamedDefTree)(using Context): UnusedSymbol = new UnusedSymbol(tree.namePos, i"unused pattern variable", List.empty)
3200+
}

compiler/src/dotty/tools/dotc/transform/CheckUnused.scala

Lines changed: 40 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import dotty.tools.dotc.core.Phases.Phase
1313
import dotty.tools.dotc.core.StdNames
1414
import dotty.tools.dotc.report
1515
import dotty.tools.dotc.reporting.Message
16+
import dotty.tools.dotc.reporting.UnusedSymbol as UnusedSymbolMessage
1617
import dotty.tools.dotc.typer.ImportInfo
1718
import dotty.tools.dotc.util.{Property, SrcPos}
1819
import dotty.tools.dotc.core.Mode
@@ -281,24 +282,27 @@ class CheckUnused private (phaseMode: CheckUnused.PhaseMode, suffix: String, _ke
281282

282283
/** Do the actual reporting given the result of the anaylsis */
283284
private def reportUnused(res: UnusedData.UnusedResult)(using Context): Unit =
284-
res.warnings.toList.sortBy(_.pos.line)(using Ordering[Int]).foreach { s =>
285+
res.warnings.toList.sortBy{
286+
case us: UnusedSymbol.ImportSelector => us.pos.line
287+
case us: UnusedSymbol.Symbol => us.tree.sourcePos.line
288+
}(using Ordering[Int]).foreach { s =>
285289
s match
286-
case UnusedSymbol(t, _, WarnTypes.Imports) =>
287-
report.warning(s"unused import", t)
288-
case UnusedSymbol(t, _, WarnTypes.LocalDefs) =>
289-
report.warning(s"unused local definition", t)
290-
case UnusedSymbol(t, _, WarnTypes.ExplicitParams) =>
291-
report.warning(s"unused explicit parameter", t)
292-
case UnusedSymbol(t, _, WarnTypes.ImplicitParams) =>
293-
report.warning(s"unused implicit parameter", t)
294-
case UnusedSymbol(t, _, WarnTypes.PrivateMembers) =>
295-
report.warning(s"unused private member", t)
296-
case UnusedSymbol(t, _, WarnTypes.PatVars) =>
297-
report.warning(s"unused pattern variable", t)
298-
case UnusedSymbol(t, _, WarnTypes.UnsetLocals) =>
299-
report.warning(s"unset local variable, consider using an immutable val instead", t)
300-
case UnusedSymbol(t, _, WarnTypes.UnsetPrivates) =>
301-
report.warning(s"unset private variable, consider using an immutable val instead", t)
290+
case UnusedSymbol.ImportSelector(srcPos, _) =>
291+
report.warning(UnusedSymbolMessage.imports(srcPos.sourcePos))
292+
case UnusedSymbol.Symbol(t, WarnTypes.LocalDefs) =>
293+
report.warning(UnusedSymbolMessage.localDefs(t), t)
294+
case UnusedSymbol.Symbol(t, WarnTypes.ExplicitParams) =>
295+
report.warning(UnusedSymbolMessage.explicitParams(t), t)
296+
case UnusedSymbol.Symbol(t, WarnTypes.ImplicitParams) =>
297+
report.warning(UnusedSymbolMessage.implicitParams(t), t)
298+
case UnusedSymbol.Symbol(t, WarnTypes.PrivateMembers) =>
299+
report.warning(UnusedSymbolMessage.privateMembers(t), t)
300+
case UnusedSymbol.Symbol(t, WarnTypes.PatVars) =>
301+
report.warning(UnusedSymbolMessage.patVars(t), t)
302+
case UnusedSymbol.Symbol(t, WarnTypes.UnsetLocals) =>
303+
report.warning("unset local variable, consider using an immutable val instead", t)
304+
case UnusedSymbol.Symbol(t, WarnTypes.UnsetPrivates) =>
305+
report.warning("unset private variable, consider using an immutable val instead", t)
302306
}
303307

304308
end CheckUnused
@@ -311,8 +315,7 @@ object CheckUnused:
311315
case Aggregate
312316
case Report
313317

314-
private enum WarnTypes:
315-
case Imports
318+
enum WarnTypes:
316319
case LocalDefs
317320
case ExplicitParams
318321
case ImplicitParams
@@ -510,7 +513,7 @@ object CheckUnused:
510513
popScope()
511514
val sortedImp =
512515
if ctx.settings.WunusedHas.imports || ctx.settings.WunusedHas.strictNoImplicitWarn then
513-
unusedImport.map(d => UnusedSymbol(d.srcPos, d.name, WarnTypes.Imports)).toList
516+
unusedImport.map(d => UnusedSymbol.ImportSelector(d.srcPos, d.name)).toList
514517
else
515518
Nil
516519
// Partition to extract unset local variables from usedLocalDefs
@@ -523,24 +526,24 @@ object CheckUnused:
523526
unusedLocalDefs
524527
.filterNot(d => usedInPosition.exists { case (pos, name) => d.span.contains(pos.span) && name == d.symbol.name})
525528
.filterNot(d => containsSyntheticSuffix(d.symbol))
526-
.map(d => UnusedSymbol(d.namePos, d.name, WarnTypes.LocalDefs)).toList
527-
val unsetLocalDefs = usedLocalDefs.filter(isUnsetVarDef).map(d => UnusedSymbol(d.namePos, d.name, WarnTypes.UnsetLocals)).toList
529+
.map(d => UnusedSymbol.Symbol(d, WarnTypes.LocalDefs)).toList
530+
val unsetLocalDefs = usedLocalDefs.filter(isUnsetVarDef).map(d => UnusedSymbol.Symbol(d, WarnTypes.UnsetLocals)).toList
528531

529532
val sortedExplicitParams =
530533
if ctx.settings.WunusedHas.explicits then
531534
explicitParamInScope
532535
.filterNot(d => d.symbol.usedDefContains)
533536
.filterNot(d => usedInPosition.exists { case (pos, name) => d.span.contains(pos.span) && name == d.symbol.name})
534537
.filterNot(d => containsSyntheticSuffix(d.symbol))
535-
.map(d => UnusedSymbol(d.namePos, d.name, WarnTypes.ExplicitParams)).toList
538+
.map(d => UnusedSymbol.Symbol(d, WarnTypes.ExplicitParams)).toList
536539
else
537540
Nil
538541
val sortedImplicitParams =
539542
if ctx.settings.WunusedHas.implicits then
540543
implicitParamInScope
541544
.filterNot(d => d.symbol.usedDefContains)
542545
.filterNot(d => containsSyntheticSuffix(d.symbol))
543-
.map(d => UnusedSymbol(d.namePos, d.name, WarnTypes.ImplicitParams)).toList
546+
.map(d => UnusedSymbol.Symbol(d, WarnTypes.ImplicitParams)).toList
544547
else
545548
Nil
546549
// Partition to extract unset private variables from usedPrivates
@@ -549,15 +552,15 @@ object CheckUnused:
549552
privateDefInScope.partition(d => d.symbol.usedDefContains)
550553
else
551554
(Nil, Nil)
552-
val sortedPrivateDefs = unusedPrivates.filterNot(d => containsSyntheticSuffix(d.symbol)).map(d => UnusedSymbol(d.namePos, d.name, WarnTypes.PrivateMembers)).toList
553-
val unsetPrivateDefs = usedPrivates.filter(isUnsetVarDef).map(d => UnusedSymbol(d.namePos, d.name, WarnTypes.UnsetPrivates)).toList
555+
val sortedPrivateDefs = unusedPrivates.filterNot(d => containsSyntheticSuffix(d.symbol)).map(d => UnusedSymbol.Symbol(d, WarnTypes.PrivateMembers)).toList
556+
val unsetPrivateDefs = usedPrivates.filter(isUnsetVarDef).map(d => UnusedSymbol.Symbol(d, WarnTypes.UnsetPrivates)).toList
554557
val sortedPatVars =
555558
if ctx.settings.WunusedHas.patvars then
556559
patVarsInScope
557560
.filterNot(d => d.symbol.usedDefContains)
558561
.filterNot(d => containsSyntheticSuffix(d.symbol))
559562
.filterNot(d => usedInPosition.exists { case (pos, name) => d.span.contains(pos.span) && name == d.symbol.name})
560-
.map(d => UnusedSymbol(d.namePos, d.name, WarnTypes.PatVars)).toList
563+
.map(d => UnusedSymbol.Symbol(d, WarnTypes.PatVars)).toList
561564
else
562565
Nil
563566
val warnings =
@@ -570,9 +573,13 @@ object CheckUnused:
570573
sortedPatVars :::
571574
unsetLocalDefs :::
572575
unsetPrivateDefs
573-
unsorted.sortBy { s =>
574-
val pos = s.pos.sourcePos
575-
(pos.line, pos.column)
576+
unsorted.sortBy {
577+
case s : UnusedSymbol.Symbol =>
578+
val pos = s.tree.sourcePos
579+
(pos.line, pos.column)
580+
case s: UnusedSymbol.ImportSelector =>
581+
val pos = s.pos.sourcePos
582+
(pos.line, pos.column)
576583
}
577584
UnusedResult(warnings.toSet)
578585
end getUnused
@@ -803,7 +810,9 @@ object CheckUnused:
803810
case _:tpd.Block => Local
804811
case _ => Other
805812

806-
case class UnusedSymbol(pos: SrcPos, name: Name, warnType: WarnTypes)
813+
enum UnusedSymbol:
814+
case Symbol(tree: tpd.NamedDefTree, warnType: WarnTypes)
815+
case ImportSelector(pos: SrcPos, name: Name)
807816
/** A container for the results of the used elements analysis */
808817
case class UnusedResult(warnings: Set[UnusedSymbol])
809818
object UnusedResult:

compiler/test/dotty/tools/DottyTest.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import Types._, Symbols._, Decorators._
1313
import dotc.core.Decorators._
1414
import dotc.ast.tpd
1515
import dotc.Compiler
16-
16+
import dotty.tools.dotc.config.Settings.Setting.ChoiceWithHelp
1717
import dotc.core.Phases.Phase
1818

1919
trait DottyTest extends ContextEscapeDetection {
@@ -41,6 +41,7 @@ trait DottyTest extends ContextEscapeDetection {
4141
fc.setSetting(fc.settings.encoding, "UTF8")
4242
fc.setSetting(fc.settings.classpath, TestConfiguration.basicClasspath)
4343
fc.setSetting(fc.settings.language, List("experimental.erasedDefinitions"))
44+
fc.setSetting(fc.settings.Wunused, List(ChoiceWithHelp("all","")))
4445
fc.setProperty(ContextDoc, new ContextDocstrings)
4546
}
4647

compiler/test/dotty/tools/dotc/reporting/CodeActionTest.scala

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import org.junit.Test
1616
* diagnostic for a given code snippet.
1717
*/
1818
class CodeActionTest extends DottyTest:
19-
19+
2020
@Test def convertToFunctionValue =
2121
checkCodeAction(
2222
"""|object Test:
@@ -136,6 +136,24 @@ class CodeActionTest extends DottyTest:
136136
afterPhase = "patternMatcher"
137137
)
138138

139+
@Test def removeUnusedLocalDefinition =
140+
checkCodeAction(
141+
code = """object Test:
142+
| def foo(): Int = {
143+
| val a = 456
144+
| 123
145+
| }
146+
|""".stripMargin,
147+
title = "Remove unused code",
148+
expected = """object Test:
149+
| def foo(): Int = {
150+
| 123
151+
| }
152+
|""".stripMargin ,
153+
afterPhase = "checkUnusedPostInlining"
154+
)
155+
156+
139157
// Make sure we're not using the default reporter, which is the ConsoleReporter,
140158
// meaning they will get reported in the test run and that's it.
141159
private def newContext =

0 commit comments

Comments
 (0)