Skip to content

Commit b2fd8cb

Browse files
committed
Add -Wunused:params,explicits,implicits,patvars
- Add warnings for unused parmaters (explicit, implicit) - Add warnings for unused pattern variables (in match case) - Add tests suits (fatal-warnings)
1 parent f993782 commit b2fd8cb

File tree

6 files changed

+138
-24
lines changed

6 files changed

+138
-24
lines changed

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,15 +163,21 @@ private sealed trait WarningSettings:
163163
name = "-Wunused",
164164
helpArg = "warning",
165165
descr = "Enable or disable specific `unused` warnings",
166-
choices = List("nowarn", "all", "imports", "locals", "privates"),
166+
choices = List("nowarn", "all", "imports", "locals", "privates", "patvars", "explicits", "implicits", "params"),
167167
default = Nil
168168
)
169169
object WunusedHas:
170170
def allOr(s: String)(using Context) = Wunused.value.pipe(us => us.contains("all") || us.contains(s))
171171
def nowarn(using Context) = allOr("nowarn")
172172
def imports(using Context) = allOr("imports")
173173
def locals(using Context) = allOr("locals")
174+
/** -Wunused:explicits OR -Wunused:params */
175+
def explicits(using Context) = allOr("explicits") || allOr("params")
176+
/** -Wunused:implicits OR -Wunused:params */
177+
def implicits(using Context) = allOr("implicits") || allOr("params")
178+
def params(using Context) = allOr("params")
174179
def privates(using Context) = allOr("privates")
180+
def patvars(using Context) = allOr("patvars")
175181

