@@ -2,6 +2,7 @@ package dotty.tools.dotc.transform
2
2
3
3
import scala .annotation .tailrec
4
4
5
+ import dotty .tools .uncheckedNN
5
6
import dotty .tools .dotc .ast .tpd
6
7
import dotty .tools .dotc .core .Symbols .*
7
8
import dotty .tools .dotc .ast .tpd .{Inlined , TreeTraverser }
@@ -109,8 +110,8 @@ class CheckUnused private (phaseMode: CheckUnused.PhaseMode, suffix: String, _ke
109
110
ctx
110
111
111
112
override def prepareForSelect (tree : tpd.Select )(using Context ): Context =
112
- val name = tree.removeAttachment(OriginalName ).orElse( Some (tree.name))
113
- unusedDataApply(_.registerUsed(tree.symbol, name))
113
+ val name = tree.removeAttachment(OriginalName )
114
+ unusedDataApply(_.registerUsed(tree.symbol, name, includeForImport = tree.qualifier.span.isSynthetic ))
114
115
115
116
override def prepareForBlock (tree : tpd.Block )(using Context ): Context =
116
117
pushInBlockTemplatePackageDef(tree)
@@ -128,7 +129,7 @@ class CheckUnused private (phaseMode: CheckUnused.PhaseMode, suffix: String, _ke
128
129
if ! tree.symbol.is(Module ) then
129
130
ud.registerDef(tree)
130
131
if tree.name.startsWith(" derived$" ) && tree.typeOpt != NoType then
131
- ud.registerUsed(tree.typeOpt.typeSymbol, None , true )
132
+ ud.registerUsed(tree.typeOpt.typeSymbol, None , isDerived = true )
132
133
ud.addIgnoredUsage(tree.symbol)
133
134
}
134
135
@@ -359,7 +360,7 @@ object CheckUnused:
359
360
var unusedAggregate : Option [UnusedResult ] = None
360
361
361
362
/* IMPORTS */
362
- private val impInScope = MutStack (MutList [tpd. Import ]())
363
+ private val impInScope = MutStack (MutList [ImportSelectorData ]())
363
364
/**
364
365
* We store the symbol along with their accessibility without import.
365
366
* Accessibility to their definition in outer context/scope
@@ -369,7 +370,7 @@ object CheckUnused:
369
370
private val usedInScope = MutStack (MutSet [(Symbol ,Boolean , Option [Name ], Boolean )]())
370
371
private val usedInPosition = MutMap .empty[Name , MutSet [Symbol ]]
371
372
/* unused import collected during traversal */
372
- private val unusedImport = new java.util. IdentityHashMap [ ImportSelector , Unit ]
373
+ private val unusedImport = MutList .empty[ ImportSelectorData ]
373
374
374
375
/* LOCAL DEF OR VAL / Private Def or Val / Pattern variables */
375
376
private val localDefInScope = MutList .empty[tpd.MemberDef ]
@@ -409,16 +410,17 @@ object CheckUnused:
409
410
* The optional name will be used to target the right import
410
411
* as the same element can be imported with different renaming
411
412
*/
412
- def registerUsed (sym : Symbol , name : Option [Name ], isDerived : Boolean = false )(using Context ): Unit =
413
+ def registerUsed (sym : Symbol , name : Option [Name ], includeForImport : Boolean = true , isDerived : Boolean = false )(using Context ): Unit =
413
414
if sym.exists && ! isConstructorOfSynth(sym) && ! doNotRegister(sym) then
414
415
if sym.isConstructor then
415
- registerUsed(sym.owner, None ) // constructor are "implicitly" imported with the class
416
+ registerUsed(sym.owner, None , includeForImport ) // constructor are "implicitly" imported with the class
416
417
else
417
418
val accessibleAsIdent = sym.isAccessibleAsIdent
418
419
def addIfExists (sym : Symbol ): Unit =
419
420
if sym.exists then
420
421
usedDef += sym
421
- usedInScope.top += ((sym, accessibleAsIdent, name, isDerived))
422
+ if includeForImport then
423
+ usedInScope.top += ((sym, accessibleAsIdent, name, isDerived))
422
424
addIfExists(sym)
423
425
addIfExists(sym.companionModule)
424
426
addIfExists(sym.companionClass)
@@ -439,12 +441,27 @@ object CheckUnused:
439
441
440
442
/** Register an import */
441
443
def registerImport (imp : tpd.Import )(using Context ): Unit =
442
- if ! tpd.languageImport(imp.expr).nonEmpty && ! imp.isGeneratedByEnum && ! isTransparentAndInline(imp) then
443
- impInScope.top += imp
444
- if currScopeType.top != ScopeType .ReplWrapper then // #18383 Do not report top-level import's in the repl as unused
445
- for s <- imp.selectors do
446
- if ! shouldSelectorBeReported(imp, s) && ! isImportExclusion(s) && ! isImportIgnored(imp, s) then
447
- unusedImport.put(s, ())
444
+ if
445
+ ! tpd.languageImport(imp.expr).nonEmpty
446
+ && ! imp.isGeneratedByEnum
447
+ && ! isTransparentAndInline(imp)
448
+ && currScopeType.top != ScopeType .ReplWrapper // #18383 Do not report top-level import's in the repl as unused
449
+ then
450
+ val qualTpe = imp.expr.tpe
451
+
452
+ // Put wildcard imports at the end, because they have lower priority within one Import
453
+ val reorderdSelectors =
454
+ val (wildcardSels, nonWildcardSels) = imp.selectors.partition(_.isWildcard)
455
+ nonWildcardSels ::: wildcardSels
456
+
457
+ val newDataInScope =
458
+ for sel <- reorderdSelectors yield
459
+ val data = new ImportSelectorData (qualTpe, sel)
460
+ if shouldSelectorBeReported(imp, sel) || isImportExclusion(sel) || isImportIgnored(imp, sel) then
461
+ // Immediately mark the selector as used
462
+ data.markUsed()
463
+ data
464
+ impInScope.top.prependAll(newDataInScope)
448
465
end registerImport
449
466
450
467
/** Register (or not) some `val` or `def` according to the context, scope and flags */
@@ -482,40 +499,27 @@ object CheckUnused:
482
499
* - If there are imports in this scope check for unused ones
483
500
*/
484
501
def popScope ()(using Context ): Unit =
485
- // used symbol in this scope
486
- val used = usedInScope.pop().toSet
487
- // used imports in this scope
488
- val imports = impInScope.pop()
489
- val kept = used.filterNot { (sym, isAccessible, optName, isDerived) =>
490
- // keep the symbol for outer scope, if it matches **no** import
491
- // This is the first matching wildcard selector
492
- var selWildCard : Option [ImportSelector ] = None
493
-
494
- val matchedExplicitImport = imports.exists { imp =>
495
- sym.isInImport(imp, isAccessible, optName, isDerived) match
496
- case None => false
497
- case optSel@ Some (sel) if sel.isWildcard =>
498
- if selWildCard.isEmpty then selWildCard = optSel
499
- // We keep wildcard symbol for the end as they have the least precedence
500
- false
501
- case Some (sel) =>
502
- unusedImport.remove(sel)
503
- true
502
+ currScopeType.pop()
503
+ val usedInfos = usedInScope.pop()
504
+ val selDatas = impInScope.pop()
505
+
506
+ for usedInfo <- usedInfos do
507
+ val (sym, isAccessible, optName, isDerived) = usedInfo
508
+ val usedData = selDatas.find { selData =>
509
+ sym.isInImport(selData, isAccessible, optName, isDerived)
504
510
}
505
- if ! matchedExplicitImport && selWildCard.isDefined then
506
- unusedImport.remove(selWildCard.get)
507
- true // a matching import exists so the symbol won't be kept for outer scope
508
- else
509
- matchedExplicitImport
510
- }
511
-
512
- // if there's an outer scope
513
- if usedInScope.nonEmpty then
514
- // we keep the symbols not referencing an import in this scope
515
- // as it can be the only reference to an outer import
516
- usedInScope.top ++= kept
517
- // retrieve previous scope type
518
- currScopeType.pop
511
+ usedData match
512
+ case Some (data) =>
513
+ data.markUsed()
514
+ case None =>
515
+ // Propagate the symbol one level up
516
+ if usedInScope.nonEmpty then
517
+ usedInScope.top += usedInfo
518
+ end for // each in `used`
519
+
520
+ for selData <- selDatas do
521
+ if ! selData.isUsed then
522
+ unusedImport += selData
519
523
end popScope
520
524
521
525
/**
@@ -534,9 +538,8 @@ object CheckUnused:
534
538
535
539
val sortedImp =
536
540
if ctx.settings.WunusedHas .imports || ctx.settings.WunusedHas .strictNoImplicitWarn then
537
- import scala .jdk .CollectionConverters .*
538
- unusedImport.keySet().nn.iterator().nn.asScala
539
- .map(d => UnusedSymbol (d.srcPos, d.name, WarnTypes .Imports )).toList
541
+ unusedImport.toList
542
+ .map(d => UnusedSymbol (d.selector.srcPos, d.selector.name, WarnTypes .Imports ))
540
543
else
541
544
Nil
542
545
// Partition to extract unset local variables from usedLocalDefs
@@ -697,52 +700,40 @@ object CheckUnused:
697
700
}
698
701
699
702
/** Given an import and accessibility, return selector that matches import<->symbol */
700
- private def isInImport (imp : tpd. Import , isAccessible : Boolean , altName : Option [Name ], isDerived : Boolean )(using Context ): Option [ ImportSelector ] =
703
+ private def isInImport (selData : ImportSelectorData , isAccessible : Boolean , altName : Option [Name ], isDerived : Boolean )(using Context ): Boolean =
701
704
assert(sym.exists)
702
705
703
- val tpd .Import (qual, sels) = imp
704
- val qualTpe = qual.tpe
705
- val dealiasedSym = sym.dealias
706
-
707
- val selectionsToDealias : List [SingleDenotation ] =
708
- val typeSelections = sels.flatMap(n => qualTpe.member(n.name.toTypeName).alternatives)
709
- val termSelections = sels.flatMap(n => qualTpe.member(n.name.toTermName).alternatives)
710
- typeSelections ::: termSelections
711
-
712
- val qualHasSymbol : Boolean =
713
- val simpleSelections = qualTpe.member(sym.name).alternatives
714
- simpleSelections.exists(d => d.symbol == sym || d.symbol.dealias == dealiasedSym)
715
- || selectionsToDealias.exists(d => d.symbol.dealias == dealiasedSym)
706
+ val selector = selData.selector
716
707
717
- def selector : Option [ImportSelector ] =
718
- sels.find(sel => sym.name.toTermName == sel.name && altName.forall(n => n.toTermName == sel.rename))
719
-
720
- def dealiasedSelector : Option [ImportSelector ] =
721
- if isDerived then
722
- sels.flatMap(sel => selectionsToDealias.map(m => (sel, m.symbol))).collectFirst {
723
- case (sel, sym) if sym.dealias == dealiasedSym => sel
724
- }
725
- else None
726
-
727
- def givenSelector : Option [ImportSelector ] =
728
- if sym.is(Given ) || sym.is(Implicit ) then
729
- sels.filter(sel => sel.isGiven && ! sel.bound.isEmpty).find(sel => sel.boundTpe =:= sym.info)
730
- else None
731
-
732
- def wildcard : Option [ImportSelector ] =
733
- sels.find(sel => sel.isWildcard && ((sym.is(Given ) == sel.isGiven && sel.bound.isEmpty) || sym.is(Implicit )))
734
-
735
- if qualHasSymbol && (! isAccessible || altName.exists(_.toSimpleName != sym.name.toSimpleName)) then
736
- selector.orElse(dealiasedSelector).orElse(givenSelector).orElse(wildcard) // selector with name or wildcard (or given)
708
+ if isAccessible && ! altName.exists(_.toTermName != sym.name.toTermName) then
709
+ // Even if this import matches, it is pointless because the symbol would be accessible anyway
710
+ false
711
+ else if ! selector.isWildcard then
712
+ if altName.exists(explicitName => selector.rename != explicitName.toTermName) then
713
+ // if there is an explicit name, it must match
714
+ false
715
+ else
716
+ if isDerived then
717
+ // See i15503i.scala, grep for "package foo.test.i17156"
718
+ selData.allSymbolsDealiasedForNamed.contains(dealias(sym))
719
+ else
720
+ selData.allSymbolsForNamed.contains(sym)
737
721
else
738
- None
722
+ // Wildcard
723
+ if ! selData.qualTpe.member(sym.name).hasAltWith(_.symbol == sym) then
724
+ // The qualifier does not have the target symbol as a member
725
+ false
726
+ else
727
+ if selector.isGiven then
728
+ // Further check that the symbol is a given or implicit and conforms to the bound
729
+ sym.isOneOf(Given | Implicit )
730
+ && (selector.bound.isEmpty || sym.info <:< selector.boundTpe)
731
+ else
732
+ // Normal wildcard, check that the symbol is not a given (but can be implicit)
733
+ ! sym.is(Given )
734
+ end if
739
735
end isInImport
740
736
741
- private def dealias (using Context ): Symbol =
742
- if sym.isType && sym.asType.denot.isAliasType then
743
- sym.asType.typeRef.dealias.typeSymbol
744
- else sym
745
-
746
737
/** Annotated with @unused */
747
738
private def isUnusedAnnot (using Context ): Boolean =
748
739
sym.annotations.exists(a => a.symbol == ctx.definitions.UnusedAnnot )
@@ -840,11 +831,40 @@ object CheckUnused:
840
831
case _:tpd.Block => Local
841
832
case _ => Other
842
833
834
+ final class ImportSelectorData (val qualTpe : Type , val selector : ImportSelector ):
835
+ private var myUsed : Boolean = false
836
+
837
+ def markUsed (): Unit = myUsed = true
838
+
839
+ def isUsed : Boolean = myUsed
840
+
841
+ private var myAllSymbols : Set [Symbol ] | Null = null
842
+
843
+ def allSymbolsForNamed (using Context ): Set [Symbol ] =
844
+ if myAllSymbols == null then
845
+ val allDenots = qualTpe.member(selector.name).alternatives ::: qualTpe.member(selector.name.toTypeName).alternatives
846
+ myAllSymbols = allDenots.map(_.symbol).toSet
847
+ myAllSymbols.uncheckedNN
848
+
849
+ private var myAllSymbolsDealiased : Set [Symbol ] | Null = null
850
+
851
+ def allSymbolsDealiasedForNamed (using Context ): Set [Symbol ] =
852
+ if myAllSymbolsDealiased == null then
853
+ myAllSymbolsDealiased = allSymbolsForNamed.map(sym => dealias(sym))
854
+ myAllSymbolsDealiased.uncheckedNN
855
+ end ImportSelectorData
856
+
843
857
case class UnusedSymbol (pos : SrcPos , name : Name , warnType : WarnTypes )
844
858
/** A container for the results of the used elements analysis */
845
859
case class UnusedResult (warnings : Set [UnusedSymbol ])
846
860
object UnusedResult :
847
861
val Empty = UnusedResult (Set .empty)
848
862
end UnusedData
849
863
864
+ private def dealias (symbol : Symbol )(using Context ): Symbol =
865
+ if symbol.isType && symbol.asType.denot.isAliasType then
866
+ symbol.asType.typeRef.dealias.typeSymbol
867
+ else
868
+ symbol
869
+
850
870
end CheckUnused
0 commit comments