From d1852bf26f84cae03674755f77006b2ac6fe7e35 Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Fri, 7 Mar 2025 10:48:54 -0800 Subject: [PATCH] Restore resolving prefixes of implicit Ident --- .../tools/dotc/transform/CheckUnused.scala | 36 ++++++++++++------- tests/warn/i22744.scala | 35 ++++++++++++++++++ 2 files changed, 58 insertions(+), 13 deletions(-) create mode 100644 tests/warn/i22744.scala diff --git a/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala b/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala index 2611652a110b..4dfaea70cb50 100644 --- a/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala +++ b/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala @@ -58,6 +58,12 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha || tree.srcPos.isZeroExtentSynthetic || refInfos.inlined.exists(_.sourcePos.contains(tree.srcPos.sourcePos)) if resolving && !ignoreTree(tree) then + def loopOverPrefixes(prefix: Type, depth: Int): Unit = + if depth < 10 && prefix.exists && !prefix.classSymbol.isEffectiveRoot then + resolveUsage(prefix.classSymbol, nme.NO_NAME, NoPrefix) + loopOverPrefixes(prefix.normalizedPrefix, depth + 1) + if tree.srcPos.isZeroExtentSynthetic then + loopOverPrefixes(tree.typeOpt.normalizedPrefix, depth = 0) resolveUsage(tree.symbol, tree.name, tree.typeOpt.importPrefix.skipPackageObject) else if tree.hasType then resolveUsage(tree.tpe.classSymbol, tree.name, tree.tpe.importPrefix.skipPackageObject) @@ -116,7 +122,7 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha tree override def prepareForMatch(tree: Match)(using Context): Context = - // exonerate case.pat against tree.selector (simple var pat only for now) + // allow case.pat against tree.selector (simple var pat only for now) tree.selector match case Ident(nm) => tree.cases.foreach(k => allowVariableBindings(List(nm), List(k.pat))) case _ => @@ -138,9 +144,9 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha refInfos.inlined.push(tree.call.srcPos) ctx override def transformInlined(tree: Inlined)(using Context): tree.type = + transformAllDeep(tree.expansion) // traverse expansion with nonempty inlined stack to avoid registering defs val _ = refInfos.inlined.pop() - if !tree.call.isEmpty && phaseMode.eq(PhaseMode.Aggregate) then - transformAllDeep(tree.call) + transformAllDeep(tree.call) tree override def prepareForBind(tree: Bind)(using Context): Context = @@ -158,11 +164,7 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha traverseAnnotations(tree.symbol) if tree.name.startsWith("derived$") && tree.hasType then def loop(t: Tree): Unit = t match - case Ident(name) => - val target = - val ts0 = t.tpe.typeSymbol - if ts0.is(ModuleClass) then ts0.companionModule else ts0 - resolveUsage(target, name, t.tpe.underlyingPrefix.skipPackageObject) + case Ident(name) => resolveUsage(t.tpe.typeSymbol, name, t.tpe.underlyingPrefix.skipPackageObject) case Select(t, _) => loop(t) case _ => tree.getAttachment(OriginalTypeClass).foreach(loop) @@ -281,8 +283,9 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha * For Select, lint does not look up `.scala` (so top-level syms look like magic) but records `scala.Int`. * For Ident, look-up finds the root import as usual. A competing import is OK because higher precedence. */ - def resolveUsage(sym: Symbol, name: Name, prefix: Type)(using Context): Unit = + def resolveUsage(sym0: Symbol, name: Name, prefix: Type)(using Context): Unit = import PrecedenceLevels.* + val sym = sym0.userSymbol def matchingSelector(info: ImportInfo): ImportSelector | Null = val qtpe = info.site @@ -328,7 +331,7 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha // Avoid spurious NoSymbol and also primary ctors which are never warned about. // Selections C.this.toString should be already excluded, but backtopped here for eq, etc. - if !sym.exists || sym.isPrimaryConstructor || defn.topClasses(sym.owner) then return + if !sym.exists || sym.isPrimaryConstructor || sym.isEffectiveRoot || defn.topClasses(sym.owner) then return // Find the innermost, highest precedence. Contexts have no nesting levels but assume correctness. // If the sym is an enclosing definition (the owner of a context), it does not count toward usages. @@ -463,11 +466,15 @@ object CheckUnused: if !tree.name.isInstanceOf[DerivedName] then pats.addOne((tree.symbol, tree.namePos)) case tree: NamedDefTree => - if (tree.symbol ne NoSymbol) && !tree.name.isWildcard && !tree.hasAttachment(NoWarn) then - defs.addOne((tree.symbol, tree.namePos)) + if (tree.symbol ne NoSymbol) + && !tree.name.isWildcard + && !tree.hasAttachment(NoWarn) + && !tree.symbol.is(ModuleVal) // track only the ModuleClass using the object symbol, with correct namePos + then + defs.addOne((tree.symbol.userSymbol, tree.namePos)) case _ => if tree.symbol ne NoSymbol then - defs.addOne((tree.symbol, tree.srcPos)) + defs.addOne((tree.symbol, tree.srcPos)) // TODO is this a code path val inlined = Stack.empty[SrcPos] // enclosing call.srcPos of inlined code (expansions) var inliners = 0 // depth of inline def (not inlined yet) @@ -906,6 +913,9 @@ object CheckUnused: def isEffectivelyPrivate(using Context): Boolean = sym.is(Private, butNot = ParamAccessor) || sym.owner.isAnonymousClass && !sym.nextOverriddenSymbol.exists + // pick the symbol the user wrote for purposes of tracking + inline def userSymbol(using Context): Symbol= + if sym.denot.is(ModuleClass) then sym.denot.companionModule else sym extension (sel: ImportSelector) def boundTpe: Type = sel.bound match diff --git a/tests/warn/i22744.scala b/tests/warn/i22744.scala new file mode 100644 index 000000000000..655a14b162ce --- /dev/null +++ b/tests/warn/i22744.scala @@ -0,0 +1,35 @@ + +//> using options -Wunused:privates -Werror + +object test { + private trait Foo[A] { val value: A } + + private object Foo { // no warn prefix of implicit value + given int: Foo[Int] = new Foo[Int] { val value = 1 } + } + + val i = summon[Foo[Int]].value +} + +object supplement { + private trait Foo[A] { val value: A } + + private object Foo { // no warn prefix of implicit value + given int: Foo[Int] = new Foo[Int] { val value = 1 } + } + + private def fooValue[A](using f: Foo[A]): A = f.value + + val i = fooValue[Int] +} + +package p: + private trait Foo[A] { val value: A } + + private object Foo { // no warn prefix of implicit value + given int: Foo[Int] = new Foo[Int] { val value = 1 } + } + + private def fooValue[A](using f: Foo[A]): A = f.value + + val i = fooValue[Int]