@@ -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,11 +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 ._
30
+ import config .Printers .{implicits , implicitsDetailed }
27
31
import config .{JavaPlatform , SJSPlatform , Platform , ScalaSettings , ScalaRelease }
28
32
import classfile .ReusableDataReader
29
33
import StdNames .nme
@@ -42,7 +46,9 @@ import dotty.tools.tasty.TastyFormat
42
46
import dotty .tools .dotc .config .{ NoScalaVersion , SpecificScalaVersion , AnyScalaVersion , ScalaBuild }
43
47
import dotty .tools .dotc .core .tasty .TastyVersion
44
48
45
- object Contexts {
49
+ import scala .util .chaining .given
50
+
51
+ object Contexts :
46
52
47
53
private val (compilerCallbackLoc, store1) = Store .empty.newLocation[CompilerCallback ]()
48
54
private val (sbtCallbackLoc, store2) = store1.newLocation[AnalysisCallback ]()
@@ -54,8 +60,9 @@ object Contexts {
54
60
private val (notNullInfosLoc, store8) = store7.newLocation[List [NotNullInfo ]]()
55
61
private val (importInfoLoc, store9) = store8.newLocation[ImportInfo | Null ]()
56
62
private val (typeAssignerLoc, store10) = store9.newLocation[TypeAssigner ](TypeAssigner )
63
+ private val (usagesLoc, store11) = store10.newLocation[Usages ]()
57
64
58
- private val initialStore = store10
65
+ private val initialStore = store11
59
66
60
67
/** The current context */
61
68
inline def ctx (using ctx : Context ): Context = ctx
@@ -241,6 +248,9 @@ object Contexts {
241
248
/** The current type assigner or typer */
242
249
def typeAssigner : TypeAssigner = store(typeAssignerLoc)
243
250
251
+ /** Tracker for usages of elements such as import selectors. */
252
+ def usages : Usages = store(usagesLoc)
253
+
244
254
/** The new implicit references that are introduced by this scope */
245
255
protected var implicitsCache : ContextualImplicits | Null = null
246
256
def implicits : ContextualImplicits = {
@@ -249,9 +259,7 @@ object Contexts {
249
259
val implicitRefs : List [ImplicitRef ] =
250
260
if (isClassDefContext)
251
261
try owner.thisType.implicitMembers
252
- catch {
253
- case ex : CyclicReference => Nil
254
- }
262
+ catch case ex : CyclicReference => Nil
255
263
else if (isImportContext) importInfo.nn.importedImplicits
256
264
else if (isNonEmptyScopeContext) scope.implicitDecls
257
265
else Nil
@@ -477,8 +485,8 @@ object Contexts {
477
485
else fresh.setOwner(exprOwner)
478
486
479
487
/** 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))
488
+ def importContext (imp : Import [? ], sym : Symbol , enteringSyms : Boolean = false ): FreshContext =
489
+ fresh.setImportInfo(ImportInfo (sym, imp.selectors, imp.expr).tap(ii => if enteringSyms && ctx.settings. WunusedHas .imports then usages += ii) )
482
490
483
491
def scalaRelease : ScalaRelease =
484
492
val releaseName = base.settings.scalaOutputVersion.value
@@ -829,6 +837,7 @@ object Contexts {
829
837
store = initialStore
830
838
.updated(settingsStateLoc, settingsGroup.defaultState)
831
839
.updated(notNullInfosLoc, Nil )
840
+ .updated(usagesLoc, Usages ())
832
841
.updated(compilationUnitLoc, NoCompilationUnit )
833
842
searchHistory = new SearchRoot
834
843
gadt = EmptyGadtConstraint
@@ -956,7 +965,7 @@ object Contexts {
956
965
private [dotc] var stopInlining : Boolean = false
957
966
958
967
/** 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
968
+ * The error will not necessarily be emitted, since it could still be that
960
969
* the enclosing context will be aborted. The variable is used as a smoke test
961
970
* to turn off assertions that might be wrong if the program is erroneous. To
962
971
* just test for `ctx.reporter.errorsReported` is not always enough, since it
@@ -1013,4 +1022,82 @@ object Contexts {
1013
1022
if (thread == null ) thread = Thread .currentThread()
1014
1023
else assert(thread == Thread .currentThread(), " illegal multithreaded access to ContextBase" )
1015
1024
}
1016
- }
1025
+ end ContextState
1026
+
1027
+ /** Collect information about the run for purposes of additional diagnostics.
1028
+ */
1029
+ class Usages :
1030
+ import rewrites .Rewrites .patch
1031
+ private val selectors = mutable.Map .empty[ImportInfo , Set [untpd.ImportSelector ]].withDefaultValue(Set .empty)
1032
+ private val importInfos = mutable.Map .empty[CompilationUnit , List [(ImportInfo , Symbol )]].withDefaultValue(Nil )
1033
+
1034
+ // register an import
1035
+ def += (info : ImportInfo )(using Context ): Unit =
1036
+ def isLanguageImport = info.isLanguageImport && allSourceVersionNames.exists(info.forwardMapping.contains)
1037
+ if ctx.settings.WunusedHas .imports && ! isLanguageImport && ! ctx.owner.is(Enum ) && ! ctx.compilationUnit.isJava then
1038
+ importInfos(ctx.compilationUnit) ::= ((info, ctx.owner))
1039
+
1040
+ // mark a selector as used
1041
+ def use (info : ImportInfo , selector : untpd.ImportSelector )(using Context ): Unit =
1042
+ if ctx.settings.WunusedHas .imports && ! info.isRootImport then
1043
+ selectors(info) += selector
1044
+
1045
+ // unused import, owner, which selector
1046
+ def unused (using Context ): List [(ImportInfo , Symbol , untpd.ImportSelector )] =
1047
+ var unusages = List .empty[(ImportInfo , Symbol , untpd.ImportSelector )]
1048
+ if ctx.settings.WunusedHas .imports && ! ctx.compilationUnit.isJava then
1049
+ // if ctx.settings.Ydebug.value then
1050
+ // println(importInfos.get(ctx.compilationUnit).map(iss => iss.map((ii, s) => s"${ii.show} ($ii)")).getOrElse(Nil).mkString("Registered ImportInfos\n", "\n", ""))
1051
+ // println(selectors.toList.flatMap((k,v) => v.toList.map(sel => s"${k.show} -> $sel")).mkString("Used selectors\n", "\n", ""))
1052
+ def checkUsed (info : ImportInfo , owner : Symbol ): Unit =
1053
+ val used = selectors(info)
1054
+ var needsPatch = false
1055
+ def cull (toCheck : List [untpd.ImportSelector ]): Unit =
1056
+ toCheck match
1057
+ case selector :: rest =>
1058
+ cull(rest) // reverse
1059
+ if ! selector.isMask && ! used(selector) then
1060
+ unusages ::= ((info, owner, selector))
1061
+ needsPatch = true
1062
+ case _ =>
1063
+ cull(info.selectors)
1064
+ if needsPatch && ctx.settings.YrewriteImports .value then
1065
+ val src = ctx.compilationUnit.source
1066
+ val infoPos = info.qualifier.sourcePos
1067
+ val lineSource = SourceFile .virtual(name = " import-line.scala" , content = infoPos.lineContent)
1068
+ val PackageDef (_, pieces) = Parsers .Parser (lineSource).parse(): @ unchecked
1069
+ // patch if there's just one import on the line, i.e., not import a.b, c.d
1070
+ if pieces.length == 1 then
1071
+ val retained = info.selectors.filter(sel => sel.isMask || used(sel))
1072
+ val selectorSpan = info.selectors.map(_.span).reduce(_ union _)
1073
+ val lineSpan = src.lineSpan(infoPos.start)
1074
+ if retained.isEmpty then
1075
+ patch(src, lineSpan, " " ) // line deletion
1076
+ else if retained.size == 1 && info.selectors.size > 1 then
1077
+ var starting = info.selectors.head.span.start
1078
+ while starting > lineSpan.start && src.content()(starting) != '{' do starting -= 1
1079
+ var ending = info.selectors.last.span.end
1080
+ while ending <= lineSpan.end && src.content()(ending) != '}' do ending += 1
1081
+ if ending < lineSpan.end then ending += 1 // past the close brace
1082
+ val widened = selectorSpan.withStart(starting).withEnd(ending)
1083
+ patch(src, widened, toText(retained)) // try to remove braces
1084
+ else
1085
+ patch(src, selectorSpan, toText(retained))
1086
+ end checkUsed
1087
+ importInfos.remove(ctx.compilationUnit).foreach(_.foreach(checkUsed))
1088
+ unusages
1089
+ end unused
1090
+
1091
+ // just the selectors, no need to add braces
1092
+ private def toText (retained : List [untpd.ImportSelector ])(using Context ): String =
1093
+ def selected (sel : untpd.ImportSelector ) =
1094
+ if sel.isGiven then " given"
1095
+ else if sel.isWildcard then " *"
1096
+ else if sel.name == sel.rename then sel.name.show
1097
+ else s " ${sel.name.show} as ${sel.rename.show}"
1098
+ retained.map(selected).mkString(" , " )
1099
+
1100
+ def clear ()(using Context ): Unit =
1101
+ importInfos.clear()
1102
+ selectors.clear()
1103
+ end Usages
0 commit comments