Skip to content

Commit 6d8d20d

Browse files
committed
Refactor CheckUnused, fix pkg false negative
- Refactor the sets of defs and usage symbols. This results in less objects created and less push-pop scope. - Fix false negative for imports in the same package
1 parent 892621b commit 6d8d20d

File tree

1 file changed

+83
-113
lines changed

1 file changed

+83
-113
lines changed

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

Lines changed: 83 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ 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
2120

2221

2322
/**
@@ -56,31 +55,33 @@ class CheckUnused extends Phase:
5655
import UnusedData.ScopeType
5756

5857
override def traverse(tree: tpd.Tree)(using Context): Unit =
58+
val newCtx = if tree.symbol.exists then ctx.withOwner(tree.symbol) else ctx
5959
if tree.isDef then // register the annotations for usage
6060
ctx.property(_key).foreach(_.registerUsedAnnotation(tree.symbol))
6161
tree match
6262
case imp:tpd.Import =>
6363
ctx.property(_key).foreach(_.registerImport(imp))
6464
case ident: Ident =>
6565
ctx.property(_key).foreach(_.registerUsed(ident.symbol))
66-
traverseChildren(tree)
66+
traverseChildren(tree)(using newCtx)
6767
case sel: Select =>
6868
ctx.property(_key).foreach(_.registerUsed(sel.symbol))
69-
traverseChildren(tree)
69+
traverseChildren(tree)(using newCtx)
7070
case _: (tpd.Block | tpd.Template) =>
7171
ctx.property(_key).foreach { ud =>
72-
ud.inNewScope(ScopeType.fromTree(tree))(traverseChildren(tree))
72+
ud.inNewScope(ScopeType.fromTree(tree))(traverseChildren(tree)(using newCtx))
7373
}
7474
case t:tpd.ValDef =>
7575
ctx.property(_key).foreach(_.registerDef(t))
76-
traverseChildren(tree)
76+
traverseChildren(tree)(using newCtx)
7777
case t:tpd.DefDef =>
7878
ctx.property(_key).foreach(_.registerDef(t))
79-
traverseChildren(tree)
79+
traverseChildren(tree)(using newCtx)
8080
case t: tpd.Bind =>
8181
ctx.property(_key).foreach(_.registerPatVar(t))
82-
traverseChildren(tree)
83-
case _ => traverseChildren(tree)
82+
traverseChildren(tree)(using newCtx)
83+
case _ =>
84+
traverseChildren(tree)(using newCtx)
8485

8586
}
8687

@@ -123,29 +124,29 @@ object CheckUnused:
123124
* - usage
124125
*/
125126
private class UnusedData:
126-
import collection.mutable.{Set => MutSet, Map => MutMap, Stack => MutStack, ListBuffer}
127+
import collection.mutable.{Set => MutSet, Map => MutMap, Stack => MutStack}
127128
import dotty.tools.dotc.core.Symbols.Symbol
128129
import UnusedData.ScopeType
129130

130131
var currScopeType: ScopeType = ScopeType.Other
131132

132133
/* IMPORTS */
133-
private val impInScope = MutStack(MutMap[Symbol, ListBuffer[ImportSelector]]())
134+
private val impInScope = MutStack(MutSet[tpd.Import]())
135+
private val usedInScope = MutStack(MutSet[(Symbol,Boolean)]())
134136
private val unusedImport = MutSet[ImportSelector]()
135-
private val usedImports = MutStack(MutSet[Symbol]())
136137

