diff --git a/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala b/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala index 6b2a987dca8c..2611652a110b 100644 --- a/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala +++ b/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala @@ -150,6 +150,9 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha override def prepareForValDef(tree: ValDef)(using Context): Context = if !tree.symbol.is(Deferred) && tree.rhs.symbol != defn.Predef_undefined then refInfos.register(tree) + tree.tpt match + case RefinedTypeTree(_, refinements) => relax(tree.rhs, refinements) + case _ => ctx override def transformValDef(tree: ValDef)(using Context): tree.type = traverseAnnotations(tree.symbol) @@ -170,11 +173,15 @@ class CheckUnused private (phaseMode: PhaseMode, suffix: String) extends MiniPha override def prepareForDefDef(tree: DefDef)(using Context): Context = def trivial = tree.symbol.is(Deferred) || isUnconsuming(tree.rhs) def nontrivial = tree.symbol.isConstructor || tree.symbol.isAnonymousFunction - if !nontrivial && trivial then refInfos.skip.addOne(tree.symbol) + if !nontrivial && trivial then + refInfos.skip.addOne(tree.symbol) if tree.symbol.is(Inline) then refInfos.inliners += 1 else if !tree.symbol.is(Deferred) && tree.rhs.symbol != defn.Predef_undefined then refInfos.register(tree) + tree.tpt match + case RefinedTypeTree(_, refinements) => relax(tree.rhs, refinements) + case _ => ctx override def transformDefDef(tree: DefDef)(using Context): tree.type = traverseAnnotations(tree.symbol) @@ -456,7 +463,7 @@ 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 then + if (tree.symbol ne NoSymbol) && !tree.name.isWildcard && !tree.hasAttachment(NoWarn) then defs.addOne((tree.symbol, tree.namePos)) case _ => if tree.symbol ne NoSymbol then @@ -520,7 +527,7 @@ object CheckUnused: def checkPrivate(sym: Symbol, pos: SrcPos) = if ctx.settings.WunusedHas.privates && !sym.isPrimaryConstructor - && sym.is(Private, butNot = SelfName | Synthetic | CaseAccessor) + && !sym.isOneOf(SelfName | Synthetic | CaseAccessor) && !sym.name.is(BodyRetainerName) && !sym.isSerializationSupport && !(sym.is(Mutable) && sym.isSetter && sym.owner.is(Trait)) // tracks sym.underlyingSymbol sibling getter @@ -764,7 +771,7 @@ object CheckUnused: for (sym, pos) <- infos.defs.iterator if !sym.hasAnnotation(defn.UnusedAnnot) do if infos.refs(sym) then checkUnassigned(sym, pos) - else if sym.is(Private, butNot = ParamAccessor) then + else if sym.isEffectivelyPrivate then checkPrivate(sym, pos) else if sym.is(Param, butNot = Given | Implicit) then checkParam(sym, pos) @@ -851,6 +858,17 @@ object CheckUnused: args.foreach(traverse) case tree => traverseChildren(tree) + // NoWarn members in tree that correspond to refinements; currently uses only names. + def relax(tree: Tree, refinements: List[Tree])(using Context): Unit = + val names = refinements.collect { case named: NamedDefTree => named.name }.toSet + val relaxer = new TreeTraverser: + def traverse(tree: Tree)(using Context) = + tree match + case tree: NamedDefTree if names(tree.name) => tree.withAttachment(NoWarn, ()) + case _ => + traverseChildren(tree) + relaxer.traverse(tree) + extension (nm: Name) inline def exists(p: Name => Boolean): Boolean = nm.ne(nme.NO_NAME) && p(nm) inline def isWildcard: Boolean = nm == nme.WILDCARD || nm.is(WildcardParamName) @@ -885,6 +903,9 @@ object CheckUnused: sym.isClass && sym.info.allMembers.forall: d => val m = d.symbol !m.isTerm || m.isSelfSym || m.is(Method) && (m.owner == defn.AnyClass || m.owner == defn.ObjectClass) + def isEffectivelyPrivate(using Context): Boolean = + sym.is(Private, butNot = ParamAccessor) + || sym.owner.isAnonymousClass && !sym.nextOverriddenSymbol.exists extension (sel: ImportSelector) def boundTpe: Type = sel.bound match diff --git a/tests/warn/i22681.scala b/tests/warn/i22681.scala new file mode 100644 index 000000000000..e15d186479c3 --- /dev/null +++ b/tests/warn/i22681.scala @@ -0,0 +1,17 @@ + +//> using options -Wunused:all + +trait T: + def t: Int + +class C: + def f: Runnable { def u: Int } = new Runnable with T: + private def v = 42 // avoid g judged too trivial to warn + def run() = () + def g = v // warn effectively private member is unused + def t = v // nowarn + def u = v // nowarn because leaked by refinement + val v: Runnable { def u: Int } = new Runnable: + private def v = 42 // avoid g judged too trivial to warn + def run() = () + def u = v // nowarn because leaked by refinement