@@ -2,6 +2,7 @@ package dotty.tools.dotc.transform
22
33import scala .annotation .tailrec
44
5+ import dotty .tools .uncheckedNN
56import dotty .tools .dotc .ast .tpd
67import dotty .tools .dotc .core .Symbols .*
78import dotty .tools .dotc .ast .tpd .{Inlined , TreeTraverser }
@@ -109,7 +110,7 @@ class CheckUnused private (phaseMode: CheckUnused.PhaseMode, suffix: String, _ke
109110 ctx
110111
111112 override def prepareForSelect (tree : tpd.Select )(using Context ): Context =
112- unusedDataApply(_.registerUsed(tree.symbol, Some ( tree.name) ))
113+ unusedDataApply(_.registerUsed(tree.symbol, None , notForImport = tree.qualifier.span.isSourceDerived ))
113114
114115 override def prepareForBlock (tree : tpd.Block )(using Context ): Context =
115116 pushInBlockTemplatePackageDef(tree)
@@ -127,7 +128,7 @@ class CheckUnused private (phaseMode: CheckUnused.PhaseMode, suffix: String, _ke
127128 if ! tree.symbol.is(Module ) then
128129 ud.registerDef(tree)
129130 if tree.name.startsWith(" derived$" ) && tree.typeOpt != NoType then
130- ud.registerUsed(tree.typeOpt.typeSymbol, None , true )
131+ ud.registerUsed(tree.typeOpt.typeSymbol, None , isDerived = true )
131132 ud.addIgnoredUsage(tree.symbol)
132133 }
133134
@@ -356,7 +357,7 @@ object CheckUnused:
356357 var unusedAggregate : Option [UnusedResult ] = None
357358
358359 /* IMPORTS */
359- private val impInScope = MutStack (MutList [tpd. Import ]())
360+ private val impInScope = MutStack (MutList [ImportSelectorData ]())
360361 /**
361362 * We store the symbol along with their accessibility without import.
362363 * Accessibility to their definition in outer context/scope
@@ -366,7 +367,7 @@ object CheckUnused:
366367 private val usedInScope = MutStack (MutSet [(Symbol ,Boolean , Option [Name ], Boolean )]())
367368 private val usedInPosition = MutMap .empty[Name , MutSet [Symbol ]]
368369 /* unused import collected during traversal */
369- private val unusedImport = new java.util. IdentityHashMap [ ImportSelector , Unit ]
370+ private val unusedImport = MutList .empty[ ImportSelectorData ]
370371
371372 /* LOCAL DEF OR VAL / Private Def or Val / Pattern variables */
372373 private val localDefInScope = MutList .empty[tpd.MemberDef ]
@@ -406,16 +407,17 @@ object CheckUnused:
406407 * The optional name will be used to target the right import
407408 * as the same element can be imported with different renaming
408409 */
409- def registerUsed (sym : Symbol , name : Option [Name ], isDerived : Boolean = false )(using Context ): Unit =
410+ def registerUsed (sym : Symbol , name : Option [Name ], notForImport : Boolean = false , isDerived : Boolean = false )(using Context ): Unit =
410411 if sym.exists && ! isConstructorOfSynth(sym) && ! doNotRegister(sym) then
411412 if sym.isConstructor then
412- registerUsed(sym.owner, None ) // constructor are "implicitly" imported with the class
413+ registerUsed(sym.owner, None , notForImport ) // constructor are "implicitly" imported with the class
413414 else
414415 val accessibleAsIdent = sym.isAccessibleAsIdent
415416 def addIfExists (sym : Symbol ): Unit =
416417 if sym.exists then
417418 usedDef += sym
418- usedInScope.top += ((sym, accessibleAsIdent, name, isDerived))
419+ if ! notForImport then
420+ usedInScope.top += ((sym, accessibleAsIdent, name, isDerived))
419421 addIfExists(sym)
420422 addIfExists(sym.companionModule)
421423 addIfExists(sym.companionClass)
@@ -436,12 +438,27 @@ object CheckUnused:
436438
437439 /** Register an import */
438440 def registerImport (imp : tpd.Import )(using Context ): Unit =
439- if ! tpd.languageImport(imp.expr).nonEmpty && ! imp.isGeneratedByEnum && ! isTransparentAndInline(imp) then
440- impInScope.top += imp
441- if currScopeType.top != ScopeType .ReplWrapper then // #18383 Do not report top-level import's in the repl as unused
442- for s <- imp.selectors do
443- if ! shouldSelectorBeReported(imp, s) && ! isImportExclusion(s) && ! isImportIgnored(imp, s) then
444- unusedImport.put(s, ())
441+ if
442+ ! tpd.languageImport(imp.expr).nonEmpty
443+ && ! imp.isGeneratedByEnum
444+ && ! isTransparentAndInline(imp)
445+ && currScopeType.top != ScopeType .ReplWrapper // #18383 Do not report top-level import's in the repl as unused
446+ then
447+ val qualTpe = imp.expr.tpe
448+
449+ // Put wildcard imports at the end, because they have lower priority within one Import
450+ val reorderdSelectors =
451+ val (wildcardSels, nonWildcardSels) = imp.selectors.partition(_.isWildcard)
452+ nonWildcardSels ::: wildcardSels
453+
454+ val newDataInScope =
455+ for sel <- reorderdSelectors yield
456+ val data = new ImportSelectorData (qualTpe, sel)
457+ if shouldSelectorBeReported(imp, sel) || isImportExclusion(sel) || isImportIgnored(imp, sel) then
458+ // Immediately mark the selector as used
459+ data.markUsed()
460+ data
461+ impInScope.top.prependAll(newDataInScope)
445462 end registerImport
446463
447464 /** Register (or not) some `val` or `def` according to the context, scope and flags */
@@ -479,40 +496,27 @@ object CheckUnused:
479496 * - If there are imports in this scope check for unused ones
480497 */
481498 def popScope ()(using Context ): Unit =
482- // used symbol in this scope
483- val used = usedInScope.pop().toSet
484- // used imports in this scope
485- val imports = impInScope.pop()
486- val kept = used.filterNot { (sym, isAccessible, optName, isDerived) =>
487- // keep the symbol for outer scope, if it matches **no** import
488- // This is the first matching wildcard selector
489- var selWildCard : Option [ImportSelector ] = None
490-
491- val matchedExplicitImport = imports.exists { imp =>
492- sym.isInImport(imp, isAccessible, optName, isDerived) match
493- case None => false
494- case optSel@ Some (sel) if sel.isWildcard =>
495- if selWildCard.isEmpty then selWildCard = optSel
496- // We keep wildcard symbol for the end as they have the least precedence
497- false
498- case Some (sel) =>
499- unusedImport.remove(sel)
500- true
499+ currScopeType.pop()
500+ val usedInfos = usedInScope.pop()
501+ val selDatas = impInScope.pop()
502+
503+ for usedInfo <- usedInfos do
504+ val (sym, isAccessible, optName, isDerived) = usedInfo
505+ val usedData = selDatas.find { selData =>
506+ sym.isInImport(selData, isAccessible, optName, isDerived)
501507 }
502- if ! matchedExplicitImport && selWildCard.isDefined then
503- unusedImport.remove(selWildCard.get)
504- true // a matching import exists so the symbol won't be kept for outer scope
505- else
506- matchedExplicitImport
507- }
508-
509- // if there's an outer scope
510- if usedInScope.nonEmpty then
511- // we keep the symbols not referencing an import in this scope
512- // as it can be the only reference to an outer import
513- usedInScope.top ++= kept
514- // retrieve previous scope type
515- currScopeType.pop
508+ usedData match
509+ case Some (data) =>
510+ data.markUsed()
511+ case None =>
512+ // Propage the symbol one level up
513+ if usedInScope.nonEmpty then
514+ usedInScope.top += usedInfo
515+ end for // each in `used`
516+
517+ for selData <- selDatas do
518+ if ! selData.isUsed then
519+ unusedImport += selData
516520 end popScope
517521
518522 /**
@@ -531,9 +535,8 @@ object CheckUnused:
531535
532536 val sortedImp =
533537 if ctx.settings.WunusedHas .imports || ctx.settings.WunusedHas .strictNoImplicitWarn then
534- import scala .jdk .CollectionConverters .*
535- unusedImport.keySet().nn.iterator().nn.asScala
536- .map(d => UnusedSymbol (d.srcPos, d.name, WarnTypes .Imports )).toList
538+ unusedImport.toList
539+ .map(d => UnusedSymbol (d.selector.srcPos, d.selector.name, WarnTypes .Imports ))
537540 else
538541 Nil
539542 // Partition to extract unset local variables from usedLocalDefs
@@ -694,61 +697,40 @@ object CheckUnused:
694697 }
695698
696699 /** Given an import and accessibility, return selector that matches import<->symbol */
697- private def isInImport (imp : tpd. Import , isAccessible : Boolean , symName : Option [Name ], isDerived : Boolean )(using Context ): Option [ ImportSelector ] =
700+ private def isInImport (selData : ImportSelectorData , isAccessible : Boolean , symName : Option [Name ], isDerived : Boolean )(using Context ): Boolean =
698701 assert(sym.exists)
699702
700- val tpd .Import (qual, sels) = imp
701-
702- val renamedSelection : Option [ImportSelector ] =
703- val sameTermPath = qual.isTerm && sym.owner.isType && qual.tpe.typeSymbol == sym.owner.asType
704- if sameTermPath then sels.find(sel => sel.name == sym.name) else None
705-
706- if renamedSelection.isDefined then
707- renamedSelection
708- else
709- val dealiasedSym : Symbol = dealias(sym)
710-
711- val selectionsToDealias : List [SingleDenotation ] =
712- val typeSelections = sels.flatMap(n => qual.tpe.member(n.name.toTypeName).alternatives)
713- val termSelections = sels.flatMap(n => qual.tpe.member(n.name.toTermName).alternatives)
714- typeSelections ::: termSelections
715-
716- val qualHasSymbol : Boolean =
717- val simpleSelections = qual.tpe.member(sym.name).alternatives
718- simpleSelections.exists(d => d.symbol == sym || dealias(d.symbol) == dealiasedSym)
719- || selectionsToDealias.exists(d => dealias(d.symbol) == dealiasedSym)
703+ val selector = selData.selector
720704
721- def selector : Option [ImportSelector ] =
722- sels.find(sel => sym.name.toTermName == sel.name && symName.forall(n => n.toTermName == sel.rename))
723-
724- def dealiasedSelector : Option [ImportSelector ] =
705+ if isAccessible && ! symName.exists(_.toTermName != sym.name.toTermName) then
706+ // Even if this import matches, it is pointless because the symbol would be accessible anyway
707+ false
708+ else if ! selector.isWildcard then
709+ if ! symName.forall(explicitName => selector.rename == explicitName.toTermName) then
710+ // if there is an explicit name, it must match
711+ false
712+ else
725713 if isDerived then
726- sels.flatMap(sel => selectionsToDealias.map(m => (sel, m.symbol))).collectFirst {
727- case (sel, sym) if dealias(sym) == dealiasedSym => sel
728- }
729- else
730- None
731-
732- def givenSelector : Option [ImportSelector ] =
733- if sym.is(Given ) || sym.is(Implicit ) then
734- sels.filter(sel => sel.isGiven && ! sel.bound.isEmpty).find(sel => sel.boundTpe =:= sym.info)
714+ // See i15503i.scala, grep for "package foo.test.i17156"
715+ selData.allSymbolsDealiasedForNamed.contains(dealias(sym))
735716 else
736- None
737-
738- def wildcard : Option [ImportSelector ] =
739- sels.find(sel => sel.isWildcard && ((sym.is(Given ) == sel.isGiven && sel.bound.isEmpty) || sym.is(Implicit )))
740-
741- if qualHasSymbol && (! isAccessible || symName.exists(_.toSimpleName != sym.name.toSimpleName)) then
742- selector.orElse(dealiasedSelector).orElse(givenSelector).orElse(wildcard) // selector with name or wildcard (or given)
717+ selData.allSymbolsForNamed.contains(sym)
718+ else
719+ // Wildcard
720+ if ! selData.qualTpe.member(sym.name).hasAltWith(_.symbol == sym) then
721+ // The qualifier does not have the target symbol as a member
722+ false
743723 else
744- None
724+ if selector.isGiven then
725+ // Further check that the symbol is a given or implicit and conforms to the bound
726+ sym.isOneOf(Given | Implicit )
727+ && (selector.bound.isEmpty || sym.info <:< selector.boundTpe)
728+ else
729+ // Normal wildcard, check that the symbol is not a given (but can be implicit)
730+ ! sym.is(Given )
745731 end if
746732 end isInImport
747733
748- private def dealias (symbol : Symbol )(using Context ): Symbol =
749- if (symbol.isType && symbol.asType.denot.isAliasType) then
750- symbol.asType.typeRef.dealias.typeSymbol
751- else symbol
752734 /** Annotated with @unused */
753735 private def isUnusedAnnot (using Context ): Boolean =
754736 sym.annotations.exists(a => a.symbol == ctx.definitions.UnusedAnnot )
@@ -846,11 +828,43 @@ object CheckUnused:
846828 case _:tpd.Block => Local
847829 case _ => Other
848830
831+ final class ImportSelectorData (
832+ val qualTpe : Type ,
833+ val selector : ImportSelector
834+ ):
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+
849857 case class UnusedSymbol (pos : SrcPos , name : Name , warnType : WarnTypes )
850858 /** A container for the results of the used elements analysis */
851859 case class UnusedResult (warnings : Set [UnusedSymbol ])
852860 object UnusedResult :
853861 val Empty = UnusedResult (Set .empty)
854862 end UnusedData
855863
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+
856870end CheckUnused
0 commit comments