137138
/* LOCAL DEF OR VAL / Private Def or Val / Pattern variables */
138-
private val localDefInScope = MutStack(MutSet[tpd.ValOrDefDef]())
139-
private val privateDefInScope = MutStack(MutSet[tpd.ValOrDefDef]())
140-
private val explicitParamInScope = MutStack(MutSet[tpd.ValOrDefDef]())
141-
private val implicitParamInScope = MutStack(MutSet[tpd.ValOrDefDef]())
142-
private val patVarsInScope = MutStack(MutSet[tpd.Bind]())
143-
144-
private val unusedLocalDef = ListBuffer[tpd.ValOrDefDef]()
145-
private val unusedPrivateDef = ListBuffer[tpd.ValOrDefDef]()
146-
private val unusedExplicitParams = ListBuffer[tpd.ValOrDefDef]()
147-
private val unusedImplicitParams = ListBuffer[tpd.ValOrDefDef]()
148-
private val unusedPatVars = ListBuffer[tpd.Bind]()
139+
private val localDefInScope = MutSet[tpd.ValOrDefDef]()
140+
private val privateDefInScope = MutSet[tpd.ValOrDefDef]()
141+
private val explicitParamInScope = MutSet[tpd.ValOrDefDef]()
142+
private val implicitParamInScope = MutSet[tpd.ValOrDefDef]()
143+
private val patVarsInScope = MutSet[tpd.Bind]()
144+
145+
private val unusedLocalDef = MutSet[tpd.ValOrDefDef]()
146+
private val unusedPrivateDef = MutSet[tpd.ValOrDefDef]()
147+
private val unusedExplicitParams = MutSet[tpd.ValOrDefDef]()
148+
private val unusedImplicitParams = MutSet[tpd.ValOrDefDef]()
149+
private val unusedPatVars = MutSet[tpd.Bind]()
149150

150151
private val usedDef = MutSet[Symbol]()
151152

@@ -161,122 +162,66 @@ object CheckUnused:
161162
popScope()
162163
currScopeType = prev
163164

164-
private def isImportExclusion(sel: ImportSelector): Boolean = sel.renamed match
165-
case untpd.Ident(name) => name == StdNames.nme.WILDCARD
166-
case _ => false
167-
168-
169165
/** Register all annotations of this symbol's denotation */
170166
def registerUsedAnnotation(sym: Symbol)(using Context): Unit =
171167
val annotSym = sym.denot.annotations.map(_.symbol)
172168
registerUsed(annotSym)
173169

174170
/** Register a found (used) symbol */
175-
def registerUsed(sym: Symbol): Unit =
176-
usedImports.top += sym
171+
def registerUsed(sym: Symbol)(using Context): Unit =
172+
usedInScope.top += sym -> sym.isAccessibleAsIdent
177173
usedDef += sym
178174

179175
/** Register a list of found (used) symbols */
180-
def registerUsed(sym: Seq[Symbol]): Unit =
181-
usedImports.top ++= sym
182-
usedDef ++= sym
176+
def registerUsed(syms: Seq[Symbol])(using Context): Unit =
177+
usedInScope.top ++= syms.map(s => s -> s.isAccessibleAsIdent)
178+
usedDef ++= syms
183179

184180
/** Register an import */
185181
def registerImport(imp: tpd.Import)(using Context): Unit =
186-
val tpd.Import(tree, sels) = imp
187-
val map = impInScope.top
188-
val entries = sels.flatMap{ s =>
189-
if isImportExclusion(s) then
190-
Nil // ignore exclusion import
191-
else if s.isWildcard then
192-
tree.tpe.allMembers
193-
.filter(m => m.symbol.is(Given) == s.isGiven) // given imports
194-
.map(_.symbol -> s)
195-
else
196-
val termSymbol = tree.tpe.member(s.name.toTermName).symbol
197-
val typeSymbol = tree.tpe.member(s.name.toTypeName).symbol
198-
List(termSymbol -> s, typeSymbol -> s)
199-
}
200-
entries.foreach{(sym, sel) =>
201-
map.get(sym) match
202-
case None => map.put(sym, ListBuffer(sel))
203-
case Some(value) => value += sel
204-
}
182+
impInScope.top += imp
183+
unusedImport ++= imp.selectors.filter(s => !isImportExclusion(s))
205184

