@@ -6,6 +6,7 @@ import interfaces.CompilerCallback
6
6
import Decorators ._
7
7
import Periods ._
8
8
import Names ._
9
+ import Flags .*
9
10
import Phases ._
10
11
import Types ._
11
12
import Symbols ._
@@ -19,12 +20,14 @@ import Nullables._
19
20
import Implicits .ContextualImplicits
20
21
import config .Settings ._
21
22
import config .Config
23
+ import config .SourceVersion .allSourceVersionNames
22
24
import reporting ._
23
25
import io .{AbstractFile , NoAbstractFile , PlainFile , Path }
24
26
import scala .io .Codec
25
27
import collection .mutable
28
+ import parsing .Parsers
26
29
import printing ._
27
- import config .{JavaPlatform , SJSPlatform , Platform , ScalaSettings }
30
+ import config .{JavaPlatform , SJSPlatform , Platform , ScalaSettings , ScalaRelease }
28
31
import classfile .ReusableDataReader
29
32
import StdNames .nme
30
33
@@ -39,7 +42,9 @@ import plugins._
39
42
import java .util .concurrent .atomic .AtomicInteger
40
43
import java .nio .file .InvalidPathException
41
44
42
- object Contexts {
45
+ import scala .util .chaining .given
46
+
47
+ object Contexts :
43
48
44
49
private val (compilerCallbackLoc, store1) = Store .empty.newLocation[CompilerCallback ]()
45
50
private val (sbtCallbackLoc, store2) = store1.newLocation[AnalysisCallback ]()
@@ -51,8 +56,9 @@ object Contexts {
51
56
private val (notNullInfosLoc, store8) = store7.newLocation[List [NotNullInfo ]]()
52
57
private val (importInfoLoc, store9) = store8.newLocation[ImportInfo | Null ]()
53
58
private val (typeAssignerLoc, store10) = store9.newLocation[TypeAssigner ](TypeAssigner )
59
+ private val (usagesLoc, store11) = store10.newLocation[Usages ]()
54
60
55
- private val initialStore = store10
61
+ private val initialStore = store11
56
62
57
63
/** The current context */
58
64
inline def ctx (using ctx : Context ): Context = ctx
@@ -238,6 +244,9 @@ object Contexts {
238
244
/** The current type assigner or typer */
239
245
def typeAssigner : TypeAssigner = store(typeAssignerLoc)
240
246
247
+ /** Tracker for usages of elements such as import selectors. */
248
+ def usages : Usages = store(usagesLoc)
249
+
241
250
/** The new implicit references that are introduced by this scope */
242
251
protected var implicitsCache : ContextualImplicits | Null = null
243
252
def implicits : ContextualImplicits = {
@@ -246,9 +255,7 @@ object Contexts {
246
255
val implicitRefs : List [ImplicitRef ] =
247
256
if (isClassDefContext)
248
257
try owner.thisType.implicitMembers
249
- catch {
250
- case ex : CyclicReference => Nil
251
- }
258
+ catch case ex : CyclicReference => Nil
252
259
else if (isImportContext) importInfo.nn.importedImplicits
253
260
else if (isNonEmptyScopeContext) scope.implicitDecls
254
261
else Nil
@@ -474,8 +481,8 @@ object Contexts {
474
481
else fresh.setOwner(exprOwner)
475
482
476
483
/** A new context that summarizes an import statement */
477
- def importContext (imp : Import [? ], sym : Symbol ): FreshContext =
478
- fresh.setImportInfo(ImportInfo (sym, imp.selectors, imp.expr))
484
+ def importContext (imp : Import [? ], sym : Symbol , enteringSyms : Boolean = false ): FreshContext =
485
+ fresh.setImportInfo(ImportInfo (sym, imp.selectors, imp.expr).tap(ii => if enteringSyms && ctx.settings. WunusedHas .imports then usages += ii ))
479
486
480
487
/** Is the debug option set? */
481
488
def debug : Boolean = base.settings.Ydebug .value
@@ -811,6 +818,7 @@ object Contexts {
811
818
store = initialStore
812
819
.updated(settingsStateLoc, settingsGroup.defaultState)
813
820
.updated(notNullInfosLoc, Nil )
821
+ .updated(usagesLoc, Usages ())
814
822
.updated(compilationUnitLoc, NoCompilationUnit )
815
823
searchHistory = new SearchRoot
816
824
gadt = EmptyGadtConstraint
@@ -938,7 +946,7 @@ object Contexts {
938
946
private [dotc] var stopInlining : Boolean = false
939
947
940
948
/** A variable that records that some error was reported in a globally committable context.
941
- * The error will not necessarlily be emitted, since it could still be that
949
+ * The error will not necessarily be emitted, since it could still be that
942
950
* the enclosing context will be aborted. The variable is used as a smoke test
943
951
* to turn off assertions that might be wrong if the program is erroneous. To
944
952
* just test for `ctx.reporter.errorsReported` is not always enough, since it
@@ -995,4 +1003,82 @@ object Contexts {
995
1003
if (thread == null ) thread = Thread .currentThread()
996
1004
else assert(thread == Thread .currentThread(), " illegal multithreaded access to ContextBase" )
997
1005
}
998
- }
1006
+ end ContextState
1007
+
1008
+ /** Collect information about the run for purposes of additional diagnostics.
1009
+ */
1010
+ class Usages :
1011
+ import rewrites .Rewrites .patch
1012
+ private val selectors = mutable.Map .empty[ImportInfo , Set [untpd.ImportSelector ]].withDefaultValue(Set .empty)
1013
+ private val importInfos = mutable.Map .empty[CompilationUnit , List [(ImportInfo , Symbol )]].withDefaultValue(Nil )
1014
+
1015
+ // register an import
1016
+ def += (info : ImportInfo )(using Context ): Unit =
1017
+ def isLanguageImport = info.isLanguageImport && allSourceVersionNames.exists(info.forwardMapping.contains)
1018
+ if ctx.settings.WunusedHas .imports && ! isLanguageImport && ! ctx.owner.is(Enum ) && ! ctx.compilationUnit.isJava then
1019
+ importInfos(ctx.compilationUnit) ::= ((info, ctx.owner))
1020
+
1021
+ // mark a selector as used
1022
+ def use (info : ImportInfo , selector : untpd.ImportSelector )(using Context ): Unit =
1023
+ if ctx.settings.WunusedHas .imports && ! info.isRootImport then
1024
+ selectors(info) += selector
1025
+
1026
+ // unused import, owner, which selector
1027
+ def unused (using Context ): List [(ImportInfo , Symbol , untpd.ImportSelector )] =
1028
+ var unusages = List .empty[(ImportInfo , Symbol , untpd.ImportSelector )]
1029
+ if ctx.settings.WunusedHas .imports && ! ctx.compilationUnit.isJava then
1030
+ // if ctx.settings.Ydebug.value then
1031
+ // println(importInfos.get(ctx.compilationUnit).map(iss => iss.map((ii, s) => s"${ii.show} ($ii)")).getOrElse(Nil).mkString("Registered ImportInfos\n", "\n", ""))
1032
+ // println(selectors.toList.flatMap((k,v) => v.toList.map(sel => s"${k.show} -> $sel")).mkString("Used selectors\n", "\n", ""))
1033
+ def checkUsed (info : ImportInfo , owner : Symbol ): Unit =
1034
+ val used = selectors(info)
1035
+ var needsPatch = false
1036
+ def cull (toCheck : List [untpd.ImportSelector ]): Unit =
1037
+ toCheck match
1038
+ case selector :: rest =>
1039
+ cull(rest) // reverse
1040
+ if ! selector.isMask && ! used(selector) then
1041
+ unusages ::= ((info, owner, selector))
1042
+ needsPatch = true
1043
+ case _ =>
1044
+ cull(info.selectors)
1045
+ if needsPatch && ctx.settings.YrewriteImports .value then
1046
+ val src = ctx.compilationUnit.source
1047
+ val infoPos = info.qualifier.sourcePos
1048
+ val lineSource = SourceFile .virtual(name = " import-line.scala" , content = infoPos.lineContent)
1049
+ val PackageDef (_, pieces) = Parsers .Parser (lineSource).parse(): @ unchecked
1050
+ // patch if there's just one import on the line, i.e., not import a.b, c.d
1051
+ if pieces.length == 1 then
1052
+ val retained = info.selectors.filter(sel => sel.isMask || used(sel))
1053
+ val selectorSpan = info.selectors.map(_.span).reduce(_ union _)
1054
+ val lineSpan = src.lineSpan(infoPos.start)
1055
+ if retained.isEmpty then
1056
+ patch(src, lineSpan, " " ) // line deletion
1057
+ else if retained.size == 1 && info.selectors.size > 1 then
1058
+ var starting = info.selectors.head.span.start
1059
+ while starting > lineSpan.start && src.content()(starting) != '{' do starting -= 1
1060
+ var ending = info.selectors.last.span.end
1061
+ while ending <= lineSpan.end && src.content()(ending) != '}' do ending += 1
1062
+ if ending < lineSpan.end then ending += 1 // past the close brace
1063
+ val widened = selectorSpan.withStart(starting).withEnd(ending)
1064
+ patch(src, widened, toText(retained)) // try to remove braces
1065
+ else
1066
+ patch(src, selectorSpan, toText(retained))
1067
+ end checkUsed
1068
+ importInfos.remove(ctx.compilationUnit).foreach(_.foreach(checkUsed))
1069
+ unusages
1070
+ end unused
1071
+
1072
+ // just the selectors, no need to add braces
1073
+ private def toText (retained : List [untpd.ImportSelector ])(using Context ): String =
1074
+ def selected (sel : untpd.ImportSelector ) =
1075
+ if sel.isGiven then " given"
1076
+ else if sel.isWildcard then " *"
1077
+ else if sel.name == sel.rename then sel.name.show
1078
+ else s " ${sel.name.show} as ${sel.rename.show}"
1079
+ retained.map(selected).mkString(" , " )
1080
+
1081
+ def clear ()(using Context ): Unit =
1082
+ importInfos.clear()
1083
+ selectors.clear()
1084
+ end Usages
0 commit comments