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