@@ -25,6 +25,7 @@ import dotty.tools.dotc.util.chaining.*
25
25
26
26
import java .util .IdentityHashMap
27
27
28
+ import scala .annotation .*
28
29
import scala .collection .mutable , mutable .{ArrayBuilder , ListBuffer , Stack }
29
30
30
31
import CheckUnused .*
@@ -309,6 +310,8 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha
309
310
alt.symbol == sym
310
311
|| nm.isTypeName && alt.symbol.isAliasType && alt.info.dealias.typeSymbol == sym
311
312
sameSym && alt.symbol.isAccessibleFrom(qtpe)
313
+ def hasAltMemberNamed (nm : Name ) = qtpe.member(nm).hasAltWith(_.symbol.isAccessibleFrom(qtpe))
314
+
312
315
def loop (sels : List [ImportSelector ]): ImportSelector | Null = sels match
313
316
case sel :: sels =>
314
317
val matches =
@@ -325,9 +328,17 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha
325
328
else
326
329
! sym.is(Given ) // Normal wildcard, check that the symbol is not a given (but can be implicit)
327
330
}
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
328
340
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
331
342
&& (prefix.eq(NoPrefix ) || qtpe =:= prefix)
332
343
&& (hasAltMember(sel.name) || hasAltMember(sel.name.toTypeName))
333
344
if matches then sel else loop(sels)
@@ -658,11 +669,11 @@ object CheckUnused:
658
669
warnAt(pos)(UnusedSymbol .unsetPrivates)
659
670
660
671
def checkImports () =
661
- // TODO check for unused masking import
662
672
import scala .jdk .CollectionConverters .given
663
673
import Rewrites .ActionPatch
664
674
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
666
677
def isUsable (imp : Import , sel : ImportSelector ): Boolean =
667
678
sel.isImportExclusion || infos.sels.containsKey(sel)
668
679
def warnImport (warnable : ImpSel , actions : List [CodeAction ] = Nil ): Unit =
@@ -673,7 +684,7 @@ object CheckUnused:
673
684
warnAt(sel.srcPos)(msg, origin)
674
685
675
686
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
677
688
warnImport(imp -> sel)
678
689
else
679
690
// If the rest of the line is blank, include it in the final edit position. (Delete trailing whitespace.)
@@ -728,7 +739,7 @@ object CheckUnused:
728
739
while index < sortedImps.length do
729
740
val nextImport = sortedImps.indexSatisfying(from = index + 1 )(_.isPrimaryClause) // next import statement
730
741
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
732
743
< nextImport then
733
744
// if no usable selectors in the import statement, delete it entirely.
734
745
// if there is exactly one usable selector, then replace with just that selector (i.e., format it).
@@ -737,7 +748,7 @@ object CheckUnused:
737
748
// Reminder that first clause span includes the keyword, so delete point-to-start instead.
738
749
val existing = sortedImps.slice(index, nextImport)
739
750
val (keeping, deleting) = existing.iterator.flatMap(imp => imp.selectors.map(imp -> _)).toList
740
- .partition(isUsable(_, _ ))
751
+ .partition((imp, sel) => isUsed(sel ))
741
752
if keeping.isEmpty then
742
753
val editPos = existing.head.srcPos.sourcePos.withSpan:
743
754
Span (start = existing.head.srcPos.span.start, end = existing.last.srcPos.span.end)
@@ -962,12 +973,11 @@ object CheckUnused:
962
973
def boundTpe : Type = sel.bound match
963
974
case untpd.TypedSplice (tree) => tree.tpe
964
975
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 .
967
978
*/
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
971
981
972
982
extension (imp : Import )(using Context )
973
983
/** Is it the first import clause in a statement? `a.x` in `import a.x, b.{y, z}` */
0 commit comments