Skip to content

Commit a55ee4d

Browse files
committed
Do not use Sets of Trees in CheckUnused.
`Tree`s have structural equality. Even if `==` should be able to exit quickly either because of `eq` or an early difference, sets systematically call `hashCode`, which is going to recurse into the entire structure.
1 parent 3188177 commit a55ee4d

File tree

1 file changed

+38
-28
lines changed

1 file changed

+38
-28
lines changed

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

+38-28
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import dotty.tools.dotc.core.Definitions
2929
import dotty.tools.dotc.core.NameKinds.WildcardParamName
3030
import dotty.tools.dotc.core.Symbols.Symbol
3131
import dotty.tools.dotc.core.StdNames.nme
32+
import dotty.tools.dotc.util.Spans.Span
3233
import scala.math.Ordering
3334

3435

@@ -365,16 +366,16 @@ object CheckUnused:
365366
* See the `isAccessibleAsIdent` extension method below in the file
366367
*/
367368
private val usedInScope = MutStack(MutSet[(Symbol,Boolean, Option[Name], Boolean)]())
368-
private val usedInPosition = MutSet[(SrcPos, Name)]()
369+
private val usedInPosition = MutMap.empty[Name, MutSet[Symbol]]
369370
/* unused import collected during traversal */
370-
private val unusedImport = MutSet[ImportSelector]()
371+
private val unusedImport = new java.util.IdentityHashMap[ImportSelector, Unit]
371372

372373
/* LOCAL DEF OR VAL / Private Def or Val / Pattern variables */
373-
private val localDefInScope = MutSet[tpd.MemberDef]()
374-
private val privateDefInScope = MutSet[tpd.MemberDef]()
375-
private val explicitParamInScope = MutSet[tpd.MemberDef]()
376-
private val implicitParamInScope = MutSet[tpd.MemberDef]()
377-
private val patVarsInScope = MutSet[tpd.Bind]()
374+
private val localDefInScope = MutList.empty[tpd.MemberDef]
375+
private val privateDefInScope = MutList.empty[tpd.MemberDef]
376+
private val explicitParamInScope = MutList.empty[tpd.MemberDef]
377+
private val implicitParamInScope = MutList.empty[tpd.MemberDef]
378+
private val patVarsInScope = MutList.empty[tpd.Bind]
378379

379380
/** All variables sets*/
380381
private val setVars = MutSet[Symbol]()
@@ -416,7 +417,8 @@ object CheckUnused:
416417
usedInScope.top += ((sym.companionModule, sym.isAccessibleAsIdent, name, isDerived))
417418
usedInScope.top += ((sym.companionClass, sym.isAccessibleAsIdent, name, isDerived))
418419
if sym.sourcePos.exists then
419-
name.map(n => usedInPosition += ((sym.sourcePos, n)))
420+
for n <- name do
421+
usedInPosition.getOrElseUpdate(n, MutSet.empty) += sym
420422

421423
/** Register a symbol that should be ignored */
422424
def addIgnoredUsage(sym: Symbol)(using Context): Unit =
@@ -434,9 +436,9 @@ object CheckUnused:
434436
if !tpd.languageImport(imp.expr).nonEmpty && !imp.isGeneratedByEnum && !isTransparentAndInline(imp) then
435437
impInScope.top += imp
436438
if currScopeType.top != ScopeType.ReplWrapper then // #18383 Do not report top-level import's in the repl as unused
437-
unusedImport ++= imp.selectors.filter { s =>
438-
!shouldSelectorBeReported(imp, s) && !isImportExclusion(s) && !isImportIgnored(imp, s)
439-
}
439+
for s <- imp.selectors do
440+
if !shouldSelectorBeReported(imp, s) && !isImportExclusion(s) && !isImportIgnored(imp, s) then
441+
unusedImport.put(s, ())
440442
end registerImport
441443

442444
/** Register (or not) some `val` or `def` according to the context, scope and flags */
@@ -491,11 +493,11 @@ object CheckUnused:
491493
// We keep wildcard symbol for the end as they have the least precedence
492494
false
493495
case Some(sel) =>
494-
unusedImport -= sel
496+
unusedImport.remove(sel)
495497
true
496498
}
497499
if !matchedExplicitImport && selWildCard.isDefined then
498-
unusedImport -= selWildCard.get
500+
unusedImport.remove(selWildCard.get)
499501
true // a matching import exists so the symbol won't be kept for outer scope
500502
else
501503
matchedExplicitImport
@@ -520,56 +522,64 @@ object CheckUnused:
520522

