@@ -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 .*
@@ -277,6 +278,8 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha
277278 alt.symbol == sym
278279 || nm.isTypeName && alt.symbol.isAliasType && alt.info.dealias.typeSymbol == sym
279280 sameSym && alt.symbol.isAccessibleFrom(qtpe)
281+ def hasAltMemberNamed (nm : Name ) = qtpe.member(nm).hasAltWith(_.symbol.isAccessibleFrom(qtpe))
282+
280283 def loop (sels : List [ImportSelector ]): ImportSelector | Null = sels match
281284 case sel :: sels =>
282285 val matches =
@@ -293,9 +296,17 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha
293296 else
294297 ! sym.is(Given ) // Normal wildcard, check that the symbol is not a given (but can be implicit)
295298 }
299+ else if sel.isUnimport then
300+ val masksMatchingMember =
301+ name != nme.NO_NAME
302+ && sels.exists(x => x.isWildcard && ! x.isGiven)
303+ && ! name.exists(_.toTermName != sel.name) // import a.b as _, b must match name
304+ && (hasAltMemberNamed(sel.name) || hasAltMemberNamed(sel.name.toTypeName))
305+ if masksMatchingMember then
306+ refInfos.sels.put(sel, ()) // imprecise due to precedence but errs on the side of false negative
307+ false
296308 else
297- // if there is an explicit name, it must match
298- ! name.exists(_.toTermName != sel.rename)
309+ ! name.exists(_.toTermName != sel.rename) // if there is an explicit name, it must match
299310 && (prefix.eq(NoPrefix ) || qtpe =:= prefix)
300311 && (hasAltMember(sel.name) || hasAltMember(sel.name.toTypeName))
301312 if matches then sel else loop(sels)
@@ -626,11 +637,11 @@ object CheckUnused:
626637 warnAt(pos)(UnusedSymbol .unsetPrivates)
627638
628639 def checkImports () =
629- // TODO check for unused masking import
630640 import scala .jdk .CollectionConverters .given
631641 import Rewrites .ActionPatch
632642 type ImpSel = (Import , ImportSelector )
633- // true if used or might be used, to imply don't warn about it
643+ def isUsed (sel : ImportSelector ): Boolean = infos.sels.containsKey(sel)
644+ @ unused // avoid merge conflict
634645 def isUsable (imp : Import , sel : ImportSelector ): Boolean =
635646 sel.isImportExclusion || infos.sels.containsKey(sel)
636647 def warnImport (warnable : ImpSel , actions : List [CodeAction ] = Nil ): Unit =
@@ -641,7 +652,7 @@ object CheckUnused:
641652 warnAt(sel.srcPos)(msg, origin)
642653
643654 if ! actionable then
644- for imp <- infos.imps.keySet.nn.asScala; sel <- imp.selectors if ! isUsable(imp, sel) do
655+ for imp <- infos.imps.keySet.nn.asScala; sel <- imp.selectors if ! isUsed( sel) do
645656 warnImport(imp -> sel)
646657 else
647658 // If the rest of the line is blank, include it in the final edit position. (Delete trailing whitespace.)
@@ -696,7 +707,7 @@ object CheckUnused:
696707 while index < sortedImps.length do
697708 val nextImport = sortedImps.indexSatisfying(from = index + 1 )(_.isPrimaryClause) // next import statement
698709 if sortedImps.indexSatisfying(from = index, until = nextImport): imp =>
699- imp.selectors.exists(! isUsable(imp, _)) // check if any selector in statement was unused
710+ imp.selectors.exists(! isUsed( _)) // check if any selector in statement was unused
700711 < nextImport then
701712 // if no usable selectors in the import statement, delete it entirely.
702713 // if there is exactly one usable selector, then replace with just that selector (i.e., format it).
@@ -705,7 +716,7 @@ object CheckUnused:
705716 // Reminder that first clause span includes the keyword, so delete point-to-start instead.
706717 val existing = sortedImps.slice(index, nextImport)
707718 val (keeping, deleting) = existing.iterator.flatMap(imp => imp.selectors.map(imp -> _)).toList
708- .partition(isUsable(_, _ ))
719+ .partition((imp, sel) => isUsed(sel ))
709720 if keeping.isEmpty then
710721 val editPos = existing.head.srcPos.sourcePos.withSpan:
711722 Span (start = existing.head.srcPos.span.start, end = existing.last.srcPos.span.end)
@@ -930,12 +941,11 @@ object CheckUnused:
930941 def boundTpe : Type = sel.bound match
931942 case untpd.TypedSplice (tree) => tree.tpe
932943 case _ => NoType
933- /** This is used to ignore exclusion imports of the form import `qual.member as _`
934- * because `sel.isUnimport` is too broad for old style `import concurrent._` .
944+ /** Is a "masking" import of the form import `qual.member as _`.
945+ * Both conditions must be checked .
935946 */
936- def isImportExclusion : Boolean = sel.renamed match
937- case untpd.Ident (nme.WILDCARD ) => true
938- case _ => false
947+ @ unused // matchingSelector checks isWildcard first
948+ def isImportExclusion : Boolean = sel.isUnimport && ! sel.isWildcard
939949
940950 extension (imp : Import )(using Context )
941951 /** Is it the first import clause in a statement? `a.x` in `import a.x, b.{y, z}` */
0 commit comments