@@ -72,19 +72,19 @@ class CheckUnused private (phaseMode: CheckUnused.PhaseMode, suffix: String, _ke
72
72
def loopOnNormalizedPrefixes (prefix : Type , depth : Int ): Unit =
73
73
// limit to 10 as failsafe for the odd case where there is an infinite cycle
74
74
if depth < 10 && prefix.exists then
75
- ud.registerUsed(prefix.classSymbol, name = None , prefix)
75
+ ud.registerUsed(prefix.classSymbol, name = None , prefix, tree = tree )
76
76
loopOnNormalizedPrefixes(prefix.normalizedPrefix, depth + 1 )
77
77
val prefix = tree.typeOpt.normalizedPrefix
78
78
loopOnNormalizedPrefixes(prefix, depth = 0 )
79
- ud.registerUsed(tree.symbol, Some (tree.name), tree.typeOpt.importPrefix.skipPackageObject)
79
+ ud.registerUsed(tree.symbol, Some (tree.name), tree.typeOpt.importPrefix.skipPackageObject, tree = tree )
80
80
else if tree.hasType then
81
- ud.registerUsed(tree.tpe.classSymbol, Some (tree.name), tree.tpe.importPrefix.skipPackageObject)
81
+ ud.registerUsed(tree.tpe.classSymbol, Some (tree.name), tree.tpe.importPrefix.skipPackageObject, tree = tree )
82
82
tree
83
83
84
84
override def transformSelect (tree : Select )(using Context ): tree.type =
85
85
preparing :
86
86
val name = tree.removeAttachment(OriginalName )
87
- ud.registerUsed(tree.symbol, name, tree.qualifier.tpe, includeForImport = tree.qualifier.span.isSynthetic)
87
+ ud.registerUsed(tree.symbol, name, tree.qualifier.tpe, includeForImport = tree.qualifier.span.isSynthetic, tree = tree )
88
88
tree
89
89
90
90
override def transformApply (tree : Apply )(using Context ): Tree =
@@ -144,7 +144,7 @@ class CheckUnused private (phaseMode: CheckUnused.PhaseMode, suffix: String, _ke
144
144
tree.getAttachment(OriginalTypeClass ) match
145
145
case Some (orig) =>
146
146
val (typsym, name, prefix) = core(orig)
147
- ud.registerUsed(typsym, name, prefix.skipPackageObject)
147
+ ud.registerUsed(typsym, name, prefix.skipPackageObject, tree = EmptyTree )
148
148
case _ =>
149
149
ud.removeIgnoredUsage(tree.symbol)
150
150
tree
@@ -176,7 +176,7 @@ class CheckUnused private (phaseMode: CheckUnused.PhaseMode, suffix: String, _ke
176
176
tree
177
177
178
178
override def prepareForTemplate (tree : Template )(using Context ): Context =
179
- pushScope(tree)
179
+ pushScope(tree, tree.parents )
180
180
181
181
override def transformTemplate (tree : Template )(using Context ): Tree =
182
182
popScope(tree)
@@ -229,9 +229,9 @@ class CheckUnused private (phaseMode: CheckUnused.PhaseMode, suffix: String, _ke
229
229
// println(s"OTHER ${tree.getClass} ${tree.show}")
230
230
tree
231
231
232
- private def pushScope (tree : Block | Template | PackageDef )(using Context ): Context =
232
+ private def pushScope (tree : Block | Template | PackageDef , parents : List [ Tree ] = Nil )(using Context ): Context =
233
233
preparing :
234
- ud.pushScope(UnusedData .ScopeType .fromTree(tree))
234
+ ud.pushScope(UnusedData .ScopeType .fromTree(tree), parents )
235
235
236
236
private def popScope (tree : Block | Template | PackageDef )(using Context ): Context =
237
237
preparing :
@@ -307,14 +307,17 @@ object CheckUnused:
307
307
308
308
var unusedAggregate : Option [UnusedResult ] = None
309
309
310
- /* IMPORTS */
310
+ // Trees of superclass constructors, i.e., template.parents when currScopeType is Template.
311
+ // Ideally, Context would supply correct context and scope; instead, trees in superclass context
312
+ // are promoted to "enclosing scope" by popScope. (This is just for import usage, so class params are ignored.)
313
+ private val parents = Stack (List .empty[Tree ])
314
+
311
315
private val impInScope = Stack (ListBuffer .empty[ImportSelectorData ])
312
316
private val usedInScope = Stack (mut.Map .empty[Symbol , ListBuffer [Usage ]])
313
317
private val usedInPosition = mut.Map .empty[Name , mut.Set [Symbol ]]
314
318
/* unused import collected during traversal */
315
319
private val unusedImport = ListBuffer .empty[ImportSelectorData ]
316
320
317
- /* LOCAL DEF OR VAL / Private Def or Val / Pattern variables */
318
321
private val localDefInScope = ListBuffer .empty[MemberDef ]
319
322
private val privateDefInScope = ListBuffer .empty[MemberDef ]
320
323
private val explicitParamInScope = ListBuffer .empty[MemberDef ]
@@ -355,11 +358,11 @@ object CheckUnused:
355
358
* The optional name will be used to target the right import
356
359
* as the same element can be imported with different renaming.
357
360
*/
358
- def registerUsed (sym : Symbol , name : Option [Name ], prefix : Type = NoType , includeForImport : Boolean = true )(using Context ): Unit =
361
+ def registerUsed (sym : Symbol , name : Option [Name ], prefix : Type = NoType , includeForImport : Boolean = true , tree : Tree )(using Context ): Unit =
359
362
if sym.exists && ! isConstructorOfSynth(sym) && ! doNotRegister(sym) && ! doNotRegisterPrefix(prefix.typeSymbol) then
360
363
if sym.isConstructor then
361
364
// constructors are "implicitly" imported with the class
362
- registerUsed(sym.owner, name = None , prefix, includeForImport = includeForImport)
365
+ registerUsed(sym.owner, name = None , prefix, includeForImport = includeForImport, tree = tree )
363
366
else
364
367
// If the symbol is accessible in this scope without an import, do not register it for unused import analysis
365
368
val includeForImport1 =
@@ -369,7 +372,7 @@ object CheckUnused:
369
372
if sym.exists then
370
373
usedDef += sym
371
374
if includeForImport1 then
372
- addUsage(Usage (sym, name, prefix))
375
+ addUsage(Usage (sym, name, prefix, isSuper = ! tree.isEmpty && parents.top.exists(t => t.find(_ eq tree).isDefined) ))
373
376
addIfExists(sym)
374
377
addIfExists(sym.companionModule)
375
378
addIfExists(sym.companionClass)
@@ -379,7 +382,7 @@ object CheckUnused:
379
382
380
383
def addUsage (usage : Usage )(using Context ): Unit =
381
384
val usages = usedInScope.top.getOrElseUpdate(usage.symbol, ListBuffer .empty)
382
- if ! usages.exists(x => x.name == usage.name && x.prefix =:= usage.prefix)
385
+ if ! usages.exists(x => x.name == usage.name && x.prefix =:= usage.prefix && x.isSuper == usage.isSuper )
383
386
then usages += usage
384
387
385
388
/** Register a symbol that should be ignored */
@@ -437,10 +440,11 @@ object CheckUnused:
437
440
patVarsInScope += patvar
438
441
439
442
/** enter a new scope */
440
- def pushScope (newScopeType : ScopeType ): Unit =
443
+ def pushScope (newScopeType : ScopeType , parents : List [ Tree ] ): Unit =
441
444
currScopeType.push(newScopeType)
442
445
impInScope.push(ListBuffer .empty)
443
446
usedInScope.push(mut.Map .empty)
447
+ this .parents.push(parents)
444
448
445
449
def registerSetVar (sym : Symbol ): Unit =
446
450
setVars += sym
@@ -453,7 +457,9 @@ object CheckUnused:
453
457
454
458
for usedInfos <- usedInScope.pop().valuesIterator; usedInfo <- usedInfos do
455
459
import usedInfo .*
456
- selDatas.find(symbol.isInImport(_, name, prefix)) match
460
+ if isSuper then
461
+ addUsage(Usage (symbol, name, prefix, isSuper = false )) // approximate superclass context
462
+ else selDatas.find(symbol.isInImport(_, name, prefix)) match
457
463
case Some (sel) =>
458
464
sel.markUsed()
459
465
case None =>
@@ -465,6 +471,8 @@ object CheckUnused:
465
471
for selData <- selDatas do
466
472
if ! selData.isUsed then
467
473
unusedImport += selData
474
+
475
+ this .parents.pop()
468
476
end popScope
469
477
470
478
/** Leave the scope and return a result set of warnings.
@@ -746,7 +754,7 @@ object CheckUnused:
746
754
/** A symbol usage includes the name under which it was observed,
747
755
* and the prefix from which it was selected.
748
756
*/
749
- case class Usage (val symbol : Symbol , val name : Option [Name ], val prefix : Type )
757
+ class Usage (val symbol : Symbol , val name : Option [Name ], val prefix : Type , val isSuper : Boolean )
750
758
end UnusedData
751
759
extension (sym : Symbol )
752
760
/** is accessible without import in current context */
0 commit comments