@@ -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,8 +110,8 @@ class CheckUnused private (phaseMode: CheckUnused.PhaseMode, suffix: String, _ke
109110      ctx
110111
111112  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 ))
114115
115116  override  def  prepareForBlock (tree : tpd.Block )(using  Context ):  Context  = 
116117    pushInBlockTemplatePackageDef(tree)
@@ -128,7 +129,7 @@ class CheckUnused private (phaseMode: CheckUnused.PhaseMode, suffix: String, _ke
128129      if  ! tree.symbol.is(Module ) then 
129130        ud.registerDef(tree)
130131      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 )
132133      ud.addIgnoredUsage(tree.symbol)
133134    }
134135
@@ -359,7 +360,7 @@ object CheckUnused:
359360    var  unusedAggregate :  Option [UnusedResult ] =  None 
360361
361362    /*  IMPORTS */ 
362-     private  val  impInScope  =  MutStack (MutList [tpd. Import ]())
363+     private  val  impInScope  =  MutStack (MutList [ImportSelectorData ]())
363364    /**  
364365     * We store the symbol along with their accessibility without import. 
365366     * Accessibility to their definition in outer context/scope 
@@ -369,7 +370,7 @@ object CheckUnused:
369370    private  val  usedInScope  =  MutStack (MutSet [(Symbol ,Boolean , Option [Name ], Boolean )]())
370371    private  val  usedInPosition  =  MutMap .empty[Name , MutSet [Symbol ]]
371372    /*  unused import collected during traversal */ 
372-     private  val  unusedImport  =  new  java.util. IdentityHashMap [ ImportSelector ,  Unit ]
373+     private  val  unusedImport  =  MutList .empty[ ImportSelectorData ]
373374
374375    /*  LOCAL DEF OR VAL / Private Def or Val / Pattern variables */ 
375376    private  val  localDefInScope  =  MutList .empty[tpd.MemberDef ]
@@ -409,16 +410,17 @@ object CheckUnused:
409410     * The optional name will be used to target the right import 
410411     * as the same element can be imported with different renaming 
411412     */  
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  = 
413414      if  sym.exists &&  ! isConstructorOfSynth(sym) &&  ! doNotRegister(sym) then 
414415        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
416417        else 
417418          val  accessibleAsIdent  =  sym.isAccessibleAsIdent
418419          def  addIfExists (sym : Symbol ):  Unit  = 
419420            if  sym.exists then 
420421              usedDef +=  sym
421-               usedInScope.top +=  ((sym, accessibleAsIdent, name, isDerived))
422+               if  includeForImport then 
423+                 usedInScope.top +=  ((sym, accessibleAsIdent, name, isDerived))
422424          addIfExists(sym)
423425          addIfExists(sym.companionModule)
424426          addIfExists(sym.companionClass)
@@ -439,12 +441,27 @@ object CheckUnused:
439441
440442    /**  Register an import */  
441443    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)
448465    end  registerImport 
449466
450467    /**  Register (or not) some `val` or `def` according to the context, scope and flags */  
@@ -482,40 +499,27 @@ object CheckUnused:
482499     * - If there are imports in this scope check for unused ones 
483500     */  
484501    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)
504510        }
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
519523    end  popScope 
520524
521525    /**  
@@ -534,9 +538,8 @@ object CheckUnused:
534538
535539      val  sortedImp  = 
536540        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 ))
540543        else 
541544          Nil 
542545      //  Partition to extract unset local variables from usedLocalDefs
@@ -697,52 +700,40 @@ object CheckUnused:
697700        }
698701
699702      /**  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  = 
701704        assert(sym.exists)
702705
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
716707
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)
737721        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 
739735      end  isInImport 
740736
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- 
746737      /**  Annotated with @unused */  
747738      private  def  isUnusedAnnot (using  Context ):  Boolean  = 
748739        sym.annotations.exists(a =>  a.symbol ==  ctx.definitions.UnusedAnnot )
@@ -840,11 +831,40 @@ object CheckUnused:
840831        case  _:tpd.Block  =>  Local 
841832        case  _ =>  Other 
842833
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+ 
843857    case  class  UnusedSymbol (pos : SrcPos , name : Name , warnType : WarnTypes )
844858    /**  A container for the results of the used elements analysis */  
845859    case  class  UnusedResult (warnings : Set [UnusedSymbol ])
846860    object  UnusedResult : 
847861      val  Empty  =  UnusedResult (Set .empty)
848862  end  UnusedData 
849863
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+ 
850870end  CheckUnused 
0 commit comments