521523
def getUnused(using Context): UnusedResult =
522524
popScope()
525+
526+
def isUsedInPosition(name: Name, span: Span): Boolean =
527+
usedInPosition.get(name) match
528+
case Some(syms) => syms.exists(sym => span.contains(sym.span))
529+
case None => false
530+
523531
val sortedImp =
524532
if ctx.settings.WunusedHas.imports || ctx.settings.WunusedHas.strictNoImplicitWarn then
525-
unusedImport.map(d => UnusedSymbol(d.srcPos, d.name, WarnTypes.Imports)).toList
533+
import scala.jdk.CollectionConverters.*
534+
unusedImport.keySet().nn.iterator().nn.asScala
535+
.map(d => UnusedSymbol(d.srcPos, d.name, WarnTypes.Imports)).toList
526536
else
527537
Nil
528538
// Partition to extract unset local variables from usedLocalDefs
529539
val (usedLocalDefs, unusedLocalDefs) =
530540
if ctx.settings.WunusedHas.locals then
531-
localDefInScope.partition(d => d.symbol.usedDefContains)
541+
localDefInScope.toList.partition(d => d.symbol.usedDefContains)
532542
else
533543
(Nil, Nil)
534544
val sortedLocalDefs =
535545
unusedLocalDefs
536-
.filterNot(d => usedInPosition.exists { case (pos, name) => d.span.contains(pos.span) && name == d.symbol.name})
546+
.filterNot(d => isUsedInPosition(d.symbol.name, d.span))
537547
.filterNot(d => containsSyntheticSuffix(d.symbol))
538-
.map(d => UnusedSymbol(d.namePos, d.name, WarnTypes.LocalDefs)).toList
548+
.map(d => UnusedSymbol(d.namePos, d.name, WarnTypes.LocalDefs))
539549
val unsetLocalDefs = usedLocalDefs.filter(isUnsetVarDef).map(d => UnusedSymbol(d.namePos, d.name, WarnTypes.UnsetLocals)).toList
540550

541551
val sortedExplicitParams =
542552
if ctx.settings.WunusedHas.explicits then
543-
explicitParamInScope
553+
explicitParamInScope.toList
544554
.filterNot(d => d.symbol.usedDefContains)
545-
.filterNot(d => usedInPosition.exists { case (pos, name) => d.span.contains(pos.span) && name == d.symbol.name})
555+
.filterNot(d => isUsedInPosition(d.symbol.name, d.span))
546556
.filterNot(d => containsSyntheticSuffix(d.symbol))
547-
.map(d => UnusedSymbol(d.namePos, d.name, WarnTypes.ExplicitParams)).toList
557+
.map(d => UnusedSymbol(d.namePos, d.name, WarnTypes.ExplicitParams))
548558
else
549559
Nil
550560
val sortedImplicitParams =
551561
if ctx.settings.WunusedHas.implicits then
552-
implicitParamInScope
562+
implicitParamInScope.toList
553563
.filterNot(d => d.symbol.usedDefContains)
554564
.filterNot(d => containsSyntheticSuffix(d.symbol))
555-
.map(d => UnusedSymbol(d.namePos, d.name, WarnTypes.ImplicitParams)).toList
565+
.map(d => UnusedSymbol(d.namePos, d.name, WarnTypes.ImplicitParams))
556566
else
557567
Nil
558568
// Partition to extract unset private variables from usedPrivates
559569
val (usedPrivates, unusedPrivates) =
560570
if ctx.settings.WunusedHas.privates then
561-
privateDefInScope.partition(d => d.symbol.usedDefContains)
571+
privateDefInScope.toList.partition(d => d.symbol.usedDefContains)
562572
else
563573
(Nil, Nil)
564-
val sortedPrivateDefs = unusedPrivates.filterNot(d => containsSyntheticSuffix(d.symbol)).map(d => UnusedSymbol(d.namePos, d.name, WarnTypes.PrivateMembers)).toList
565-
val unsetPrivateDefs = usedPrivates.filter(isUnsetVarDef).map(d => UnusedSymbol(d.namePos, d.name, WarnTypes.UnsetPrivates)).toList
574+
val sortedPrivateDefs = unusedPrivates.filterNot(d => containsSyntheticSuffix(d.symbol)).map(d => UnusedSymbol(d.namePos, d.name, WarnTypes.PrivateMembers))
575+
val unsetPrivateDefs = usedPrivates.filter(isUnsetVarDef).map(d => UnusedSymbol(d.namePos, d.name, WarnTypes.UnsetPrivates))
566576
val sortedPatVars =
567577
if ctx.settings.WunusedHas.patvars then
568-
patVarsInScope
578+
patVarsInScope.toList
569579
.filterNot(d => d.symbol.usedDefContains)
570580
.filterNot(d => containsSyntheticSuffix(d.symbol))
571-
.filterNot(d => usedInPosition.exists { case (pos, name) => d.span.contains(pos.span) && name == d.symbol.name})
572-
.map(d => UnusedSymbol(d.namePos, d.name, WarnTypes.PatVars)).toList
581+
.filterNot(d => isUsedInPosition(d.symbol.name, d.span))
582+
.map(d => UnusedSymbol(d.namePos, d.name, WarnTypes.PatVars))
573583
else
574584
Nil
575585
val warnings =

0 commit comments

Comments
 (0)