Skip to content

Commit f354b96

Browse files
committed
Clean CheckUnused phase code, add doc+comments
1 parent 412412c commit f354b96

File tree

1 file changed

+47
-21
lines changed

1 file changed

+47
-21
lines changed

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

Lines changed: 47 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -51,40 +51,42 @@ class CheckUnused extends Phase:
5151
* It traverse the tree the tree and gather the data in the
5252
* corresponding context property
5353
*/
54-
private def traverser = new TreeTraverser {
54+
private def traverser = new TreeTraverser:
5555
import tpd._
5656
import UnusedData.ScopeType
5757

58+
/* Register every imports, definition and usage */
5859
override def traverse(tree: tpd.Tree)(using Context): Unit =
60+
val unusedDataApply = ctx.property(_key).foreach
5961
val newCtx = if tree.symbol.exists then ctx.withOwner(tree.symbol) else ctx
6062
if tree.isDef then // register the annotations for usage
61-
ctx.property(_key).foreach(_.registerUsedAnnotation(tree.symbol))
63+
unusedDataApply(_.registerUsedAnnotation(tree.symbol))
6264
tree match
6365
case imp:tpd.Import =>
64-
ctx.property(_key).foreach(_.registerImport(imp))
66+
unusedDataApply(_.registerImport(imp))
6567
case ident: Ident =>
66-
ctx.property(_key).foreach(_.registerUsed(ident.symbol))
68+
unusedDataApply(_.registerUsed(ident.symbol))
6769
traverseChildren(tree)(using newCtx)
6870
case sel: Select =>
69-
ctx.property(_key).foreach(_.registerUsed(sel.symbol))
71+
unusedDataApply(_.registerUsed(sel.symbol))
7072
traverseChildren(tree)(using newCtx)
7173
case _: (tpd.Block | tpd.Template) =>
72-
ctx.property(_key).foreach { ud =>
74+
unusedDataApply { ud =>
7375
ud.inNewScope(ScopeType.fromTree(tree))(traverseChildren(tree)(using newCtx))
7476
}
7577
case t:tpd.ValDef =>
76-
ctx.property(_key).foreach(_.registerDef(t))
78+
unusedDataApply(_.registerDef(t))
7779
traverseChildren(tree)(using newCtx)
7880
case t:tpd.DefDef =>
79-
ctx.property(_key).foreach(_.registerDef(t))
81+
unusedDataApply(_.registerDef(t))
8082
traverseChildren(tree)(using newCtx)
8183
case t: tpd.Bind =>
82-
ctx.property(_key).foreach(_.registerPatVar(t))
84+
unusedDataApply(_.registerPatVar(t))
8385
traverseChildren(tree)(using newCtx)
8486
case _ =>
8587
traverseChildren(tree)(using newCtx)
86-
87-
}
88+
end traverse
89+
end traverser
8890

8991
private def reportUnused(res: UnusedData.UnusedResult)(using Context): Unit =
9092
import CheckUnused.WarnTypes
@@ -129,11 +131,19 @@ object CheckUnused:
129131
import dotty.tools.dotc.core.Symbols.Symbol
130132
import UnusedData.ScopeType
131133

134+
/** The current scope during the tree traversal */
132135
var currScopeType: ScopeType = ScopeType.Other
133136

134137
/* IMPORTS */
135138
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+
*/
136145
private val usedInScope = MutStack(MutSet[(Symbol,Boolean)]())
146+
/* unused import collected during traversal */
137147
private val unusedImport = MutSet[ImportSelector]()
138148

139149
/* LOCAL DEF OR VAL / Private Def or Val / Pattern variables */
@@ -143,12 +153,14 @@ object CheckUnused:
143153
private val implicitParamInScope = MutSet[tpd.ValOrDefDef]()
144154
private val patVarsInScope = MutSet[tpd.Bind]()
145155

156+
/* Unused collection collected at the end */
146157
private val unusedLocalDef = MutSet[tpd.ValOrDefDef]()
147158
private val unusedPrivateDef = MutSet[tpd.ValOrDefDef]()
148159
private val unusedExplicitParams = MutSet[tpd.ValOrDefDef]()
149160
private val unusedImplicitParams = MutSet[tpd.ValOrDefDef]()
150161
private val unusedPatVars = MutSet[tpd.Bind]()
151162

163+
/** All used symbols */
152164
private val usedDef = MutSet[Symbol]()
153165

154166
/**
@@ -184,6 +196,7 @@ object CheckUnused:
184196
impInScope.top += imp
185197
unusedImport ++= imp.selectors.filter(s => !isImportExclusion(s))
186198

199+
/** Register (or not) some `val` or `def` according to the context, scope and flags */
187200
def registerDef(valOrDef: tpd.ValOrDefDef)(using Context): Unit =
188201
if valOrDef.symbol.is(Param) then
189202
if valOrDef.symbol.is(Given) then
@@ -195,6 +208,7 @@ object CheckUnused:
195208
else if currScopeType == ScopeType.Template && valOrDef.symbol.is(Private, butNot = SelfName) then
196209
privateDefInScope += valOrDef
197210

211+
/** Register pattern variable */
198212
def registerPatVar(patvar: tpd.Bind)(using Context): Unit =
199213
patVarsInScope += patvar
200214

@@ -204,15 +218,19 @@ object CheckUnused:
204218
impInScope.push(MutSet())
205219
usedInScope.push(MutSet())
206220

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+
*/
208226
def popScope()(using Context): Unit =
209-
popScopeImport()
210-
211-
def popScopeImport()(using Context): Unit =
227+
// used symbol in this scope
212228
val used = usedInScope.pop().toSet
229+
// used imports in this scope
213230
val imports = impInScope.pop().toSet
214231
val kept = used.filter { t =>
215232
val (sym, isAccessible) = t
233+
// keep the symbol for outer scope, if it matches **no** import
216234
!imports.exists { imp =>
217235
sym.isInImport(imp, isAccessible) match
218236
case None => false
@@ -221,9 +239,14 @@ object CheckUnused:
221239
true
222240
}
223241
}
242+
// if there's an outer scope
224243
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
225246
usedInScope.top ++= kept
247+
// register usage in this scope for other warnings at the end of the phase
226248
usedDef ++= used.map(_._1)
249+
end popScope
227250

228251
/**
229252
* Leave the scope and return a `List` of unused `ImportSelector`s
@@ -267,13 +290,15 @@ object CheckUnused:
267290
(pos.line, pos.column)
268291
}
269292
UnusedResult(warnings, Nil)
270-
293+
end getUnused
271294
//============================ HELPERS ====================================
272295

273296
private def isImportExclusion(sel: ImportSelector): Boolean = sel.renamed match
274297
case untpd.Ident(name) => name == StdNames.nme.WILDCARD
275298
case _ => false
299+
276300
extension (sym: Symbol)
301+
/** is accessible without import in current context */
277302
def isAccessibleAsIdent(using Context): Boolean =
278303
sym.exists &&
279304
ctx.outersIterator.exists{ c =>
@@ -282,17 +307,18 @@ object CheckUnused:
282307
&& c.owner.thisType.baseClasses.contains(sym.owner)
283308
&& c.owner.thisType.member(sym.name).alternatives.contains(sym)
284309
}
310+
311+
/** Given an import and accessibility, return an option of selector that match import<->symbol */
285312
def isInImport(imp: tpd.Import, isAccessible: Boolean)(using Context): Option[ImportSelector] =
286313
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
290315
def selector = sels.find(sel => sel.name.toTermName == sym.name || sel.name.toTypeName == sym.name)
291316
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)
294319
else
295320
None
321+
296322
end UnusedData
297323

298324
object UnusedData:

0 commit comments

Comments
 (0)