diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index 210322841158..dd6730f698f4 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -3381,11 +3381,13 @@ extends Message(UnusedSymbolID): object UnusedSymbol: def imports(actions: List[CodeAction])(using Context): UnusedSymbol = UnusedSymbol(i"unused import", actions) def localDefs(using Context): UnusedSymbol = UnusedSymbol(i"unused local definition") + def localVars(using Context): UnusedSymbol = UnusedSymbol(i"local variable was mutated but not read") def explicitParams(sym: Symbol)(using Context): UnusedSymbol = UnusedSymbol(i"unused explicit parameter${paramAddendum(sym)}") def implicitParams(sym: Symbol)(using Context): UnusedSymbol = UnusedSymbol(i"unused implicit parameter${paramAddendum(sym)}") def privateMembers(using Context): UnusedSymbol = UnusedSymbol(i"unused private member") + def privateVars(using Context): UnusedSymbol = UnusedSymbol(i"private variable was mutated but not read") def patVars(using Context): UnusedSymbol = UnusedSymbol(i"unused pattern variable") def unsetLocals(using Context): UnusedSymbol = UnusedSymbol(i"unset local variable, consider using an immutable val instead") diff --git a/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala b/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala index a37dbce5bc2e..35310dd91ccc 100644 --- a/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala +++ b/compiler/src/dotty/tools/dotc/transform/CheckUnused.scala @@ -498,8 +498,11 @@ object CheckUnused: val warnings = ArrayBuilder.make[MessageInfo] def warnAt(pos: SrcPos)(msg: UnusedSymbol, origin: String = ""): Unit = warnings.addOne((msg, pos, origin)) val infos = refInfos - //println(infos.defs.mkString("DEFS\n", "\n", "\n---")) - //println(infos.refs.mkString("REFS\n", "\n", "\n---")) + + // non-local sym was target of assignment or has a sibling setter that was referenced + def isMutated(sym: Symbol): Boolean = + infos.asss(sym) + || infos.refs(sym.owner.info.member(sym.name.asTermName.setterName).symbol) def checkUnassigned(sym: Symbol, pos: SrcPos) = if sym.isLocalToBlock then @@ -509,8 +512,7 @@ object CheckUnused: && sym.is(Mutable) && (sym.is(Private) || sym.isEffectivelyPrivate) && !sym.isSetter // tracks sym.underlyingSymbol sibling getter, check setter below - && !infos.asss(sym) - && !infos.refs(sym.owner.info.member(sym.name.asTermName.setterName).symbol) + && !isMutated(sym) then warnAt(pos)(UnusedSymbol.unsetPrivates) @@ -526,7 +528,10 @@ object CheckUnused: ) && !infos.nowarn(sym) then - warnAt(pos)(UnusedSymbol.privateMembers) + if sym.is(Mutable) && isMutated(sym) then + warnAt(pos)(UnusedSymbol.privateVars) + else + warnAt(pos)(UnusedSymbol.privateMembers) def checkParam(sym: Symbol, pos: SrcPos) = val m = sym.owner @@ -629,7 +634,10 @@ object CheckUnused: && !sym.is(InlineProxy) && !sym.isCanEqual then - warnAt(pos)(UnusedSymbol.localDefs) + if sym.is(Mutable) && infos.asss(sym) then + warnAt(pos)(UnusedSymbol.localVars) + else + warnAt(pos)(UnusedSymbol.localDefs) def checkPatvars() = // convert the one non-synthetic span so all are comparable; filter NoSpan below diff --git a/tests/warn/i23704.check b/tests/warn/i23704.check new file mode 100644 index 000000000000..71a69aefef11 --- /dev/null +++ b/tests/warn/i23704.check @@ -0,0 +1,20 @@ +-- [E198] Unused Symbol Warning: tests/warn/i23704.scala:9:8 ----------------------------------------------------------- +9 | var position: Int = 0 // warn position is assigned but not read + | ^^^^^^^^ + | private variable was mutated but not read +-- [E198] Unused Symbol Warning: tests/warn/i23704.scala:16:14 --------------------------------------------------------- +16 | private var myvar: Int = 0 // warn for the same case with simpler syntax + | ^^^^^ + | private variable was mutated but not read +-- [E198] Unused Symbol Warning: tests/warn/i23704.scala:26:8 ---------------------------------------------------------- +26 | var localvar = 0 // warn local variable was mutated but not read + | ^^^^^^^^ + | local variable was mutated but not read +-- [E198] Unused Symbol Warning: tests/warn/i23704.scala:33:8 ---------------------------------------------------------- +33 | var settable: Int = 0 // warn private variable was mutated but not read + | ^^^^^^^^ + | private variable was mutated but not read +-- [E198] Unused Symbol Warning: tests/warn/i23704.scala:39:14 --------------------------------------------------------- +39 | private var myvar: Int = 0 // warn plain unused + | ^^^^^ + | unused private member diff --git a/tests/warn/i23704.scala b/tests/warn/i23704.scala new file mode 100644 index 000000000000..9cfaae3278c1 --- /dev/null +++ b/tests/warn/i23704.scala @@ -0,0 +1,39 @@ +//> using options -Wunused:all + +trait Test { + def incr(): Unit +} + +object Test { + val test = new Test { + var position: Int = 0 // warn position is assigned but not read + + def incr(): Unit = { position += 1 } + } +} + +class C: + private var myvar: Int = 0 // warn for the same case with simpler syntax + def mine: Int = + myvar = 42 + 27 + +class D: + private var myvar: Int = 0 // nowarn (although read is RHS of assignment) + def incr(): Unit = myvar = myvar + 1 + + def local(): Unit = + var localvar = 0 // warn local variable was mutated but not read + localvar += 1 + +class E: + trait Setting: + def setting(): Unit + val test = new Test: + var settable: Int = 0 // warn private variable was mutated but not read + def setting(): Unit = + settable_=(42) + def incr() = setting() + +class F: + private var myvar: Int = 0 // warn plain unused