@@ -6,6 +6,7 @@ import interfaces.CompilerCallback
66import Decorators ._
77import Periods ._
88import Names ._
9+ import Flags .*
910import Phases ._
1011import Types ._
1112import Symbols ._
@@ -19,12 +20,14 @@ import Nullables._
1920import Implicits .ContextualImplicits
2021import config .Settings ._
2122import config .Config
23+ import config .SourceVersion .allSourceVersionNames
2224import reporting ._
2325import io .{AbstractFile , NoAbstractFile , PlainFile , Path }
2426import scala .io .Codec
2527import collection .mutable
28+ import parsing .Parsers
2629import printing ._
27- import config .{JavaPlatform , SJSPlatform , Platform , ScalaSettings }
30+ import config .{JavaPlatform , SJSPlatform , Platform , ScalaSettings , ScalaRelease }
2831import classfile .ReusableDataReader
2932import StdNames .nme
3033
@@ -39,7 +42,9 @@ import plugins._
3942import java .util .concurrent .atomic .AtomicInteger
4043import java .nio .file .InvalidPathException
4144
42- object Contexts {
45+ import scala .util .chaining .given
46+
47+ object Contexts :
4348
4449 private val (compilerCallbackLoc, store1) = Store .empty.newLocation[CompilerCallback ]()
4550 private val (sbtCallbackLoc, store2) = store1.newLocation[AnalysisCallback ]()
@@ -51,8 +56,9 @@ object Contexts {
5156 private val (notNullInfosLoc, store8) = store7.newLocation[List [NotNullInfo ]]()
5257 private val (importInfoLoc, store9) = store8.newLocation[ImportInfo | Null ]()
5358 private val (typeAssignerLoc, store10) = store9.newLocation[TypeAssigner ](TypeAssigner )
59+ private val (usagesLoc, store11) = store10.newLocation[Usages ]()
5460
55- private val initialStore = store10
61+ private val initialStore = store11
5662
5763 /** The current context */
5864 inline def ctx (using ctx : Context ): Context = ctx
@@ -238,6 +244,9 @@ object Contexts {
238244 /** The current type assigner or typer */
239245 def typeAssigner : TypeAssigner = store(typeAssignerLoc)
240246
247+ /** Tracker for usages of elements such as import selectors. */
248+ def usages : Usages = store(usagesLoc)
249+
241250 /** The new implicit references that are introduced by this scope */
242251 protected var implicitsCache : ContextualImplicits | Null = null
243252 def implicits : ContextualImplicits = {
@@ -246,9 +255,7 @@ object Contexts {
246255 val implicitRefs : List [ImplicitRef ] =
247256 if (isClassDefContext)
248257 try owner.thisType.implicitMembers
249- catch {
250- case ex : CyclicReference => Nil
251- }
258+ catch case ex : CyclicReference => Nil
252259 else if (isImportContext) importInfo.nn.importedImplicits
253260 else if (isNonEmptyScopeContext) scope.implicitDecls
254261 else Nil
@@ -474,8 +481,24 @@ object Contexts {
474481 else fresh.setOwner(exprOwner)
475482
476483 /** 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
479502
480503 /** Is the debug option set? */
481504 def debug : Boolean = base.settings.Ydebug .value
@@ -811,6 +834,7 @@ object Contexts {
811834 store = initialStore
812835 .updated(settingsStateLoc, settingsGroup.defaultState)
813836 .updated(notNullInfosLoc, Nil )
837+ .updated(usagesLoc, Usages ())
814838 .updated(compilationUnitLoc, NoCompilationUnit )
815839 searchHistory = new SearchRoot
816840 gadt = EmptyGadtConstraint
@@ -938,7 +962,7 @@ object Contexts {
938962 private [dotc] var stopInlining : Boolean = false
939963
940964 /** 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
942966 * the enclosing context will be aborted. The variable is used as a smoke test
943967 * to turn off assertions that might be wrong if the program is erroneous. To
944968 * just test for `ctx.reporter.errorsReported` is not always enough, since it
@@ -995,4 +1019,82 @@ object Contexts {
9951019 if (thread == null ) thread = Thread .currentThread()
9961020 else assert(thread == Thread .currentThread(), " illegal multithreaded access to ContextBase" )
9971021 }
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