@@ -6,6 +6,7 @@ import interfaces.CompilerCallback
66import Decorators ._
77import Periods ._
88import Names ._
9+ import Flags .*
910import Phases ._
1011import Types ._
1112import Symbols ._
@@ -20,14 +21,17 @@ import Nullables._
2021import Implicits .ContextualImplicits
2122import config .Settings ._
2223import config .Config
24+ import config .SourceVersion .allSourceVersionNames
2325import reporting ._
2426import io .{AbstractFile , NoAbstractFile , PlainFile , Path }
2527import scala .io .Codec
2628import collection .mutable
2729import printing ._
28- import config .{JavaPlatform , SJSPlatform , Platform , ScalaSettings }
30+ import config .{JavaPlatform , SJSPlatform , Platform , ScalaSettings , ScalaRelease }
2931import classfile .ReusableDataReader
3032import StdNames .nme
33+ import parsing .Parsers .EnclosingSpan
34+ import util .Spans .NoSpan
3135
3236import scala .annotation .internal .sharable
3337
@@ -40,7 +44,9 @@ import plugins._
4044import java .util .concurrent .atomic .AtomicInteger
4145import java .nio .file .InvalidPathException
4246
43- object Contexts {
47+ import scala .util .chaining .given
48+
49+ object Contexts :
4450
4551 private val (compilerCallbackLoc, store1) = Store .empty.newLocation[CompilerCallback ]()
4652 private val (sbtCallbackLoc, store2) = store1.newLocation[AnalysisCallback ]()
@@ -52,8 +58,9 @@ object Contexts {
5258 private val (notNullInfosLoc, store8) = store7.newLocation[List [NotNullInfo ]]()
5359 private val (importInfoLoc, store9) = store8.newLocation[ImportInfo | Null ]()
5460 private val (typeAssignerLoc, store10) = store9.newLocation[TypeAssigner ](TypeAssigner )
61+ private val (usagesLoc, store11) = store10.newLocation[Usages ]()
5562
56- private val initialStore = store10
63+ private val initialStore = store11
5764
5865 /** The current context */
5966 inline def ctx (using ctx : Context ): Context = ctx
@@ -239,6 +246,9 @@ object Contexts {
239246 /** The current type assigner or typer */
240247 def typeAssigner : TypeAssigner = store(typeAssignerLoc)
241248
249+ /** Tracker for usages of elements such as import selectors. */
250+ def usages : Usages = store(usagesLoc)
251+
242252 /** The new implicit references that are introduced by this scope */
243253 protected var implicitsCache : ContextualImplicits | Null = null
244254 def implicits : ContextualImplicits = {
@@ -247,9 +257,7 @@ object Contexts {
247257 val implicitRefs : List [ImplicitRef ] =
248258 if (isClassDefContext)
249259 try owner.thisType.implicitMembers
250- catch {
251- case ex : CyclicReference => Nil
252- }
260+ catch case ex : CyclicReference => Nil
253261 else if (isImportContext) importInfo.nn.importedImplicits
254262 else if (isNonEmptyScopeContext) scope.implicitDecls
255263 else Nil
@@ -475,8 +483,8 @@ object Contexts {
475483 else fresh.setOwner(exprOwner)
476484
477485 /** A new context that summarizes an import statement */
478- def importContext (imp : Import [? ], sym : Symbol ): FreshContext =
479- fresh.setImportInfo(ImportInfo (sym, imp.selectors, imp.expr))
486+ def importContext (imp : Import [? ], sym : Symbol , enteringSyms : Boolean = false ): FreshContext =
487+ fresh.setImportInfo(ImportInfo (sym, imp.selectors, imp.expr, imp.attachmentOrElse( EnclosingSpan , NoSpan )).tap(importInfo => if enteringSyms && ctx.settings. WunusedHas .imports then usages += importInfo ))
480488
481489 /** Is the debug option set? */
482490 def debug : Boolean = base.settings.Ydebug .value
@@ -812,6 +820,7 @@ object Contexts {
812820 store = initialStore
813821 .updated(settingsStateLoc, settingsGroup.defaultState)
814822 .updated(notNullInfosLoc, Nil )
823+ .updated(usagesLoc, Usages ())
815824 .updated(compilationUnitLoc, NoCompilationUnit )
816825 searchHistory = new SearchRoot
817826 gadt = EmptyGadtConstraint
@@ -939,7 +948,7 @@ object Contexts {
939948 private [dotc] var stopInlining : Boolean = false
940949
941950 /** A variable that records that some error was reported in a globally committable context.
942- * The error will not necessarlily be emitted, since it could still be that
951+ * The error will not necessarily be emitted, since it could still be that
943952 * the enclosing context will be aborted. The variable is used as a smoke test
944953 * to turn off assertions that might be wrong if the program is erroneous. To
945954 * just test for `ctx.reporter.errorsReported` is not always enough, since it
@@ -996,4 +1005,49 @@ object Contexts {
9961005 if (thread == null ) thread = Thread .currentThread()
9971006 else assert(thread == Thread .currentThread(), " illegal multithreaded access to ContextBase" )
9981007 }
999- }
1008+ end ContextState
1009+
1010+ /** Collect information about the run for purposes of additional diagnostics.
1011+ */
1012+ class Usages :
1013+ private val selectors = mutable.Map .empty[ImportInfo , Set [untpd.ImportSelector ]].withDefaultValue(Set .empty)
1014+ private val importInfos = mutable.Map .empty[CompilationUnit , List [(ImportInfo , Symbol )]].withDefaultValue(Nil )
1015+
1016+ // register an import
1017+ def += (info : ImportInfo )(using Context ): Unit =
1018+ def isLanguageImport = info.isLanguageImport && allSourceVersionNames.exists(info.forwardMapping.contains)
1019+ if ctx.settings.WunusedHas .imports && ! isLanguageImport && ! ctx.owner.is(Enum ) && ! ctx.compilationUnit.isJava then
1020+ importInfos(ctx.compilationUnit) ::= ((info, ctx.owner))
1021+
1022+ // mark a selector as used
1023+ def use (info : ImportInfo , selector : untpd.ImportSelector )(using Context ): Unit =
1024+ if ctx.settings.WunusedHas .imports && ! info.isRootImport then
1025+ selectors(info) += selector
1026+
1027+ // unused import, owner, which selector
1028+ def unused (using Context ): List [(ImportInfo , Symbol , List [untpd.ImportSelector ])] =
1029+ if ctx.settings.WunusedHas .imports && ! ctx.compilationUnit.isJava then
1030+ var unusages = List .empty[(ImportInfo , Symbol , List [untpd.ImportSelector ])]
1031+ def checkUsed (info : ImportInfo , owner : Symbol ): Unit =
1032+ val usedSelectors = selectors.remove(info).getOrElse(Set .empty)
1033+ var unusedSelectors = List .empty[untpd.ImportSelector ]
1034+ def cull (toCheck : List [untpd.ImportSelector ]): Unit =
1035+ toCheck match
1036+ case selector :: rest =>
1037+ cull(rest) // reverse
1038+ if ! selector.isMask && ! usedSelectors(selector) then
1039+ unusedSelectors ::= selector
1040+ case _ =>
1041+ cull(info.selectors)
1042+ if unusedSelectors.nonEmpty then unusages ::= (info, owner, unusedSelectors)
1043+ end checkUsed
1044+ importInfos.remove(ctx.compilationUnit).foreach(_.foreach(checkUsed))
1045+ unusages
1046+ else
1047+ Nil
1048+ end unused
1049+
1050+ def clear ()(using Context ): Unit =
1051+ importInfos.clear()
1052+ selectors.clear()
1053+ end Usages
0 commit comments