@@ -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,24 @@ 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))
486
+
487
+ def scalaRelease : ScalaRelease =
488
+ val releaseName = base.settings.scalaOutputVersion.value
489
+ if releaseName.nonEmpty then ScalaRelease .parse(releaseName).get else ScalaRelease .latest
490
+
491
+ def tastyVersion : TastyVersion =
492
+ import math .Ordered .orderingToOrdered
493
+ val latestRelease = ScalaRelease .latest
494
+ val specifiedRelease = scalaRelease
495
+ if specifiedRelease < latestRelease then
496
+ // This is needed to make -scala-output-version a no-op when set to the latest release for unstable versions of the compiler
497
+ // (which might have the tasty format version numbers set to higher values before they're decreased during a release)
498
+ TastyVersion .fromStableScalaRelease(specifiedRelease.majorVersion, specifiedRelease.minorVersion)
499
+ else
500
+ TastyVersion .compilerVersion
501
+ >>>>>>> Warn unused imports
479
502
480
503
/** Is the debug option set? */
481
504
def debug : Boolean = base.settings.Ydebug .value
@@ -811,6 +834,7 @@ object Contexts {
811
834
store = initialStore
812
835
.updated(settingsStateLoc, settingsGroup.defaultState)
813
836
.updated(notNullInfosLoc, Nil )
837
+ .updated(usagesLoc, Usages ())
814
838
.updated(compilationUnitLoc, NoCompilationUnit )
815
839
searchHistory = new SearchRoot
816
840
gadt = EmptyGadtConstraint
@@ -938,7 +962,7 @@ object Contexts {
938
962
private [dotc] var stopInlining : Boolean = false
939
963
940
964
/** 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
965
+ * The error will not necessarily be emitted, since it could still be that
942
966
* the enclosing context will be aborted. The variable is used as a smoke test
943
967
* to turn off assertions that might be wrong if the program is erroneous. To
944
968
* just test for `ctx.reporter.errorsReported` is not always enough, since it
@@ -995,4 +1019,82 @@ object Contexts {
995
1019
if (thread == null ) thread = Thread .currentThread()
996
1020
else assert(thread == Thread .currentThread(), " illegal multithreaded access to ContextBase" )
997
1021
}
998
- }
1022
+ end ContextState
1023
+
1024
+ /** Collect information about the run for purposes of additional diagnostics.
1025
+ */
1026
+ class Usages :
1027
+ import rewrites .Rewrites .patch
1028
+ private val selectors = mutable.Map .empty[ImportInfo , Set [untpd.ImportSelector ]].withDefaultValue(Set .empty)
1029
+ private val importInfos = mutable.Map .empty[CompilationUnit , List [(ImportInfo , Symbol )]].withDefaultValue(Nil )
1030
+
1031
+ // register an import
1032
+ def += (info : ImportInfo )(using Context ): Unit =
1033
+ def isLanguageImport = info.isLanguageImport && allSourceVersionNames.exists(info.forwardMapping.contains)
1034
+ if ctx.settings.WunusedHas .imports && ! isLanguageImport && ! 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
+ if needsPatch && ctx.settings.YrewriteImports .value then
1062
+ val src = ctx.compilationUnit.source
1063
+ val infoPos = info.qualifier.sourcePos
1064
+ val lineSource = SourceFile .virtual(name = " import-line.scala" , content = infoPos.lineContent)
1065
+ val PackageDef (_, pieces) = Parsers .Parser (lineSource).parse(): @ unchecked
1066
+ // patch if there's just one import on the line, i.e., not import a.b, c.d
1067
+ if pieces.length == 1 then
1068
+ val retained = info.selectors.filter(sel => sel.isMask || used(sel))
1069
+ val selectorSpan = info.selectors.map(_.span).reduce(_ union _)
1070
+ val lineSpan = src.lineSpan(infoPos.start)
1071
+ if retained.isEmpty then
1072
+ patch(src, lineSpan, " " ) // line deletion
1073
+ else if retained.size == 1 && info.selectors.size > 1 then
1074
+ var starting = info.selectors.head.span.start
1075
+ while starting > lineSpan.start && src.content()(starting) != '{' do starting -= 1
1076
+ var ending = info.selectors.last.span.end
1077
+ while ending <= lineSpan.end && src.content()(ending) != '}' do ending += 1
1078
+ if ending < lineSpan.end then ending += 1 // past the close brace
1079
+ val widened = selectorSpan.withStart(starting).withEnd(ending)
1080
+ patch(src, widened, toText(retained)) // try to remove braces
1081
+ else
1082
+ patch(src, selectorSpan, toText(retained))
1083
+ end checkUsed
1084
+ importInfos.remove(ctx.compilationUnit).foreach(_.foreach(checkUsed))
1085
+ unusages
1086
+ end unused
1087
+
1088
+ // just the selectors, no need to add braces
1089
+ private def toText (retained : List [untpd.ImportSelector ])(using Context ): String =
1090
+ def selected (sel : untpd.ImportSelector ) =
1091
+ if sel.isGiven then " given"
1092
+ else if sel.isWildcard then " *"
1093
+ else if sel.name == sel.rename then sel.name.show
1094
+ else s " ${sel.name.show} as ${sel.rename.show}"
1095
+ retained.map(selected).mkString(" , " )
1096
+
1097
+ def clear ()(using Context ): Unit =
1098
+ importInfos.clear()
1099
+ selectors.clear()
1100
+ end Usages
0 commit comments