@@ -21,7 +21,7 @@ import dotty.tools.dotc.core.NameOps.isReplWrapperName
21
21
import dotty .tools .dotc .core .Annotations
22
22
import dotty .tools .dotc .core .Definitions
23
23
import dotty .tools .dotc .core .NameKinds .WildcardParamName
24
- import dotty .tools .dotc .core .Symbols .Symbol
24
+ import dotty .tools .dotc .core .Symbols .{ Symbol , isDeprecated }
25
25
import dotty .tools .dotc .report
26
26
import dotty .tools .dotc .reporting .{Message , UnusedSymbol as UnusedSymbolMessage }
27
27
import dotty .tools .dotc .transform .MegaPhase .MiniPhase
@@ -30,19 +30,21 @@ import dotty.tools.dotc.util.{Property, SrcPos}
30
30
import dotty .tools .dotc .util .Spans .Span
31
31
import scala .util .chaining .given
32
32
33
+ import CheckUnused .*
34
+
33
35
/** A compiler phase that checks for unused imports or definitions.
34
36
*
35
37
* Every construct that introduces a name must have at least one corresponding reference.
36
38
* The analysis is restricted to definitions of limited scope, i.e., private and local definitions.
37
39
*/
38
- class CheckUnused private (phaseMode : CheckUnused . PhaseMode , suffix : String , _key : Property . Key [ CheckUnused . UnusedData ] ) extends MiniPhase :
39
- import CheckUnused . *
40
+ class CheckUnused private (phaseMode : PhaseMode , suffix : String )( using Key ) extends MiniPhase :
41
+
40
42
import UnusedData .*
41
43
42
44
private inline def ud (using ud : UnusedData ): UnusedData = ud
43
45
44
46
private inline def go [U ](inline op : UnusedData ?=> U )(using ctx : Context ): ctx.type =
45
- ctx.property(_key ) match
47
+ ctx.property(summon[ Key ] ) match
46
48
case Some (ud) => op(using ud)
47
49
case None =>
48
50
ctx
@@ -59,11 +61,12 @@ class CheckUnused private (phaseMode: CheckUnused.PhaseMode, suffix: String, _ke
59
61
// ========== SETUP ============
60
62
61
63
override def prepareForUnit (tree : tpd.Tree )(using Context ): Context =
64
+ val key = summon[Key ]
62
65
val data = UnusedData ()
63
- tree.getAttachment(_key ).foreach(oldData =>
66
+ tree.getAttachment(key ).foreach(oldData =>
64
67
data.unusedAggregate = oldData.unusedAggregate
65
68
)
66
- ctx.fresh.setProperty(_key , data).tap(_ => tree.putAttachment(_key , data))
69
+ ctx.fresh.setProperty(key , data).tap(_ => tree.putAttachment(key , data))
67
70
68
71
// ========== END + REPORTING ==========
69
72
@@ -75,14 +78,11 @@ class CheckUnused private (phaseMode: CheckUnused.PhaseMode, suffix: String, _ke
75
78
tree
76
79
77
80
// ========== MiniPhase Prepare ==========
78
- override def prepareForOther (tree : tpd.Tree )(using Context ): Context =
79
- // A standard tree traverser covers cases not handled by the Mega/MiniPhase
81
+ override def prepareForOther (tree : tpd.Tree )(using Context ): Context = go :
80
82
traverser.traverse(tree)
81
- ctx
82
83
83
- override def prepareForInlined (tree : tpd.Inlined )(using Context ): Context =
84
+ override def prepareForInlined (tree : tpd.Inlined )(using Context ): Context = go :
84
85
traverser.traverse(tree.call)
85
- ctx
86
86
87
87
override def prepareForIdent (tree : tpd.Ident )(using Context ): Context = go :
88
88
if tree.symbol.exists then
@@ -102,13 +102,13 @@ class CheckUnused private (phaseMode: CheckUnused.PhaseMode, suffix: String, _ke
102
102
ud.registerUsed(tree.symbol, name, tree.qualifier.tpe, includeForImport = tree.qualifier.span.isSynthetic)
103
103
104
104
override def prepareForBlock (tree : tpd.Block )(using Context ): Context =
105
- pushInBlockTemplatePackageDef (tree)
105
+ pushScope (tree)
106
106
107
107
override def prepareForTemplate (tree : tpd.Template )(using Context ): Context =
108
- pushInBlockTemplatePackageDef (tree)
108
+ pushScope (tree)
109
109
110
110
override def prepareForPackageDef (tree : tpd.PackageDef )(using Context ): Context =
111
- pushInBlockTemplatePackageDef (tree)
111
+ pushScope (tree)
112
112
113
113
override def prepareForValDef (tree : tpd.ValDef )(using Context ): Context = go :
114
114
traverseAnnotations(tree.symbol)
@@ -137,9 +137,8 @@ class CheckUnused private (phaseMode: CheckUnused.PhaseMode, suffix: String, _ke
137
137
traverseAnnotations(tree.symbol)
138
138
ud.registerPatVar(tree)
139
139
140
- override def prepareForTypeTree (tree : tpd.TypeTree )(using Context ): Context =
140
+ override def prepareForTypeTree (tree : tpd.TypeTree )(using Context ): Context = go :
141
141
if ! tree.isInstanceOf [tpd.InferredTypeTree ] then typeTraverser.traverse(tree.tpe)
142
- ctx
143
142
144
143
override def prepareForAssign (tree : tpd.Assign )(using Context ): Context = go :
145
144
val sym = tree.lhs.symbol
@@ -149,47 +148,47 @@ class CheckUnused private (phaseMode: CheckUnused.PhaseMode, suffix: String, _ke
149
148
// ========== MiniPhase Transform ==========
150
149
151
150
override def transformBlock (tree : tpd.Block )(using Context ): tpd.Tree =
152
- popOutBlockTemplatePackageDef( )
151
+ popScope(tree )
153
152
tree
154
153
155
154
override def transformTemplate (tree : tpd.Template )(using Context ): tpd.Tree =
156
- popOutBlockTemplatePackageDef( )
155
+ popScope(tree )
157
156
tree
158
157
159
158
override def transformPackageDef (tree : tpd.PackageDef )(using Context ): tpd.Tree =
160
- popOutBlockTemplatePackageDef( )
159
+ popScope(tree )
161
160
tree
162
161
163
162
override def transformValDef (tree : tpd.ValDef )(using Context ): tpd.Tree =
164
- go(ud.removeIgnoredUsage(tree.symbol))
163
+ go :
164
+ ud.removeIgnoredUsage(tree.symbol)
165
165
tree
166
166
167
167
override def transformDefDef (tree : tpd.DefDef )(using Context ): tpd.Tree =
168
- go(ud.removeIgnoredUsage(tree.symbol))
168
+ go :
169
+ ud.removeIgnoredUsage(tree.symbol)
169
170
tree
170
171
171
172
override def transformTypeDef (tree : tpd.TypeDef )(using Context ): tpd.Tree =
172
- go(ud.removeIgnoredUsage(tree.symbol))
173
+ go :
174
+ ud.removeIgnoredUsage(tree.symbol)
173
175
tree
174
176
177
+ private def pushScope (tree : tpd.Block | tpd.Template | tpd.PackageDef )(using Context ): Context = go :
178
+ ud.pushScope(ScopeType .fromTree(tree))
175
179
176
- // ---------- MiniPhase HELPERS -----------
177
-
178
- private def pushInBlockTemplatePackageDef (tree : tpd.Block | tpd.Template | tpd.PackageDef )(using Context ): Context = go :
179
- ud.pushScope(UnusedData .ScopeType .fromTree(tree))
180
-
181
- private def popOutBlockTemplatePackageDef ()(using Context ): Context = go :
182
- ud.popScope()
180
+ private def popScope (tree : tpd.Block | tpd.Template | tpd.PackageDef )(using Context ): Context = go :
181
+ ud.popScope(ScopeType .fromTree(tree))
183
182
184
183
/**
185
184
* This traverse is the **main** component of this phase
186
185
*
187
186
* It traverse the tree the tree and gather the data in the
188
187
* corresponding context property
188
+ *
189
+ * A standard tree traverser covers cases not handled by the Mega/MiniPhase
189
190
*/
190
191
private def traverser = new TreeTraverser :
191
- import tpd .*
192
- import UnusedData .ScopeType
193
192
194
193
// Register every import, definition and usage
195
194
override def traverse (tree : tpd.Tree )(using Context ): Unit =
@@ -201,17 +200,17 @@ class CheckUnused private (phaseMode: CheckUnused.PhaseMode, suffix: String, _ke
201
200
case untpd.TypedSplice (tree1) => tree1
202
201
}.foreach(traverse(_)(using newCtx))
203
202
traverseChildren(tree)(using newCtx)
204
- case ident : Ident =>
203
+ case ident : tpd. Ident =>
205
204
prepareForIdent(ident)
206
205
traverseChildren(tree)(using newCtx)
207
- case sel : Select =>
206
+ case sel : tpd. Select =>
208
207
prepareForSelect(sel)
209
208
traverseChildren(tree)(using newCtx)
210
209
case tree : (tpd.Block | tpd.Template | tpd.PackageDef ) =>
211
210
// ! DIFFERS FROM MINIPHASE
212
- pushInBlockTemplatePackageDef (tree)
211
+ pushScope (tree)
213
212
traverseChildren(tree)(using newCtx)
214
- popOutBlockTemplatePackageDef( )
213
+ popScope(tree )
215
214
case t : tpd.ValDef =>
216
215
prepareForValDef(t)
217
216
traverseChildren(tree)(using newCtx)
@@ -301,13 +300,14 @@ object CheckUnused:
301
300
* The key used to retrieve the "unused entity" analysis metadata,
302
301
* from the compilation `Context`
303
302
*/
304
- private val _key = Property .StickyKey [UnusedData ]
303
+ private type Key = Property .StickyKey [UnusedData ]
304
+ private given Key = Property .StickyKey [UnusedData ]
305
305
306
306
val OriginalName = Property .StickyKey [Name ]
307
307
308
- class PostTyper extends CheckUnused (PhaseMode .Aggregate , " PostTyper" , _key )
308
+ class PostTyper extends CheckUnused (PhaseMode .Aggregate , " PostTyper" )
309
309
310
- class PostInlining extends CheckUnused (PhaseMode .Report , " PostInlining" , _key )
310
+ class PostInlining extends CheckUnused (PhaseMode .Report , " PostInlining" )
311
311
312
312
/** Track usages at a Context.
313
313
*
@@ -321,12 +321,13 @@ object CheckUnused:
321
321
322
322
/** The current scope during the tree traversal */
323
323
val currScopeType : Stack [ScopeType ] = Stack (ScopeType .Other )
324
+ inline def peekScopeType = currScopeType.top
324
325
325
326
var unusedAggregate : Option [UnusedResult ] = None
326
327
327
328
/* IMPORTS */
328
329
private val impInScope = Stack (ListBuffer .empty[ImportSelectorData ])
329
- private val usedInScope = Stack (mut.Set .empty[Usage ])
330
+ private val usedInScope = Stack (mut.Map .empty[Symbol , ListBuffer [ Usage ] ])
330
331
private val usedInPosition = mut.Map .empty[Name , mut.Set [Symbol ]]
331
332
/* unused import collected during traversal */
332
333
private val unusedImport = ListBuffer .empty[ImportSelectorData ]
@@ -382,14 +383,19 @@ object CheckUnused:
382
383
if sym.exists then
383
384
usedDef += sym
384
385
if includeForImport1 then
385
- usedInScope.top += Usage (sym, name, prefix, isDerived)
386
+ addUsage( Usage (sym, name, prefix, isDerived) )
386
387
addIfExists(sym)
387
388
addIfExists(sym.companionModule)
388
389
addIfExists(sym.companionClass)
389
390
if sym.sourcePos.exists then
390
391
for n <- name do
391
392
usedInPosition.getOrElseUpdate(n, mut.Set .empty) += sym
392
393
394
+ def addUsage (usage : Usage )(using Context ): Unit =
395
+ val usages = usedInScope.top.getOrElseUpdate(usage.symbol, ListBuffer .empty)
396
+ if ! usages.exists(cur => cur.name == usage.name && cur.isDerived == usage.isDerived && cur.prefix =:= usage.prefix)
397
+ then usages += usage
398
+
393
399
/** Register a symbol that should be ignored */
394
400
def addIgnoredUsage (sym : Symbol )(using Context ): Unit =
395
401
doNotRegister ++= sym.everySymbol
@@ -407,7 +413,7 @@ object CheckUnused:
407
413
! tpd.languageImport(imp.expr).nonEmpty
408
414
&& ! imp.isGeneratedByEnum
409
415
&& ! isTransparentAndInline(imp)
410
- && currScopeType.top != ScopeType .ReplWrapper // #18383 Do not report top-level import's in the repl as unused
416
+ && peekScopeType != ScopeType .ReplWrapper // #18383 Do not report top-level import's in the repl as unused
411
417
then
412
418
val qualTpe = imp.expr.tpe
413
419
@@ -435,7 +441,7 @@ object CheckUnused:
435
441
implicitParamInScope += memDef
436
442
else if ! paramsToSkip.contains(memDef.symbol) then
437
443
explicitParamInScope += memDef
438
- else if currScopeType.top == ScopeType .Local then
444
+ else if peekScopeType == ScopeType .Local then
439
445
localDefInScope += memDef
440
446
else if memDef.shouldReportPrivateDef then
441
447
privateDefInScope += memDef
@@ -447,10 +453,9 @@ object CheckUnused:
447
453
448
454
/** enter a new scope */
449
455
def pushScope (newScopeType : ScopeType ): Unit =
450
- // unused imports :
451
456
currScopeType.push(newScopeType)
452
457
impInScope.push(ListBuffer .empty)
453
- usedInScope.push(mut.Set .empty)
458
+ usedInScope.push(mut.Map .empty)
454
459
455
460
def registerSetVar (sym : Symbol ): Unit =
456
461
setVars += sym
@@ -460,20 +465,19 @@ object CheckUnused:
460
465
*
461
466
* - If there are imports in this scope check for unused ones
462
467
*/
463
- def popScope ()(using Context ): Unit =
464
- currScopeType.pop()
465
- val usedInfos = usedInScope.pop()
468
+ def popScope (scopeType : ScopeType )(using Context ): Unit =
469
+ assert(currScopeType.pop() == scopeType)
466
470
val selDatas = impInScope.pop()
467
471
468
- for usedInfo <- usedInfos do
469
- val Usage (sym, optName, prefix, isDerived) = usedInfo
470
- selDatas.find(sym .isInImport(_, optName , prefix, isDerived)) match
472
+ for usedInfos <- usedInScope.pop().valuesIterator; usedInfo <- usedInfos do
473
+ import usedInfo . *
474
+ selDatas.find(symbol .isInImport(_, name , prefix, isDerived)) match
471
475
case Some (sel) =>
472
476
sel.markUsed()
473
477
case None =>
474
478
// Propagate the symbol one level up
475
479
if usedInScope.nonEmpty then
476
- usedInScope.top += usedInfo
480
+ addUsage( usedInfo)
477
481
end for // each in usedInfos
478
482
479
483
for selData <- selDatas do
@@ -484,7 +488,7 @@ object CheckUnused:
484
488
/** Leave the scope and return a result set of warnings.
485
489
*/
486
490
def getUnused (using Context ): UnusedResult =
487
- popScope()
491
+ popScope(ScopeType . Other ) // sentinel
488
492
489
493
def isUsedInPosition (name : Name , span : Span ): Boolean =
490
494
usedInPosition.get(name) match
@@ -533,8 +537,6 @@ object CheckUnused:
533
537
534
538
UnusedResult (warnings.result)
535
539
end getUnused
536
- // ============================ HELPERS ====================================
537
-
538
540
539
541
/**
540
542
* Checks if import selects a def that is transparent and inline
@@ -634,14 +636,11 @@ object CheckUnused:
634
636
if altName.exists(explicitName => selector.rename != explicitName.toTermName) then
635
637
// if there is an explicit name, it must match
636
638
false
637
- else
638
- (isDerived || prefix.typeSymbol.isPackageObject || selData.qualTpe =:= prefix) && (
639
- if isDerived then
640
- // See i15503i.scala, grep for "package foo.test.i17156"
641
- selData.allSymbolsDealiasedForNamed.contains(sym.dealiasAsType)
642
- else
643
- selData.allSymbolsForNamed.contains(sym)
644
- )
639
+ else if isDerived then
640
+ // See i15503i.scala, grep for "package foo.test.i17156"
641
+ selData.allSymbolsDealiasedForNamed.contains(sym.dealiasAsType)
642
+ else (prefix.typeSymbol.isPackageObject || selData.qualTpe =:= prefix) &&
643
+ selData.allSymbolsForNamed.contains(sym)
645
644
else
646
645
// Wildcard
647
646
if ! selData.qualTpe.member(sym.name).hasAltWith(_.symbol == sym) then
@@ -668,9 +667,7 @@ object CheckUnused:
668
667
val owner = sym.owner
669
668
trivialDefs(owner) || // is a trivial def
670
669
owner.isPrimaryConstructor ||
671
- owner.annotations.exists ( // @depreacated
672
- _.symbol == ctx.definitions.DeprecatedAnnot
673
- ) ||
670
+ owner.isDeprecated ||
674
671
owner.isAllOf(Synthetic | PrivateLocal ) ||
675
672
owner.is(Accessor ) ||
676
673
owner.isOverridden
@@ -724,7 +721,7 @@ object CheckUnused:
724
721
! sym.shouldNotReportParamOwner
725
722
726
723
private def shouldReportPrivateDef (using Context ): Boolean =
727
- currScopeType.top == ScopeType .Template && ! memDef.symbol.isConstructor && memDef.symbol.is(Private , butNot = SelfName | Synthetic | CaseAccessor )
724
+ peekScopeType == ScopeType .Template && ! memDef.symbol.isConstructor && memDef.symbol.is(Private , butNot = SelfName | Synthetic | CaseAccessor )
728
725
729
726
private def isUnsetVarDef (using Context ): Boolean =
730
727
val sym = memDef.symbol
@@ -751,11 +748,11 @@ object CheckUnused:
751
748
object ScopeType :
752
749
/** return the scope corresponding to the enclosing scope of the given tree */
753
750
def fromTree (tree : tpd.Tree )(using Context ): ScopeType = tree match
754
- case tree : tpd.Template => if tree.symbol.name.isReplWrapperName then ReplWrapper else Template
755
- case _:tpd.Block => Local
751
+ case _ : tpd.Template => if tree.symbol.name.isReplWrapperName then ReplWrapper else Template
752
+ case _ : tpd.Block => Local
756
753
case _ => Other
757
754
758
- final case class ImportSelectorData (val qualTpe : Type , val selector : ImportSelector ):
755
+ final class ImportSelectorData (val qualTpe : Type , val selector : ImportSelector ):
759
756
private var myUsed : Boolean = false
760
757
761
758
def markUsed (): Unit = myUsed = true
@@ -787,7 +784,7 @@ object CheckUnused:
787
784
/** A symbol usage includes the name under which it was observed,
788
785
* the prefix from which it was selected, and whether it is in a derived element.
789
786
*/
790
- case class Usage (symbol : Symbol , name : Option [Name ], prefix : Type , isDerived : Boolean )
787
+ class Usage (val symbol : Symbol , val name : Option [Name ], val prefix : Type , val isDerived : Boolean )
791
788
end UnusedData
792
789
extension (sym : Symbol )
793
790
/** is accessible without import in current context */
@@ -808,5 +805,5 @@ object CheckUnused:
808
805
case tp : NamedType => tp.prefix
809
806
case tp : ClassInfo => tp.prefix
810
807
case tp : TypeProxy => tp.superType.normalizedPrefix
811
- case _ => tp
808
+ case _ => NoType
812
809
end CheckUnused
0 commit comments