Skip to content

Commit 9588eaf

Browse files
authored
Merge pull request #308 from scala/backport-lts-3.3-22751
Backport "Restore resolving prefixes of implicit Ident" to 3.3 LTS
2 parents 30b1338 + f8cb874 commit 9588eaf

File tree

3 files changed

+65
-16
lines changed

3 files changed

+65
-16
lines changed

compiler/src/dotty/tools/dotc/transform/CheckUnused.scala

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -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

tests/warn/i22681.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ class C:
88
def f: Runnable { def u: Int } = new Runnable with T:
99
private def v = 42 // avoid g judged too trivial to warn
1010
def run() = ()
11-
def g = v // LTS specific: no warn, althougheffectively private member is unused
11+
def g = v // warn, effectively private member is unused
1212
def t = v // nowarn
1313
def u = v // nowarn because leaked by refinement
1414
val v: Runnable { def u: Int } = new Runnable:

tests/warn/i22744.scala

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
2+
//> using options -Wunused:privates -Werror
3+
4+
object test {
5+
private trait Foo[A] { val value: A }
6+
7+
private object Foo { // no warn prefix of implicit value
8+
given int: Foo[Int] = new Foo[Int] { val value = 1 }
9+
}
10+
11+
val i = summon[Foo[Int]].value
12+
}
13+
14+
object supplement {
15+
private trait Foo[A] { val value: A }
16+
17+
private object Foo { // no warn prefix of implicit value
18+
given int: Foo[Int] = new Foo[Int] { val value = 1 }
19+
}
20+
21+
private def fooValue[A](using f: Foo[A]): A = f.value
22+
23+
val i = fooValue[Int]
24+
}
25+
26+
package p:
27+
private trait Foo[A] { val value: A }
28+
29+
private object Foo { // no warn prefix of implicit value
30+
given int: Foo[Int] = new Foo[Int] { val value = 1 }
31+
}
32+
33+
private def fooValue[A](using f: Foo[A]): A = f.value
34+
35+
val i = fooValue[Int]

0 commit comments

Comments
 (0)