@@ -58,6 +58,12 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha
58
58
|| tree.srcPos.isZeroExtentSynthetic
59
59
|| refInfos.inlined.exists(_.sourcePos.contains(tree.srcPos.sourcePos))
60
60
if resolving && ! ignoreTree(tree) then
61
+ def loopOverPrefixes (prefix : Type , depth : Int ): Unit =
62
+ if depth < 10 && prefix.exists && ! prefix.classSymbol.isEffectiveRoot then
63
+ resolveUsage(prefix.classSymbol, nme.NO_NAME , NoPrefix )
64
+ loopOverPrefixes(prefix.normalizedPrefix, depth + 1 )
65
+ if tree.srcPos.isZeroExtentSynthetic then
66
+ loopOverPrefixes(tree.typeOpt.normalizedPrefix, depth = 0 )
61
67
resolveUsage(tree.symbol, tree.name, tree.typeOpt.importPrefix.skipPackageObject)
62
68
else if tree.hasType then
63
69
resolveUsage(tree.tpe.classSymbol, tree.name, tree.tpe.importPrefix.skipPackageObject)
@@ -116,7 +122,7 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha
116
122
tree
117
123
118
124
override def prepareForMatch (tree : Match )(using Context ): Context =
119
- // exonerate case.pat against tree.selector (simple var pat only for now)
125
+ // allow case.pat against tree.selector (simple var pat only for now)
120
126
tree.selector match
121
127
case Ident (nm) => tree.cases.foreach(k => allowVariableBindings(List (nm), List (k.pat)))
122
128
case _ =>
@@ -138,9 +144,9 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha
138
144
refInfos.inlined.push(tree.call.srcPos)
139
145
ctx
140
146
override def transformInlined (tree : Inlined )(using Context ): tree.type =
147
+ transformAllDeep(tree.expansion) // traverse expansion with nonempty inlined stack to avoid registering defs
141
148
val _ = refInfos.inlined.pop()
142
- if ! tree.call.isEmpty && phaseMode.eq(PhaseMode .Aggregate ) then
143
- transformAllDeep(tree.call)
149
+ transformAllDeep(tree.call)
144
150
tree
145
151
146
152
override def prepareForBind (tree : Bind )(using Context ): Context =
@@ -158,11 +164,7 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha
158
164
traverseAnnotations(tree.symbol)
159
165
if tree.name.startsWith(" derived$" ) && tree.hasType then
160
166
def loop (t : Tree ): Unit = t match
161
- case Ident (name) =>
162
- val target =
163
- val ts0 = t.tpe.typeSymbol
164
- if ts0.is(ModuleClass ) then ts0.companionModule else ts0
165
- resolveUsage(target, name, t.tpe.underlyingPrefix.skipPackageObject)
167
+ case Ident (name) => resolveUsage(t.tpe.typeSymbol, name, t.tpe.underlyingPrefix.skipPackageObject)
166
168
case Select (t, _) => loop(t)
167
169
case _ =>
168
170
tree.getAttachment(OriginalTypeClass ).foreach(loop)
@@ -281,8 +283,9 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha
281
283
* For Select, lint does not look up `<empty>.scala` (so top-level syms look like magic) but records `scala.Int`.
282
284
* For Ident, look-up finds the root import as usual. A competing import is OK because higher precedence.
283
285
*/
284
- def resolveUsage (sym : Symbol , name : Name , prefix : Type )(using Context ): Unit =
286
+ def resolveUsage (sym0 : Symbol , name : Name , prefix : Type )(using Context ): Unit =
285
287
import PrecedenceLevels .*
288
+ val sym = sym0.userSymbol
286
289
287
290
def matchingSelector (info : ImportInfo ): ImportSelector | Null =
288
291
val qtpe = info.site
@@ -328,7 +331,7 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha
328
331
329
332
// Avoid spurious NoSymbol and also primary ctors which are never warned about.
330
333
// Selections C.this.toString should be already excluded, but backtopped here for eq, etc.
331
- if ! sym.exists || sym.isPrimaryConstructor || defn.topClasses(sym.owner) then return
334
+ if ! sym.exists || sym.isPrimaryConstructor || sym.isEffectiveRoot || defn.topClasses(sym.owner) then return
332
335
333
336
// Find the innermost, highest precedence. Contexts have no nesting levels but assume correctness.
334
337
// If the sym is an enclosing definition (the owner of a context), it does not count toward usages.
@@ -463,11 +466,15 @@ object CheckUnused:
463
466
if ! tree.name.isInstanceOf [DerivedName ] then
464
467
pats.addOne((tree.symbol, tree.namePos))
465
468
case tree : NamedDefTree =>
466
- if (tree.symbol ne NoSymbol ) && ! tree.name.isWildcard && ! tree.hasAttachment(NoWarn ) then
467
- defs.addOne((tree.symbol, tree.namePos))
469
+ if (tree.symbol ne NoSymbol )
470
+ && ! tree.name.isWildcard
471
+ && ! tree.hasAttachment(NoWarn )
472
+ && ! tree.symbol.is(ModuleVal ) // track only the ModuleClass using the object symbol, with correct namePos
473
+ then
474
+ defs.addOne((tree.symbol.userSymbol, tree.namePos))
468
475
case _ =>
469
476
if tree.symbol ne NoSymbol then
470
- defs.addOne((tree.symbol, tree.srcPos))
477
+ defs.addOne((tree.symbol, tree.srcPos)) // TODO is this a code path
471
478
472
479
val inlined = Stack .empty[SrcPos ] // enclosing call.srcPos of inlined code (expansions)
473
480
var inliners = 0 // depth of inline def (not inlined yet)
@@ -906,6 +913,9 @@ object CheckUnused:
906
913
def isEffectivelyPrivate (using Context ): Boolean =
907
914
sym.is(Private , butNot = ParamAccessor )
908
915
|| sym.owner.isAnonymousClass && ! sym.nextOverriddenSymbol.exists
916
+ // pick the symbol the user wrote for purposes of tracking
917
+ inline def userSymbol (using Context ): Symbol =
918
+ if sym.denot.is(ModuleClass ) then sym.denot.companionModule else sym
909
919
910
920
extension (sel : ImportSelector )
911
921
def boundTpe : Type = sel.bound match
0 commit comments