@@ -58,6 +58,12 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha
5858 || tree.srcPos.isZeroExtentSynthetic
5959 || refInfos.inlined.exists(_.sourcePos.contains(tree.srcPos.sourcePos))
6060 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 )
6167 resolveUsage(tree.symbol, tree.name, tree.typeOpt.importPrefix.skipPackageObject)
6268 else if tree.hasType then
6369 resolveUsage(tree.tpe.classSymbol, tree.name, tree.tpe.importPrefix.skipPackageObject)
@@ -116,7 +122,7 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha
116122 tree
117123
118124 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)
120126 tree.selector match
121127 case Ident (nm) => tree.cases.foreach(k => allowVariableBindings(List (nm), List (k.pat)))
122128 case _ =>
@@ -138,9 +144,9 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha
138144 refInfos.inlined.push(tree.call.srcPos)
139145 ctx
140146 override def transformInlined (tree : Inlined )(using Context ): tree.type =
147+ transformAllDeep(tree.expansion) // traverse expansion with nonempty inlined stack to avoid registering defs
141148 val _ = refInfos.inlined.pop()
142- if ! tree.call.isEmpty && phaseMode.eq(PhaseMode .Aggregate ) then
143- transformAllDeep(tree.call)
149+ transformAllDeep(tree.call)
144150 tree
145151
146152 override def prepareForBind (tree : Bind )(using Context ): Context =
@@ -158,11 +164,7 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha
158164 traverseAnnotations(tree.symbol)
159165 if tree.name.startsWith(" derived$" ) && tree.hasType then
160166 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)
166168 case Select (t, _) => loop(t)
167169 case _ =>
168170 tree.getAttachment(OriginalTypeClass ).foreach(loop)
@@ -272,8 +274,9 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha
272274 * For Select, lint does not look up `<empty>.scala` (so top-level syms look like magic) but records `scala.Int`.
273275 * For Ident, look-up finds the root import as usual. A competing import is OK because higher precedence.
274276 */
275- def resolveUsage (sym : Symbol , name : Name , prefix : Type )(using Context ): Unit =
277+ def resolveUsage (sym0 : Symbol , name : Name , prefix : Type )(using Context ): Unit =
276278 import PrecedenceLevels .*
279+ val sym = sym0.userSymbol
277280
278281 def matchingSelector (info : ImportInfo ): ImportSelector | Null =
279282 val qtpe = info.site
@@ -319,7 +322,7 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha
319322
320323 // Avoid spurious NoSymbol and also primary ctors which are never warned about.
321324 // Selections C.this.toString should be already excluded, but backtopped here for eq, etc.
322- if ! sym.exists || sym.isPrimaryConstructor || defn.topClasses(sym.owner) then return
325+ if ! sym.exists || sym.isPrimaryConstructor || sym.isEffectiveRoot || defn.topClasses(sym.owner) then return
323326
324327 // Find the innermost, highest precedence. Contexts have no nesting levels but assume correctness.
325328 // If the sym is an enclosing definition (the owner of a context), it does not count toward usages.
@@ -454,11 +457,15 @@ object CheckUnused:
454457 if ! tree.name.isInstanceOf [DerivedName ] then
455458 pats.addOne((tree.symbol, tree.namePos))
456459 case tree : NamedDefTree =>
457- if (tree.symbol ne NoSymbol ) && ! tree.name.isWildcard && ! tree.hasAttachment(NoWarn ) then
458- defs.addOne((tree.symbol, tree.namePos))
460+ if (tree.symbol ne NoSymbol )
461+ && ! tree.name.isWildcard
462+ && ! tree.hasAttachment(NoWarn )
463+ && ! tree.symbol.is(ModuleVal ) // track only the ModuleClass using the object symbol, with correct namePos
464+ then
465+ defs.addOne((tree.symbol.userSymbol, tree.namePos))
459466 case _ =>
460467 if tree.symbol ne NoSymbol then
461- defs.addOne((tree.symbol, tree.srcPos))
468+ defs.addOne((tree.symbol, tree.srcPos)) // TODO is this a code path
462469
463470 val inlined = Stack .empty[SrcPos ] // enclosing call.srcPos of inlined code (expansions)
464471 var inliners = 0 // depth of inline def (not inlined yet)
@@ -518,7 +525,7 @@ object CheckUnused:
518525 def checkPrivate (sym : Symbol , pos : SrcPos ) =
519526 if ctx.settings.WunusedHas .privates
520527 && ! sym.isPrimaryConstructor
521- && sym.is( Private , butNot = SelfName | Synthetic | CaseAccessor )
528+ && ! sym.isOneOf( SelfName | Synthetic | CaseAccessor )
522529 && ! sym.name.is(BodyRetainerName )
523530 && ! sym.isSerializationSupport
524531 && ! (sym.is(Mutable ) && sym.isSetter && sym.owner.is(Trait )) // tracks sym.underlyingSymbol sibling getter
@@ -763,7 +770,7 @@ object CheckUnused:
763770 for (sym, pos) <- infos.defs.iterator if ! sym.hasAnnotation(defn.UnusedAnnot ) do
764771 if infos.refs(sym) then
765772 checkUnassigned(sym, pos)
766- else if sym.is( Private , butNot = ParamAccessor ) then
773+ else if sym.isEffectivelyPrivate then
767774 checkPrivate(sym, pos)
768775 else if sym.is(Param , butNot = Given | Implicit ) then
769776 checkParam(sym, pos)
@@ -903,6 +910,13 @@ object CheckUnused:
903910 val base = if owner.classInfo.selfInfo != NoType then owner.thisType else owner.info
904911 base.baseClasses.drop(1 ).iterator.exists(sym.overriddenSymbol(_).exists)
905912 }
913+ def isEffectivelyPrivate : Boolean =
914+ sym.is(Private , butNot = ParamAccessor )
915+ || sym.owner.isAnonymousClass && ! sym.nextOverriddenSymbol.exists
916+ // pick the symbol the user wrote for purposes of tracking
917+ inline def userSymbol : Symbol =
918+ if sym.denot.is(ModuleClass ) then sym.denot.companionModule else sym
919+
906920 extension (sel : ImportSelector )
907921 def boundTpe : Type = sel.bound match
908922 case untpd.TypedSplice (tree) => tree.tpe
0 commit comments