Skip to content

Commit 937b91c

Browse files
authored
Improve -Wunused: locals, privates with unset vars warning #16639 (#17160)
This PR is related to my Bachelor Semester Project, supervised by @anatoliykmetyuk. The latter consist in improving and implementing more Scala 3 linter options (see #15503), with #16639 as a starting issue fixed in this PR. - During the traversal in CheckUnused.scala (Miniphase & local TreeTraverser), when reaching an `Assign` case, symbols are collected as set, and then used to filter used locals and privates variable at reporting time. - Adapt test suit, and Add more test accordingly. - Note that for a same variable the unused warning always has priority and shadows the unset warning. That feature follows the Scala 2 `-Ywarn-unused:<args>` behavior.
2 parents 9923e29 + d6696d8 commit 937b91c

File tree

6 files changed

+371
-48
lines changed

6 files changed

+371
-48
lines changed

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

Lines changed: 51 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import dotty.tools.dotc.core.Symbols.Symbol
2727
import dotty.tools.dotc.core.StdNames.nme
2828
import scala.math.Ordering
2929

30+
3031
/**
3132
* A compiler phase that checks for unused imports or definitions
3233
*
@@ -146,6 +147,13 @@ class CheckUnused private (phaseMode: CheckUnused.PhaseMode, suffix: String, _ke
146147
if !tree.isInstanceOf[tpd.InferredTypeTree] then typeTraverser(unusedDataApply).traverse(tree.tpe)
147148
ctx
148149

150+
override def prepareForAssign(tree: tpd.Assign)(using Context): Context =
151+
unusedDataApply{ ud =>
152+
val sym = tree.lhs.symbol
153+
if sym.exists then
154+
ud.registerSetVar(sym)
155+
}
156+
149157
// ========== MiniPhase Transform ==========
150158

151159
override def transformBlock(tree: tpd.Block)(using Context): tpd.Tree =
@@ -172,6 +180,7 @@ class CheckUnused private (phaseMode: CheckUnused.PhaseMode, suffix: String, _ke
172180
unusedDataApply(_.removeIgnoredUsage(tree.symbol))
173181
tree
174182

183+
175184
// ---------- MiniPhase HELPERS -----------
176185

177186
private def pushInBlockTemplatePackageDef(tree: tpd.Block | tpd.Template | tpd.PackageDef)(using Context): Context =
@@ -215,11 +224,11 @@ class CheckUnused private (phaseMode: CheckUnused.PhaseMode, suffix: String, _ke
215224
case sel: Select =>
216225
prepareForSelect(sel)
217226
traverseChildren(tree)(using newCtx)
218-
case _: (tpd.Block | tpd.Template | tpd.PackageDef) =>
227+
case tree: (tpd.Block | tpd.Template | tpd.PackageDef) =>
219228
//! DIFFERS FROM MINIPHASE
220-
unusedDataApply { ud =>
221-
ud.inNewScope(ScopeType.fromTree(tree))(traverseChildren(tree)(using newCtx))
222-
}
229+
pushInBlockTemplatePackageDef(tree)
230+
traverseChildren(tree)(using newCtx)
231+
popOutBlockTemplatePackageDef()
223232
case t:tpd.ValDef =>
224233
prepareForValDef(t)
225234
traverseChildren(tree)(using newCtx)
@@ -235,6 +244,9 @@ class CheckUnused private (phaseMode: CheckUnused.PhaseMode, suffix: String, _ke
235244
case t: tpd.Bind =>
236245
prepareForBind(t)
237246
traverseChildren(tree)(using newCtx)
247+
case t:tpd.Assign =>
248+
prepareForAssign(t)
249+
traverseChildren(tree)
238250
case _: tpd.InferredTypeTree =>
239251
case t@tpd.TypeTree() =>
240252
//! DIFFERS FROM MINIPHASE
@@ -278,6 +290,10 @@ class CheckUnused private (phaseMode: CheckUnused.PhaseMode, suffix: String, _ke
278290
report.warning(s"unused private member", t)
279291
case UnusedSymbol(t, _, WarnTypes.PatVars) =>
280292
report.warning(s"unused pattern variable", t)
293+
case UnusedSymbol(t, _, WarnTypes.UnsetLocals) =>
294+
report.warning(s"unset local variable", t)
295+
case UnusedSymbol(t, _, WarnTypes.UnsetPrivates) =>
296+
report.warning(s"unset private variable", t)
281297
}
282298

283299
end CheckUnused
@@ -297,6 +313,8 @@ object CheckUnused:
297313
case ImplicitParams
298314
case PrivateMembers
299315
case PatVars
316+
case UnsetLocals
317+
case UnsetPrivates
300318

301319
/**
302320
* The key used to retrieve the "unused entity" analysis metadata,
@@ -343,12 +361,8 @@ object CheckUnused:
343361
private val implicitParamInScope = MutSet[tpd.MemberDef]()
344362
private val patVarsInScope = MutSet[tpd.Bind]()
345363

346-
/* Unused collection collected at the end */
347-
private val unusedLocalDef = MutSet[tpd.MemberDef]()
348-
private val unusedPrivateDef = MutSet[tpd.MemberDef]()
349-
private val unusedExplicitParams = MutSet[tpd.MemberDef]()
350-
private val unusedImplicitParams = MutSet[tpd.MemberDef]()
351-
private val unusedPatVars = MutSet[tpd.Bind]()
364+
/** All variables sets*/
365+
private val setVars = MutSet[Symbol]()
352366

353367
/** All used symbols */
354368
private val usedDef = MutSet[Symbol]()
@@ -360,15 +374,6 @@ object CheckUnused:
360374

361375
private val paramsToSkip = MutSet[Symbol]()
362376

363-
/**
364-
* Push a new Scope of the given type, executes the given Unit and
365-
* pop it back to the original type.
366-
*/
367-
def inNewScope(newScope: ScopeType)(execInNewScope: => Unit)(using Context): Unit =
368-
val prev = currScopeType
369-
pushScope(newScope)
370-
execInNewScope
371-
popScope()
372377

373378
def finishAggregation(using Context)(): Unit =
374379
val unusedInThisStage = this.getUnused
@@ -443,6 +448,9 @@ object CheckUnused:
443448
impInScope.push(MutSet())
444449
usedInScope.push(MutSet())
445450

451+
def registerSetVar(sym: Symbol): Unit =
452+
setVars += sym
453+
446454
/**
447455
* leave the current scope and do :
448456
*
@@ -501,15 +509,19 @@ object CheckUnused:
501509
unusedImport.map(d => UnusedSymbol(d.srcPos, d.name, WarnTypes.Imports)).toList
502510
else
503511
Nil
504-
val sortedLocalDefs =
512+
// Partition to extract unset local variables from usedLocalDefs
513+
val (usedLocalDefs, unusedLocalDefs) =
505514
if ctx.settings.WunusedHas.locals then
506-
localDefInScope
507-
.filterNot(d => d.symbol.usedDefContains)
508-
.filterNot(d => usedInPosition.exists { case (pos, name) => d.span.contains(pos.span) && name == d.symbol.name})
509-
.filterNot(d => containsSyntheticSuffix(d.symbol))
510-
.map(d => UnusedSymbol(d.namePos, d.name, WarnTypes.LocalDefs)).toList
515+
localDefInScope.partition(d => d.symbol.usedDefContains)
511516
else
512-
Nil
517+
(Nil, Nil)
518+
val sortedLocalDefs =
519+
unusedLocalDefs
520+
.filterNot(d => usedInPosition.exists { case (pos, name) => d.span.contains(pos.span) && name == d.symbol.name})
521+
.filterNot(d => containsSyntheticSuffix(d.symbol))
522+
.map(d => UnusedSymbol(d.namePos, d.name, WarnTypes.LocalDefs)).toList
523+
val unsetLocalDefs = usedLocalDefs.filter(isUnsetVarDef).map(d => UnusedSymbol(d.namePos, d.name, WarnTypes.UnsetLocals)).toList
524+
513525
val sortedExplicitParams =
514526
if ctx.settings.WunusedHas.explicits then
515527
explicitParamInScope
@@ -527,14 +539,14 @@ object CheckUnused:
527539
.map(d => UnusedSymbol(d.namePos, d.name, WarnTypes.ImplicitParams)).toList
528540
else
529541
Nil
530-
val sortedPrivateDefs =
542+
// Partition to extract unset private variables from usedPrivates
543+
val (usedPrivates, unusedPrivates) =
531544
if ctx.settings.WunusedHas.privates then
532-
privateDefInScope
533-
.filterNot(d => d.symbol.usedDefContains)
534-
.filterNot(d => containsSyntheticSuffix(d.symbol))
535-
.map(d => UnusedSymbol(d.namePos, d.name, WarnTypes.PrivateMembers)).toList
545+
privateDefInScope.partition(d => d.symbol.usedDefContains)
536546
else
537-
Nil
547+
(Nil, Nil)
548+
val sortedPrivateDefs = unusedPrivates.filterNot(d => containsSyntheticSuffix(d.symbol)).map(d => UnusedSymbol(d.namePos, d.name, WarnTypes.PrivateMembers)).toList
549+
val unsetPrivateDefs = usedPrivates.filter(isUnsetVarDef).map(d => UnusedSymbol(d.namePos, d.name, WarnTypes.UnsetPrivates)).toList
538550
val sortedPatVars =
539551
if ctx.settings.WunusedHas.patvars then
540552
patVarsInScope
@@ -544,7 +556,9 @@ object CheckUnused:
544556
.map(d => UnusedSymbol(d.namePos, d.name, WarnTypes.PatVars)).toList
545557
else
546558
Nil
547-
val warnings = List(sortedImp, sortedLocalDefs, sortedExplicitParams, sortedImplicitParams, sortedPrivateDefs, sortedPatVars).flatten.sortBy { s =>
559+
val warnings =
560+
List(sortedImp, sortedLocalDefs, sortedExplicitParams, sortedImplicitParams,
561+
sortedPrivateDefs, sortedPatVars, unsetLocalDefs, unsetPrivateDefs).flatten.sortBy { s =>
548562
val pos = s.pos.sourcePos
549563
(pos.line, pos.column)
550564
}
@@ -731,10 +745,13 @@ object CheckUnused:
731745
!isSyntheticMainParam(sym) &&
732746
!sym.shouldNotReportParamOwner
733747

734-
735748
private def shouldReportPrivateDef(using Context): Boolean =
736749
currScopeType.top == ScopeType.Template && !memDef.symbol.isConstructor && memDef.symbol.is(Private, butNot = SelfName | Synthetic | CaseAccessor)
737750

751+
private def isUnsetVarDef(using Context): Boolean =
752+
val sym = memDef.symbol
753+
sym.is(Mutable) && !setVars(sym)
754+
738755
extension (imp: tpd.Import)
739756
/** Enum generate an import for its cases (but outside them), which should be ignored */
740757
def isGeneratedByEnum(using Context): Boolean =

tests/neg-custom-args/fatal-warnings/i15503b.scala

Lines changed: 50 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,49 +2,91 @@
22

33
val a = 1 // OK
44

5+
var cs = 3 // OK
6+
57
val b = // OK
8+
var e3 = 2 // error
69
val e1 = 1 // error
710
def e2 = 2 // error
811
1
912

1013
val c = // OK
11-
val e1 = 1 // OK
14+
var e1 = 1 // error not set
15+
def e2 = e1 // OK
16+
val e3 = e2 // OK
17+
e3
18+
19+
val g = // OK
20+
var e1 = 1 // OK
1221
def e2 = e1 // OK
13-
e2
22+
val e3 = e2 // OK
23+
e1 = e3 // OK
24+
e3
1425

1526
def d = 1 // OK
1627

1728
def e = // OK
1829
val e1 = 1 // error
1930
def e2 = 2 // error
31+
var e3 = 4 // error
2032
1
2133

2234
def f = // OK
2335
val f1 = 1 // OK
24-
def f2 = f1 // OK
36+
var f2 = f1 // error not set
37+
def f3 = f2 // OK
38+
f3
39+
40+
def h = // OK
41+
val f1 = 1 // OK
42+
var f2 = f1 // OK
43+
def f3 = f2 // OK
44+
f2 = f3 // OK
2545
f2
2646

2747
class Foo {
48+
val a = 1 // OK
49+
50+
var cs = 3 // OK
51+
2852
val b = // OK
53+
var e3 = 2 // error
2954
val e1 = 1 // error
3055
def e2 = 2 // error
3156
1
3257

3358
val c = // OK
34-
val e1 = 1 // OK
59+
var e1 = 1 // error not set
60+
def e2 = e1 // OK
61+
val e3 = e2 // OK
62+
e3
63+
64+
val g = // OK
65+
var e1 = 1 // OK
3566
def e2 = e1 // OK
36-
e2
67+
val e3 = e2 // OK
68+
e1 = e3 // OK
69+
e3
3770

3871
def d = 1 // OK
3972

4073
def e = // OK
4174
val e1 = 1 // error
4275
def e2 = 2 // error
76+
var e3 = 4 // error
4377
1
4478

4579
def f = // OK
4680
val f1 = 1 // OK
47-
def f2 = f1 // OK
81+
var f2 = f1 // error not set
82+
def f3 = f2 // OK
83+
f3
84+
85+
def h = // OK
86+
val f1 = 1 // OK
87+
var f2 = f1 // OK
88+
def f3 = f2 // OK
89+
f2 = f3 // OK
4890
f2
4991
}
5092

@@ -68,7 +110,7 @@ package foo.scala2.tests:
68110
new a.Inner
69111
}
70112
def f2 = {
71-
var x = 100
113+
var x = 100 // error not set
72114
x
73115
}
74116
}
@@ -89,7 +131,7 @@ package foo.scala2.tests:
89131
}
90132

