@@ -26,6 +26,7 @@ import dotty.tools.dotc.util.chaining.*
26
26
27
27
import java .util .IdentityHashMap
28
28
29
+ import scala .annotation .*
29
30
import scala .collection .mutable , mutable .{ArrayBuilder , ListBuffer , Stack }
30
31
31
32
import CheckUnused .*
@@ -288,6 +289,8 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha
288
289
alt.symbol == sym
289
290
|| nm.isTypeName && alt.symbol.isAliasType && alt.info.dealias.typeSymbol == sym
290
291
sameSym && alt.symbol.isAccessibleFrom(qtpe)
292
+ def hasAltMemberNamed (nm : Name ) = qtpe.member(nm).hasAltWith(_.symbol.isAccessibleFrom(qtpe))
293
+
291
294
def loop (sels : List [ImportSelector ]): ImportSelector | Null = sels match
292
295
case sel :: sels =>
293
296
val matches =
@@ -304,9 +307,17 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha
304
307
else
305
308
! sym.is(Given ) // Normal wildcard, check that the symbol is not a given (but can be implicit)
306
309
}
310
+ else if sel.isUnimport then
311
+ val masksMatchingMember =
312
+ name != nme.NO_NAME
313
+ && sels.exists(x => x.isWildcard && ! x.isGiven)
314
+ && ! name.exists(_.toTermName != sel.name) // import a.b as _, b must match name
315
+ && (hasAltMemberNamed(sel.name) || hasAltMemberNamed(sel.name.toTypeName))
316
+ if masksMatchingMember then
317
+ refInfos.sels.put(sel, ()) // imprecise due to precedence but errs on the side of false negative
318
+ false
307
319
else
308
- // if there is an explicit name, it must match
309
- ! name.exists(_.toTermName != sel.rename)
320
+ ! name.exists(_.toTermName != sel.rename) // if there is an explicit name, it must match
310
321
&& (prefix.eq(NoPrefix ) || qtpe =:= prefix)
311
322
&& (hasAltMember(sel.name) || hasAltMember(sel.name.toTypeName))
312
323
if matches then sel else loop(sels)
@@ -697,11 +708,11 @@ object CheckUnused:
697
708
warnAt(pos)(UnusedSymbol .unsetPrivates)
698
709
699
710
def checkImports () =
700
- // TODO check for unused masking import
701
711
import scala .jdk .CollectionConverters .given
702
712
import Rewrites .ActionPatch
703
713
type ImpSel = (Import , ImportSelector )
704
- // true if used or might be used, to imply don't warn about it
714
+ def isUsed (sel : ImportSelector ): Boolean = infos.sels.containsKey(sel)
715
+ @ unused // avoid merge conflict
705
716
def isUsable (imp : Import , sel : ImportSelector ): Boolean =
706
717
sel.isImportExclusion || infos.sels.containsKey(sel)
707
718
def warnImport (warnable : ImpSel , actions : List [CodeAction ] = Nil ): Unit =
@@ -712,7 +723,7 @@ object CheckUnused:
712
723
warnAt(sel.srcPos)(msg, origin)
713
724
714
725
if ! actionable then
715
- for imp <- infos.imps.keySet.nn.asScala; sel <- imp.selectors if ! isUsable(imp, sel) do
726
+ for imp <- infos.imps.keySet.nn.asScala; sel <- imp.selectors if ! isUsed( sel) do
716
727
warnImport(imp -> sel)
717
728
else
718
729
// If the rest of the line is blank, include it in the final edit position. (Delete trailing whitespace.)
@@ -767,7 +778,7 @@ object CheckUnused:
767
778
while index < sortedImps.length do
768
779
val nextImport = sortedImps.indexSatisfying(from = index + 1 )(_.isPrimaryClause) // next import statement
769
780
if sortedImps.indexSatisfying(from = index, until = nextImport): imp =>
770
- imp.selectors.exists(! isUsable(imp, _)) // check if any selector in statement was unused
781
+ imp.selectors.exists(! isUsed( _)) // check if any selector in statement was unused
771
782
< nextImport then
772
783
// if no usable selectors in the import statement, delete it entirely.
773
784
// if there is exactly one usable selector, then replace with just that selector (i.e., format it).
@@ -776,7 +787,7 @@ object CheckUnused:
776
787
// Reminder that first clause span includes the keyword, so delete point-to-start instead.
777
788
val existing = sortedImps.slice(index, nextImport)
778
789
val (keeping, deleting) = existing.iterator.flatMap(imp => imp.selectors.map(imp -> _)).toList
779
- .partition(isUsable(_, _ ))
790
+ .partition((imp, sel) => isUsed(sel ))
780
791
if keeping.isEmpty then
781
792
val editPos = existing.head.srcPos.sourcePos.withSpan:
782
793
Span (start = existing.head.srcPos.span.start, end = existing.last.srcPos.span.end)
@@ -1001,6 +1012,11 @@ object CheckUnused:
1001
1012
def boundTpe : Type = sel.bound match
1002
1013
case untpd.TypedSplice (tree) => tree.tpe
1003
1014
case _ => NoType
1015
+ /** Is a "masking" import of the form import `qual.member as _`.
1016
+ * Both conditions must be checked.
1017
+ */
1018
+ @ unused // matchingSelector checks isWildcard first
1019
+ def isImportExclusion : Boolean = sel.isUnimport && ! sel.isWildcard
1004
1020
1005
1021
extension (imp : Import )(using Context )
1006
1022
/** Is it the first import clause in a statement? `a.x` in `import a.x, b.{y, z}` */
0 commit comments