176182
val Wconf: Setting[List[String]] = MultiStringSetting(
177183
"-Wconf",

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

Lines changed: 88 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ import dotty.tools.dotc.ast.tpd.TreeTraverser
55
import dotty.tools.dotc.ast.untpd
66
import dotty.tools.dotc.ast.untpd.ImportSelector
77
import dotty.tools.dotc.config.ScalaSettings
8-
import dotty.tools.dotc.core.Contexts._
9-
import dotty.tools.dotc.core.Decorators.{i,em}
10-
import dotty.tools.dotc.core.Flags.{Private, Given}
8+
import dotty.tools.dotc.core.Contexts.*
9+
import dotty.tools.dotc.core.Decorators.{em, i}
10+
import dotty.tools.dotc.core.Flags.{Given, GivenVal, Param, Private}
1111
import dotty.tools.dotc.core.Phases.Phase
1212
import dotty.tools.dotc.core.StdNames
1313
import dotty.tools.dotc.report
@@ -17,6 +17,8 @@ import dotty.tools.dotc.util.Property
1717
import dotty.tools.dotc.transform.CheckUnused.UnusedData.UnusedResult
1818
import dotty.tools.dotc.core.Mode
1919

20+
import scala.collection.mutable
21+
2022

2123
/**
2224
* A compiler phase that checks for unused imports or definitions
@@ -36,11 +38,14 @@ class CheckUnused extends Phase:
3638
override def isRunnable(using Context): Boolean =
3739
ctx.settings.WunusedHas.imports ||
3840
ctx.settings.WunusedHas.locals ||
39-
ctx.settings.WunusedHas.privates
41+
ctx.settings.WunusedHas.explicits ||
42+
ctx.settings.WunusedHas.implicits ||
43+
ctx.settings.WunusedHas.privates ||
44+
ctx.settings.WunusedHas.patvars
4045

4146
override def run(using Context): Unit =
4247
val tree = ctx.compilationUnit.tpdTree
43-
val data = UnusedData(ctx)
48+
val data = UnusedData()
4449
val fresh = ctx.fresh.setProperty(_key, data)
4550
traverser.traverse(tree)(using fresh)
4651
reportUnused(data.getUnused)
@@ -56,7 +61,7 @@ class CheckUnused extends Phase:
5661
import UnusedData.ScopeType
5762

5863
override def traverse(tree: tpd.Tree)(using Context): Unit = tree match
59-
case imp@Import(_, sels) => sels.foreach { s =>
64+
case imp@Import(_, sels) => sels.foreach { _ =>
6065
ctx.property(_key).foreach(_.registerImport(imp))
6166
}
6267
case ident: Ident =>
@@ -97,20 +102,29 @@ class CheckUnused extends Phase:
97102
case t:tpd.DefDef =>
98103
ctx.property(_key).foreach(_.registerDef(t))
99104
traverseChildren(tree)
105+
case t: tpd.Bind =>
106+
ctx.property(_key).foreach(_.registerPatVar(t))
107+
traverseChildren(tree)
100108
case _ => traverseChildren(tree)
101109

102110
}
103111

104-
private def reportUnused(res: UnusedData.UnusedResult)(using Context) =
112+
private def reportUnused(res: UnusedData.UnusedResult)(using Context): Unit =
105113
import CheckUnused.WarnTypes
106114
res.foreach { s =>
107115
s match
108116
case (t, WarnTypes.Imports) =>
109117
report.warning(s"unused import", t)
110118
case (t, WarnTypes.LocalDefs) =>
111119
report.warning(s"unused local definition", t)
120+
case (t, WarnTypes.ExplicitParams) =>
121+
report.warning(s"unused explicit parameter", t)
122+
case (t, WarnTypes.ImplicitParams) =>
123+
report.warning(s"unused implicit parameter", t)
112124
case (t, WarnTypes.PrivateMembers) =>
113125
report.warning(s"unused private member", t)
126+
case (t, WarnTypes.PatVars) =>
127+
report.warning(s"unused pattern variable", t)
114128
}
115129

116130
end CheckUnused
@@ -122,34 +136,45 @@ object CheckUnused:
122136
enum WarnTypes:
123137
case Imports
124138
case LocalDefs
139+
case ExplicitParams
140+
case ImplicitParams
125141
case PrivateMembers
142+
case PatVars
126143

127144
/**
128145
* A stateful class gathering the infos on :
129146
* - imports
130147
* - definitions
131148
* - usage
132149
*/
133-
private class UnusedData(initctx: Context):
134-
import collection.mutable.{Set => MutSet, Map => MutMap, Stack, ListBuffer}
150+
private class UnusedData:
151+
import collection.mutable.{Set => MutSet, Map => MutMap, Stack => MutStack, ListBuffer}
135152
import UnusedData.ScopeType
136153

137154
var currScope: ScopeType = ScopeType.Other
138155

139156
/* IMPORTS */
140-
private val impInScope = Stack(MutMap[Int, ListBuffer[ImportSelector]]())
157+
private val impInScope = MutStack(MutMap[Int, ListBuffer[ImportSelector]]())
141158
private val unusedImport = MutSet[ImportSelector]()
142-
private val usedImports = Stack(MutSet[Int]())
159+
private val usedImports = MutStack(MutSet[Int]())
160+
161+
/* LOCAL DEF OR VAL / Private Def or Val / Pattern variables */
162+
private val localDefInScope = MutStack(MutSet[tpd.ValOrDefDef]())
163+
private val privateDefInScope = MutStack(MutSet[tpd.ValOrDefDef]())
164+
private val explicitParamInScope = MutStack(MutSet[tpd.ValOrDefDef]())
165+
private val implicitParamInScope = MutStack(MutSet[tpd.ValOrDefDef]())
166+
private val patVarsInScope = MutStack(MutSet[tpd.Bind]())
143167

144-
/* LOCAL DEF OR VAL / Private Def or Val*/
145-
private val localDefInScope = Stack(MutSet[tpd.ValOrDefDef]())
146-
private val privateDefInScope = Stack(MutSet[tpd.ValOrDefDef]())
147168
private val unusedLocalDef = ListBuffer[tpd.ValOrDefDef]()
148169
private val unusedPrivateDef = ListBuffer[tpd.ValOrDefDef]()
170+
private val unusedExplicitParams = ListBuffer[tpd.ValOrDefDef]()
171+
private val unusedImplicitParams = ListBuffer[tpd.ValOrDefDef]()
172+
private val unusedPatVars = ListBuffer[tpd.Bind]()
173+
149174
private val usedDef = MutSet[Int]()
150175

151176
private def isImportExclusion(sel: ImportSelector): Boolean = sel.renamed match
152-
case ident@untpd.Ident(name) => name == StdNames.nme.WILDCARD
177+
case untpd.Ident(name) => name == StdNames.nme.WILDCARD
153178
case _ => false
154179

155180
/** Register the id of a found (used) symbol */
@@ -181,25 +206,39 @@ object CheckUnused:
181206
}
182207