206185
def registerDef(valOrDef: tpd.ValOrDefDef)(using Context): Unit =
207186
if valOrDef.symbol.is(Param) then
208187
if valOrDef.symbol.is(Given) then
209-
implicitParamInScope.top += valOrDef
188+
implicitParamInScope += valOrDef
210189
else
211-
explicitParamInScope.top += valOrDef
190+
explicitParamInScope += valOrDef
212191
else if currScopeType == ScopeType.Local then
213-
localDefInScope.top += valOrDef
192+
localDefInScope += valOrDef
214193
else if currScopeType == ScopeType.Template && valOrDef.symbol.is(Private, butNot = SelfName) then
215-
privateDefInScope.top += valOrDef
194+
privateDefInScope += valOrDef
216195

217196
def registerPatVar(patvar: tpd.Bind)(using Context): Unit =
218-
patVarsInScope.top += patvar
197+
patVarsInScope += patvar
219198

220199
/** enter a new scope */
221200
def pushScope(): Unit =
222201
// unused imports :
223-
usedImports.push(MutSet())
224-
impInScope.push(MutMap())
225-
// local and private defs :
226-
localDefInScope.push(MutSet())
227-
explicitParamInScope.push(MutSet())
228-
implicitParamInScope.push(MutSet())
229-
privateDefInScope.push(MutSet())
230-
patVarsInScope.push(MutSet())
202+
impInScope.push(MutSet())
203+
usedInScope.push(MutSet())
231204

232205
/** leave the current scope */
233206
def popScope()(using Context): Unit =
234207
popScopeImport()
235-
popScopeLocalDef()
236-
popScopeExplicitParam()
237-
popScopeImplicitParam()
238-
popScopePrivateDef()
239-
popScopePatVars()
240-
241-
def popScopeImport(): Unit =
242-
val usedImp = MutSet[ImportSelector]()
243-
val poppedImp = impInScope.pop()
244-
val notDefined = usedImports.pop.filter{sym =>
245-
poppedImp.remove(sym) match
246-
case None => true
247-
case Some(value) =>
248-
usedImp.addAll(value)
249-
false
250-
}
251-
if usedImports.nonEmpty then
252-
usedImports.top.addAll(notDefined)
253-
254-
poppedImp.values.flatten.foreach{ sel =>
255-
// If **any** of the entities used by the import is used,
256-
// do not add to the `unused` Set
257-
if !usedImp(sel) then
258-
unusedImport += sel
259-
}
260-
261-
def popScopeLocalDef()(using Context): Unit =
262-
val unused = localDefInScope.pop().filterInPlace(d => !usedDef(d.symbol))
263-
unusedLocalDef ++= unused
264208

265-
def popScopeExplicitParam()(using Context): Unit =
266-
val unused = explicitParamInScope.pop().filterInPlace(d => !usedDef(d.symbol))
267-
unusedExplicitParams ++= unused
268-
269-
def popScopeImplicitParam()(using Context): Unit =
270-
val unused = implicitParamInScope.pop().filterInPlace(d => !usedDef(d.symbol))
271-
unusedImplicitParams ++= unused
272-
273-
def popScopePrivateDef()(using Context): Unit =
274-
val unused = privateDefInScope.pop().filterInPlace(d => !usedDef(d.symbol))
275-
unusedPrivateDef ++= unused
276-
277-
def popScopePatVars()(using Context): Unit =
278-
val unused = patVarsInScope.pop().filterInPlace(d => !usedDef(d.symbol))
279-
unusedPatVars ++= unused
209+
def popScopeImport()(using Context): Unit =
210+
val used = usedInScope.pop().toSet
211+
val imports = impInScope.pop().toSet
212+
val kept = used.filter { t =>
213+
val (sym, isAccessible) = t
214+
!imports.exists { imp =>
215+
sym.isInImport(imp, isAccessible) match
216+
case None => false
217+
case Some(sel) =>
218+
unusedImport -= sel
219+
true
220+
}
221+
}
222+
if usedInScope.nonEmpty then
223+
usedInScope.top ++= kept
224+
usedDef ++= used.map(_._1)
280225

