Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 937b91c

Browse files
authoredMay 16, 2023
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
}
Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
// scalac: -Wunused:all
2+
//
3+
class Bippy(a: Int, b: Int) {
4+
private def this(c: Int) = this(c, c) // warn /Dotty:NoWarn
5+
private def boop(x: Int) = x+a+b // error
6+
private def bippy(x: Int): Int = bippy(x) // error TODO: could warn
7+
final private val MILLIS1 = 2000 // error no warn, /Dotty:Warn
8+
final private val MILLIS2: Int = 1000 // error
9+
final private val HI_COMPANION: Int = 500 // no warn, accessed from companion
10+
def hi() = Bippy.HI_INSTANCE
11+
}
12+
object Bippy {
13+
def hi(x: Bippy) = x.HI_COMPANION
14+
private val HI_INSTANCE: Int = 500 // no warn, accessed from instance
15+
private val HEY_INSTANCE: Int = 1000 // error warn
16+
private lazy val BOOL: Boolean = true // error warn
17+
}
18+
19+
class A(val msg: String)
20+
class B1(msg: String) extends A(msg)
21+
class B2(msg0: String) extends A(msg0)
22+
class B3(msg0: String) extends A("msg") // error /Dotty: unused explicit parameter
23+
24+
trait Bing
25+
26+
trait Accessors {
27+
private var v1: Int = 0 // error warn
28+
private var v2: Int = 0 // error warn, never set
29+
private var v3: Int = 0 // warn, never got /Dotty: no warn even if not used
30+
private var v4: Int = 0 // no warn
31+
32+
private[this] var v5 = 0 // error warn, never set
33+
private[this] var v6 = 0 // warn, never got /Dotty: no warn even if not used
34+
private[this] var v7 = 0 // no warn
35+
36+
def bippy(): Int = {
37+
v3 = 3
38+
v4 = 4
39+
v6 = 6
40+
v7 = 7
41+
v2 + v4 + v5 + v7
42+
}
43+
}
44+
45+
class StableAccessors {
46+
private var s1: Int = 0 // error warn
47+
private var s2: Int = 0 // error warn, never set
48+
private var s3: Int = 0 // warn, never got /Dotty: no warn even if not usued
49+
private var s4: Int = 0 // no warn
50+
51+
private[this] var s5 = 0 // error warn, never set
52+
private[this] var s6 = 0 // no warn, limitation /Dotty: Why limitation ?
53+
private[this] var s7 = 0 // no warn
54+
55+
def bippy(): Int = {
56+
s3 = 3
57+
s4 = 4
58+
s6 = 6
59+
s7 = 7
60+
s2 + s4 + s5 + s7
61+
}
62+
}
63+
64+
trait DefaultArgs {
65+
// warn about default getters for x2 and x3
66+
private def bippy(x1: Int, x2: Int = 10, x3: Int = 15): Int = x1 + x2 + x3 // no more warn warn since #17061
67+
68+
def boppy() = bippy(5, 100, 200)
69+
}
70+
71+
72+
class Outer {
73+
class Inner
74+
}
75+
76+
trait Locals {
77+
def f0 = {
78+
var x = 1 // error warn
79+
var y = 2
80+
y = 3
81+
y + y
82+
}
83+
def f1 = {
84+
val a = new Outer // no warn
85+
val b = new Outer // error warn
86+
new a.Inner
87+
}
88+
def f2 = {
89+
var x = 100 // error warn about it being a var, var not set
90+
x
91+
}
92+
}
93+
94+
object Types {
95+
private object Dongo { def f = this } // no more warn since #17061
96+
private class Bar1 // error warn
97+
private class Bar2 // no warn
98+
private type Alias1 = String // error warn
99+
private type Alias2 = String // no warn
100+
def bippo = (new Bar2).toString
101+
102+
def f(x: Alias2) = x.length
103+
104+
def l1() = {
105+
object HiObject { def f = this } // no more warn since #17061
106+
class Hi { // error warn
107+
def f1: Hi = new Hi
108+
def f2(x: Hi) = x
109+
}
110+
class DingDongDoobie // error warn
111+
class Bippy // no warn
112+
type Something = Bippy // no warn
113+
type OtherThing = String // error warn
114+
(new Bippy): Something
115+
}
116+
}
117+
118+
trait Underwarn {
119+
def f(): Seq[Int]
120+
121+
def g() = {
122+
val Seq(_, _) = f() // no warn
123+
true
124+
}
125+
}
126+
127+
class OtherNames {
128+
private def x_=(i: Int): Unit = () // no more warn since #17061
129+
private def x: Int = 42 // error Dotty triggers unused private member : To investigate
130+
private def y_=(i: Int): Unit = () // // no more warn since #17061
131+
private def y: Int = 42
132+
133+
def f = y
134+
}
135+
136+
137+
trait Forever {
138+
def f = {
139+
val t = Option((17, 42))
140+
for {
141+
ns <- t
142+
(i, j) = ns // no warn
143+
} yield (i + j)
144+
}
145+
def g = {
146+
val t = Option((17, 42))
147+
for {
148+
ns <- t
149+
(i, j) = ns // no warn
150+
} yield 42 // val emitted only if needed, hence nothing unused
151+
}
152+
}
153+
154+
trait Ignorance {
155+
private val readResolve = 42 // error ignore /dotty triggers unused private member/ why should we ignore ?
156+
}
157+
158+
trait CaseyKasem {
159+
def f = 42 match {
160+
case x if x < 25 => "no warn"
161+
case y if toString.nonEmpty => "no warn" + y
162+
case z => "warn"
163+
}
164+
}
165+
trait CaseyAtTheBat {
166+
def f = Option(42) match {
167+
case Some(x) if x < 25 => "no warn"
168+
case Some(y @ _) if toString.nonEmpty => "no warn"
169+
case Some(z) => "warn"
170+
case None => "no warn"
171+
}
172+
}
173+
174+
class `not even using companion privates`
175+
176+
object `not even using companion privates` {
177+
private implicit class `for your eyes only`(i: Int) { // no more warn since #17061
178+
def f = i
179+
}
180+
}
181+
182+
class `no warn in patmat anonfun isDefinedAt` {
183+
def f(pf: PartialFunction[String, Int]) = pf("42")
184+
def g = f {
185+
case s => s.length // no warn (used to warn case s => true in isDefinedAt)
186+
}
187+
}
188+
189+
// this is the ordinary case, as AnyRef is an alias of Object
190+
class `nonprivate alias is enclosing` {
191+
class C
192+
type C2 = C
193+
private class D extends C2 // error warn
194+
}
195+
196+
object `classof something` {
197+
private class intrinsically
198+
def f = classOf[intrinsically].toString()
199+
}
200+
201+
trait `short comings` {
202+
def f: Int = {
203+
val x = 42 // error /Dotty only triggers in dotty
204+
17
205+
}
206+
}
207+
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// scalac -Wunsued:all
2+
//Avoid warning on setter in trait Regression test : issue10154 scala
3+
4+
trait T {
5+
private var x: String = _
6+
7+
def y: String = {
8+
if (x eq null) x = "hello, world"
9+
x
10+
}
11+
}
12+
13+
/*
14+
➜ skalac -version
15+
Scala compiler version 2.13.10-20220920-001308-98972e5 -- Copyright 2002-2022, LAMP/EPFL and Lightbend, Inc.
16+
17+
➜ skalac -d /tmp -Wunused -Vprint:typer t12646.scala
18+
t12646.scala:3: warning: parameter value x_= in variable x is never used
19+
private var x: String = _
20+
^
21+
[[syntax trees at end of typer]] // t12646.scala
22+
package <empty> {
23+
abstract trait T extends scala.AnyRef {
24+
def /*T*/$init$(): Unit = {
25+
()
26+
};
27+
<accessor> private val x: String = _;
28+
<accessor> private def x_=(x$1: String): Unit;
29+
def y: String = {
30+
if (T.this.x.eq(null))
31+
T.this.x_=("hello, world")
32+
else
33+
();
34+
T.this.x
35+
}
36+
}
37+
}
38+
39+
1 warning
40+
*/

0 commit comments

Comments
 (0)
Please sign in to comment.