@@ -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