91133
package test.foo.twisted.i16682:
92-
def myPackage =
134+
def myPackage =
93135
object IntExtractor: // OK
94136
def unapply(s: String): Option[Int] = s.toIntOption
95137

tests/neg-custom-args/fatal-warnings/i15503c.scala

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,37 @@ class A:
1212
private[this] val f = e // OK
1313
private val g = f // OK
1414

15+
private[A] var h = 1 // OK
16+
private[this] var i = h // error not set
17+
private var j = i // error not set
18+
19+
private[this] var k = 1 // OK
20+
private var l = 2 // OK
21+
private val m = // error
22+
k = l
23+
l = k
24+
l
25+
1526
private def fac(x: Int): Int = // error
1627
if x == 0 then 1 else x * fac(x - 1)
1728

1829
val x = 1 // OK
1930
def y = 2 // OK
2031
def z = g // OK
32+
var w = 2 // OK
2133

2234
package foo.test.contructors:
2335
case class A private (x:Int) // OK
2436
class B private (val x: Int) // OK
2537
class C private (private val x: Int) // error
2638
class D private (private val x: Int): // OK
2739
def y = x
28-
40+
class E private (private var x: Int): // error not set
41+
def y = x
42+
class F private (private var x: Int): // OK
43+
def y =
44+
x = 3
45+
x
2946

3047
package test.foo.i16682:
3148
object myPackage:

tests/neg-custom-args/fatal-warnings/i15503i.scala

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -142,8 +142,8 @@ package foo.test.possibleclasses.withvar:
142142
private var y: Int // OK
143143
)(
144144
s: Int, // OK
145-
var t: Int, // OK
146-
private var z: Int // OK
145+
var t: Int, // OK global scope can be set somewhere else
146+
private var z: Int // error not set
147147
) {
148148
def a = k + y + s + t + z
149149
}
@@ -159,11 +159,11 @@ package foo.test.possibleclasses.withvar:
159159

160160
class AllUsed(
161161
k: Int, // OK
162-
private var y: Int // OK
162+
private var y: Int // error not set
163163
)(
164164
s: Int, // OK
165-
var t: Int, // OK
166-
private var z: Int // OK
165+
var t: Int, // OK global scope can be set somewhere else
166+
private var z: Int // error not set
167167
) {
168168
def a = k + y + s + t + z
169169
}

0 commit comments

Comments
 (0)