@@ -34,7 +34,7 @@ import dotty.tools.dotc.core.Symbols.Symbol
34
34
import dotty .tools .dotc .core .StdNames .nme
35
35
import dotty .tools .dotc .util .Spans .Span
36
36
import scala .math .Ordering
37
-
37
+ import scala . util . chaining . given
38
38
39
39
/**
40
40
* A compiler phase that checks for unused imports or definitions
@@ -68,16 +68,14 @@ class CheckUnused private (phaseMode: CheckUnused.PhaseMode, suffix: String, _ke
68
68
tree.getAttachment(_key).foreach(oldData =>
69
69
data.unusedAggregate = oldData.unusedAggregate
70
70
)
71
- val fresh = ctx.fresh.setProperty(_key, data)
72
- tree.putAttachment(_key, data)
73
- fresh
71
+ ctx.fresh.setProperty(_key, data).tap(_ => tree.putAttachment(_key, data))
74
72
75
73
// ========== END + REPORTING ==========
76
74
77
75
override def transformUnit (tree : tpd.Tree )(using Context ): tpd.Tree =
78
76
unusedDataApply { ud =>
79
77
ud.finishAggregation()
80
- if ( phaseMode == PhaseMode .Report ) then
78
+ if phaseMode == PhaseMode .Report then
81
79
ud.unusedAggregate.foreach(reportUnused)
82
80
}
83
81
tree
@@ -99,20 +97,21 @@ class CheckUnused private (phaseMode: CheckUnused.PhaseMode, suffix: String, _ke
99
97
def loopOnNormalizedPrefixes (prefix : Type , depth : Int ): Unit =
100
98
// limit to 10 as failsafe for the odd case where there is an infinite cycle
101
99
if depth < 10 && prefix.exists then
102
- ud.registerUsed(prefix.classSymbol, None )
100
+ ud.registerUsed(prefix.classSymbol, None , prefix )
103
101
loopOnNormalizedPrefixes(prefix.normalizedPrefix, depth + 1 )
104
102
105
- loopOnNormalizedPrefixes(tree.typeOpt.normalizedPrefix, depth = 0 )
106
- ud.registerUsed(tree.symbol, Some (tree.name))
103
+ val prefix = tree.typeOpt.normalizedPrefix
104
+ loopOnNormalizedPrefixes(prefix, depth = 0 )
105
+ ud.registerUsed(tree.symbol, Some (tree.name), prefix)
107
106
}
108
107
else if tree.hasType then
109
- unusedDataApply(_.registerUsed(tree.tpe.classSymbol, Some (tree.name)))
108
+ unusedDataApply(_.registerUsed(tree.tpe.classSymbol, Some (tree.name), tree.tpe.normalizedPrefix ))
110
109
else
111
110
ctx
112
111
113
112
override def prepareForSelect (tree : tpd.Select )(using Context ): Context =
114
113
val name = tree.removeAttachment(OriginalName )
115
- unusedDataApply(_.registerUsed(tree.symbol, name, includeForImport = tree.qualifier.span.isSynthetic))
114
+ unusedDataApply(_.registerUsed(tree.symbol, name, tree.qualifier.tpe, includeForImport = tree.qualifier.span.isSynthetic))
116
115
117
116
override def prepareForBlock (tree : tpd.Block )(using Context ): Context =
118
117
pushInBlockTemplatePackageDef(tree)
@@ -125,12 +124,12 @@ class CheckUnused private (phaseMode: CheckUnused.PhaseMode, suffix: String, _ke
125
124
126
125
override def prepareForValDef (tree : tpd.ValDef )(using Context ): Context =
127
126
unusedDataApply{ud =>
128
- // do not register the ValDef generated for `object`
129
127
traverseAnnotations(tree.symbol)
128
+ // do not register the ValDef generated for `object`
130
129
if ! tree.symbol.is(Module ) then
131
130
ud.registerDef(tree)
132
- if tree.name.startsWith(" derived$" ) && tree.typeOpt != NoType then
133
- ud.registerUsed(tree.typeOpt .typeSymbol, None , isDerived = true )
131
+ if tree.name.startsWith(" derived$" ) && tree.hasType then
132
+ ud.registerUsed(tree.tpe .typeSymbol, None , tree.tpe.normalizedPrefix , isDerived = true )
134
133
ud.addIgnoredUsage(tree.symbol)
135
134
}
136
135
@@ -278,10 +277,10 @@ class CheckUnused private (phaseMode: CheckUnused.PhaseMode, suffix: String, _ke
278
277
/** This is a type traverser which catch some special Types not traversed by the term traverser above */
279
278
private def typeTraverser (dt : (UnusedData => Any ) => Unit )(using Context ) = new TypeTraverser :
280
279
override def traverse (tp : Type ): Unit =
281
- if tp.typeSymbol.exists then dt(_.registerUsed(tp.typeSymbol, Some (tp.typeSymbol.name)))
280
+ if tp.typeSymbol.exists then dt(_.registerUsed(tp.typeSymbol, Some (tp.typeSymbol.name), tp.normalizedPrefix ))
282
281
tp match
283
282
case AnnotatedType (_, annot) =>
284
- dt(_.registerUsed(annot.symbol, None ))
283
+ dt(_.registerUsed(annot.symbol, None , annot.symbol.info.normalizedPrefix ))
285
284
traverseChildren(tp)
286
285
case _ =>
287
286
traverseChildren(tp)
@@ -293,25 +292,23 @@ class CheckUnused private (phaseMode: CheckUnused.PhaseMode, suffix: String, _ke
293
292
294
293
/** Do the actual reporting given the result of the anaylsis */
295
294
private def reportUnused (res : UnusedData .UnusedResult )(using Context ): Unit =
296
- res.warnings.toList.sortBy(_.pos.span.point)(using Ordering [Int ]).foreach { s =>
297
- s match
298
- case UnusedSymbol (t, _, WarnTypes .Imports ) =>
299
- report.warning(UnusedSymbolMessage .imports, t)
300
- case UnusedSymbol (t, _, WarnTypes .LocalDefs ) =>
301
- report.warning(UnusedSymbolMessage .localDefs, t)
302
- case UnusedSymbol (t, _, WarnTypes .ExplicitParams ) =>
303
- report.warning(UnusedSymbolMessage .explicitParams, t)
304
- case UnusedSymbol (t, _, WarnTypes .ImplicitParams ) =>
305
- report.warning(UnusedSymbolMessage .implicitParams, t)
306
- case UnusedSymbol (t, _, WarnTypes .PrivateMembers ) =>
307
- report.warning(UnusedSymbolMessage .privateMembers, t)
308
- case UnusedSymbol (t, _, WarnTypes .PatVars ) =>
309
- report.warning(UnusedSymbolMessage .patVars, t)
310
- case UnusedSymbol (t, _, WarnTypes .UnsetLocals ) =>
311
- report.warning(" unset local variable, consider using an immutable val instead" , t)
312
- case UnusedSymbol (t, _, WarnTypes .UnsetPrivates ) =>
313
- report.warning(" unset private variable, consider using an immutable val instead" , t)
314
- }
295
+ res.warnings.toArray.sortInPlaceBy(_.pos.span.point).foreach:
296
+ case UnusedSymbol (t, _, WarnTypes .Imports ) =>
297
+ report.warning(UnusedSymbolMessage .imports, t)
298
+ case UnusedSymbol (t, _, WarnTypes .LocalDefs ) =>
299
+ report.warning(UnusedSymbolMessage .localDefs, t)
300
+ case UnusedSymbol (t, _, WarnTypes .ExplicitParams ) =>
301
+ report.warning(UnusedSymbolMessage .explicitParams, t)
302
+ case UnusedSymbol (t, _, WarnTypes .ImplicitParams ) =>
303
+ report.warning(UnusedSymbolMessage .implicitParams, t)
304
+ case UnusedSymbol (t, _, WarnTypes .PrivateMembers ) =>
305
+ report.warning(UnusedSymbolMessage .privateMembers, t)
306
+ case UnusedSymbol (t, _, WarnTypes .PatVars ) =>
307
+ report.warning(UnusedSymbolMessage .patVars, t)
308
+ case UnusedSymbol (t, _, WarnTypes .UnsetLocals ) =>
309
+ report.warning(" unset local variable, consider using an immutable val instead" , t)
310
+ case UnusedSymbol (t, _, WarnTypes .UnsetPrivates ) =>
311
+ report.warning(" unset private variable, consider using an immutable val instead" , t)
315
312
316
313
end CheckUnused
317
314
@@ -352,69 +349,58 @@ object CheckUnused:
352
349
* - usage
353
350
*/
354
351
private class UnusedData :
355
- import collection .mutable .{ Set => MutSet , Map => MutMap , Stack => MutStack , ListBuffer => MutList }
352
+ import collection .mutable as mut , mut . Stack , mut . ListBuffer
356
353
import UnusedData .*
357
354
358
355
/** The current scope during the tree traversal */
359
- val currScopeType : MutStack [ScopeType ] = MutStack (ScopeType .Other )
356
+ val currScopeType : Stack [ScopeType ] = Stack (ScopeType .Other )
360
357
361
358
var unusedAggregate : Option [UnusedResult ] = None
362
359
363
360
/* IMPORTS */
364
- private val impInScope = MutStack (MutList [ImportSelectorData ]())
365
- /**
366
- * We store the symbol along with their accessibility without import.
367
- * Accessibility to their definition in outer context/scope
368
- *
369
- * See the `isAccessibleAsIdent` extension method below in the file
370
- */
371
- private val usedInScope = MutStack (MutSet [(Symbol , Option [Name ], Boolean )]())
372
- private val usedInPosition = MutMap .empty[Name , MutSet [Symbol ]]
361
+ private val impInScope = Stack (ListBuffer .empty[ImportSelectorData ])
362
+ private val usedInScope = Stack (mut.Set .empty[Usage ])
363
+ private val usedInPosition = mut.Map .empty[Name , mut.Set [Symbol ]]
373
364
/* unused import collected during traversal */
374
- private val unusedImport = MutList .empty[ImportSelectorData ]
365
+ private val unusedImport = ListBuffer .empty[ImportSelectorData ]
375
366
376
367
/* LOCAL DEF OR VAL / Private Def or Val / Pattern variables */
377
- private val localDefInScope = MutList .empty[tpd.MemberDef ]
378
- private val privateDefInScope = MutList .empty[tpd.MemberDef ]
379
- private val explicitParamInScope = MutList .empty[tpd.MemberDef ]
380
- private val implicitParamInScope = MutList .empty[tpd.MemberDef ]
381
- private val patVarsInScope = MutList .empty[tpd.Bind ]
368
+ private val localDefInScope = ListBuffer .empty[tpd.MemberDef ]
369
+ private val privateDefInScope = ListBuffer .empty[tpd.MemberDef ]
370
+ private val explicitParamInScope = ListBuffer .empty[tpd.MemberDef ]
371
+ private val implicitParamInScope = ListBuffer .empty[tpd.MemberDef ]
372
+ private val patVarsInScope = ListBuffer .empty[tpd.Bind ]
382
373
383
374
/** All variables sets*/
384
- private val setVars = MutSet [Symbol ]()
375
+ private val setVars = mut. Set .empty [Symbol ]
385
376
386
377
/** All used symbols */
387
- private val usedDef = MutSet [Symbol ]()
378
+ private val usedDef = mut. Set .empty [Symbol ]
388
379
/** Do not register as used */
389
- private val doNotRegister = MutSet [Symbol ]()
380
+ private val doNotRegister = mut. Set .empty [Symbol ]
390
381
391
382
/** Trivial definitions, avoid registering params */
392
- private val trivialDefs = MutSet [Symbol ]()
393
-
394
- private val paramsToSkip = MutSet [Symbol ]()
383
+ private val trivialDefs = mut.Set .empty[Symbol ]
395
384
385
+ private val paramsToSkip = mut.Set .empty[Symbol ]
396
386
397
387
def finishAggregation (using Context )(): Unit =
398
- val unusedInThisStage = this .getUnused
399
- this .unusedAggregate match {
388
+ unusedAggregate = unusedAggregate match
400
389
case None =>
401
- this .unusedAggregate = Some (unusedInThisStage )
390
+ Some (getUnused )
402
391
case Some (prevUnused) =>
403
- val intersection = unusedInThisStage.warnings.intersect(prevUnused.warnings)
404
- this .unusedAggregate = Some (UnusedResult (intersection))
405
- }
406
-
392
+ Some (UnusedResult (getUnused.warnings.intersect(prevUnused.warnings)))
407
393
408
394
/**
409
395
* Register a found (used) symbol along with its name
410
396
*
411
397
* The optional name will be used to target the right import
412
398
* as the same element can be imported with different renaming
413
399
*/
414
- def registerUsed (sym : Symbol , name : Option [Name ], includeForImport : Boolean = true , isDerived : Boolean = false )(using Context ): Unit =
400
+ def registerUsed (sym : Symbol , name : Option [Name ], prefix : Type = NoType , includeForImport : Boolean = true , isDerived : Boolean = false )(using Context ): Unit =
415
401
if sym.exists && ! isConstructorOfSynth(sym) && ! doNotRegister(sym) then
416
402
if sym.isConstructor then
417
- registerUsed(sym.owner, None , includeForImport) // constructor are "implicitly" imported with the class
403
+ registerUsed(sym.owner, None , prefix, includeForImport) // constructor are "implicitly" imported with the class
418
404
else
419
405
// If the symbol is accessible in this scope without an import, do not register it for unused import analysis
420
406
val includeForImport1 =
@@ -425,13 +411,13 @@ object CheckUnused:
425
411
if sym.exists then
426
412
usedDef += sym
427
413
if includeForImport1 then
428
- usedInScope.top += (( sym, name, isDerived) )
414
+ usedInScope.top += Usage ( sym, name, prefix, isDerived)
429
415
addIfExists(sym)
430
416
addIfExists(sym.companionModule)
431
417
addIfExists(sym.companionClass)
432
418
if sym.sourcePos.exists then
433
419
for n <- name do
434
- usedInPosition.getOrElseUpdate(n, MutSet .empty) += sym
420
+ usedInPosition.getOrElseUpdate(n, mut. Set .empty) += sym
435
421
436
422
/** Register a symbol that should be ignored */
437
423
def addIgnoredUsage (sym : Symbol )(using Context ): Unit =
@@ -455,12 +441,12 @@ object CheckUnused:
455
441
val qualTpe = imp.expr.tpe
456
442
457
443
// Put wildcard imports at the end, because they have lower priority within one Import
458
- val reorderdSelectors =
444
+ val reorderedSelectors =
459
445
val (wildcardSels, nonWildcardSels) = imp.selectors.partition(_.isWildcard)
460
446
nonWildcardSels ::: wildcardSels
461
447
462
448
val newDataInScope =
463
- for sel <- reorderdSelectors yield
449
+ for sel <- reorderedSelectors yield
464
450
val data = new ImportSelectorData (qualTpe, sel)
465
451
if shouldSelectorBeReported(imp, sel) || isImportExclusion(sel) || isImportIgnored(imp, sel) then
466
452
// Immediately mark the selector as used
@@ -492,8 +478,8 @@ object CheckUnused:
492
478
def pushScope (newScopeType : ScopeType ): Unit =
493
479
// unused imports :
494
480
currScopeType.push(newScopeType)
495
- impInScope.push(MutList () )
496
- usedInScope.push(MutSet () )
481
+ impInScope.push(ListBuffer .empty )
482
+ usedInScope.push(mut. Set .empty )
497
483
498
484
def registerSetVar (sym : Symbol ): Unit =
499
485
setVars += sym
@@ -509,18 +495,15 @@ object CheckUnused:
509
495
val selDatas = impInScope.pop()
510
496
511
497
for usedInfo <- usedInfos do
512
- val (sym, optName, isDerived) = usedInfo
513
- val usedData = selDatas.find { selData =>
514
- sym.isInImport(selData, optName, isDerived)
515
- }
516
- usedData match
517
- case Some (data) =>
518
- data.markUsed()
498
+ val Usage (sym, optName, prefix, isDerived) = usedInfo
499
+ selDatas.find(sym.isInImport(_, optName, prefix, isDerived)) match
500
+ case Some (sel) =>
501
+ sel.markUsed()
519
502
case None =>
520
503
// Propagate the symbol one level up
521
504
if usedInScope.nonEmpty then
522
505
usedInScope.top += usedInfo
523
- end for // each in `used`
506
+ end for // each in usedInfos
524
507
525
508
for selData <- selDatas do
526
509
if ! selData.isUsed then
@@ -697,15 +680,14 @@ object CheckUnused:
697
680
extension (sym : Symbol )
698
681
/** is accessible without import in current context */
699
682
private def isAccessibleAsIdent (using Context ): Boolean =
700
- ctx.outersIterator.exists{ c =>
683
+ ctx.outersIterator.exists: c =>
701
684
c.owner == sym.owner
702
685
|| sym.owner.isClass && c.owner.isClass
703
686
&& c.owner.thisType.baseClasses.contains(sym.owner)
704
687
&& c.owner.thisType.member(sym.name).alternatives.contains(sym)
705
- }
706
688
707
689
/** Given an import and accessibility, return selector that matches import<->symbol */
708
- private def isInImport (selData : ImportSelectorData , altName : Option [Name ], isDerived : Boolean )(using Context ): Boolean =
690
+ private def isInImport (selData : ImportSelectorData , altName : Option [Name ], prefix : Type , isDerived : Boolean )(using Context ): Boolean =
709
691
assert(sym.exists)
710
692
711
693
val selector = selData.selector
@@ -715,11 +697,13 @@ object CheckUnused:
715
697
// if there is an explicit name, it must match
716
698
false
717
699
else
718
- if isDerived then
719
- // See i15503i.scala, grep for "package foo.test.i17156"
720
- selData.allSymbolsDealiasedForNamed.contains(dealias(sym))
721
- else
722
- selData.allSymbolsForNamed.contains(sym)
700
+ (isDerived || prefix.typeSymbol.isPackageObject || selData.qualTpe =:= prefix) && (
701
+ if isDerived then
702
+ // See i15503i.scala, grep for "package foo.test.i17156"
703
+ selData.allSymbolsDealiasedForNamed.contains(dealias(sym))
704
+ else
705
+ selData.allSymbolsForNamed.contains(sym)
706
+ )
723
707
else
724
708
// Wildcard
725
709
if ! selData.qualTpe.member(sym.name).hasAltWith(_.symbol == sym) then
@@ -730,6 +714,7 @@ object CheckUnused:
730
714
// Further check that the symbol is a given or implicit and conforms to the bound
731
715
sym.isOneOf(Given | Implicit )
732
716
&& (selector.bound.isEmpty || sym.info.finalResultType <:< selector.boundTpe)
717
+ && selData.qualTpe =:= prefix
733
718
else
734
719
// Normal wildcard, check that the symbol is not a given (but can be implicit)
735
720
! sym.is(Given )
@@ -833,7 +818,7 @@ object CheckUnused:
833
818
case _:tpd.Block => Local
834
819
case _ => Other
835
820
836
- final class ImportSelectorData (val qualTpe : Type , val selector : ImportSelector ):
821
+ final case class ImportSelectorData (val qualTpe : Type , val selector : ImportSelector ):
837
822
private var myUsed : Boolean = false
838
823
839
824
def markUsed (): Unit = myUsed = true
@@ -861,6 +846,11 @@ object CheckUnused:
861
846
case class UnusedResult (warnings : Set [UnusedSymbol ])
862
847
object UnusedResult :
863
848
val Empty = UnusedResult (Set .empty)
849
+
850
+ /** A symbol usage includes the name under which it was observed,
851
+ * the prefix from which it was selected, and whether it is in a derived element.
852
+ */
853
+ case class Usage (symbol : Symbol , name : Option [Name ], prefix : Type , isDerived : Boolean )
864
854
end UnusedData
865
855
866
856
private def dealias (symbol : Symbol )(using Context ): Symbol =
0 commit comments