183208
def registerDef(valOrDef: tpd.ValOrDefDef)(using Context): Unit =
184-
if currScope == ScopeType.Local then
209+
if valOrDef.symbol.is(Param) then
210+
if valOrDef.symbol.is(Given) then
211+
implicitParamInScope.top += valOrDef
212+
else
213+
explicitParamInScope.top += valOrDef
214+
else if currScope == ScopeType.Local then
185215
localDefInScope.top += valOrDef
186216
else if currScope == ScopeType.Template && valOrDef.symbol.is(Private) then
187217
privateDefInScope.top += valOrDef
188218

219+
def registerPatVar(patvar: tpd.Bind)(using Context): Unit =
220+
patVarsInScope.top += patvar
221+
189222
/** enter a new scope */
190223
def pushScope(): Unit =
191224
// unused imports :
192225
usedImports.push(MutSet())
193226
impInScope.push(MutMap())
194227
// local and private defs :
195228
localDefInScope.push(MutSet())
229+
explicitParamInScope.push(MutSet())
230+
implicitParamInScope.push(MutSet())
196231
privateDefInScope.push(MutSet())
232+
patVarsInScope.push(MutSet())
197233

198234
/** leave the current scope */
199235
def popScope()(using Context): Unit =
200236
popScopeImport()
201237
popScopeLocalDef()
238+
popScopeExplicitParam()
239+
popScopeImplicitParam()
202240
popScopePrivateDef()
241+
popScopePatVars()
203242

204243
def popScopeImport(): Unit =
205244
val usedImp = MutSet[ImportSelector]()
@@ -211,7 +250,7 @@ object CheckUnused:
211250
usedImp.addAll(value)
212251
false
213252
}
214-
if usedImports.size > 0 then
253+
if usedImports.nonEmpty then
215254
usedImports.top.addAll(notDefined)
216255

