@@ -25,30 +25,23 @@ import dotty.tools.dotc.core.Definitions
25
25
import dotty .tools .dotc .core .NameKinds .WildcardParamName
26
26
import dotty .tools .dotc .core .Symbols .Symbol
27
27
import dotty .tools .dotc .core .StdNames .nme
28
-
28
+ import scala . math . Ordering
29
29
30
30
/**
31
31
* A compiler phase that checks for unused imports or definitions
32
32
*
33
33
* Basically, it gathers definition/imports and their usage. If a
34
34
* definition/imports does not have any usage, then it is reported.
35
35
*/
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 .*
44
39
45
40
private def unusedDataApply [U ](f : UnusedData => U )(using Context ): Context =
46
41
ctx.property(_key).foreach(f)
47
42
ctx
48
- private def getUnusedData (using Context ): Option [UnusedData ] =
49
- ctx.property(_key)
50
43
51
- override def phaseName : String = CheckUnused .phaseName
44
+ override def phaseName : String = CheckUnused .phaseNamePrefix + suffix
52
45
53
46
override def description : String = CheckUnused .description
54
47
@@ -60,13 +53,21 @@ class CheckUnused extends MiniPhase:
60
53
61
54
override def prepareForUnit (tree : tpd.Tree )(using Context ): Context =
62
55
val data = UnusedData ()
56
+ tree.getAttachment(_key).foreach(oldData =>
57
+ data.unusedAggregate = oldData.unusedAggregate
58
+ )
63
59
val fresh = ctx.fresh.setProperty(_key, data)
60
+ tree.putAttachment(_key, data)
64
61
fresh
65
62
66
63
// ========== END + REPORTING ==========
67
64
68
65
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
+ }
70
71
tree
71
72
72
73
// ========== MiniPhase Prepare ==========
@@ -252,31 +253,35 @@ class CheckUnused extends MiniPhase:
252
253
private def traverseAnnotations (sym : Symbol )(using Context ): Unit =
253
254
sym.denot.annotations.foreach(annot => traverser.traverse(annot.tree))
254
255
256
+
255
257
/** Do the actual reporting given the result of the anaylsis */
256
258
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 =>
259
260
s match
260
- case (t , WarnTypes .Imports ) =>
261
+ case UnusedSymbol (t, _ , WarnTypes .Imports ) =>
261
262
report.warning(s " unused import " , t)
262
- case (t , WarnTypes .LocalDefs ) =>
263
+ case UnusedSymbol (t, _ , WarnTypes .LocalDefs ) =>
263
264
report.warning(s " unused local definition " , t)
264
- case (t , WarnTypes .ExplicitParams ) =>
265
+ case UnusedSymbol (t, _ , WarnTypes .ExplicitParams ) =>
265
266
report.warning(s " unused explicit parameter " , t)
266
- case (t , WarnTypes .ImplicitParams ) =>
267
+ case UnusedSymbol (t, _ , WarnTypes .ImplicitParams ) =>
267
268
report.warning(s " unused implicit parameter " , t)
268
- case (t , WarnTypes .PrivateMembers ) =>
269
+ case UnusedSymbol (t, _ , WarnTypes .PrivateMembers ) =>
269
270
report.warning(s " unused private member " , t)
270
- case (t , WarnTypes .PatVars ) =>
271
+ case UnusedSymbol (t, _ , WarnTypes .PatVars ) =>
271
272
report.warning(s " unused pattern variable " , t)
272
273
}
273
274
274
275
end CheckUnused
275
276
276
277
object CheckUnused :
277
- val phaseName : String = " checkUnused"
278
+ val phaseNamePrefix : String = " checkUnused"
278
279
val description : String = " check for unused elements"
279
280
281
+ enum PhaseMode :
282
+ case Aggregate
283
+ case Report
284
+
280
285
private enum WarnTypes :
281
286
case Imports
282
287
case LocalDefs
@@ -285,19 +290,30 @@ object CheckUnused:
285
290
case PrivateMembers
286
291
case PatVars
287
292
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
+
288
303
/**
289
304
* A stateful class gathering the infos on :
290
305
* - imports
291
306
* - definitions
292
307
* - usage
293
308
*/
294
309
private class UnusedData :
295
- import dotty .tools .dotc .transform .CheckUnused .UnusedData .UnusedResult
296
310
import collection .mutable .{Set => MutSet , Map => MutMap , Stack => MutStack }
297
- import UnusedData .ScopeType
311
+ import UnusedData .*
298
312
299
313
/** 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
301
317
302
318
/* IMPORTS */
303
319
private val impInScope = MutStack (MutSet [tpd.Import ]())
@@ -344,6 +360,17 @@ object CheckUnused:
344
360
execInNewScope
345
361
popScope()
346
362
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
+
347
374
/**
348
375
* Register a found (used) symbol along with its name
349
376
*
@@ -453,12 +480,13 @@ object CheckUnused:
453
480
*
454
481
* The given `List` is sorted by line and then column of the position
455
482
*/
483
+
456
484
def getUnused (using Context ): UnusedResult =
457
485
popScope()
458
486
459
487
val sortedImp =
460
488
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
462
490
else
463
491
Nil
464
492
val sortedLocalDefs =
@@ -467,7 +495,7 @@ object CheckUnused:
467
495
.filterNot(d => d.symbol.usedDefContains)
468
496
.filterNot(d => usedInPosition.exists { case (pos, name) => d.span.contains(pos.span) && name == d.symbol.name})
469
497
.filterNot(d => containsSyntheticSuffix(d.symbol))
470
- .map(d => d.namePos -> WarnTypes .LocalDefs ).toList
498
+ .map(d => UnusedSymbol ( d.namePos, d.name, WarnTypes .LocalDefs ) ).toList
471
499
else
472
500
Nil
473
501
val sortedExplicitParams =
@@ -476,23 +504,23 @@ object CheckUnused:
476
504
.filterNot(d => d.symbol.usedDefContains)
477
505
.filterNot(d => usedInPosition.exists { case (pos, name) => d.span.contains(pos.span) && name == d.symbol.name})
478
506
.filterNot(d => containsSyntheticSuffix(d.symbol))
479
- .map(d => d.namePos -> WarnTypes .ExplicitParams ).toList
507
+ .map(d => UnusedSymbol ( d.namePos, d.name, WarnTypes .ExplicitParams ) ).toList
480
508
else
481
509
Nil
482
510
val sortedImplicitParams =
483
511
if ctx.settings.WunusedHas .implicits then
484
512
implicitParamInScope
485
513
.filterNot(d => d.symbol.usedDefContains)
486
514
.filterNot(d => containsSyntheticSuffix(d.symbol))
487
- .map(d => d.namePos -> WarnTypes .ImplicitParams ).toList
515
+ .map(d => UnusedSymbol ( d.namePos, d.name, WarnTypes .ImplicitParams ) ).toList
488
516
else
489
517
Nil
490
518
val sortedPrivateDefs =
491
519
if ctx.settings.WunusedHas .privates then
492
520
privateDefInScope
493
521
.filterNot(d => d.symbol.usedDefContains)
494
522
.filterNot(d => containsSyntheticSuffix(d.symbol))
495
- .map(d => d.namePos -> WarnTypes .PrivateMembers ).toList
523
+ .map(d => UnusedSymbol ( d.namePos, d.name, WarnTypes .PrivateMembers ) ).toList
496
524
else
497
525
Nil
498
526
val sortedPatVars =
@@ -501,14 +529,14 @@ object CheckUnused:
501
529
.filterNot(d => d.symbol.usedDefContains)
502
530
.filterNot(d => containsSyntheticSuffix(d.symbol))
503
531
.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
505
533
else
506
534
Nil
507
535
val warnings = List (sortedImp, sortedLocalDefs, sortedExplicitParams, sortedImplicitParams, sortedPrivateDefs, sortedPatVars).flatten.sortBy { s =>
508
- val pos = s._1 .sourcePos
536
+ val pos = s.pos .sourcePos
509
537
(pos.line, pos.column)
510
538
}
511
- UnusedResult (warnings, Nil )
539
+ UnusedResult (warnings.toSet )
512
540
end getUnused
513
541
// ============================ HELPERS ====================================
514
542
@@ -707,7 +735,11 @@ object CheckUnused:
707
735
case _:tpd.Block => Local
708
736
case _ => Other
709
737
738
+ case class UnusedSymbol (pos : SrcPos , name : Name , warnType : WarnTypes )
710
739
/** 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
+
712
744
end CheckUnused
713
745
0 commit comments