@@ -6,6 +6,7 @@ import interfaces.CompilerCallback
66import  Decorators ._ 
77import  Periods ._ 
88import  Names ._ 
9+ import  Flags .* 
910import  Phases ._ 
1011import  Types ._ 
1112import  Symbols ._ 
@@ -23,7 +24,9 @@ import reporting._
2324import  io .{AbstractFile , NoAbstractFile , PlainFile , Path }
2425import  scala .io .Codec 
2526import  collection .mutable 
27+ import  parsing .Parsers 
2628import  printing ._ 
29+ import  config .Printers .{implicits , implicitsDetailed }
2730import  config .{JavaPlatform , SJSPlatform , Platform , ScalaSettings , ScalaRelease }
2831import  classfile .ReusableDataReader 
2932import  StdNames .nme 
@@ -42,7 +45,9 @@ import dotty.tools.tasty.TastyFormat
4245import  dotty .tools .dotc .config .{ NoScalaVersion , SpecificScalaVersion , AnyScalaVersion , ScalaBuild  }
4346import  dotty .tools .dotc .core .tasty .TastyVersion 
4447
45- object  Contexts  {
48+ import  scala .util .chaining .given 
49+ 
50+ object  Contexts : 
4651
4752  private  val  (compilerCallbackLoc, store1) =  Store .empty.newLocation[CompilerCallback ]()
4853  private  val  (sbtCallbackLoc,      store2) =  store1.newLocation[AnalysisCallback ]()
@@ -54,8 +59,9 @@ object Contexts {
5459  private  val  (notNullInfosLoc,     store8) =  store7.newLocation[List [NotNullInfo ]]()
5560  private  val  (importInfoLoc,       store9) =  store8.newLocation[ImportInfo  |  Null ]()
5661  private  val  (typeAssignerLoc,    store10) =  store9.newLocation[TypeAssigner ](TypeAssigner )
62+   private  val  (usagesLoc,          store11) =  store10.newLocation[Usages ](Usages ())
5763
58-   private  val  initialStore  =  store10 
64+   private  val  initialStore  =  store11 
5965
6066  /**  The current context */  
6167  inline  def  ctx (using  ctx : Context ):  Context  =  ctx
@@ -241,6 +247,9 @@ object Contexts {
241247    /**  The current type assigner or typer */  
242248    def  typeAssigner :  TypeAssigner  =  store(typeAssignerLoc)
243249
250+     /**  Tracker for usages of elements such as import selectors. */  
251+     def  usages :  Usages  =  store(usagesLoc)
252+ 
244253    /**  The new implicit references that are introduced by this scope */  
245254    protected  var  implicitsCache :  ContextualImplicits  |  Null  =  null 
246255    def  implicits :  ContextualImplicits  =  {
@@ -249,9 +258,7 @@ object Contexts {
249258          val  implicitRefs :  List [ImplicitRef ] = 
250259            if  (isClassDefContext)
251260              try  owner.thisType.implicitMembers
252-               catch  {
253-                 case  ex : CyclicReference  =>  Nil 
254-               }
261+               catch  case  ex : CyclicReference  =>  Nil 
255262            else  if  (isImportContext) importInfo.nn.importedImplicits
256263            else  if  (isNonEmptyScopeContext) scope.implicitDecls
257264            else  Nil 
@@ -477,8 +484,8 @@ object Contexts {
477484      else  fresh.setOwner(exprOwner)
478485
479486    /**  A new context that summarizes an import statement */  
480-     def  importContext (imp : Import [? ], sym : Symbol ):  FreshContext  = 
481-       fresh.setImportInfo(ImportInfo (sym, imp.selectors, imp.expr))
487+     def  importContext (imp : Import [? ], sym : Symbol ,  enteringSyms :  Boolean   =   false ):  FreshContext  = 
488+       fresh.setImportInfo(ImportInfo (sym, imp.selectors, imp.expr).tap(ii  =>   if  enteringSyms  &&  ctx.settings. WunusedHas .imports  then  usages  +=  ii) )
482489
483490    def  scalaRelease :  ScalaRelease  = 
484491      val  releaseName  =  base.settings.scalaOutputVersion.value
@@ -956,7 +963,7 @@ object Contexts {
956963    private [dotc] var  stopInlining :  Boolean  =  false 
957964
958965    /**  A variable that records that some error was reported in a globally committable context. 
959-      *  The error will not necessarlily  be emitted, since it could still be that 
966+      *  The error will not necessarily  be emitted, since it could still be that 
960967     *  the enclosing context will be aborted. The variable is used as a smoke test 
961968     *  to turn off assertions that might be wrong if the program is erroneous. To 
962969     *  just test for `ctx.reporter.errorsReported` is not always enough, since it 
@@ -1013,4 +1020,85 @@ object Contexts {
10131020      if  (thread ==  null ) thread =  Thread .currentThread()
10141021      else  assert(thread ==  Thread .currentThread(), " illegal multithreaded access to ContextBase"  )
10151022  }
1016- }
1023+   end  ContextState 
1024+ 
1025+   /**  Collect information about the run for purposes of additional diagnostics. 
1026+    */  
1027+   class  Usages : 
1028+     import  rewrites .Rewrites .patch 
1029+     private  val  selectors    =  mutable.Map .empty[ImportInfo , Set [untpd.ImportSelector ]].withDefaultValue(Set .empty)
1030+     private  val  importInfos  =  mutable.Map .empty[CompilationUnit , List [(ImportInfo , Symbol )]].withDefaultValue(Nil )
1031+ 
1032+     //  register an import
1033+     def  += (info : ImportInfo )(using  Context ):  Unit  = 
1034+       if  ctx.settings.WunusedHas .imports &&  ! ctx.owner.is(Enum ) &&  ! ctx.compilationUnit.isJava then 
1035+         importInfos(ctx.compilationUnit) ::=  ((info, ctx.owner))
1036+ 
1037+     //  mark a selector as used
1038+     def  use (info : ImportInfo , selector : untpd.ImportSelector )(using  Context ):  Unit  = 
1039+       if  ctx.settings.WunusedHas .imports &&  ! info.isRootImport then 
1040+         selectors(info) +=  selector
1041+ 
1042+     //  unused import, owner, which selector
1043+     def  unused (using  Context ):  List [(ImportInfo , Symbol , untpd.ImportSelector )] = 
1044+       var  unusages  =  List .empty[(ImportInfo , Symbol , untpd.ImportSelector )]
1045+       if  ctx.settings.WunusedHas .imports &&  ! ctx.compilationUnit.isJava then 
1046+         // if ctx.settings.Ydebug.value then
1047+         //   println(importInfos.get(ctx.compilationUnit).map(iss => iss.map((ii, s) => s"${ii.show} ($ii)")).getOrElse(Nil).mkString("Registered ImportInfos\n", "\n", ""))
1048+         //   println(selectors.toList.flatMap((k,v) => v.toList.map(sel => s"${k.show} -> $sel")).mkString("Used selectors\n", "\n", ""))
1049+         def  checkUsed (info : ImportInfo , owner : Symbol ):  Unit  = 
1050+           val  used  =  selectors(info)
1051+           var  needsPatch  =  false 
1052+           def  cull (toCheck : List [untpd.ImportSelector ]):  Unit  = 
1053+             toCheck match 
1054+               case  selector ::  rest => 
1055+                 cull(rest) //  reverse
1056+                 if  ! selector.isMask &&  ! used(selector) then 
1057+                   unusages ::=  ((info, owner, selector))
1058+                   needsPatch =  true 
1059+               case  _ => 
1060+           cull(info.selectors)
1061+           val  decidingFactor  =  false  //  whether to apply patches
1062+           if  decidingFactor &&  needsPatch &&  ! ctx.settings.rewrite.value.isEmpty then 
1063+             val  retained  =  info.selectors.filter(sel =>  sel.isMask ||  used(sel))
1064+             val  infoPos  =  info.qualifier.sourcePos
1065+             val  lineText  =  infoPos.lineContent
1066+             val  src  =  ctx.compilationUnit.source
1067+             val  lineSpan  =  src.lineSpan(infoPos.start)
1068+             val  selectorSpan  =  info.selectors.map(_.span).reduce(_ union _)
1069+             val  lineSource  =  SourceFile .virtual(name =  " import-line.scala"  , content =  lineText)
1070+             val  parser  =  Parsers .Parser (lineSource)
1071+             val  lineTree  =  parser.parse()
1072+             val  PackageDef (_, pieces) =  lineTree : @ unchecked
1073+             //  patch if there's just one import on the line, i.e., not import a.b, c.d
1074+             if  pieces.length ==  1  then 
1075+               if  retained.isEmpty then 
1076+                 patch(src, lineSpan, " "  )  //  line deletion
1077+               else  if  retained.size ==  1  &&  info.selectors.size >  1  then 
1078+                 var  starting  =  info.selectors.head.span.start
1079+                 while  starting >  lineSpan.start &&  src.content()(starting) !=  '{'  do  starting -=  1 
1080+                 var  ending  =  info.selectors.last.span.end
1081+                 while  ending <=  lineSpan.end &&  src.content()(ending) !=  '}'  do  ending +=  1 
1082+                 if  ending <  lineSpan.end then  ending +=  1   //  past the close brace
1083+                 val  widened  =  selectorSpan.withStart(starting).withEnd(ending)
1084+                 patch(src, widened, toText(retained))  //  try to remove braces
1085+               else 
1086+                 patch(src, selectorSpan, toText(retained))
1087+         end  checkUsed 
1088+         importInfos.remove(ctx.compilationUnit).foreach(_.foreach(checkUsed))
1089+       unusages
1090+     end  unused 
1091+ 
1092+     //  just the selectors, no need to add braces
1093+     private  def  toText (retained : List [untpd.ImportSelector ])(using  Context ):  String  = 
1094+       def  selected (sel : untpd.ImportSelector ) = 
1095+         if  sel.isGiven then  " given" 
1096+         else  if  sel.isWildcard then  " *" 
1097+         else  if  sel.name ==  sel.rename then  sel.name.show
1098+         else  s " ${sel.name.show} as  ${sel.rename.show}" 
1099+       retained.map(selected).mkString(" , "  )
1100+ 
1101+     def  clear ()(using  Context ):  Unit  = 
1102+       importInfos.clear()
1103+       selectors.clear()
1104+   end  Usages 
0 commit comments