217256
poppedImp.values.flatten.foreach{ sel =>
@@ -225,12 +264,22 @@ object CheckUnused:
225264
val unused = localDefInScope.pop().filterInPlace(d => !usedDef(d.symbol.id))
226265
unusedLocalDef ++= unused
227266

267+
def popScopeExplicitParam()(using Context): Unit =
268+
val unused = explicitParamInScope.pop().filterInPlace(d => !usedDef(d.symbol.id))
269+
unusedExplicitParams ++= unused
270+
271+
def popScopeImplicitParam()(using Context): Unit =
272+
val unused = implicitParamInScope.pop().filterInPlace(d => !usedDef(d.symbol.id))
273+
unusedImplicitParams ++= unused
274+
228275
def popScopePrivateDef()(using Context): Unit =
229-
val unused = privateDefInScope.pop().filterInPlace{d =>
230-
!usedDef(d.symbol.id)
231-
}
276+
val unused = privateDefInScope.pop().filterInPlace(d => !usedDef(d.symbol.id))
232277
unusedPrivateDef ++= unused
233278

279+
def popScopePatVars()(using Context): Unit =
280+
val unused = patVarsInScope.pop().filterInPlace(d => !usedDef(d.symbol.id))
281+
unusedPatVars ++= unused
282+
234283
/**
235284
* Leave the scope and return a `List` of unused `ImportSelector`s
236285
*
@@ -245,15 +294,30 @@ object CheckUnused:
245294
Nil
246295
val sortedLocalDefs =
247296
if ctx.settings.WunusedHas.locals then
248-
unusedLocalDef.map(d => d.withSpan(d.span.withEnd(d.tpt.startPos.start)) -> WarnTypes.LocalDefs).toList
297+
unusedLocalDef.map(d => d.namePos -> WarnTypes.LocalDefs).toList
298+
else
299+
Nil
300+
val sortedExplicitParams =
301+
if ctx.settings.WunusedHas.explicits then
302+
unusedExplicitParams.map(d => d.namePos -> WarnTypes.ExplicitParams).toList
303+
else
304+
Nil
305+
val sortedImplicitParams =
306+
if ctx.settings.WunusedHas.implicits then
307+
unusedImplicitParams.map(d => d.namePos -> WarnTypes.ImplicitParams).toList
249308
else
250309
Nil
251310
val sortedPrivateDefs =
252311
if ctx.settings.WunusedHas.privates then
253-
unusedPrivateDef.map(d => d.withSpan(d.span.withEnd(d.tpt.startPos.start)) -> WarnTypes.PrivateMembers).toList
312+
unusedPrivateDef.map(d => d.namePos -> WarnTypes.PrivateMembers).toList
313+
else
314+
Nil
315+
val sortedPatVars =
316+
if ctx.settings.WunusedHas.patvars then
317+
unusedPatVars.map(d => d.namePos -> WarnTypes.PatVars).toList
254318
else
255319
Nil
256-
List(sortedImp, sortedLocalDefs, sortedPrivateDefs).flatten.sortBy { s =>
320+
List(sortedImp, sortedLocalDefs, sortedExplicitParams, sortedImplicitParams, sortedPrivateDefs, sortedPatVars).flatten.sortBy { s =>
257321
val pos = s._1.sourcePos
258322
(pos.line, pos.column)
259323
}
@@ -264,6 +328,7 @@ object CheckUnused:
264328
enum ScopeType:
265329
case Local
266330
case Template
331+
case Param
267332
case Other
268333

269334
type UnusedResult = List[(dotty.tools.dotc.util.SrcPos, WarnTypes)]
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// scalac: -Wunused:patvars
2+
3+
sealed trait Calc
4+
sealed trait Const extends Calc
5+
case class Sum(a: Calc, b: Calc) extends Calc
6+
case class S(pred: Const) extends Const
7+
case object Z extends Const
8+
9+
val a = Sum(S(S(Z)),Z) match {
10+
case Sum(a,Z) => Z // error
11+
case Sum(a@S(_),Z) => Z // error
12+
case Sum(a@S(_),Z) => a // OK
13+
case Sum(a@S(b@S(_)), Z) => a // error
14+
case Sum(a@S(b@(S(_))), Z) => Sum(a,b) // OK
15+
case Sum(_,_) => Z // OK
16+
case _ => Z // OK
17+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// scalac: -Wunused:explicits
2+
3+
def f1(a: Int) = a // OK
4+
def f2(a: Int) = 1 // error
5+
def f3(a: Int)(using Int) = a // OK
6+
def f4(a: Int)(using Int) = 1 // error
7+
def f6(a: Int)(using Int) = summon[Int] // error
8+
def f7(a: Int)(using Int) = summon[Int] + a // OK
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// scalac: -Wunused:implicits
2+
3+
def f1(a: Int) = a // OK
4+
def f2(a: Int) = 1 // OK
5+
def f3(a: Int)(using Int) = a // error
6+
def f4(a: Int)(using Int) = 1 // error
7+
def f6(a: Int)(using Int) = summon[Int] // OK
8+
def f7(a: Int)(using Int) = summon[Int] + a // OK
9+
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// scalac: -Wunused:params
2+
3+
def f1(a: Int) = a // OK
4+
def f2(a: Int) = 1 // error
5+
def f3(a: Int)(using Int) = a // error
6+
def f4(a: Int)(using Int) = 1 // error // error
7+
def f6(a: Int)(using Int) = summon[Int] // error
8+
def f7(a: Int)(using Int) = summon[Int] + a // OK
9+

0 commit comments

Comments
 (0)