Skip to content

Commit 37d9640

Browse files
authored
[Semester Project] Add new front-end phase for unused entities and add support for unused imports (#16157)
This PR, related to my **semester project** #15503 on adding **dotty's linter features**, adds the following: - [x] Add the `CheckUnused` front-end phase, which will check the the tree produced by the typer, for unused entites (imports, local defs, ...) - [x] Emit warning for `-Wunused:imports` including **given imports** and **wildcard imports** - [x] Emit warning for `-Wunused:locals` - [x] Emit warning for `-Wunused:privates` - [x] Emit warning for `-Wunused:params` - ~~Emit warning for `-Wunused:patvars`~~ - [x] Emit warning for `-Wunused:unsafe-warn-patvars` - [x] Emit warning for `-Wunused:linted` - [x] Add a simple _fatal-warning_ compilation-test suit - [x] _Fixes for the warning format_ - [x] Better help in CLI for `-Wunused` - [x] Add `-Wunused:givens` alias to `-Wunused:implicits` Here are a few examples: #### Unused Imports ```scala object Foo { import collection.mutable.{Set, Map} def main(args: Array[String]) = val bar = Set("this","set","is","used") println(s"Hello World: $bar") } ``` ``` sbt:scala3> scalac -Wunused:imports ../Foo.scala [...] -- Warning: ../scratch_files/Hello.scala:2:34 ---------------------------------- 2 | import collection.mutable.{Set, Map} | ^^^ | unused import 1 warning found ``` #### Unused local definitions ```scala class Foo { def bar = val a = 1 2 + 2 } ``` ``` sbt:scala3> scalac -Wunused:locals ../Foo.scala [...] -- Warning: ../scratch_files/MyHello.scala:3:8 --------------------------------- 3 | val a = 1 | ^^^^^^^^^ | unused local definition 1 warning found ``` #### Unused private members ```scala class Foo { private def a = 1 private def b = 2 def doSomething = b } ``` ``` sbt:scala3> scalac -Wunused:privates ../Foo.scala [...] -- Warning: ../scratch_files/MyHello.scala:2:14 -------------------------------- 2 | private def a = 1 | ^^^^^^^^^^^^^^^^^ | unused private member 1 warning found ``` #### Unused parameters ```scala def foo(a: String)(using Int) = bar ``` ``` sbt:scala3> scalac -Wunused:params ../scratch_files/Foo.scala [...] -- Warning: ../scratch_files/MyHello.scala:1:8 --------------------------------- 1 |def foo(a: String)(using Int) = bar | ^ | unused explicit parameter -- Warning: ../scratch_files/MyHello.scala:1:25 -------------------------------- 1 |def foo(a: String)(using Int) = bar | ^ | unused implicit parameter 2 warnings found ``` #### Unused pattern variables ```scala def foo(a: List[Int]) = a match case head :: tail => ??? case Nil => ??? ``` ``` sbt:scala3> scalac -Wunused:unsafe-warn-patvars ../scratch_files/Foo.scala [...] -- Warning: ../scratch_files/MyHello.scala:2:9 --------------------------------- 2 | case head :: tail => ??? | ^^^^ | unused pattern variable -- Warning: ../scratch_files/MyHello.scala:2:17 -------------------------------- 2 | case head :: tail => ??? | ^^^^ | unused pattern variable 2 warnings found ``` Please check the [test file](tests/neg-custom-args/fatal-warnings/i15503/i15503a.scala) for the handled cases. @odersky @anatoliykmetyuk
2 parents d49c2d9 + 08f807c commit 37d9640

17 files changed

+1416
-3
lines changed

compiler/src/dotty/tools/dotc/Compiler.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ class Compiler {
3535
protected def frontendPhases: List[List[Phase]] =
3636
List(new Parser) :: // Compiler frontend: scanner, parser
3737
List(new TyperPhase) :: // Compiler frontend: namer, typer
38+
List(new CheckUnused) :: // Check for unused elements
3839
List(new YCheckPositions) :: // YCheck positions
3940
List(new sbt.ExtractDependencies) :: // Sends information on classes' dependencies to sbt via callbacks
4041
List(new semanticdb.ExtractSemanticDB) :: // Extract info into .semanticdb files

compiler/src/dotty/tools/dotc/config/CliCommand.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ trait CliCommand:
6060
def defaultValue = s.default match
6161
case _: Int | _: String => s.default.toString
6262
case _ => ""
63-
val info = List(shortHelp(s), if defaultValue.nonEmpty then s"Default $defaultValue" else "", if s.legalChoices.nonEmpty then s"Choices ${s.legalChoices}" else "")
63+
val info = List(shortHelp(s), if defaultValue.nonEmpty then s"Default $defaultValue" else "", if s.legalChoices.nonEmpty then s"Choices : ${s.legalChoices}" else "")
6464
(s.name, info.filter(_.nonEmpty).mkString("\n"))
6565
end help
6666

compiler/src/dotty/tools/dotc/config/ScalaSettings.scala

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -155,20 +155,70 @@ private sealed trait VerboseSettings:
155155
*/
156156
private sealed trait WarningSettings:
157157
self: SettingGroup =>
158+
import Setting.ChoiceWithHelp
159+
158160
val Whelp: Setting[Boolean] = BooleanSetting("-W", "Print a synopsis of warning options.")
159161
val XfatalWarnings: Setting[Boolean] = BooleanSetting("-Werror", "Fail the compilation if there are any warnings.", aliases = List("-Xfatal-warnings"))
160162

161-
val Wunused: Setting[List[String]] = MultiChoiceSetting(
163+
val Wunused: Setting[List[ChoiceWithHelp[String]]] = MultiChoiceHelpSetting(
162164
name = "-Wunused",
163165
helpArg = "warning",
164166
descr = "Enable or disable specific `unused` warnings",
165-
choices = List("nowarn", "all"),
167+
choices = List(
168+
ChoiceWithHelp("nowarn", ""),
169+
ChoiceWithHelp("all",""),
170+
ChoiceWithHelp(
171+
name = "imports",
172+
description = "Warn if an import selector is not referenced.\n" +
173+
"NOTE : overrided by -Wunused:strict-no-implicit-warn"),
174+
ChoiceWithHelp("privates","Warn if a private member is unused"),
175+
ChoiceWithHelp("locals","Warn if a local definition is unused"),
176+
ChoiceWithHelp("explicits","Warn if an explicit parameter is unused"),
177+
ChoiceWithHelp("implicits","Warn if an implicit parameter is unused"),
178+
ChoiceWithHelp("params","Enable -Wunused:explicits,implicits"),
179+
ChoiceWithHelp("linted","Enable -Wunused:imports,privates,locals,implicits"),
180+
ChoiceWithHelp(
181+
name = "strict-no-implicit-warn",
182+
description = "Same as -Wunused:import, only for imports of explicit named members.\n" +
183+
"NOTE : This overrides -Wunused:imports and NOT set by -Wunused:all"
184+
),
185+
// ChoiceWithHelp("patvars","Warn if a variable bound in a pattern is unused"),
186+
ChoiceWithHelp(
187+
name = "unsafe-warn-patvars",
188+
description = "(UNSAFE) Warn if a variable bound in a pattern is unused.\n" +
189+
"This warning can generate false positive, as warning cannot be\n" +
190+
"suppressed yet."
191+
)
192+
),
166193
default = Nil
167194
)
168195
object WunusedHas:
196+
def isChoiceSet(s: String)(using Context) = Wunused.value.pipe(us => us.contains(s))
169197
def allOr(s: String)(using Context) = Wunused.value.pipe(us => us.contains("all") || us.contains(s))
170198
def nowarn(using Context) = allOr("nowarn")
171199

200+
// overrided by strict-no-implicit-warn
201+
def imports(using Context) =
202+
(allOr("imports") || allOr("linted")) && !(strictNoImplicitWarn)
203+
def locals(using Context) =
204+
allOr("locals") || allOr("linted")
205+
/** -Wunused:explicits OR -Wunused:params */
206+
def explicits(using Context) =
207+
allOr("explicits") || allOr("params")
208+
/** -Wunused:implicits OR -Wunused:params */
209+
def implicits(using Context) =
210+
allOr("implicits") || allOr("params") || allOr("linted")
211+
def params(using Context) = allOr("params")
212+
def privates(using Context) =
213+
allOr("privates") || allOr("linted")
214+
def patvars(using Context) =
215+
isChoiceSet("unsafe-warn-patvars") // not with "all"
216+
// allOr("patvars") // todo : rename once fixed
217+
def linted(using Context) =
218+
allOr("linted")
219+
def strictNoImplicitWarn(using Context) =
220+
isChoiceSet("strict-no-implicit-warn")
221+
172222
val Wconf: Setting[List[String]] = MultiStringSetting(
173223
"-Wconf",
174224
"patterns",

compiler/src/dotty/tools/dotc/config/Settings.scala

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import annotation.tailrec
1111
import collection.mutable.ArrayBuffer
1212
import reflect.ClassTag
1313
import scala.util.{Success, Failure}
14+
import dotty.tools.dotc.config.Settings.Setting.ChoiceWithHelp
1415

1516
object Settings:
1617

@@ -189,6 +190,19 @@ object Settings:
189190
def update(x: T)(using Context): SettingsState = setting.updateIn(ctx.settingsState, x)
190191
def isDefault(using Context): Boolean = setting.isDefaultIn(ctx.settingsState)
191192

193+
/**
194+
* A choice with help description.
195+
*
196+
* NOTE : `equals` and `toString` have special behaviors
197+
*/
198+
case class ChoiceWithHelp[T](name: T, description: String):
199+
override def equals(x: Any): Boolean = x match
200+
case s:String => s == name.toString()
201+
case _ => false
202+
override def toString(): String =
203+
s"\n- $name${if description.isEmpty() then "" else s" :\n\t${description.replace("\n","\n\t")}"}"
204+
end Setting
205+
192206
class SettingGroup {
193207

194208
private val _allSettings = new ArrayBuffer[Setting[?]]
@@ -270,6 +284,9 @@ object Settings:
270284
def MultiChoiceSetting(name: String, helpArg: String, descr: String, choices: List[String], default: List[String], aliases: List[String] = Nil): Setting[List[String]] =
271285
publish(Setting(name, descr, default, helpArg, Some(choices), aliases = aliases))
272286

287+
def MultiChoiceHelpSetting(name: String, helpArg: String, descr: String, choices: List[ChoiceWithHelp[String]], default: List[ChoiceWithHelp[String]], aliases: List[String] = Nil): Setting[List[ChoiceWithHelp[String]]] =
288+
publish(Setting(name, descr, default, helpArg, Some(choices), aliases = aliases))
289+
273290
def IntSetting(name: String, descr: String, default: Int, aliases: List[String] = Nil): Setting[Int] =
274291
publish(Setting(name, descr, default, aliases = aliases))
275292

compiler/src/dotty/tools/dotc/core/Definitions.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1001,6 +1001,7 @@ class Definitions {
10011001
@tu lazy val MappedAlternativeAnnot: ClassSymbol = requiredClass("scala.annotation.internal.MappedAlternative")
10021002
@tu lazy val MigrationAnnot: ClassSymbol = requiredClass("scala.annotation.migration")
10031003
@tu lazy val NowarnAnnot: ClassSymbol = requiredClass("scala.annotation.nowarn")
1004+
@tu lazy val UnusedAnnot: ClassSymbol = requiredClass("scala.annotation.unused")
10041005
@tu lazy val TransparentTraitAnnot: ClassSymbol = requiredClass("scala.annotation.transparentTrait")
10051006
@tu lazy val NativeAnnot: ClassSymbol = requiredClass("scala.native")
10061007
@tu lazy val RepeatedAnnot: ClassSymbol = requiredClass("scala.annotation.internal.Repeated")

0 commit comments

Comments
 (0)