@@ -7,9 +7,10 @@ import dotty.tools.dotc.config.ScalaSettings
77import dotty .tools .dotc .core .Contexts .*
88import dotty .tools .dotc .core .Flags .*
99import dotty .tools .dotc .core .Names .{Name , SimpleName , DerivedName , TermName , termName }
10- import dotty .tools .dotc .core .NameOps .{isAnonymousFunctionName , isReplWrapperName , isContextFunction , setterName }
1110import dotty .tools .dotc .core .NameKinds .{
1211 BodyRetainerName , ContextBoundParamName , ContextFunctionParamName , DefaultGetterName , WildcardParamName }
12+ import dotty .tools .dotc .core .NameOps .{isAnonymousFunctionName , isReplWrapperName , isContextFunction , setterName }
13+ import dotty .tools .dotc .core .Scopes .newScope
1314import dotty .tools .dotc .core .StdNames .nme
1415import dotty .tools .dotc .core .Symbols .{ClassSymbol , NoSymbol , Symbol , defn , isDeprecated , requiredClass , requiredModule }
1516import dotty .tools .dotc .core .Types .*
@@ -19,6 +20,7 @@ import dotty.tools.dotc.rewrites.Rewrites
1920import dotty .tools .dotc .transform .MegaPhase .MiniPhase
2021import dotty .tools .dotc .typer .{ImportInfo , Typer }
2122import dotty .tools .dotc .typer .Deriving .OriginalTypeClass
23+ import dotty .tools .dotc .typer .Implicits .{ContextualImplicits , RenamedImplicitRef }
2224import dotty .tools .dotc .util .{Property , Spans , SrcPos }, Spans .Span
2325import dotty .tools .dotc .util .Chars .{isLineBreakChar , isWhitespace }
2426import dotty .tools .dotc .util .chaining .*
@@ -115,6 +117,14 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha
115117 args.foreach(_.withAttachment(ForArtifact , ()))
116118 case _ =>
117119 ctx
120+ override def transformApply (tree : Apply )(using Context ): tree.type =
121+ // check for multiversal equals
122+ tree match
123+ case Apply (Select (left, nme.Equals | nme.NotEquals ), right :: Nil ) =>
124+ val caneq = defn.CanEqualClass .typeRef.appliedTo(left.tpe.widen :: right.tpe.widen :: Nil )
125+ resolveScoped(caneq)
126+ case _ =>
127+ tree
118128
119129 override def prepareForAssign (tree : Assign )(using Context ): Context =
120130 tree.lhs.putAttachment(AssignmentTarget , ()) // don't take LHS reference as a read
@@ -195,6 +205,16 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha
195205 refInfos.register(tree)
196206 tree
197207
208+ override def prepareForStats (trees : List [Tree ])(using Context ): Context =
209+ // gather local implicits while ye may
210+ if ! ctx.owner.isClass then
211+ if trees.exists(t => t.isDef && t.symbol.is(Given ) && t.symbol.isLocalToBlock) then
212+ val scope = newScope.openForMutations
213+ for tree <- trees if tree.isDef && tree.symbol.is(Given ) do
214+ scope.enter(tree.symbol.name, tree.symbol)
215+ return ctx.fresh.setScope(scope)
216+ ctx
217+
198218 override def transformOther (tree : Tree )(using Context ): tree.type =
199219 tree match
200220 case imp : Import =>
@@ -277,6 +297,8 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha
277297 alt.symbol == sym
278298 || nm.isTypeName && alt.symbol.isAliasType && alt.info.dealias.typeSymbol == sym
279299 sameSym && alt.symbol.isAccessibleFrom(qtpe)
300+ def hasAltMemberNamed (nm : Name ) = qtpe.member(nm).hasAltWith(_.symbol.isAccessibleFrom(qtpe))
301+
280302 def loop (sels : List [ImportSelector ]): ImportSelector | Null = sels match
281303 case sel :: sels =>
282304 val matches =
@@ -293,9 +315,17 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha
293315 else
294316 ! sym.is(Given ) // Normal wildcard, check that the symbol is not a given (but can be implicit)
295317 }
318+ else if sel.isUnimport then
319+ val masksMatchingMember =
320+ name != nme.NO_NAME
321+ && sels.exists(x => x.isWildcard && ! x.isGiven)
322+ && ! name.exists(_.toTermName != sel.name) // import a.b as _, b must match name
323+ && (hasAltMemberNamed(sel.name) || hasAltMemberNamed(sel.name.toTypeName))
324+ if masksMatchingMember then
325+ refInfos.sels.put(sel, ()) // imprecise due to precedence but errs on the side of false negative
326+ false
296327 else
297- // if there is an explicit name, it must match
298- ! name.exists(_.toTermName != sel.rename)
328+ ! name.exists(_.toTermName != sel.rename) // if there is an explicit name, it must match
299329 && (prefix.eq(NoPrefix ) || qtpe =:= prefix)
300330 && (hasAltMember(sel.name) || hasAltMember(sel.name.toTypeName))
301331 if matches then sel else loop(sels)
@@ -363,6 +393,38 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha
363393 if candidate != NoContext && candidate.isImportContext && importer != null then
364394 refInfos.sels.put(importer, ())
365395 end resolveUsage
396+
397+ /** Simulate implicit search for contextual implicits in lexical scope and mark any definitions or imports as used.
398+ * Avoid cached ctx.implicits because it needs the precise import context that introduces the given.
399+ */
400+ def resolveScoped (tp : Type )(using Context ): Unit =
401+ var done = false
402+ val ctxs = ctx.outersIterator
403+ while ! done && ctxs.hasNext do
404+ val cur = ctxs.next()
405+ val implicitRefs : List [ImplicitRef ] =
406+ if (cur.isClassDefContext) cur.owner.thisType.implicitMembers
407+ else if (cur.isImportContext) cur.importInfo.nn.importedImplicits
408+ else if (cur.isNonEmptyScopeContext) cur.scope.implicitDecls
409+ else Nil
410+ implicitRefs.find(ref => ref.underlyingRef.widen <:< tp) match
411+ case Some (found : TermRef ) =>
412+ refInfos.addRef(found.denot.symbol)
413+ if cur.isImportContext then
414+ cur.importInfo.nn.selectors.find(sel => sel.isGiven || sel.rename == found.name) match
415+ case Some (sel) =>
416+ refInfos.sels.put(sel, ())
417+ case _ =>
418+ return
419+ case Some (found : RenamedImplicitRef ) if cur.isImportContext =>
420+ refInfos.addRef(found.underlyingRef.denot.symbol)
421+ cur.importInfo.nn.selectors.find(sel => sel.rename == found.implicitName) match
422+ case Some (sel) =>
423+ refInfos.sels.put(sel, ())
424+ case _ =>
425+ return
426+ case _ =>
427+ end resolveScoped
366428end CheckUnused
367429
368430object CheckUnused :
@@ -568,7 +630,6 @@ object CheckUnused:
568630 || m.is(Synthetic )
569631 || m.hasAnnotation(dd.UnusedAnnot ) // param of unused method
570632 || sym.owner.name.isContextFunction // a ubiquitous parameter
571- || sym.isCanEqual
572633 || sym.info.dealias.typeSymbol.match // more ubiquity
573634 case dd.DummyImplicitClass | dd.SubTypeClass | dd.SameTypeClass => true
574635 case tps =>
@@ -600,7 +661,6 @@ object CheckUnused:
600661 def checkLocal (sym : Symbol , pos : SrcPos ) =
601662 if ctx.settings.WunusedHas .locals
602663 && ! sym.is(InlineProxy )
603- && ! sym.isCanEqual
604664 then
605665 if sym.is(Mutable ) && infos.asss(sym) then
606666 warnAt(pos)(UnusedSymbol .localVars)
@@ -628,12 +688,10 @@ object CheckUnused:
628688 warnAt(pos)(UnusedSymbol .unsetPrivates)
629689
630690 def checkImports () =
631- // TODO check for unused masking import
632691 import scala .jdk .CollectionConverters .given
633692 import Rewrites .ActionPatch
634693 type ImpSel = (Import , ImportSelector )
635- def isUsable (imp : Import , sel : ImportSelector ): Boolean =
636- sel.isImportExclusion || infos.sels.containsKey(sel) || imp.isLoose(sel)
694+ def isUsed (sel : ImportSelector ): Boolean = infos.sels.containsKey(sel)
637695 def warnImport (warnable : ImpSel , actions : List [CodeAction ] = Nil ): Unit =
638696 val (imp, sel) = warnable
639697 val msg = UnusedSymbol .imports(actions)
@@ -642,7 +700,7 @@ object CheckUnused:
642700 warnAt(sel.srcPos)(msg, origin)
643701
644702 if ! actionable then
645- for imp <- infos.imps.keySet.nn.asScala; sel <- imp.selectors if ! isUsable(imp, sel) do
703+ for imp <- infos.imps.keySet.nn.asScala; sel <- imp.selectors if ! isUsed( sel) do
646704 warnImport(imp -> sel)
647705 else
648706 // If the rest of the line is blank, include it in the final edit position. (Delete trailing whitespace.)
@@ -697,7 +755,7 @@ object CheckUnused:
697755 while index < sortedImps.length do
698756 val nextImport = sortedImps.indexSatisfying(from = index + 1 )(_.isPrimaryClause) // next import statement
699757 if sortedImps.indexSatisfying(from = index, until = nextImport): imp =>
700- imp.selectors.exists(! isUsable(imp, _)) // check if any selector in statement was unused
758+ imp.selectors.exists(! isUsed( _)) // check if any selector in statement was unused
701759 < nextImport then
702760 // if no usable selectors in the import statement, delete it entirely.
703761 // if there is exactly one usable selector, then replace with just that selector (i.e., format it).
@@ -706,7 +764,7 @@ object CheckUnused:
706764 // Reminder that first clause span includes the keyword, so delete point-to-start instead.
707765 val existing = sortedImps.slice(index, nextImport)
708766 val (keeping, deleting) = existing.iterator.flatMap(imp => imp.selectors.map(imp -> _)).toList
709- .partition(isUsable(_, _ ))
767+ .partition((imp, sel) => isUsed(sel ))
710768 if keeping.isEmpty then
711769 val editPos = existing.head.srcPos.sourcePos.withSpan:
712770 Span (start = existing.head.srcPos.span.start, end = existing.last.srcPos.span.end)
@@ -908,8 +966,6 @@ object CheckUnused:
908966 def isSerializationSupport : Boolean =
909967 sym.is(Method ) && serializationNames(sym.name.toTermName) && sym.owner.isClass
910968 && sym.owner.derivesFrom(defn.JavaSerializableClass )
911- def isCanEqual : Boolean =
912- sym.isOneOf(GivenOrImplicit ) && sym.info.finalResultType.baseClasses.exists(_.derivesFrom(defn.CanEqualClass ))
913969 def isMarkerTrait : Boolean =
914970 sym.info.hiBound.resultType.allMembers.forall: d =>
915971 val m = d.symbol
@@ -933,12 +989,6 @@ object CheckUnused:
933989 def boundTpe : Type = sel.bound match
934990 case untpd.TypedSplice (tree) => tree.tpe
935991 case _ => NoType
936- /** This is used to ignore exclusion imports of the form import `qual.member as _`
937- * because `sel.isUnimport` is too broad for old style `import concurrent._`.
938- */
939- def isImportExclusion : Boolean = sel.renamed match
940- case untpd.Ident (nme.WILDCARD ) => true
941- case _ => false
942992
943993 extension (imp : Import )(using Context )
944994 /** Is it the first import clause in a statement? `a.x` in `import a.x, b.{y, z}` */
@@ -949,21 +999,6 @@ object CheckUnused:
949999 def isGeneratedByEnum : Boolean =
9501000 imp.symbol.exists && imp.symbol.owner.is(Enum , butNot = Case )
9511001
952- /** Under -Wunused:strict-no-implicit-warn, avoid false positives
953- * if this selector is a wildcard that might import implicits or
954- * specifically does import an implicit.
955- * Similarly, import of CanEqual must not warn, as it is always witness.
956- */
957- def isLoose (sel : ImportSelector ): Boolean =
958- if ctx.settings.WunusedHas .strictNoImplicitWarn then
959- if sel.isWildcard
960- || imp.expr.tpe.member(sel.name.toTermName).hasAltWith(_.symbol.isOneOf(GivenOrImplicit ))
961- || imp.expr.tpe.member(sel.name.toTypeName).hasAltWith(_.symbol.isOneOf(GivenOrImplicit ))
962- then return true
963- if sel.isWildcard && sel.isGiven
964- then imp.expr.tpe.allMembers.exists(_.symbol.isCanEqual)
965- else imp.expr.tpe.member(sel.name.toTermName).hasAltWith(_.symbol.isCanEqual)
966-
9671002 extension (pos : SrcPos )
9681003 def isZeroExtentSynthetic : Boolean = pos.span.isSynthetic && pos.span.isZeroExtent
9691004 def isSynthetic : Boolean = pos.span.isSynthetic && pos.span.exists
0 commit comments