@@ -25,30 +25,23 @@ import dotty.tools.dotc.core.Definitions
2525import dotty .tools .dotc .core .NameKinds .WildcardParamName
2626import dotty .tools .dotc .core .Symbols .Symbol
2727import dotty .tools .dotc .core .StdNames .nme
28-
28+ import scala . math . Ordering
2929
3030/**
3131 * A compiler phase that checks for unused imports or definitions
3232 *
3333 * Basically, it gathers definition/imports and their usage. If a
3434 * definition/imports does not have any usage, then it is reported.
3535 */
36- class CheckUnused extends MiniPhase :
37- import CheckUnused .UnusedData
38-
39- /**
40- * The key used to retrieve the "unused entity" analysis metadata,
41- * from the compilation `Context`
42- */
43- private val _key = Property .Key [UnusedData ]
36+ class CheckUnused private (phaseMode : CheckUnused .PhaseMode , suffix : String , _key : Property .Key [CheckUnused .UnusedData ]) extends MiniPhase :
37+ import CheckUnused .*
38+ import UnusedData .*
4439
4540 private def unusedDataApply [U ](f : UnusedData => U )(using Context ): Context =
4641 ctx.property(_key).foreach(f)
4742 ctx
48- private def getUnusedData (using Context ): Option [UnusedData ] =
49- ctx.property(_key)
5043
51- override def phaseName : String = CheckUnused .phaseName
44+ override def phaseName : String = CheckUnused .phaseNamePrefix + suffix
5245
5346 override def description : String = CheckUnused .description
5447
@@ -60,13 +53,21 @@ class CheckUnused extends MiniPhase:
6053
6154 override def prepareForUnit (tree : tpd.Tree )(using Context ): Context =
6255 val data = UnusedData ()
56+ tree.getAttachment(_key).foreach(oldData =>
57+ data.unusedAggregate = oldData.unusedAggregate
58+ )
6359 val fresh = ctx.fresh.setProperty(_key, data)
60+ tree.putAttachment(_key, data)
6461 fresh
6562
6663 // ========== END + REPORTING ==========
6764
6865 override def transformUnit (tree : tpd.Tree )(using Context ): tpd.Tree =
69- unusedDataApply(ud => reportUnused(ud.getUnused))
66+ unusedDataApply { ud =>
67+ ud.finishAggregation()
68+ if (phaseMode == PhaseMode .Report ) then
69+ ud.unusedAggregate.foreach(reportUnused)
70+ }
7071 tree
7172
7273 // ========== MiniPhase Prepare ==========
@@ -252,31 +253,35 @@ class CheckUnused extends MiniPhase:
252253 private def traverseAnnotations (sym : Symbol )(using Context ): Unit =
253254 sym.denot.annotations.foreach(annot => traverser.traverse(annot.tree))
254255
256+
255257 /** Do the actual reporting given the result of the anaylsis */
256258 private def reportUnused (res : UnusedData .UnusedResult )(using Context ): Unit =
257- import CheckUnused .WarnTypes
258- res.warnings.foreach { s =>
259+ res.warnings.toList.sortBy(_.pos.line)(using Ordering [Int ]).foreach { s =>
259260 s match
260- case (t , WarnTypes .Imports ) =>
261+ case UnusedSymbol (t, _ , WarnTypes .Imports ) =>
261262 report.warning(s " unused import " , t)
262- case (t , WarnTypes .LocalDefs ) =>
263+ case UnusedSymbol (t, _ , WarnTypes .LocalDefs ) =>
263264 report.warning(s " unused local definition " , t)
264- case (t , WarnTypes .ExplicitParams ) =>
265+ case UnusedSymbol (t, _ , WarnTypes .ExplicitParams ) =>
265266 report.warning(s " unused explicit parameter " , t)
266- case (t , WarnTypes .ImplicitParams ) =>
267+ case UnusedSymbol (t, _ , WarnTypes .ImplicitParams ) =>
267268 report.warning(s " unused implicit parameter " , t)
268- case (t , WarnTypes .PrivateMembers ) =>
269+ case UnusedSymbol (t, _ , WarnTypes .PrivateMembers ) =>
269270 report.warning(s " unused private member " , t)
270- case (t , WarnTypes .PatVars ) =>
271+ case UnusedSymbol (t, _ , WarnTypes .PatVars ) =>
271272 report.warning(s " unused pattern variable " , t)
272273 }
273274
274275end CheckUnused
275276
276277object CheckUnused :
277- val phaseName : String = " checkUnused"
278+ val phaseNamePrefix : String = " checkUnused"
278279 val description : String = " check for unused elements"
279280
281+ enum PhaseMode :
282+ case Aggregate
283+ case Report
284+
280285 private enum WarnTypes :
281286 case Imports
282287 case LocalDefs
@@ -285,19 +290,30 @@ object CheckUnused:
285290 case PrivateMembers
286291 case PatVars
287292
293+ /**
294+ * The key used to retrieve the "unused entity" analysis metadata,
295+ * from the compilation `Context`
296+ */
297+ private val _key = Property .StickyKey [UnusedData ]
298+
299+ class PostTyper extends CheckUnused (PhaseMode .Aggregate , " PostTyper" , _key)
300+
301+ class PostInlining extends CheckUnused (PhaseMode .Report , " PostInlining" , _key)
302+
288303 /**
289304 * A stateful class gathering the infos on :
290305 * - imports
291306 * - definitions
292307 * - usage
293308 */
294309 private class UnusedData :
295- import dotty .tools .dotc .transform .CheckUnused .UnusedData .UnusedResult
296310 import collection .mutable .{Set => MutSet , Map => MutMap , Stack => MutStack }
297- import UnusedData .ScopeType
311+ import UnusedData .*
298312
299313 /** The current scope during the tree traversal */
300- var currScopeType : MutStack [ScopeType ] = MutStack (ScopeType .Other )
314+ val currScopeType : MutStack [ScopeType ] = MutStack (ScopeType .Other )
315+
316+ var unusedAggregate : Option [UnusedResult ] = None
301317
302318 /* IMPORTS */
303319 private val impInScope = MutStack (MutSet [tpd.Import ]())
@@ -344,6 +360,17 @@ object CheckUnused:
344360 execInNewScope
345361 popScope()
346362
363+ def finishAggregation (using Context )(): Unit =
364+ val unusedInThisStage = this .getUnused
365+ this .unusedAggregate match {
366+ case None =>
367+ this .unusedAggregate = Some (unusedInThisStage)
368+ case Some (prevUnused) =>
369+ val intersection = unusedInThisStage.warnings.intersect(prevUnused.warnings)
370+ this .unusedAggregate = Some (UnusedResult (intersection))
371+ }
372+
373+
347374 /**
348375 * Register a found (used) symbol along with its name
349376 *
@@ -453,12 +480,13 @@ object CheckUnused:
453480 *
454481 * The given `List` is sorted by line and then column of the position
455482 */
483+
456484 def getUnused (using Context ): UnusedResult =
457485 popScope()
458486
459487 val sortedImp =
460488 if ctx.settings.WunusedHas .imports || ctx.settings.WunusedHas .strictNoImplicitWarn then
461- unusedImport.map(d => d.srcPos -> WarnTypes .Imports ).toList
489+ unusedImport.map(d => UnusedSymbol ( d.srcPos, d.name, WarnTypes .Imports ) ).toList
462490 else
463491 Nil
464492 val sortedLocalDefs =
@@ -467,7 +495,7 @@ object CheckUnused:
467495 .filterNot(d => d.symbol.usedDefContains)
468496 .filterNot(d => usedInPosition.exists { case (pos, name) => d.span.contains(pos.span) && name == d.symbol.name})
469497 .filterNot(d => containsSyntheticSuffix(d.symbol))
470- .map(d => d.namePos -> WarnTypes .LocalDefs ).toList
498+ .map(d => UnusedSymbol ( d.namePos, d.name, WarnTypes .LocalDefs ) ).toList
471499 else
472500 Nil
473501 val sortedExplicitParams =
@@ -476,23 +504,23 @@ object CheckUnused:
476504 .filterNot(d => d.symbol.usedDefContains)
477505 .filterNot(d => usedInPosition.exists { case (pos, name) => d.span.contains(pos.span) && name == d.symbol.name})
478506 .filterNot(d => containsSyntheticSuffix(d.symbol))
479- .map(d => d.namePos -> WarnTypes .ExplicitParams ).toList
507+ .map(d => UnusedSymbol ( d.namePos, d.name, WarnTypes .ExplicitParams ) ).toList
480508 else
481509 Nil
482510 val sortedImplicitParams =
483511 if ctx.settings.WunusedHas .implicits then
484512 implicitParamInScope
485513 .filterNot(d => d.symbol.usedDefContains)
486514 .filterNot(d => containsSyntheticSuffix(d.symbol))
487- .map(d => d.namePos -> WarnTypes .ImplicitParams ).toList
515+ .map(d => UnusedSymbol ( d.namePos, d.name, WarnTypes .ImplicitParams ) ).toList
488516 else
489517 Nil
490518 val sortedPrivateDefs =
491519 if ctx.settings.WunusedHas .privates then
492520 privateDefInScope
493521 .filterNot(d => d.symbol.usedDefContains)
494522 .filterNot(d => containsSyntheticSuffix(d.symbol))
495- .map(d => d.namePos -> WarnTypes .PrivateMembers ).toList
523+ .map(d => UnusedSymbol ( d.namePos, d.name, WarnTypes .PrivateMembers ) ).toList
496524 else
497525 Nil
498526 val sortedPatVars =
@@ -501,14 +529,14 @@ object CheckUnused:
501529 .filterNot(d => d.symbol.usedDefContains)
502530 .filterNot(d => containsSyntheticSuffix(d.symbol))
503531 .filterNot(d => usedInPosition.exists { case (pos, name) => d.span.contains(pos.span) && name == d.symbol.name})
504- .map(d => d.namePos -> WarnTypes .PatVars ).toList
532+ .map(d => UnusedSymbol ( d.namePos, d.name, WarnTypes .PatVars ) ).toList
505533 else
506534 Nil
507535 val warnings = List (sortedImp, sortedLocalDefs, sortedExplicitParams, sortedImplicitParams, sortedPrivateDefs, sortedPatVars).flatten.sortBy { s =>
508- val pos = s._1 .sourcePos
536+ val pos = s.pos .sourcePos
509537 (pos.line, pos.column)
510538 }
511- UnusedResult (warnings, Nil )
539+ UnusedResult (warnings.toSet )
512540 end getUnused
513541 // ============================ HELPERS ====================================
514542
@@ -707,7 +735,11 @@ object CheckUnused:
707735 case _:tpd.Block => Local
708736 case _ => Other
709737
738+ case class UnusedSymbol (pos : SrcPos , name : Name , warnType : WarnTypes )
710739 /** A container for the results of the used elements analysis */
711- case class UnusedResult (warnings : List [(dotty.tools.dotc.util.SrcPos , WarnTypes )], usedImports : List [(tpd.Import , untpd.ImportSelector )])
740+ case class UnusedResult (warnings : Set [UnusedSymbol ])
741+ object UnusedResult :
742+ val Empty = UnusedResult (Set .empty)
743+
712744end CheckUnused
713745
0 commit comments