Skip to content

Commit 4b1cf4f

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 43f98b7 commit 4b1cf4f

File tree

1 file changed

+38
-28
lines changed

1 file changed

+38
-28
lines changed

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

Lines changed: 38 additions & 28 deletions
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

@@ -362,16 +363,16 @@ object CheckUnused:
362363
* See the `isAccessibleAsIdent` extension method below in the file
363364
*/
364365
private val usedInScope = MutStack(MutSet[(Symbol,Boolean, Option[Name], Boolean)]())
365-
private val usedInPosition = MutSet[(SrcPos, Name)]()
366+
private val usedInPosition = MutMap.empty[Name, MutSet[Symbol]]
366367
/* unused import collected during traversal */
367-
private val unusedImport = MutSet[ImportSelector]()
368+
private val unusedImport = new java.util.IdentityHashMap[ImportSelector, Unit]
368369

369370
/* LOCAL DEF OR VAL / Private Def or Val / Pattern variables */
370-
private val localDefInScope = MutSet[tpd.MemberDef]()
371-
private val privateDefInScope = MutSet[tpd.MemberDef]()
372-
private val explicitParamInScope = MutSet[tpd.MemberDef]()
373-
private val implicitParamInScope = MutSet[tpd.MemberDef]()
374-
private val patVarsInScope = MutSet[tpd.Bind]()
371+
private val localDefInScope = MutList.empty[tpd.MemberDef]
372+
private val privateDefInScope = MutList.empty[tpd.MemberDef]
373+
private val explicitParamInScope = MutList.empty[tpd.MemberDef]
374+
private val implicitParamInScope = MutList.empty[tpd.MemberDef]
375+
private val patVarsInScope = MutList.empty[tpd.Bind]
375376

376377
/** All variables sets*/
377378
private val setVars = MutSet[Symbol]()
@@ -413,7 +414,8 @@ object CheckUnused:
413414
usedInScope.top += ((sym.companionModule, sym.isAccessibleAsIdent, name, isDerived))
414415
usedInScope.top += ((sym.companionClass, sym.isAccessibleAsIdent, name, isDerived))
415416
if sym.sourcePos.exists then
416-
name.map(n => usedInPosition += ((sym.sourcePos, n)))
417+
for n <- name do
418+
usedInPosition.getOrElseUpdate(n, MutSet.empty) += sym
417419

418420
/** Register a symbol that should be ignored */
419421
def addIgnoredUsage(sym: Symbol)(using Context): Unit =
@@ -431,9 +433,9 @@ object CheckUnused:
431433
if !tpd.languageImport(imp.expr).nonEmpty && !imp.isGeneratedByEnum && !isTransparentAndInline(imp) then
432434
impInScope.top += imp
433435
if currScopeType.top != ScopeType.ReplWrapper then // #18383 Do not report top-level import's in the repl as unused
434-
unusedImport ++= imp.selectors.filter { s =>
435-
!shouldSelectorBeReported(imp, s) && !isImportExclusion(s) && !isImportIgnored(imp, s)
436-
}
436+
for s <- imp.selectors do
437+
if !shouldSelectorBeReported(imp, s) && !isImportExclusion(s) && !isImportIgnored(imp, s) then
438+
unusedImport.put(s, ())
437439
end registerImport
438440

439441
/** Register (or not) some `val` or `def` according to the context, scope and flags */
@@ -488,11 +490,11 @@ object CheckUnused:
488490
// We keep wildcard symbol for the end as they have the least precedence
489491
false
490492
case Some(sel) =>
491-
unusedImport -= sel
493+
unusedImport.remove(sel)
492494
true
493495
}
494496
if !matchedExplicitImport && selWildCard.isDefined then
495-
unusedImport -= selWildCard.get
497+
unusedImport.remove(selWildCard.get)
496498
true // a matching import exists so the symbol won't be kept for outer scope
497499
else
498500
matchedExplicitImport
@@ -517,56 +519,64 @@ object CheckUnused:
517519

