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