281226
/**
282227
* Leave the scope and return a `List` of unused `ImportSelector`s
@@ -292,27 +237,27 @@ object CheckUnused:
292237
Nil
293238
val sortedLocalDefs =
294239
if ctx.settings.WunusedHas.locals then
295-
unusedLocalDef.map(d => d.namePos -> WarnTypes.LocalDefs).toList
240+
localDefInScope.filter(d => !usedDef(d.symbol)).map(d => d.namePos -> WarnTypes.LocalDefs).toList
296241
else
297242
Nil
298243
val sortedExplicitParams =
299244
if ctx.settings.WunusedHas.explicits then
300-
unusedExplicitParams.map(d => d.namePos -> WarnTypes.ExplicitParams).toList
245+
explicitParamInScope.filter(d => !usedDef(d.symbol)).map(d => d.namePos -> WarnTypes.ExplicitParams).toList
301246
else
302247
Nil
303248
val sortedImplicitParams =
304249
if ctx.settings.WunusedHas.implicits then
305-
unusedImplicitParams.map(d => d.namePos -> WarnTypes.ImplicitParams).toList
250+
implicitParamInScope.filter(d => !usedDef(d.symbol)).map(d => d.namePos -> WarnTypes.ImplicitParams).toList
306251
else
307252
Nil
308253
val sortedPrivateDefs =
309254
if ctx.settings.WunusedHas.privates then
310-
unusedPrivateDef.map(d => d.namePos -> WarnTypes.PrivateMembers).toList
255+
privateDefInScope.filter(d => !usedDef(d.symbol)).map(d => d.namePos -> WarnTypes.PrivateMembers).toList
311256
else
312257
Nil
313258
val sortedPatVars =
314259
if ctx.settings.WunusedHas.patvars then
315-
unusedPatVars.map(d => d.namePos -> WarnTypes.PatVars).toList
260+
patVarsInScope.filter(d => !usedDef(d.symbol)).map(d => d.namePos -> WarnTypes.PatVars).toList
316261
else
317262
Nil
318263
val warnings = List(sortedImp, sortedLocalDefs, sortedExplicitParams, sortedImplicitParams, sortedPrivateDefs, sortedPatVars).flatten.sortBy { s =>
@@ -321,6 +266,31 @@ object CheckUnused:
321266
}
322267
UnusedResult(warnings, Nil)
323268

269+
//============================ HELPERS ====================================
270+
271+
private def isImportExclusion(sel: ImportSelector): Boolean = sel.renamed match
272+
case untpd.Ident(name) => name == StdNames.nme.WILDCARD
273+
case _ => false
274+
extension (sym: Symbol)
275+
def isAccessibleAsIdent(using Context): Boolean =
276+
sym.exists &&
277+
ctx.outersIterator.exists{ c =>
278+
c.owner == sym.owner
279+
|| sym.owner.isClass && c.owner.isClass
280+
&& c.owner.thisType.baseClasses.contains(sym.owner)
281+
&& c.owner.thisType.member(sym.name).alternatives.contains(sym)
282+
}
283+
def isInImport(imp: tpd.Import, isAccessible: Boolean)(using Context): Option[ImportSelector] =
284+
val tpd.Import(qual, sels) = imp
285+
val sameQualType =
286+
qual.tpe.member(sym.name.toTermName).symbol == sym ||
287+
qual.tpe.member(sym.name.toTypeName).symbol == sym
288+
def selector = sels.find(sel => sel.name.toTermName == sym.name || sel.name.toTypeName == sym.name)
289+
def wildcard = sels.find(sel => sel.isWildcard && (sym.is(Given) == sel.isGiven))
290+
if sameQualType && !isAccessible then
291+
selector.orElse(wildcard)
292+
else
293+
None
324294
end UnusedData
325295

326296
object UnusedData:

0 commit comments

Comments
 (0)