@@ -51,40 +51,42 @@ class CheckUnused extends Phase:
51
51
* It traverse the tree the tree and gather the data in the
52
52
* corresponding context property
53
53
*/
54
- private def traverser = new TreeTraverser {
54
+ private def traverser = new TreeTraverser :
55
55
import tpd ._
56
56
import UnusedData .ScopeType
57
57
58
+ /* Register every imports, definition and usage */
58
59
override def traverse (tree : tpd.Tree )(using Context ): Unit =
60
+ val unusedDataApply = ctx.property(_key).foreach
59
61
val newCtx = if tree.symbol.exists then ctx.withOwner(tree.symbol) else ctx
60
62
if tree.isDef then // register the annotations for usage
61
- ctx.property(_key).foreach (_.registerUsedAnnotation(tree.symbol))
63
+ unusedDataApply (_.registerUsedAnnotation(tree.symbol))
62
64
tree match
63
65
case imp: tpd.Import =>
64
- ctx.property(_key).foreach (_.registerImport(imp))
66
+ unusedDataApply (_.registerImport(imp))
65
67
case ident : Ident =>
66
- ctx.property(_key).foreach (_.registerUsed(ident.symbol))
68
+ unusedDataApply (_.registerUsed(ident.symbol))
67
69
traverseChildren(tree)(using newCtx)
68
70
case sel : Select =>
69
- ctx.property(_key).foreach (_.registerUsed(sel.symbol))
71
+ unusedDataApply (_.registerUsed(sel.symbol))
70
72
traverseChildren(tree)(using newCtx)
71
73
case _ : (tpd.Block | tpd.Template ) =>
72
- ctx.property(_key).foreach { ud =>
74
+ unusedDataApply { ud =>
73
75
ud.inNewScope(ScopeType .fromTree(tree))(traverseChildren(tree)(using newCtx))
74
76
}
75
77
case t: tpd.ValDef =>
76
- ctx.property(_key).foreach (_.registerDef(t))
78
+ unusedDataApply (_.registerDef(t))
77
79
traverseChildren(tree)(using newCtx)
78
80
case t: tpd.DefDef =>
79
- ctx.property(_key).foreach (_.registerDef(t))
81
+ unusedDataApply (_.registerDef(t))
80
82
traverseChildren(tree)(using newCtx)
81
83
case t : tpd.Bind =>
82
- ctx.property(_key).foreach (_.registerPatVar(t))
84
+ unusedDataApply (_.registerPatVar(t))
83
85
traverseChildren(tree)(using newCtx)
84
86
case _ =>
85
87
traverseChildren(tree)(using newCtx)
86
-
87
- }
88
+ end traverse
89
+ end traverser
88
90
89
91
private def reportUnused (res : UnusedData .UnusedResult )(using Context ): Unit =
90
92
import CheckUnused .WarnTypes
@@ -129,11 +131,19 @@ object CheckUnused:
129
131
import dotty .tools .dotc .core .Symbols .Symbol
130
132
import UnusedData .ScopeType
131
133
134
+ /** The current scope during the tree traversal */
132
135
var currScopeType : ScopeType = ScopeType .Other
133
136
134
137
/* IMPORTS */
135
138
private val impInScope = MutStack (MutSet [tpd.Import ]())
139
+ /**
140
+ * We store the symbol along with their accessibility without import.
141
+ * Accessibility to their definition in outer context/scope
142
+ *
143
+ * See the `isAccessibleAsIdent` extension method below in the file
144
+ */
136
145
private val usedInScope = MutStack (MutSet [(Symbol ,Boolean )]())
146
+ /* unused import collected during traversal */
137
147
private val unusedImport = MutSet [ImportSelector ]()
138
148
139
149
/* LOCAL DEF OR VAL / Private Def or Val / Pattern variables */
@@ -143,12 +153,14 @@ object CheckUnused:
143
153
private val implicitParamInScope = MutSet [tpd.ValOrDefDef ]()
144
154
private val patVarsInScope = MutSet [tpd.Bind ]()
145
155
156
+ /* Unused collection collected at the end */
146
157
private val unusedLocalDef = MutSet [tpd.ValOrDefDef ]()
147
158
private val unusedPrivateDef = MutSet [tpd.ValOrDefDef ]()
148
159
private val unusedExplicitParams = MutSet [tpd.ValOrDefDef ]()
149
160
private val unusedImplicitParams = MutSet [tpd.ValOrDefDef ]()
150
161
private val unusedPatVars = MutSet [tpd.Bind ]()
151
162
163
+ /** All used symbols */
152
164
private val usedDef = MutSet [Symbol ]()
153
165
154
166
/**
@@ -184,6 +196,7 @@ object CheckUnused:
184
196
impInScope.top += imp
185
197
unusedImport ++= imp.selectors.filter(s => ! isImportExclusion(s))
186
198
199
+ /** Register (or not) some `val` or `def` according to the context, scope and flags */
187
200
def registerDef (valOrDef : tpd.ValOrDefDef )(using Context ): Unit =
188
201
if valOrDef.symbol.is(Param ) then
189
202
if valOrDef.symbol.is(Given ) then
@@ -195,6 +208,7 @@ object CheckUnused:
195
208
else if currScopeType == ScopeType .Template && valOrDef.symbol.is(Private , butNot = SelfName ) then
196
209
privateDefInScope += valOrDef
197
210
211
+ /** Register pattern variable */
198
212
def registerPatVar (patvar : tpd.Bind )(using Context ): Unit =
199
213
patVarsInScope += patvar
200
214
@@ -204,15 +218,19 @@ object CheckUnused:
204
218
impInScope.push(MutSet ())
205
219
usedInScope.push(MutSet ())
206
220
207
- /** leave the current scope */
221
+ /**
222
+ * leave the current scope and do :
223
+ *
224
+ * - If there are imports in this scope check for unused ones
225
+ */
208
226
def popScope ()(using Context ): Unit =
209
- popScopeImport()
210
-
211
- def popScopeImport ()(using Context ): Unit =
227
+ // used symbol in this scope
212
228
val used = usedInScope.pop().toSet
229
+ // used imports in this scope
213
230
val imports = impInScope.pop().toSet
214
231
val kept = used.filter { t =>
215
232
val (sym, isAccessible) = t
233
+ // keep the symbol for outer scope, if it matches **no** import
216
234
! imports.exists { imp =>
217
235
sym.isInImport(imp, isAccessible) match
218
236
case None => false
@@ -221,9 +239,14 @@ object CheckUnused:
221
239
true
222
240
}
223
241
}
242
+ // if there's an outer scope
224
243
if usedInScope.nonEmpty then
244
+ // we keep the symbols not referencing an import in this scope
245
+ // as it can be the only reference to an outer import
225
246
usedInScope.top ++= kept
247
+ // register usage in this scope for other warnings at the end of the phase
226
248
usedDef ++= used.map(_._1)
249
+ end popScope
227
250
228
251
/**
229
252
* Leave the scope and return a `List` of unused `ImportSelector`s
@@ -267,13 +290,15 @@ object CheckUnused:
267
290
(pos.line, pos.column)
268
291
}
269
292
UnusedResult (warnings, Nil )
270
-
293
+ end getUnused
271
294
// ============================ HELPERS ====================================
272
295
273
296
private def isImportExclusion (sel : ImportSelector ): Boolean = sel.renamed match
274
297
case untpd.Ident (name) => name == StdNames .nme.WILDCARD
275
298
case _ => false
299
+
276
300
extension (sym : Symbol )
301
+ /** is accessible without import in current context */
277
302
def isAccessibleAsIdent (using Context ): Boolean =
278
303
sym.exists &&
279
304
ctx.outersIterator.exists{ c =>
@@ -282,17 +307,18 @@ object CheckUnused:
282
307
&& c.owner.thisType.baseClasses.contains(sym.owner)
283
308
&& c.owner.thisType.member(sym.name).alternatives.contains(sym)
284
309
}
310
+
311
+ /** Given an import and accessibility, return an option of selector that match import<->symbol */
285
312
def isInImport (imp : tpd.Import , isAccessible : Boolean )(using Context ): Option [ImportSelector ] =
286
313
val tpd .Import (qual, sels) = imp
287
- val sameQualType =
288
- qual.tpe.member(sym.name.toTermName).symbol == sym ||
289
- qual.tpe.member(sym.name.toTypeName).symbol == sym
314
+ val qualHasSymbol = qual.tpe.member(sym.name).symbol == sym
290
315
def selector = sels.find(sel => sel.name.toTermName == sym.name || sel.name.toTypeName == sym.name)
291
316
def wildcard = sels.find(sel => sel.isWildcard && (sym.is(Given ) == sel.isGiven))
292
- if sameQualType && ! isAccessible then
293
- selector.orElse(wildcard)
317
+ if qualHasSymbol && ! isAccessible then
318
+ selector.orElse(wildcard) // selector with name or wildcard (or given)
294
319
else
295
320
None
321
+
296
322
end UnusedData
297
323
298
324
object UnusedData :
0 commit comments