@@ -25,6 +25,7 @@ import dotty.tools.dotc.util.chaining.*
2525
2626import  java .util .IdentityHashMap 
2727
28+ import  scala .annotation .* 
2829import  scala .collection .mutable , mutable .{ArrayBuilder , ListBuffer , Stack }
2930
3031import  CheckUnused .* 
@@ -309,6 +310,8 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha
309310             alt.symbol ==  sym
310311          ||  nm.isTypeName &&  alt.symbol.isAliasType &&  alt.info.dealias.typeSymbol ==  sym
311312        sameSym &&  alt.symbol.isAccessibleFrom(qtpe)
313+       def  hasAltMemberNamed (nm : Name ) =  qtpe.member(nm).hasAltWith(_.symbol.isAccessibleFrom(qtpe))
314+ 
312315      def  loop (sels : List [ImportSelector ]):  ImportSelector  |  Null  =  sels match 
313316        case  sel ::  sels => 
314317          val  matches  = 
@@ -325,9 +328,17 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha
325328                else 
326329                  ! sym.is(Given ) //  Normal wildcard, check that the symbol is not a given (but can be implicit)
327330              }
331+             else  if  sel.isUnimport then 
332+               val  masksMatchingMember  = 
333+                    name !=  nme.NO_NAME 
334+                 &&  sels.exists(x =>  x.isWildcard &&  ! x.isGiven)
335+                 &&  ! name.exists(_.toTermName !=  sel.name) //  import a.b as _, b must match name
336+                 &&  (hasAltMemberNamed(sel.name) ||  hasAltMemberNamed(sel.name.toTypeName))
337+               if  masksMatchingMember then 
338+                 refInfos.sels.put(sel, ()) //  imprecise due to precedence but errs on the side of false negative
339+               false 
328340            else 
329-               //  if there is an explicit name, it must match
330-                  ! name.exists(_.toTermName !=  sel.rename)
341+                  ! name.exists(_.toTermName !=  sel.rename) //  if there is an explicit name, it must match
331342              &&  (prefix.eq(NoPrefix ) ||  qtpe =:=  prefix)
332343              &&  (hasAltMember(sel.name) ||  hasAltMember(sel.name.toTypeName))
333344          if  matches then  sel else  loop(sels)
@@ -658,11 +669,11 @@ object CheckUnused:
658669              warnAt(pos)(UnusedSymbol .unsetPrivates)
659670
660671    def  checkImports () = 
661-       //  TODO check for unused masking import
662672      import  scala .jdk .CollectionConverters .given 
663673      import  Rewrites .ActionPatch 
664674      type  ImpSel  =  (Import , ImportSelector )
665-       //  true if used or might be used, to imply don't warn about it
675+       def  isUsed (sel : ImportSelector ):  Boolean  =  infos.sels.containsKey(sel)
676+       @ unused //  avoid merge conflict
666677      def  isUsable (imp : Import , sel : ImportSelector ):  Boolean  = 
667678        sel.isImportExclusion ||  infos.sels.containsKey(sel)
668679      def  warnImport (warnable : ImpSel , actions : List [CodeAction ] =  Nil ):  Unit  = 
@@ -673,7 +684,7 @@ object CheckUnused:
673684        warnAt(sel.srcPos)(msg, origin)
674685
675686      if  ! actionable then 
676-         for  imp <-  infos.imps.keySet.nn.asScala; sel <-  imp.selectors if  ! isUsable(imp,  sel) do 
687+         for  imp <-  infos.imps.keySet.nn.asScala; sel <-  imp.selectors if  ! isUsed( sel) do 
677688          warnImport(imp ->  sel)
678689      else 
679690        //  If the rest of the line is blank, include it in the final edit position. (Delete trailing whitespace.)
@@ -728,7 +739,7 @@ object CheckUnused:
728739        while  index <  sortedImps.length do 
729740          val  nextImport  =  sortedImps.indexSatisfying(from =  index +  1 )(_.isPrimaryClause) //  next import statement
730741          if  sortedImps.indexSatisfying(from =  index, until =  nextImport):  imp => 
731-               imp.selectors.exists(! isUsable(imp,  _)) //  check if any selector in statement was unused
742+               imp.selectors.exists(! isUsed( _)) //  check if any selector in statement was unused
732743          <  nextImport then 
733744            //  if no usable selectors in the import statement, delete it entirely.
734745            //  if there is exactly one usable selector, then replace with just that selector (i.e., format it).
@@ -737,7 +748,7 @@ object CheckUnused:
737748            //  Reminder that first clause span includes the keyword, so delete point-to-start instead.
738749            val  existing  =  sortedImps.slice(index, nextImport)
739750            val  (keeping, deleting) =  existing.iterator.flatMap(imp =>  imp.selectors.map(imp ->  _)).toList
740-                                       .partition(isUsable(_, _ ))
751+                                       .partition((imp, sel)  =>  isUsed(sel ))
741752            if  keeping.isEmpty then 
742753              val  editPos  =  existing.head.srcPos.sourcePos.withSpan: 
743754                Span (start =  existing.head.srcPos.span.start, end =  existing.last.srcPos.span.end)
@@ -962,12 +973,11 @@ object CheckUnused:
962973    def  boundTpe :  Type  =  sel.bound match 
963974      case  untpd.TypedSplice (tree) =>  tree.tpe
964975      case  _ =>  NoType 
965-     /**  This is used to ignore exclusion imports  of the form import `qual.member as _` 
966-      *  because `sel.isUnimport` is too broad for old style `import concurrent._` . 
976+     /**  Is a "masking" import  of the form import `qual.member as _`.  
977+      *  Both conditions must be checked . 
967978     */  
968-     def  isImportExclusion :  Boolean  =  sel.renamed match 
969-       case  untpd.Ident (nme.WILDCARD ) =>  true 
970-       case  _ =>  false 
979+     @ unused //  matchingSelector checks isWildcard first
980+     def  isImportExclusion :  Boolean  =  sel.isUnimport &&  ! sel.isWildcard
971981
972982  extension  (imp : Import )(using  Context )
973983    /**  Is it the first import clause in a statement? `a.x` in `import a.x, b.{y, z}` */  
0 commit comments