518520
def getUnused(using Context): UnusedResult =
519521
popScope()
522+
523+
def isUsedInPosition(name: Name, span: Span): Boolean =
524+
usedInPosition.get(name) match
525+
case Some(syms) => syms.exists(sym => span.contains(sym.span))
526+
case None => false
527+
520528
val sortedImp =
521529
if ctx.settings.WunusedHas.imports || ctx.settings.WunusedHas.strictNoImplicitWarn then
522-
unusedImport.map(d => UnusedSymbol(d.srcPos, d.name, WarnTypes.Imports)).toList
530+
import scala.jdk.CollectionConverters.*
531+
unusedImport.keySet().nn.iterator().nn.asScala
532+
.map(d => UnusedSymbol(d.srcPos, d.name, WarnTypes.Imports)).toList
523533
else
524534
Nil
525535
// Partition to extract unset local variables from usedLocalDefs
526536
val (usedLocalDefs, unusedLocalDefs) =
527537
if ctx.settings.WunusedHas.locals then
528-
localDefInScope.partition(d => d.symbol.usedDefContains)
538+
localDefInScope.toList.partition(d => d.symbol.usedDefContains)
529539
else
530540
(Nil, Nil)
531541
val sortedLocalDefs =
532542
unusedLocalDefs
533-
.filterNot(d => usedInPosition.exists { case (pos, name) => d.span.contains(pos.span) && name == d.symbol.name})
543+
.filterNot(d => isUsedInPosition(d.symbol.name, d.span))
534544
.filterNot(d => containsSyntheticSuffix(d.symbol))
535-
.map(d => UnusedSymbol(d.namePos, d.name, WarnTypes.LocalDefs)).toList
545+
.map(d => UnusedSymbol(d.namePos, d.name, WarnTypes.LocalDefs))
536546
val unsetLocalDefs = usedLocalDefs.filter(isUnsetVarDef).map(d => UnusedSymbol(d.namePos, d.name, WarnTypes.UnsetLocals)).toList
537547

538548
val sortedExplicitParams =
539549
if ctx.settings.WunusedHas.explicits then
540-
explicitParamInScope
550+
explicitParamInScope.toList
541551
.filterNot(d => d.symbol.usedDefContains)
542-
.filterNot(d => usedInPosition.exists { case (pos, name) => d.span.contains(pos.span) && name == d.symbol.name})
552+
.filterNot(d => isUsedInPosition(d.symbol.name, d.span))
543553
.filterNot(d => containsSyntheticSuffix(d.symbol))
544-
.map(d => UnusedSymbol(d.namePos, d.name, WarnTypes.ExplicitParams)).toList
554+
.map(d => UnusedSymbol(d.namePos, d.name, WarnTypes.ExplicitParams))
545555
else
546556
Nil
547557
val sortedImplicitParams =
548558
if ctx.settings.WunusedHas.implicits then
549-
implicitParamInScope
559+
implicitParamInScope.toList
550560
.filterNot(d => d.symbol.usedDefContains)
551561
.filterNot(d => containsSyntheticSuffix(d.symbol))
552-
.map(d => UnusedSymbol(d.namePos, d.name, WarnTypes.ImplicitParams)).toList
562+
.map(d => UnusedSymbol(d.namePos, d.name, WarnTypes.ImplicitParams))
553563
else
554564
Nil
555565
// Partition to extract unset private variables from usedPrivates
556566
val (usedPrivates, unusedPrivates) =
557567
if ctx.settings.WunusedHas.privates then
558-
privateDefInScope.partition(d => d.symbol.usedDefContains)
568+
privateDefInScope.toList.partition(d => d.symbol.usedDefContains)
559569
else
560570
(Nil, Nil)
561-
val sortedPrivateDefs = unusedPrivates.filterNot(d => containsSyntheticSuffix(d.symbol)).map(d => UnusedSymbol(d.namePos, d.name, WarnTypes.PrivateMembers)).toList
562-
val unsetPrivateDefs = usedPrivates.filter(isUnsetVarDef).map(d => UnusedSymbol(d.namePos, d.name, WarnTypes.UnsetPrivates)).toList
571+
val sortedPrivateDefs = unusedPrivates.filterNot(d => containsSyntheticSuffix(d.symbol)).map(d => UnusedSymbol(d.namePos, d.name, WarnTypes.PrivateMembers))
572+
val unsetPrivateDefs = usedPrivates.filter(isUnsetVarDef).map(d => UnusedSymbol(d.namePos, d.name, WarnTypes.UnsetPrivates))
563573
val sortedPatVars =
564574
if ctx.settings.WunusedHas.patvars then
565-
patVarsInScope
575+
patVarsInScope.toList
566576
.filterNot(d => d.symbol.usedDefContains)
567577
.filterNot(d => containsSyntheticSuffix(d.symbol))
568-
.filterNot(d => usedInPosition.exists { case (pos, name) => d.span.contains(pos.span) && name == d.symbol.name})
569-
.map(d => UnusedSymbol(d.namePos, d.name, WarnTypes.PatVars)).toList
578+
.filterNot(d => isUsedInPosition(d.symbol.name, d.span))
579+
.map(d => UnusedSymbol(d.namePos, d.name, WarnTypes.PatVars))
570580
else
571581
Nil
572582
val warnings =

0 commit comments

Comments
 (0)