From 9d9abffc94b28785e54bc2179b495d81f29b1e7f Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Wed, 14 Sep 2016 13:02:11 -0700 Subject: SI-8040 Improve unused warnings Add symbol names, don't warn for both getters and setters or for synthetics (except default arg getters). Tweak messages for readability. --- .../tools/nsc/typechecker/TypeDiagnostics.scala | 33 +++++++++--- test/files/neg/warn-unused-privates.check | 60 ++++++++++++++-------- test/files/neg/warn-unused-privates.scala | 36 +++++++++++++ 3 files changed, 101 insertions(+), 28 deletions(-) diff --git a/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala b/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala index 36b9a65334..3cc896f3bd 100644 --- a/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala +++ b/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala @@ -464,7 +464,9 @@ trait TypeDiagnostics { context.warning(pos, "imported `%s' is permanently hidden by definition of %s".format(hidden, defn.fullLocationString)) object checkUnused { - val ignoreNames: Set[TermName] = Set(TermName("readResolve"), TermName("readObject"), TermName("writeObject"), TermName("writeReplace")) + val ignoreNames: Set[TermName] = Set( + "readResolve", "readObject", "writeObject", "writeReplace" + ).map(TermName(_)) class UnusedPrivates extends Traverser { val defnTrees = ListBuffer[MemberDef]() @@ -523,8 +525,12 @@ trait TypeDiagnostics { && (m.isPrivate || m.isLocalToBlock) && !(treeTypes.exists(tp => tp exists (t => t.typeSymbolDirect == m))) ) + def isSyntheticWarnable(sym: Symbol) = ( + sym.isDefaultGetter + ) def isUnusedTerm(m: Symbol): Boolean = ( (m.isTerm) + && (!m.isSynthetic || isSyntheticWarnable(m)) && (m.isPrivate || m.isLocalToBlock) && !targets(m) && !(m.name == nme.WILDCARD) // e.g. val _ = foo @@ -533,7 +539,13 @@ trait TypeDiagnostics { && !treeTypes.exists(_ contains m) // e.g. val a = new Foo ; new a.Bar ) def unusedTypes = defnTrees.toList filter (t => isUnusedType(t.symbol)) - def unusedTerms = defnTrees.toList filter (v => isUnusedTerm(v.symbol)) + def unusedTerms = { + val all = defnTrees.toList.filter(v => isUnusedTerm(v.symbol)) + // filter out setters if already warning for getter, indicated by position + if (all.exists(_.symbol.isSetter)) + all.filterNot(v => v.symbol.isSetter && all.exists(g => g.symbol.isGetter && g.symbol.pos.point == v.symbol.pos.point)) + else all + } // local vars which are never set, except those already returned in unused def unsetVars = localVars filter (v => !setVars(v) && !isUnusedTerm(v)) } @@ -556,11 +568,18 @@ trait TypeDiagnostics { val what = ( if (sym.isDefaultGetter) "default argument" else if (sym.isConstructor) "constructor" - else if (sym.isVar || sym.isGetter && (sym.accessed.isVar || (sym.owner.isTrait && !sym.hasFlag(STABLE)))) "var" - else if (sym.isVal || sym.isGetter && (sym.accessed.isVal || (sym.owner.isTrait && sym.hasFlag(STABLE))) || sym.isLazy) "val" - else if (sym.isSetter) "setter" - else if (sym.isMethod) "method" - else if (sym.isModule) "object" + else if ( + sym.isVar + || sym.isGetter && (sym.accessed.isVar || (sym.owner.isTrait && !sym.hasFlag(STABLE))) + ) s"var ${sym.name.getterName.decoded}" + else if ( + sym.isVal + || sym.isGetter && (sym.accessed.isVal || (sym.owner.isTrait && sym.hasFlag(STABLE))) + || sym.isLazy + ) s"val ${sym.name.decoded}" + else if (sym.isSetter) s"setter of ${sym.name.getterName.decoded}" + else if (sym.isMethod) s"method ${sym.name.decoded}" + else if (sym.isModule) s"object ${sym.name.decoded}" else "term" ) reporter.warning(pos, s"$why $what in ${sym.owner} is never used") diff --git a/test/files/neg/warn-unused-privates.check b/test/files/neg/warn-unused-privates.check index 2e93f338bb..6e1511d0e5 100644 --- a/test/files/neg/warn-unused-privates.check +++ b/test/files/neg/warn-unused-privates.check @@ -1,66 +1,84 @@ warn-unused-privates.scala:2: warning: private constructor in class Bippy is never used private def this(c: Int) = this(c, c) // warn ^ -warn-unused-privates.scala:4: warning: private method in class Bippy is never used +warn-unused-privates.scala:4: warning: private method boop in class Bippy is never used private def boop(x: Int) = x+a+b // warn ^ -warn-unused-privates.scala:6: warning: private val in class Bippy is never used +warn-unused-privates.scala:6: warning: private val MILLIS2 in class Bippy is never used final private val MILLIS2: Int = 1000 // warn ^ -warn-unused-privates.scala:13: warning: private val in object Bippy is never used +warn-unused-privates.scala:13: warning: private val HEY_INSTANCE in object Bippy is never used private val HEY_INSTANCE: Int = 1000 // warn ^ -warn-unused-privates.scala:14: warning: private val in object Bippy is never used +warn-unused-privates.scala:14: warning: private val BOOL in object Bippy is never used private lazy val BOOL: Boolean = true // warn ^ -warn-unused-privates.scala:36: warning: private val in class Boppy is never used +warn-unused-privates.scala:36: warning: private val hummer in class Boppy is never used private val hummer = "def" // warn ^ -warn-unused-privates.scala:43: warning: private var in trait Accessors is never used +warn-unused-privates.scala:43: warning: private var v1 in trait Accessors is never used private var v1: Int = 0 // warn ^ -warn-unused-privates.scala:44: warning: private var in trait Accessors is never used +warn-unused-privates.scala:44: warning: private var v2 in trait Accessors is never used private var v2: Int = 0 // warn, never set ^ -warn-unused-privates.scala:45: warning: private var in trait Accessors is never used +warn-unused-privates.scala:45: warning: private var v3 in trait Accessors is never used private var v3: Int = 0 // warn, never got ^ -warn-unused-privates.scala:57: warning: private default argument in trait DefaultArgs is never used +warn-unused-privates.scala:56: warning: private var s1 in class StableAccessors is never used + private var s1: Int = 0 // warn + ^ +warn-unused-privates.scala:57: warning: private setter of s2 in class StableAccessors is never used + private var s2: Int = 0 // warn, never set + ^ +warn-unused-privates.scala:58: warning: private var s3 in class StableAccessors is never used + private var s3: Int = 0 // warn, never got + ^ +warn-unused-privates.scala:70: warning: private default argument in trait DefaultArgs is never used private def bippy(x1: Int, x2: Int = 10, x3: Int = 15): Int = x1 + x2 + x3 ^ -warn-unused-privates.scala:57: warning: private default argument in trait DefaultArgs is never used +warn-unused-privates.scala:70: warning: private default argument in trait DefaultArgs is never used private def bippy(x1: Int, x2: Int = 10, x3: Int = 15): Int = x1 + x2 + x3 ^ -warn-unused-privates.scala:68: warning: local var in method f0 is never used +warn-unused-privates.scala:86: warning: local var x in method f0 is never used var x = 1 // warn ^ -warn-unused-privates.scala:75: warning: local val in method f1 is never used +warn-unused-privates.scala:93: warning: local val b in method f1 is never used val b = new Outer // warn ^ -warn-unused-privates.scala:85: warning: private object in object Types is never used +warn-unused-privates.scala:103: warning: private object Dongo in object Types is never used private object Dongo { def f = this } // warn ^ -warn-unused-privates.scala:95: warning: local object in method l1 is never used +warn-unused-privates.scala:113: warning: local object HiObject in method l1 is never used object HiObject { def f = this } // warn ^ -warn-unused-privates.scala:79: warning: local var x in method f2 is never set - it could be a val +warn-unused-privates.scala:136: warning: private method x_= in class OtherNames is never used + private def x_=(i: Int): Unit = ??? + ^ +warn-unused-privates.scala:137: warning: private method x in class OtherNames is never used + private def x: Int = 42 + ^ +warn-unused-privates.scala:138: warning: private method y_= in class OtherNames is never used + private def y_=(i: Int): Unit = ??? + ^ +warn-unused-privates.scala:97: warning: local var x in method f2 is never set - it could be a val var x = 100 // warn about it being a var ^ -warn-unused-privates.scala:86: warning: private class Bar1 in object Types is never used +warn-unused-privates.scala:104: warning: private class Bar1 in object Types is never used private class Bar1 // warn ^ -warn-unused-privates.scala:88: warning: private type Alias1 in object Types is never used +warn-unused-privates.scala:106: warning: private type Alias1 in object Types is never used private type Alias1 = String // warn ^ -warn-unused-privates.scala:96: warning: local class Hi is never used +warn-unused-privates.scala:114: warning: local class Hi is never used class Hi { // warn ^ -warn-unused-privates.scala:100: warning: local class DingDongDoobie is never used +warn-unused-privates.scala:118: warning: local class DingDongDoobie is never used class DingDongDoobie // warn ^ -warn-unused-privates.scala:103: warning: local type OtherThing is never used +warn-unused-privates.scala:121: warning: local type OtherThing is never used type OtherThing = String // warn ^ error: No warnings can be incurred under -Xfatal-warnings. -21 warnings found +27 warnings found one error found diff --git a/test/files/neg/warn-unused-privates.scala b/test/files/neg/warn-unused-privates.scala index 2eda280d40..2f67882632 100644 --- a/test/files/neg/warn-unused-privates.scala +++ b/test/files/neg/warn-unused-privates.scala @@ -52,6 +52,19 @@ trait Accessors { } } +class StableAccessors { + private var s1: Int = 0 // warn + private var s2: Int = 0 // warn, never set + private var s3: Int = 0 // warn, never got + private var s4: Int = 0 // no warn + + def bippy(): Int = { + s3 = 5 + s4 = 6 + s2 + s4 + } +} + trait DefaultArgs { // warn about default getters for x2 and x3 private def bippy(x1: Int, x2: Int = 10, x3: Int = 15): Int = x1 + x2 + x3 @@ -59,6 +72,11 @@ trait DefaultArgs { def boppy() = bippy(5, 100, 200) } +/* SI-7707 Both usages warn default arg because using PrivateRyan.apply, not new. +case class PrivateRyan private (ryan: Int = 42) { def f = PrivateRyan() } +object PrivateRyan { def f = PrivateRyan() } +*/ + class Outer { class Inner } @@ -104,3 +122,21 @@ object Types { (new Bippy): Something } } + +trait Underwarn { + def f(): Seq[Int] + + def g() = { + val Seq(_, _) = f() // no warn + true + } +} + +class OtherNames { + private def x_=(i: Int): Unit = ??? + private def x: Int = 42 + private def y_=(i: Int): Unit = ??? + private def y: Int = 42 + + def f = y +} -- cgit v1.2.3 From f672aff3f3b92506741b62d8f7eae6d1e0dc36a7 Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Wed, 14 Sep 2016 23:55:16 -0700 Subject: SI-8040 Warn unused pattern vars Warn for unused `case X(x) =>` but, as an escape hatch, not for `case X(x @ _) =>`. The latter form is deemed documentary. (Named args could serve a similar purpose, `case X(x = _) =>`.) An attachment is used to mark the bound var, and the symbol position is used to correlate the identifier with the variable that is introduced. This mechanism doesn't work yet when only a single var is defined. --- .../scala/tools/nsc/ast/parser/Parsers.scala | 9 +++++- .../tools/nsc/typechecker/TypeDiagnostics.scala | 17 ++++++++---- .../scala/tools/nsc/typechecker/Typers.scala | 3 +- .../scala/reflect/internal/StdAttachments.scala | 6 ++++ .../scala/reflect/runtime/JavaUniverseForce.scala | 1 + src/repl/scala/tools/nsc/interpreter/ILoop.scala | 6 ++-- test/files/neg/warn-unused-privates.check | 20 +++++++++++++- test/files/neg/warn-unused-privates.scala | 32 ++++++++++++++++++++++ 8 files changed, 84 insertions(+), 10 deletions(-) diff --git a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala index 0cdba861a5..dabce69b2f 100644 --- a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala @@ -1957,7 +1957,14 @@ self => pattern3() case Ident(name) => in.nextToken() - atPos(p.pos.start) { Bind(name, pattern3()) } + val body = pattern3() + atPos(p.pos.start, p.pos.start, body.pos.end) { + val t = Bind(name, body) + body match { + case Ident(nme.WILDCARD) => t updateAttachment AtBoundIdentifierAttachment + case _ => t + } + } case _ => p } } diff --git a/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala b/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala index 3cc896f3bd..f344364a75 100644 --- a/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala +++ b/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala @@ -33,7 +33,7 @@ import scala.annotation.tailrec * @version 1.0 */ trait TypeDiagnostics { - self: Analyzer => + self: Analyzer with StdAttachments => import global._ import definitions._ @@ -79,6 +79,8 @@ trait TypeDiagnostics { prefix + name.decode } + private def atBounded(t: Tree) = t.hasAttachment[AtBoundIdentifierAttachment.type] + /** Does the positioned line assigned to t1 precede that of t2? */ def posPrecedes(p1: Position, p2: Position) = p1.isDefined && p2.isDefined && p1.line < p2.line @@ -473,6 +475,7 @@ trait TypeDiagnostics { val targets = mutable.Set[Symbol]() val setVars = mutable.Set[Symbol]() val treeTypes = mutable.Set[Type]() + val atBounds = mutable.Set[Symbol]() def defnSymbols = defnTrees.toList map (_.symbol) def localVars = defnSymbols filter (t => t.isLocalToBlock && t.isVar) @@ -496,6 +499,7 @@ trait TypeDiagnostics { case t: MemberDef if qualifies(t.symbol) => defnTrees += t case t: RefTree if t.symbol ne null => targets += t.symbol case Assign(lhs, _) if lhs.symbol != null => setVars += lhs.symbol + case Bind(n, _) if atBounded(t) => atBounds += t.symbol case _ => } // Only record type references which don't originate within the @@ -541,10 +545,13 @@ trait TypeDiagnostics { def unusedTypes = defnTrees.toList filter (t => isUnusedType(t.symbol)) def unusedTerms = { val all = defnTrees.toList.filter(v => isUnusedTerm(v.symbol)) - // filter out setters if already warning for getter, indicated by position - if (all.exists(_.symbol.isSetter)) - all.filterNot(v => v.symbol.isSetter && all.exists(g => g.symbol.isGetter && g.symbol.pos.point == v.symbol.pos.point)) - else all + + // filter out setters if already warning for getter, indicated by position. + // also documentary names in patterns. + all.filterNot(v => + v.symbol.isSetter && all.exists(g => g.symbol.isGetter && g.symbol.pos.point == v.symbol.pos.point) + || atBounds.exists(x => v.symbol.pos.point == x.pos.point) + ) } // local vars which are never set, except those already returned in unused def unsetVars = localVars filter (v => !setVars(v) && !isUnusedTerm(v)) diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index 8333d5d295..b0a8b5d4c6 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -4266,7 +4266,8 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper val name = tree.name val body = tree.body name match { - case name: TypeName => assert(body == EmptyTree, context.unit + " typedBind: " + name.debugString + " " + body + " " + body.getClass) + case name: TypeName => + assert(body == EmptyTree, s"${context.unit} typedBind: ${name.debugString} ${body} ${body.getClass}") val sym = if (tree.symbol != NoSymbol) tree.symbol else { diff --git a/src/reflect/scala/reflect/internal/StdAttachments.scala b/src/reflect/scala/reflect/internal/StdAttachments.scala index fc49de1cf6..f72c1eb1b3 100644 --- a/src/reflect/scala/reflect/internal/StdAttachments.scala +++ b/src/reflect/scala/reflect/internal/StdAttachments.scala @@ -57,6 +57,12 @@ trait StdAttachments { */ case object BackquotedIdentifierAttachment extends PlainAttachment + /** Indicates that the host `Ident` has been created from a pattern2 binding, `case x @ p`. + * In the absence of named parameters in patterns, allows nuanced warnings for unused variables. + * Hence, `case X(x = _) =>` would not warn; for now, `case X(x @ _) =>` is documentary if x is unused. + */ + case object AtBoundIdentifierAttachment extends PlainAttachment + /** Identifies trees are either result or intermediate value of for loop desugaring. */ case object ForAttachment extends PlainAttachment diff --git a/src/reflect/scala/reflect/runtime/JavaUniverseForce.scala b/src/reflect/scala/reflect/runtime/JavaUniverseForce.scala index 72e21f67fe..7aa3b113dd 100644 --- a/src/reflect/scala/reflect/runtime/JavaUniverseForce.scala +++ b/src/reflect/scala/reflect/runtime/JavaUniverseForce.scala @@ -40,6 +40,7 @@ trait JavaUniverseForce { self: runtime.JavaUniverse => this.SAMFunction this.DelambdafyTarget this.BackquotedIdentifierAttachment + this.AtBoundIdentifierAttachment this.ForAttachment this.SyntheticUnitAttachment this.SubpatternsAttachment diff --git a/src/repl/scala/tools/nsc/interpreter/ILoop.scala b/src/repl/scala/tools/nsc/interpreter/ILoop.scala index 9635f320fe..cd263a26f6 100644 --- a/src/repl/scala/tools/nsc/interpreter/ILoop.scala +++ b/src/repl/scala/tools/nsc/interpreter/ILoop.scala @@ -958,17 +958,19 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter) def withSuppressedSettings[A](body: => A): A = { val ss = this.settings import ss._ - val noisy = List(Xprint, Ytyperdebug) + val noisy = List(Xprint, Ytyperdebug, browse) val noisesome = noisy.exists(!_.isDefault) - val current = (Xprint.value, Ytyperdebug.value) + val current = (Xprint.value, Ytyperdebug.value, browse.value) if (isReplDebug || !noisesome) body else { this.settings.Xprint.value = List.empty + this.settings.browse.value = List.empty this.settings.Ytyperdebug.value = false try body finally { Xprint.value = current._1 Ytyperdebug.value = current._2 + browse.value = current._3 intp.global.printTypings = current._2 } } diff --git a/test/files/neg/warn-unused-privates.check b/test/files/neg/warn-unused-privates.check index 6e1511d0e5..d273aa40f4 100644 --- a/test/files/neg/warn-unused-privates.check +++ b/test/files/neg/warn-unused-privates.check @@ -61,6 +61,24 @@ warn-unused-privates.scala:137: warning: private method x in class OtherNames is warn-unused-privates.scala:138: warning: private method y_= in class OtherNames is never used private def y_=(i: Int): Unit = ??? ^ +warn-unused-privates.scala:153: warning: local val x in method f is never used + val C(x, y, Some(z)) = c // warn + ^ +warn-unused-privates.scala:153: warning: local val y in method f is never used + val C(x, y, Some(z)) = c // warn + ^ +warn-unused-privates.scala:153: warning: local val z in method f is never used + val C(x, y, Some(z)) = c // warn + ^ +warn-unused-privates.scala:161: warning: local val z in method h is never used + val C(x @ _, y @ _, z @ Some(_)) = c // warn? + ^ +warn-unused-privates.scala:166: warning: local val x in method v is never used + val D(x) = d // warn + ^ +warn-unused-privates.scala:170: warning: local val x in method w is never used + val D(x @ _) = d // fixme + ^ warn-unused-privates.scala:97: warning: local var x in method f2 is never set - it could be a val var x = 100 // warn about it being a var ^ @@ -80,5 +98,5 @@ warn-unused-privates.scala:121: warning: local type OtherThing is never used type OtherThing = String // warn ^ error: No warnings can be incurred under -Xfatal-warnings. -27 warnings found +33 warnings found one error found diff --git a/test/files/neg/warn-unused-privates.scala b/test/files/neg/warn-unused-privates.scala index 2f67882632..1b702c7555 100644 --- a/test/files/neg/warn-unused-privates.scala +++ b/test/files/neg/warn-unused-privates.scala @@ -140,3 +140,35 @@ class OtherNames { def f = y } + +case class C(a: Int, b: String, c: Option[String]) +case class D(a: Int) + +trait Boundings { + + def c = C(42, "hello", Some("world")) + def d = D(42) + + def f() = { + val C(x, y, Some(z)) = c // warn + 17 + } + def g() = { + val C(x @ _, y @ _, Some(z @ _)) = c // no warn + 17 + } + def h() = { + val C(x @ _, y @ _, z @ Some(_)) = c // warn? + 17 + } + + def v() = { + val D(x) = d // warn + 17 + } + def w() = { + val D(x @ _) = d // fixme + 17 + } + +} -- cgit v1.2.3 From 1b415c904bece230a706c0d19462345fc87abf67 Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Tue, 13 Sep 2016 13:33:05 -0700 Subject: SI-8040 Heeding -Ywarn-unused Polish notation, as in shoe-shine, as recommended by the warning. Minor clean-ups as advocated by `Ywarn-unused` and `Xlint`. --- src/compiler/scala/reflect/reify/codegen/GenAnnotationInfos.scala | 3 ++- src/compiler/scala/tools/nsc/ast/parser/Parsers.scala | 5 ++--- src/compiler/scala/tools/nsc/transform/Delambdafy.scala | 2 +- src/compiler/scala/tools/nsc/typechecker/RefChecks.scala | 2 -- src/compiler/scala/tools/reflect/FormatInterpolator.scala | 6 +++--- 5 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/compiler/scala/reflect/reify/codegen/GenAnnotationInfos.scala b/src/compiler/scala/reflect/reify/codegen/GenAnnotationInfos.scala index ce26232e5f..2dded48251 100644 --- a/src/compiler/scala/reflect/reify/codegen/GenAnnotationInfos.scala +++ b/src/compiler/scala/reflect/reify/codegen/GenAnnotationInfos.scala @@ -10,7 +10,8 @@ trait GenAnnotationInfos { // however, when reifying free and tough types, we're forced to reify annotation infos as is // why is that bad? take a look inside def reifyAnnotationInfo(ann: AnnotationInfo): Tree = { - val reifiedArgs = ann.args map { arg => + //val reifiedArgs = ann.args map // SI-8915 + ann.args.foreach { arg => val saved1 = reifyTreeSymbols val saved2 = reifyTreeTypes diff --git a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala index dabce69b2f..ba1659d274 100644 --- a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala @@ -2861,9 +2861,8 @@ self => val (constrMods, vparamss) = if (mods.isTrait) (Modifiers(Flags.TRAIT), List()) else (accessModifierOpt(), paramClauses(name, classContextBounds, ofCaseClass = mods.isCase)) - var mods1 = mods - val template = templateOpt(mods1, name, constrMods withAnnotations constrAnnots, vparamss, tstart) - val result = gen.mkClassDef(mods1, name, tparams, template) + val template = templateOpt(mods, name, constrMods withAnnotations constrAnnots, vparamss, tstart) + val result = gen.mkClassDef(mods, name, tparams, template) // Context bounds generate implicit parameters (part of the template) with types // from tparams: we need to ensure these don't overlap if (!classContextBounds.isEmpty) diff --git a/src/compiler/scala/tools/nsc/transform/Delambdafy.scala b/src/compiler/scala/tools/nsc/transform/Delambdafy.scala index 2e7ab8a887..034cf118d7 100644 --- a/src/compiler/scala/tools/nsc/transform/Delambdafy.scala +++ b/src/compiler/scala/tools/nsc/transform/Delambdafy.scala @@ -347,7 +347,7 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre // recursively find methods that refer to 'this' directly or indirectly via references to other methods // for each method found add it to the referrers set private def refersToThis(symbol: Symbol): Boolean = { - var seen = mutable.Set[Symbol]() + val seen = mutable.Set[Symbol]() def loop(symbol: Symbol): Boolean = { if (seen(symbol)) false else { diff --git a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala index 31476e86cd..b1e7592b47 100644 --- a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala +++ b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala @@ -323,8 +323,6 @@ abstract class RefChecks extends Transform { import pair._ val member = low val other = high - def memberTp = lowType - def otherTp = highType // debuglog(s"Checking validity of ${member.fullLocationString} overriding ${other.fullLocationString}") diff --git a/src/compiler/scala/tools/reflect/FormatInterpolator.scala b/src/compiler/scala/tools/reflect/FormatInterpolator.scala index 9825acd39f..bbb2987679 100644 --- a/src/compiler/scala/tools/reflect/FormatInterpolator.scala +++ b/src/compiler/scala/tools/reflect/FormatInterpolator.scala @@ -19,7 +19,7 @@ abstract class FormatInterpolator { @inline private def truly(body: => Unit): Boolean = { body ; true } @inline private def falsely(body: => Unit): Boolean = { body ; false } - private def fail(msg: String) = c.abort(c.enclosingPosition, msg) + //private def fail(msg: String) = c.abort(c.enclosingPosition, msg) private def bail(msg: String) = global.abort(msg) def interpolate: Tree = c.macroApplication match { @@ -93,8 +93,8 @@ abstract class FormatInterpolator { case '\n' => "\\n" case '\f' => "\\f" case '\r' => "\\r" - case '\"' => "${'\"'}" /* avoid lint warn */ + - " or a triple-quoted literal \"\"\"with embedded \" or \\u0022\"\"\"" // $" in future + case '\"' => "$" /* avoid lint warn */ + + "{'\"'} or a triple-quoted literal \"\"\"with embedded \" or \\u0022\"\"\"" // $" in future? case '\'' => "'" case '\\' => """\\""" case x => "\\u%04x" format x -- cgit v1.2.3 From bd280077d04a3ac84ca48f549faaa8915d46ef2e Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Thu, 15 Sep 2016 12:58:21 -0700 Subject: SI-9158 No warn for comprehension patvars Midstream assignments should not cause unused warnings. Currently the encoding doesn't pass them along, but passes the value from which they were destructured. For for-comprehensions only, the patvar transform tags the binds so that they are not warned if they turn up in a valdef and are unused. Extractors are invoked multiple times if the patvar is used later, as noted on the ticket. In a yield, the valdef is emitted only if the patvar is referenced (possibly saving the extra extraction), so there is no warning there currently. --- src/reflect/scala/reflect/internal/TreeGen.scala | 17 ++++++++++++----- test/files/neg/warn-unused-privates.check | 4 ++-- test/files/neg/warn-unused-privates.scala | 21 +++++++++++++++++++-- 3 files changed, 33 insertions(+), 9 deletions(-) diff --git a/src/reflect/scala/reflect/internal/TreeGen.scala b/src/reflect/scala/reflect/internal/TreeGen.scala index 4fecaf70df..373758d0a5 100644 --- a/src/reflect/scala/reflect/internal/TreeGen.scala +++ b/src/reflect/scala/reflect/internal/TreeGen.scala @@ -797,7 +797,7 @@ abstract class TreeGen { /** Create tree for for-comprehension generator */ def mkGenerator(pos: Position, pat: Tree, valeq: Boolean, rhs: Tree)(implicit fresh: FreshNameCreator): Tree = { - val pat1 = patvarTransformer.transform(pat) + val pat1 = patvarTransformerForFor.transform(pat) if (valeq) ValEq(pat1, rhs).setPos(pos) else ValFrom(pat1, mkCheckIfRefutable(pat1, rhs)).setPos(pos) } @@ -894,11 +894,15 @@ abstract class TreeGen { * x becomes x @ _ * x: T becomes x @ (_: T) */ - object patvarTransformer extends Transformer { + class PatvarTransformer(forFor: Boolean) extends Transformer { override def transform(tree: Tree): Tree = tree match { - case Ident(name) if (treeInfo.isVarPattern(tree) && name != nme.WILDCARD) => - atPos(tree.pos)(Bind(name, atPos(tree.pos.focus) (Ident(nme.WILDCARD)))) - case Typed(id @ Ident(name), tpt) if (treeInfo.isVarPattern(id) && name != nme.WILDCARD) => + case Ident(name) if treeInfo.isVarPattern(tree) && name != nme.WILDCARD => + atPos(tree.pos) { + val b = Bind(name, atPos(tree.pos.focus) (Ident(nme.WILDCARD))) + if (forFor) b updateAttachment AtBoundIdentifierAttachment + else b + } + case Typed(id @ Ident(name), tpt) if treeInfo.isVarPattern(id) && name != nme.WILDCARD => atPos(tree.pos.withPoint(id.pos.point)) { Bind(name, atPos(tree.pos.withStart(tree.pos.point)) { Typed(Ident(nme.WILDCARD), tpt) @@ -918,6 +922,9 @@ abstract class TreeGen { tree } } + object patvarTransformer extends PatvarTransformer(forFor = false) + /** Tag pat vars in for comprehensions. */ + object patvarTransformerForFor extends PatvarTransformer(forFor = true) // annotate the expression with @unchecked def mkUnchecked(expr: Tree): Tree = atPos(expr.pos) { diff --git a/test/files/neg/warn-unused-privates.check b/test/files/neg/warn-unused-privates.check index d273aa40f4..c77fcff420 100644 --- a/test/files/neg/warn-unused-privates.check +++ b/test/files/neg/warn-unused-privates.check @@ -71,13 +71,13 @@ warn-unused-privates.scala:153: warning: local val z in method f is never used val C(x, y, Some(z)) = c // warn ^ warn-unused-privates.scala:161: warning: local val z in method h is never used - val C(x @ _, y @ _, z @ Some(_)) = c // warn? + val C(x @ _, y @ _, z @ Some(_)) = c // warn for z? ^ warn-unused-privates.scala:166: warning: local val x in method v is never used val D(x) = d // warn ^ warn-unused-privates.scala:170: warning: local val x in method w is never used - val D(x @ _) = d // fixme + val D(x @ _) = d // warn, fixme (valdef pos is different) ^ warn-unused-privates.scala:97: warning: local var x in method f2 is never set - it could be a val var x = 100 // warn about it being a var diff --git a/test/files/neg/warn-unused-privates.scala b/test/files/neg/warn-unused-privates.scala index 1b702c7555..bc2799067e 100644 --- a/test/files/neg/warn-unused-privates.scala +++ b/test/files/neg/warn-unused-privates.scala @@ -158,7 +158,7 @@ trait Boundings { 17 } def h() = { - val C(x @ _, y @ _, z @ Some(_)) = c // warn? + val C(x @ _, y @ _, z @ Some(_)) = c // warn for z? 17 } @@ -167,8 +167,25 @@ trait Boundings { 17 } def w() = { - val D(x @ _) = d // fixme + val D(x @ _) = d // warn, fixme (valdef pos is different) 17 } } + +trait Forever { + def f = { + val t = Option((17, 42)) + for { + ns <- t + (i, j) = ns // no warn + } yield (i + j) + } + def g = { + val t = Option((17, 42)) + for { + ns <- t + (i, j) = ns // warn, fixme + } yield 42 // val emitted only if needed, hence nothing unused + } +} -- cgit v1.2.3 From 94b938bb290a231694e5721368023bd6693bb2ed Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Thu, 15 Sep 2016 23:16:40 -0700 Subject: SI-8040 Warn unused flags Introduce `-Ywarn-unused:x,y,z` and exploit `-Ywarn-unused:patvars`. Although the tree attachment for shielding patvars from warnings is not structural, sneaking the settings flag into the reflection internal TreeGen is awkward. Add test to ensure isolation of patvars warning from others. `-Ywarn-unused-import` is an alias for `-Ywarn-unused:imports`. `-Xlint:unused` is an alias for `-Ywarn-unused`, but not enabled yet. The help text advises to use `-Ywarn-unused`. The future can decide if `-Xlint:unused-imports` is warranted. --- .../reflect/reify/codegen/GenAnnotationInfos.scala | 1 - src/compiler/scala/tools/nsc/ast/TreeGen.scala | 2 + .../scala/tools/nsc/ast/parser/Parsers.scala | 3 +- .../scala/tools/nsc/settings/Warnings.scala | 40 ++++++++++++++--- .../scala/tools/nsc/typechecker/Analyzer.scala | 2 +- .../tools/nsc/typechecker/TypeDiagnostics.scala | 19 +++++--- .../scala/tools/reflect/FormatInterpolator.scala | 3 +- src/reflect/scala/reflect/internal/TreeGen.scala | 10 ++++- .../warn-unused-imports_2.scala | 2 +- test/files/neg/warn-unused-patvars.check | 12 +++++ test/files/neg/warn-unused-patvars.flags | 1 + test/files/neg/warn-unused-patvars.scala | 51 ++++++++++++++++++++++ 12 files changed, 126 insertions(+), 20 deletions(-) create mode 100644 test/files/neg/warn-unused-patvars.check create mode 100644 test/files/neg/warn-unused-patvars.flags create mode 100644 test/files/neg/warn-unused-patvars.scala diff --git a/src/compiler/scala/reflect/reify/codegen/GenAnnotationInfos.scala b/src/compiler/scala/reflect/reify/codegen/GenAnnotationInfos.scala index 2dded48251..089f07de06 100644 --- a/src/compiler/scala/reflect/reify/codegen/GenAnnotationInfos.scala +++ b/src/compiler/scala/reflect/reify/codegen/GenAnnotationInfos.scala @@ -10,7 +10,6 @@ trait GenAnnotationInfos { // however, when reifying free and tough types, we're forced to reify annotation infos as is // why is that bad? take a look inside def reifyAnnotationInfo(ann: AnnotationInfo): Tree = { - //val reifiedArgs = ann.args map // SI-8915 ann.args.foreach { arg => val saved1 = reifyTreeSymbols val saved2 = reifyTreeTypes diff --git a/src/compiler/scala/tools/nsc/ast/TreeGen.scala b/src/compiler/scala/tools/nsc/ast/TreeGen.scala index 762456c9c9..a2c37b59fb 100644 --- a/src/compiler/scala/tools/nsc/ast/TreeGen.scala +++ b/src/compiler/scala/tools/nsc/ast/TreeGen.scala @@ -371,4 +371,6 @@ abstract class TreeGen extends scala.reflect.internal.TreeGen with TreeDSL { Typed(New(anonClass.tpe), TypeTree(fun.tpe))) } } + + override def isPatVarWarnable = settings.warnUnusedPatVars } diff --git a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala index ba1659d274..68bd663ab8 100644 --- a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala @@ -1950,7 +1950,7 @@ self => def pattern2(): Tree = { val p = pattern3() - if (in.token != AT) p + if (in.token != AT) p // var pattern upgraded later to x @ _ else p match { case Ident(nme.WILDCARD) => in.nextToken() @@ -1962,6 +1962,7 @@ self => val t = Bind(name, body) body match { case Ident(nme.WILDCARD) => t updateAttachment AtBoundIdentifierAttachment + case _ if !settings.warnUnusedPatVars => t updateAttachment AtBoundIdentifierAttachment case _ => t } } diff --git a/src/compiler/scala/tools/nsc/settings/Warnings.scala b/src/compiler/scala/tools/nsc/settings/Warnings.scala index 87534656f9..1a16480149 100644 --- a/src/compiler/scala/tools/nsc/settings/Warnings.scala +++ b/src/compiler/scala/tools/nsc/settings/Warnings.scala @@ -20,10 +20,35 @@ trait Warnings { val warnDeadCode = BooleanSetting("-Ywarn-dead-code", "Warn when dead code is identified.") val warnValueDiscard = BooleanSetting("-Ywarn-value-discard", "Warn when non-Unit expression results are unused.") val warnNumericWiden = BooleanSetting("-Ywarn-numeric-widen", "Warn when numerics are widened.") - // SI-7712, SI-7707 warnUnused not quite ready for prime-time - val warnUnused = BooleanSetting("-Ywarn-unused", "Warn when local and private vals, vars, defs, and types are unused.") - // currently considered too noisy for general use - val warnUnusedImport = BooleanSetting("-Ywarn-unused-import", "Warn when imports are unused.") + + object UnusedWarnings extends MultiChoiceEnumeration { + val Imports = Choice("imports", "Warn if an import selector is not referenced.") + val PatVars = Choice("patvars", "Warn if a variable bound in a pattern is unused.") + val Privates = Choice("privates", "Warn if a private member is unused.") + val Locals = Choice("locals", "Warn if a local definition is unused.") + val Params = Choice("params", "Warn if a value parameter is unused.") + val Implicits = Choice("implicits", "Warn if an implicit parameter is unused.") + } + + // The -Ywarn-unused warning group. + val warnUnused = MultiChoiceSetting( + name = "-Ywarn-unused", + helpArg = "warning", + descr = "Enable or disable specific `unused' warnings", + domain = UnusedWarnings, + default = Some(List("_")) + ) + + def warnUnusedImport = warnUnused contains UnusedWarnings.Imports + def warnUnusedPatVars = warnUnused contains UnusedWarnings.PatVars + def warnUnusedPrivates = warnUnused contains UnusedWarnings.Privates + def warnUnusedLocals = warnUnused contains UnusedWarnings.Locals + def warnUnusedParams = warnUnused contains UnusedWarnings.Params + def warnUnusedImplicits = warnUnused contains UnusedWarnings.Implicits + + BooleanSetting("-Ywarn-unused-import", "Warn when imports are unused.") withPostSetHook { s => + warnUnused.add(s"${if (s) "" else "-"}imports") + } // withDeprecationMessage s"Enable -Ywarn-unused:imports" val warnExtraImplicit = BooleanSetting("-Ywarn-extra-implicit", "Warn when more than one implicit parameter section is defined.") @@ -60,6 +85,8 @@ trait Warnings { val UnsoundMatch = LintWarning("unsound-match", "Pattern match may not be typesafe.") val StarsAlign = LintWarning("stars-align", "Pattern sequence wildcard must align with sequence component.") val Constant = LintWarning("constant", "Evaluation of a constant arithmetic expression results in an error.") + //val Unused = LintWarning("unused", "Warn when private and local definitions are unused.") + val Unused = LintWarning("unused", "Use -Ywarn-unused to warn when private and local definitions are unused.") def allLintWarnings = values.toSeq.asInstanceOf[Seq[LintWarning]] } @@ -82,6 +109,7 @@ trait Warnings { def warnUnsoundMatch = lint contains UnsoundMatch def warnStarsAlign = lint contains StarsAlign def warnConstant = lint contains Constant + def lintUnused = lint contains Unused // Lint warnings that are currently -Y, but deprecated in that usage @deprecated("Use warnAdaptedArgs", since="2.11.2") @@ -101,7 +129,9 @@ trait Warnings { helpArg = "warning", descr = "Enable or disable specific warnings", domain = LintWarnings, - default = Some(List("_"))) + default = Some(List("_"))) //.withPostSetHook (s => if (s contains Unused) warnUnused.add("_")) + + // restore -Xlint:unused hook when SI-8040 is complete allLintWarnings foreach { case w if w.yAliased => diff --git a/src/compiler/scala/tools/nsc/typechecker/Analyzer.scala b/src/compiler/scala/tools/nsc/typechecker/Analyzer.scala index 323fe1c171..b8ef439e03 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Analyzer.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Analyzer.scala @@ -104,7 +104,7 @@ trait Analyzer extends AnyRef for (workItem <- unit.toCheck) workItem() if (settings.warnUnusedImport) warnUnusedImports(unit) - if (settings.warnUnused) + if (settings.warnUnused.isSetByUser) typer checkUnused unit } finally { diff --git a/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala b/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala index f344364a75..b263a0fffd 100644 --- a/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala +++ b/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala @@ -505,7 +505,7 @@ trait TypeDiagnostics { // Only record type references which don't originate within the // definition of the class being referenced. if (t.tpe ne null) { - for (tp <- t.tpe ; if !treeTypes(tp) && !currentOwner.ownerChain.contains(tp.typeSymbol)) { + for (tp <- t.tpe if !treeTypes(tp) && !currentOwner.ownerChain.contains(tp.typeSymbol)) { tp match { case NoType | NoPrefix => case NullaryMethodType(_) => @@ -557,12 +557,17 @@ trait TypeDiagnostics { def unsetVars = localVars filter (v => !setVars(v) && !isUnusedTerm(v)) } - def apply(unit: CompilationUnit) = { + private def warningsEnabled: Boolean = { + val ss = settings + import ss._ + warnUnusedPatVars || warnUnusedPrivates || warnUnusedLocals || warnUnusedParams || warnUnusedImplicits + } + + def apply(unit: CompilationUnit): Unit = if (warningsEnabled) { val p = new UnusedPrivates p traverse unit.body - val unused = p.unusedTerms - unused foreach { defn: DefTree => - val sym = defn.symbol + for (defn: DefTree <- p.unusedTerms) { + val sym = defn.symbol val pos = ( if (defn.pos.isDefined) defn.pos else if (sym.pos.isDefined) sym.pos @@ -591,10 +596,10 @@ trait TypeDiagnostics { ) reporter.warning(pos, s"$why $what in ${sym.owner} is never used") } - p.unsetVars foreach { v => + for (v <- p.unsetVars) { reporter.warning(v.pos, s"local var ${v.name} in ${v.owner} is never set - it could be a val") } - p.unusedTypes foreach { t => + for (t <- p.unusedTypes) { val sym = t.symbol val why = if (sym.isPrivate) "private" else "local" reporter.warning(t.pos, s"$why ${sym.fullLocationString} is never used") diff --git a/src/compiler/scala/tools/reflect/FormatInterpolator.scala b/src/compiler/scala/tools/reflect/FormatInterpolator.scala index bbb2987679..857b733f59 100644 --- a/src/compiler/scala/tools/reflect/FormatInterpolator.scala +++ b/src/compiler/scala/tools/reflect/FormatInterpolator.scala @@ -19,7 +19,6 @@ abstract class FormatInterpolator { @inline private def truly(body: => Unit): Boolean = { body ; true } @inline private def falsely(body: => Unit): Boolean = { body ; false } - //private def fail(msg: String) = c.abort(c.enclosingPosition, msg) private def bail(msg: String) = global.abort(msg) def interpolate: Tree = c.macroApplication match { @@ -94,7 +93,7 @@ abstract class FormatInterpolator { case '\f' => "\\f" case '\r' => "\\r" case '\"' => "$" /* avoid lint warn */ + - "{'\"'} or a triple-quoted literal \"\"\"with embedded \" or \\u0022\"\"\"" // $" in future? + "{'\"'} or a triple-quoted literal \"\"\"with embedded \" or \\u0022\"\"\"" case '\'' => "'" case '\\' => """\\""" case x => "\\u%04x" format x diff --git a/src/reflect/scala/reflect/internal/TreeGen.scala b/src/reflect/scala/reflect/internal/TreeGen.scala index 373758d0a5..ade9ee84ac 100644 --- a/src/reflect/scala/reflect/internal/TreeGen.scala +++ b/src/reflect/scala/reflect/internal/TreeGen.scala @@ -899,8 +899,8 @@ abstract class TreeGen { case Ident(name) if treeInfo.isVarPattern(tree) && name != nme.WILDCARD => atPos(tree.pos) { val b = Bind(name, atPos(tree.pos.focus) (Ident(nme.WILDCARD))) - if (forFor) b updateAttachment AtBoundIdentifierAttachment - else b + if (!forFor && isPatVarWarnable) b + else b updateAttachment AtBoundIdentifierAttachment } case Typed(id @ Ident(name), tpt) if treeInfo.isVarPattern(id) && name != nme.WILDCARD => atPos(tree.pos.withPoint(id.pos.point)) { @@ -922,7 +922,13 @@ abstract class TreeGen { tree } } + + /** Can be overridden to depend on settings.warnUnusedPatvars. */ + def isPatVarWarnable: Boolean = true + + /** Not in for comprehensions, whether to warn unused pat vars depends on flag. */ object patvarTransformer extends PatvarTransformer(forFor = false) + /** Tag pat vars in for comprehensions. */ object patvarTransformerForFor extends PatvarTransformer(forFor = true) diff --git a/test/files/neg/warn-unused-imports/warn-unused-imports_2.scala b/test/files/neg/warn-unused-imports/warn-unused-imports_2.scala index ded1186209..56ad3393a1 100644 --- a/test/files/neg/warn-unused-imports/warn-unused-imports_2.scala +++ b/test/files/neg/warn-unused-imports/warn-unused-imports_2.scala @@ -96,7 +96,7 @@ trait Warn { trait Nested { { import p1._ // warn - trait Warn { // warn about unused local trait for good measure + trait Warn { // don't warn about unused local trait with -Ywarn-unused:imports import p2._ println(new A) println("abc".bippy) diff --git a/test/files/neg/warn-unused-patvars.check b/test/files/neg/warn-unused-patvars.check new file mode 100644 index 0000000000..002f7a07ca --- /dev/null +++ b/test/files/neg/warn-unused-patvars.check @@ -0,0 +1,12 @@ +warn-unused-patvars.scala:7: warning: private val x in trait Boundings is never used + private val x = 42 // warn, sanity check + ^ +warn-unused-patvars.scala:26: warning: local val x in method v is never used + val D(x) = d // warn, fixme + ^ +warn-unused-patvars.scala:30: warning: local val x in method w is never used + val D(x @ _) = d // warn, fixme (valdef pos is different) + ^ +error: No warnings can be incurred under -Xfatal-warnings. +three warnings found +one error found diff --git a/test/files/neg/warn-unused-patvars.flags b/test/files/neg/warn-unused-patvars.flags new file mode 100644 index 0000000000..d5bd86a658 --- /dev/null +++ b/test/files/neg/warn-unused-patvars.flags @@ -0,0 +1 @@ +-Ywarn-unused:-patvars,_ -Xfatal-warnings diff --git a/test/files/neg/warn-unused-patvars.scala b/test/files/neg/warn-unused-patvars.scala new file mode 100644 index 0000000000..6f4620c0c7 --- /dev/null +++ b/test/files/neg/warn-unused-patvars.scala @@ -0,0 +1,51 @@ + +case class C(a: Int, b: String, c: Option[String]) +case class D(a: Int) + +trait Boundings { + + private val x = 42 // warn, sanity check + + def c = C(42, "hello", Some("world")) + def d = D(42) + + def f() = { + val C(x, y, Some(z)) = c // no warn + 17 + } + def g() = { + val C(x @ _, y @ _, Some(z @ _)) = c // no warn + 17 + } + def h() = { + val C(x @ _, y @ _, z @ Some(_)) = c // no warn for z? + 17 + } + + def v() = { + val D(x) = d // warn, fixme + 17 + } + def w() = { + val D(x @ _) = d // warn, fixme (valdef pos is different) + 17 + } + +} + +trait Forever { + def f = { + val t = Option((17, 42)) + for { + ns <- t + (i, j) = ns // no warn + } yield (i + j) + } + def g = { + val t = Option((17, 42)) + for { + ns <- t + (i, j) = ns // no warn + } yield 42 + } +} -- cgit v1.2.3 From 22f98d5189b61200aaf11cec7a0a96d5cfa86a5e Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Mon, 19 Sep 2016 00:46:17 -0700 Subject: SI-8040 Warn unused parameters One can `-Ywarn-unused:params` or more narrowly warn only for unused implicit parameters with `-Ywarn-unused:implicits`. Params includes constructor parameters. The settings for privates and locals are not yet distinguished. ``` $ skalac -Ywarn-unused:help Enable or disable specific `unused' warnings imports Warn if an import selector is not referenced. patvars Warn if a variable bound in a pattern is unused. privates Warn if a private member is unused. locals Warn if a local definition is unused. params Warn if a value parameter is unused. implicits Warn if an implicit parameter is unused. ``` --- .../scala/tools/nsc/settings/Warnings.scala | 2 +- .../tools/nsc/typechecker/TypeDiagnostics.scala | 115 +++++++++++++-------- test/files/neg/warn-unused-implicits.check | 9 ++ test/files/neg/warn-unused-implicits.flags | 1 + test/files/neg/warn-unused-implicits.scala | 32 ++++++ test/files/neg/warn-unused-params.check | 18 ++++ test/files/neg/warn-unused-params.flags | 1 + test/files/neg/warn-unused-params.scala | 61 +++++++++++ test/files/neg/warn-unused-patvars.check | 6 +- test/files/neg/warn-unused-patvars.scala | 2 + test/files/neg/warn-unused-privates.flags | 2 +- test/files/neg/warn-unused-privates.scala | 4 + 12 files changed, 207 insertions(+), 46 deletions(-) create mode 100644 test/files/neg/warn-unused-implicits.check create mode 100644 test/files/neg/warn-unused-implicits.flags create mode 100644 test/files/neg/warn-unused-implicits.scala create mode 100644 test/files/neg/warn-unused-params.check create mode 100644 test/files/neg/warn-unused-params.flags create mode 100644 test/files/neg/warn-unused-params.scala diff --git a/src/compiler/scala/tools/nsc/settings/Warnings.scala b/src/compiler/scala/tools/nsc/settings/Warnings.scala index 1a16480149..29138c78d1 100644 --- a/src/compiler/scala/tools/nsc/settings/Warnings.scala +++ b/src/compiler/scala/tools/nsc/settings/Warnings.scala @@ -15,7 +15,7 @@ trait Warnings { // Warning semantics. val fatalWarnings = BooleanSetting("-Xfatal-warnings", "Fail the compilation if there are any warnings.") - // Non-lint warnings + // Non-lint warnings. val warnDeadCode = BooleanSetting("-Ywarn-dead-code", "Warn when dead code is identified.") val warnValueDiscard = BooleanSetting("-Ywarn-value-discard", "Warn when non-Unit expression results are unused.") diff --git a/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala b/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala index b263a0fffd..529d68901d 100644 --- a/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala +++ b/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala @@ -476,6 +476,7 @@ trait TypeDiagnostics { val setVars = mutable.Set[Symbol]() val treeTypes = mutable.Set[Type]() val atBounds = mutable.Set[Symbol]() + val params = mutable.Set[Symbol]() def defnSymbols = defnTrees.toList map (_.symbol) def localVars = defnSymbols filter (t => t.isLocalToBlock && t.isVar) @@ -496,8 +497,16 @@ trait TypeDiagnostics { override def traverse(t: Tree): Unit = { t match { - case t: MemberDef if qualifies(t.symbol) => defnTrees += t - case t: RefTree if t.symbol ne null => targets += t.symbol + case m: MemberDef if qualifies(t.symbol) => defnTrees += m + t match { + case DefDef(mods@_, name@_, tparams@_, vparamss, tpt@_, rhs@_) if !t.symbol.isAbstract && !t.symbol.isDeprecated => + if (t.symbol.isPrimaryConstructor) + for (cpa <- t.symbol.owner.constrParamAccessors if cpa.isPrivateLocal) params += cpa + else if (!t.symbol.isConstructor) + for (vs <- vparamss) params ++= vs.map(_.symbol) + case _ => + } + case _: RefTree if t.symbol ne null => targets += t.symbol case Assign(lhs, _) if lhs.symbol != null => setVars += lhs.symbol case Bind(n, _) if atBounded(t) => atBounds += t.symbol case _ => @@ -538,11 +547,16 @@ trait TypeDiagnostics { && (m.isPrivate || m.isLocalToBlock) && !targets(m) && !(m.name == nme.WILDCARD) // e.g. val _ = foo - && !ignoreNames(m.name.toTermName) // serialization methods + && (m.isValueParameter || !ignoreNames(m.name.toTermName)) // serialization methods && !isConstantType(m.info.resultType) // subject to constant inlining && !treeTypes.exists(_ contains m) // e.g. val a = new Foo ; new a.Bar ) - def unusedTypes = defnTrees.toList filter (t => isUnusedType(t.symbol)) + def sympos(s: Symbol): Int = + if (s.pos.isDefined) s.pos.point else if (s.isTerm) s.asTerm.referenced.pos.point else -1 + def treepos(t: Tree): Int = + if (t.pos.isDefined) t.pos.point else sympos(t.symbol) + + def unusedTypes = defnTrees.toList filter (t => isUnusedType(t.symbol)) sortBy treepos def unusedTerms = { val all = defnTrees.toList.filter(v => isUnusedTerm(v.symbol)) @@ -551,10 +565,11 @@ trait TypeDiagnostics { all.filterNot(v => v.symbol.isSetter && all.exists(g => g.symbol.isGetter && g.symbol.pos.point == v.symbol.pos.point) || atBounds.exists(x => v.symbol.pos.point == x.pos.point) - ) + ).sortBy(treepos) } // local vars which are never set, except those already returned in unused - def unsetVars = localVars filter (v => !setVars(v) && !isUnusedTerm(v)) + def unsetVars = localVars.filter(v => !setVars(v) && !isUnusedTerm(v)).sortBy(sympos) + def unusedParams = params.toList.filter(isUnusedTerm).sortBy(sympos) } private def warningsEnabled: Boolean = { @@ -566,43 +581,61 @@ trait TypeDiagnostics { def apply(unit: CompilationUnit): Unit = if (warningsEnabled) { val p = new UnusedPrivates p traverse unit.body - for (defn: DefTree <- p.unusedTerms) { - val sym = defn.symbol - val pos = ( - if (defn.pos.isDefined) defn.pos - else if (sym.pos.isDefined) sym.pos - else sym match { - case sym: TermSymbol => sym.referenced.pos - case _ => NoPosition + if (settings.warnUnusedLocals || settings.warnUnusedPrivates) { + for (defn: DefTree <- p.unusedTerms) { + val sym = defn.symbol + val pos = ( + if (defn.pos.isDefined) defn.pos + else if (sym.pos.isDefined) sym.pos + else sym match { + case sym: TermSymbol => sym.referenced.pos + case _ => NoPosition + } + ) + val why = if (sym.isPrivate) "private" else "local" + val what = ( + if (sym.isDefaultGetter) "default argument" + else if (sym.isConstructor) "constructor" + else if ( + sym.isVar + || sym.isGetter && (sym.accessed.isVar || (sym.owner.isTrait && !sym.hasFlag(STABLE))) + ) s"var ${sym.name.getterName.decoded}" + else if ( + sym.isVal + || sym.isGetter && (sym.accessed.isVal || (sym.owner.isTrait && sym.hasFlag(STABLE))) + || sym.isLazy + ) s"val ${sym.name.decoded}" + else if (sym.isSetter) s"setter of ${sym.name.getterName.decoded}" + else if (sym.isMethod) s"method ${sym.name.decoded}" + else if (sym.isModule) s"object ${sym.name.decoded}" + else "term" + ) + reporter.warning(pos, s"$why $what in ${sym.owner} is never used") + } + for (v <- p.unsetVars) { + reporter.warning(v.pos, s"local var ${v.name} in ${v.owner} is never set - it could be a val") + } + for (t <- p.unusedTypes) { + val sym = t.symbol + val wrn = if (sym.isPrivate) settings.warnUnusedPrivates else settings.warnUnusedLocals + if (wrn) { + val why = if (sym.isPrivate) "private" else "local" + reporter.warning(t.pos, s"$why ${sym.fullLocationString} is never used") } - ) - val why = if (sym.isPrivate) "private" else "local" - val what = ( - if (sym.isDefaultGetter) "default argument" - else if (sym.isConstructor) "constructor" - else if ( - sym.isVar - || sym.isGetter && (sym.accessed.isVar || (sym.owner.isTrait && !sym.hasFlag(STABLE))) - ) s"var ${sym.name.getterName.decoded}" - else if ( - sym.isVal - || sym.isGetter && (sym.accessed.isVal || (sym.owner.isTrait && sym.hasFlag(STABLE))) - || sym.isLazy - ) s"val ${sym.name.decoded}" - else if (sym.isSetter) s"setter of ${sym.name.getterName.decoded}" - else if (sym.isMethod) s"method ${sym.name.decoded}" - else if (sym.isModule) s"object ${sym.name.decoded}" - else "term" - ) - reporter.warning(pos, s"$why $what in ${sym.owner} is never used") - } - for (v <- p.unsetVars) { - reporter.warning(v.pos, s"local var ${v.name} in ${v.owner} is never set - it could be a val") + } } - for (t <- p.unusedTypes) { - val sym = t.symbol - val why = if (sym.isPrivate) "private" else "local" - reporter.warning(t.pos, s"$why ${sym.fullLocationString} is never used") + if (settings.warnUnusedParams || settings.warnUnusedImplicits) { + def classOf(s: Symbol): Symbol = if (s.isClass || s == NoSymbol) s else classOf(s.owner) + def isImplementation(m: Symbol): Boolean = { + val opc = new overridingPairs.Cursor(classOf(m)) + opc.iterator.exists(pair => pair.low == m) + } + def warnable(s: Symbol) = ( + (settings.warnUnusedParams || s.isImplicit) + && !isImplementation(s.owner) + ) + for (s <- p.unusedParams if warnable(s)) + reporter.warning(s.pos, s"parameter $s in ${s.owner} is never used") } } } diff --git a/test/files/neg/warn-unused-implicits.check b/test/files/neg/warn-unused-implicits.check new file mode 100644 index 0000000000..4cc5836800 --- /dev/null +++ b/test/files/neg/warn-unused-implicits.check @@ -0,0 +1,9 @@ +warn-unused-implicits.scala:11: warning: parameter value s in method f is never used + )(implicit s: String): Int = { // warn + ^ +warn-unused-implicits.scala:31: warning: parameter value s in method i is never used + def i(implicit s: String, t: Int) = t // yes, warn + ^ +error: No warnings can be incurred under -Xfatal-warnings. +two warnings found +one error found diff --git a/test/files/neg/warn-unused-implicits.flags b/test/files/neg/warn-unused-implicits.flags new file mode 100644 index 0000000000..18169f3218 --- /dev/null +++ b/test/files/neg/warn-unused-implicits.flags @@ -0,0 +1 @@ +-Ywarn-unused:implicits -Xfatal-warnings diff --git a/test/files/neg/warn-unused-implicits.scala b/test/files/neg/warn-unused-implicits.scala new file mode 100644 index 0000000000..54f924eac0 --- /dev/null +++ b/test/files/neg/warn-unused-implicits.scala @@ -0,0 +1,32 @@ + +trait InterFace { + /** Call something. */ + def call(a: Int, b: String, c: Double)(implicit s: String): Int +} + +trait BadAPI extends InterFace { + def f(a: Int, + b: String, + c: Double + )(implicit s: String): Int = { // warn + println(b + c) + a + } + @deprecated ("no warn in deprecated API", since="yesterday") + def g(a: Int, + b: String, + c: Double + )(implicit s: String): Int = { // no warn + println(b + c) + a + } + override def call(a: Int, + b: String, + c: Double + )(implicit s: String): Int = { // no warn, required by superclass + println(b + c) + a + } + + def i(implicit s: String, t: Int) = t // yes, warn +} diff --git a/test/files/neg/warn-unused-params.check b/test/files/neg/warn-unused-params.check new file mode 100644 index 0000000000..ca6320ccd9 --- /dev/null +++ b/test/files/neg/warn-unused-params.check @@ -0,0 +1,18 @@ +warn-unused-params.scala:9: warning: parameter value b in method f is never used + b: String, // warn + ^ +warn-unused-params.scala:32: warning: parameter value s in method i is never used + def i(implicit s: String) = 42 // yes, warn + ^ +warn-unused-params.scala:49: warning: parameter value u in class Unusing is never used +class Unusing(u: Int) { // warn + ^ +warn-unused-params.scala:57: warning: parameter value s in class CaseyAtTheBat is never used +case class CaseyAtTheBat(k: Int)(s: String) // warn + ^ +warn-unused-params.scala:60: warning: parameter value readResolve in method f is never used + def f(readResolve: Int) = 42 // warn + ^ +error: No warnings can be incurred under -Xfatal-warnings. +5 warnings found +one error found diff --git a/test/files/neg/warn-unused-params.flags b/test/files/neg/warn-unused-params.flags new file mode 100644 index 0000000000..795fb74272 --- /dev/null +++ b/test/files/neg/warn-unused-params.flags @@ -0,0 +1 @@ +-Ywarn-unused:params -Xfatal-warnings diff --git a/test/files/neg/warn-unused-params.scala b/test/files/neg/warn-unused-params.scala new file mode 100644 index 0000000000..c7578e53a4 --- /dev/null +++ b/test/files/neg/warn-unused-params.scala @@ -0,0 +1,61 @@ + +trait InterFace { + /** Call something. */ + def call(a: Int, b: String, c: Double): Int +} + +trait BadAPI extends InterFace { + def f(a: Int, + b: String, // warn + c: Double): Int = { + println(c) + a + } + @deprecated ("no warn in deprecated API", since="yesterday") + def g(a: Int, + b: String, // no warn + c: Double): Int = { + println(c) + a + } + override def call(a: Int, + b: String, // no warn, required by superclass + c: Double): Int = { + println(c) + a + } + + def meth(x: Int) = x + + override def equals(other: Any): Boolean = true // no warn + + def i(implicit s: String) = 42 // yes, warn + + /* + def future(x: Int): Int = { + val y = 42 + val x = y // maybe option to warn only if shadowed + x + } + */ +} + +// mustn't alter warnings in super +trait PoorClient extends BadAPI { + override def meth(x: Int) = ??? // no warn + override def f(a: Int, b: String, c: Double): Int = a + b.toInt + c.toInt +} + +class Unusing(u: Int) { // warn + def f = ??? +} + +class Valuing(val u: Int) // no warn + +case class CaseyKasem(k: Int) // no warn + +case class CaseyAtTheBat(k: Int)(s: String) // warn + +trait Ignorance { + def f(readResolve: Int) = 42 // warn +} diff --git a/test/files/neg/warn-unused-patvars.check b/test/files/neg/warn-unused-patvars.check index 002f7a07ca..2665126a36 100644 --- a/test/files/neg/warn-unused-patvars.check +++ b/test/files/neg/warn-unused-patvars.check @@ -1,10 +1,10 @@ -warn-unused-patvars.scala:7: warning: private val x in trait Boundings is never used +warn-unused-patvars.scala:9: warning: private val x in trait Boundings is never used private val x = 42 // warn, sanity check ^ -warn-unused-patvars.scala:26: warning: local val x in method v is never used +warn-unused-patvars.scala:28: warning: local val x in method v is never used val D(x) = d // warn, fixme ^ -warn-unused-patvars.scala:30: warning: local val x in method w is never used +warn-unused-patvars.scala:32: warning: local val x in method w is never used val D(x @ _) = d // warn, fixme (valdef pos is different) ^ error: No warnings can be incurred under -Xfatal-warnings. diff --git a/test/files/neg/warn-unused-patvars.scala b/test/files/neg/warn-unused-patvars.scala index 6f4620c0c7..3d35dfedd6 100644 --- a/test/files/neg/warn-unused-patvars.scala +++ b/test/files/neg/warn-unused-patvars.scala @@ -1,4 +1,6 @@ +// verify no warning when -Ywarn-unused:-patvars + case class C(a: Int, b: String, c: Option[String]) case class D(a: Int) diff --git a/test/files/neg/warn-unused-privates.flags b/test/files/neg/warn-unused-privates.flags index 25474aefb3..5ab4f36371 100644 --- a/test/files/neg/warn-unused-privates.flags +++ b/test/files/neg/warn-unused-privates.flags @@ -1 +1 @@ --Ywarn-unused -Xfatal-warnings +-Ywarn-unused:privates,locals,patvars -Xfatal-warnings diff --git a/test/files/neg/warn-unused-privates.scala b/test/files/neg/warn-unused-privates.scala index bc2799067e..f0580f02d5 100644 --- a/test/files/neg/warn-unused-privates.scala +++ b/test/files/neg/warn-unused-privates.scala @@ -189,3 +189,7 @@ trait Forever { } yield 42 // val emitted only if needed, hence nothing unused } } + +trait Ignorance { + private val readResolve = 42 // ignore +} -- cgit v1.2.3 From c9682121b6ed33fe67dad445ebc665d13b369bbb Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Tue, 20 Sep 2016 23:42:38 -0700 Subject: SI-9839 Avoid crash in macro import selector pos Ignore bad name pos. Also delete unused val. Thanks, `-Ywarn-unused`! --- src/compiler/scala/tools/nsc/typechecker/Contexts.scala | 10 ++++------ test/files/neg/warn-unused-imports.check | 5 ++++- test/files/neg/warn-unused-imports/sample_1.scala | 15 +++++++++++++++ .../neg/warn-unused-imports/warn-unused-imports_2.scala | 4 ++++ 4 files changed, 27 insertions(+), 7 deletions(-) diff --git a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala index 7a3b8d2ab6..67168917e1 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala @@ -64,10 +64,8 @@ trait Contexts { self: Analyzer => for (imps <- allImportInfos.remove(unit)) { for (imp <- imps.distinct.reverse) { val used = allUsedSelectors(imp) - - imp.tree.selectors filterNot (s => isMaskImport(s) || used(s)) foreach { sel => - reporter.warning(imp posOf sel, "Unused import") - } + for (sel <- imp.tree.selectors if !isMaskImport(sel) && !used(sel)) + reporter.warning(imp.posOf(sel), "Unused import") } allUsedSelectors --= imps } @@ -825,7 +823,6 @@ trait Contexts { self: Analyzer => private def collectImplicitImports(imp: ImportInfo): List[ImplicitInfo] = { val qual = imp.qual - val qualSym = qual.tpe.typeSymbol val pre = qual.tpe def collect(sels: List[ImportSelector]): List[ImplicitInfo] = sels match { case List() => @@ -1412,7 +1409,8 @@ trait Contexts { self: Analyzer => class ImportInfo(val tree: Import, val depth: Int) { def pos = tree.pos - def posOf(sel: ImportSelector) = tree.pos withPoint sel.namePos + def posOf(sel: ImportSelector) = + if (sel.namePos >= 0) tree.pos withPoint sel.namePos else tree.pos /** The prefix expression */ def qual: Tree = tree.symbol.info match { diff --git a/test/files/neg/warn-unused-imports.check b/test/files/neg/warn-unused-imports.check index 0a53d7a9cd..29d73a6264 100644 --- a/test/files/neg/warn-unused-imports.check +++ b/test/files/neg/warn-unused-imports.check @@ -51,5 +51,8 @@ warn-unused-imports_2.scala:149: warning: Unused import warn-unused-imports_2.scala:150: warning: Unused import import p1.A // warn ^ -16 warnings found +warn-unused-imports_2.scala:158: warning: Unused import + def x = Macro.f // warn, not crash + ^ +17 warnings found one error found diff --git a/test/files/neg/warn-unused-imports/sample_1.scala b/test/files/neg/warn-unused-imports/sample_1.scala index d2f86239db..eea4d0eb4c 100644 --- a/test/files/neg/warn-unused-imports/sample_1.scala +++ b/test/files/neg/warn-unused-imports/sample_1.scala @@ -15,3 +15,18 @@ object Sample { def f(x: X) = ??? def g(y: Y) = ??? } + +import scala.language.experimental.macros +import scala.reflect.macros.blackbox.Context + +object Macro { + def f: Int = macro fImpl + def fImpl(c: Context): c.Tree = { + import c.universe._ + + q""" + import scala.util.Random + 42 // TODO randomize + """ + } +} diff --git a/test/files/neg/warn-unused-imports/warn-unused-imports_2.scala b/test/files/neg/warn-unused-imports/warn-unused-imports_2.scala index 56ad3393a1..58fe0131d9 100644 --- a/test/files/neg/warn-unused-imports/warn-unused-imports_2.scala +++ b/test/files/neg/warn-unused-imports/warn-unused-imports_2.scala @@ -153,3 +153,7 @@ trait Outsiders { //Future("abc".bippy) } } + +class MacroClient { + def x = Macro.f // warn, not crash +} -- cgit v1.2.3 From a85521efbbf65161debc460ab5cb55562db051e9 Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Wed, 21 Sep 2016 11:03:53 -0700 Subject: SI-8040 Warn patvars in casedefs Collect bindings in casedefs unless "@-bound to _". Also minor refactor to make it easier to see the cases of `id @ _`. Tupled matching is supposed to be efficient either now or soon. --- .../scala/tools/nsc/ast/parser/Parsers.scala | 35 ++++++++++------------ .../tools/nsc/typechecker/TypeDiagnostics.scala | 13 +++++++- test/files/neg/warn-unused-privates.check | 10 +++++-- test/files/neg/warn-unused-privates.scala | 16 ++++++++++ 4 files changed, 51 insertions(+), 23 deletions(-) diff --git a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala index 68bd663ab8..01a083018c 100644 --- a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala @@ -1947,27 +1947,22 @@ self => * | Pattern3 * }}} */ - def pattern2(): Tree = { - val p = pattern3() - - if (in.token != AT) p // var pattern upgraded later to x @ _ - else p match { - case Ident(nme.WILDCARD) => - in.nextToken() - pattern3() - case Ident(name) => - in.nextToken() - val body = pattern3() - atPos(p.pos.start, p.pos.start, body.pos.end) { - val t = Bind(name, body) - body match { - case Ident(nme.WILDCARD) => t updateAttachment AtBoundIdentifierAttachment - case _ if !settings.warnUnusedPatVars => t updateAttachment AtBoundIdentifierAttachment - case _ => t - } + def pattern2(): Tree = (pattern3(), in.token) match { + case (Ident(nme.WILDCARD), AT) => + in.nextToken() + pattern3() + case (p @ Ident(name), AT) => + in.nextToken() + val body = pattern3() + atPos(p.pos.start, p.pos.start, body.pos.end) { + val t = Bind(name, body) + body match { + case Ident(nme.WILDCARD) => t updateAttachment AtBoundIdentifierAttachment + case _ if !settings.warnUnusedPatVars => t updateAttachment AtBoundIdentifierAttachment + case _ => t } - case _ => p - } + } + case (p, _) => p } /** {{{ diff --git a/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala b/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala index 529d68901d..ee2d00900a 100644 --- a/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala +++ b/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala @@ -477,6 +477,7 @@ trait TypeDiagnostics { val treeTypes = mutable.Set[Type]() val atBounds = mutable.Set[Symbol]() val params = mutable.Set[Symbol]() + val patvars = mutable.Set[Symbol]() def defnSymbols = defnTrees.toList map (_.symbol) def localVars = defnSymbols filter (t => t.isLocalToBlock && t.isVar) @@ -506,6 +507,11 @@ trait TypeDiagnostics { for (vs <- vparamss) params ++= vs.map(_.symbol) case _ => } + case CaseDef(pat, guard@_, rhs@_) if settings.warnUnusedPatVars + => pat.foreach { + case b @ Bind(_, _) if !atBounded(b) => patvars += b.symbol + case _ => + } case _: RefTree if t.symbol ne null => targets += t.symbol case Assign(lhs, _) if lhs.symbol != null => setVars += lhs.symbol case Bind(n, _) if atBounded(t) => atBounds += t.symbol @@ -570,6 +576,7 @@ trait TypeDiagnostics { // local vars which are never set, except those already returned in unused def unsetVars = localVars.filter(v => !setVars(v) && !isUnusedTerm(v)).sortBy(sympos) def unusedParams = params.toList.filter(isUnusedTerm).sortBy(sympos) + def unusedPatVars = patvars.toList.filter(isUnusedTerm).sortBy(sympos) } private def warningsEnabled: Boolean = { @@ -613,7 +620,7 @@ trait TypeDiagnostics { reporter.warning(pos, s"$why $what in ${sym.owner} is never used") } for (v <- p.unsetVars) { - reporter.warning(v.pos, s"local var ${v.name} in ${v.owner} is never set - it could be a val") + reporter.warning(v.pos, s"local var ${v.name} in ${v.owner} is never set: consider using immutable val") } for (t <- p.unusedTypes) { val sym = t.symbol @@ -624,6 +631,10 @@ trait TypeDiagnostics { } } } + if (settings.warnUnusedPatVars) { + for (v <- p.unusedPatVars) + reporter.warning(v.pos, s"pattern var ${v.name} in ${v.owner} is never used; `${v.name}@_' suppresses this warning") + } if (settings.warnUnusedParams || settings.warnUnusedImplicits) { def classOf(s: Symbol): Symbol = if (s.isClass || s == NoSymbol) s else classOf(s.owner) def isImplementation(m: Symbol): Boolean = { diff --git a/test/files/neg/warn-unused-privates.check b/test/files/neg/warn-unused-privates.check index c77fcff420..490e070794 100644 --- a/test/files/neg/warn-unused-privates.check +++ b/test/files/neg/warn-unused-privates.check @@ -79,7 +79,7 @@ warn-unused-privates.scala:166: warning: local val x in method v is never used warn-unused-privates.scala:170: warning: local val x in method w is never used val D(x @ _) = d // warn, fixme (valdef pos is different) ^ -warn-unused-privates.scala:97: warning: local var x in method f2 is never set - it could be a val +warn-unused-privates.scala:97: warning: local var x in method f2 is never set: consider using immutable val var x = 100 // warn about it being a var ^ warn-unused-privates.scala:104: warning: private class Bar1 in object Types is never used @@ -97,6 +97,12 @@ warn-unused-privates.scala:118: warning: local class DingDongDoobie is never use warn-unused-privates.scala:121: warning: local type OtherThing is never used type OtherThing = String // warn ^ +warn-unused-privates.scala:201: warning: pattern var z in method f is never used; `z@_' suppresses this warning + case z => "warn" + ^ +warn-unused-privates.scala:208: warning: pattern var z in method f is never used; `z@_' suppresses this warning + case Some(z) => "warn" + ^ error: No warnings can be incurred under -Xfatal-warnings. -33 warnings found +35 warnings found one error found diff --git a/test/files/neg/warn-unused-privates.scala b/test/files/neg/warn-unused-privates.scala index f0580f02d5..777e0f1579 100644 --- a/test/files/neg/warn-unused-privates.scala +++ b/test/files/neg/warn-unused-privates.scala @@ -193,3 +193,19 @@ trait Forever { trait Ignorance { private val readResolve = 42 // ignore } + +trait CaseyKasem { + def f = 42 match { + case x if x < 25 => "no warn" + case y if toString.nonEmpty => "no warn" + y + case z => "warn" + } +} +trait CaseyAtTheBat { + def f = Option(42) match { + case Some(x) if x < 25 => "no warn" + case Some(y @ _) if toString.nonEmpty => "no warn" + case Some(z) => "warn" + case None => "no warn" + } +} -- cgit v1.2.3 From 833724b131f86b978f0831f637d386d7ca37a2aa Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Wed, 19 Oct 2016 12:09:40 -0700 Subject: SI-7860 No warn private implicit value class Previously, warned on unused synthetic companion. Also avoid false negative via hashcode reference to the underlying value. Avoid the synthetic conversion method for the implicit class (whose RHS always uses the class); the def itself is synthetic so is normally not warned. --- .../tools/nsc/typechecker/TypeDiagnostics.scala | 13 +++++-- test/files/neg/t7860.check | 9 +++++ test/files/neg/t7860.flags | 1 + test/files/neg/t7860.scala | 42 ++++++++++++++++++++++ test/files/neg/warn-unused-privates.check | 5 ++- test/files/neg/warn-unused-privates.scala | 8 +++++ 6 files changed, 74 insertions(+), 4 deletions(-) create mode 100644 test/files/neg/t7860.check create mode 100644 test/files/neg/t7860.flags create mode 100644 test/files/neg/t7860.scala diff --git a/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala b/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala index ee2d00900a..b30972f931 100644 --- a/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala +++ b/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala @@ -355,7 +355,10 @@ trait TypeDiagnostics { // functions to manipulate the name def preQualify() = modifyName(trueOwner.fullName + "." + _) - def postQualify() = if (!(postQualifiedWith contains trueOwner)) { postQualifiedWith ::= trueOwner; modifyName(_ + "(in " + trueOwner + ")") } + def postQualify() = if (!(postQualifiedWith contains trueOwner)) { + postQualifiedWith ::= trueOwner + modifyName(s => s"$s(in $trueOwner)") + } def typeQualify() = if (sym.isTypeParameterOrSkolem) postQualify() def nameQualify() = if (trueOwner.isPackageClass) preQualify() else postQualify() @@ -498,11 +501,13 @@ trait TypeDiagnostics { override def traverse(t: Tree): Unit = { t match { - case m: MemberDef if qualifies(t.symbol) => defnTrees += m + case m: MemberDef if qualifies(t.symbol) => + defnTrees += m t match { case DefDef(mods@_, name@_, tparams@_, vparamss, tpt@_, rhs@_) if !t.symbol.isAbstract && !t.symbol.isDeprecated => if (t.symbol.isPrimaryConstructor) for (cpa <- t.symbol.owner.constrParamAccessors if cpa.isPrivateLocal) params += cpa + else if (t.symbol.isSynthetic && t.symbol.isImplicit) return else if (!t.symbol.isConstructor) for (vs <- vparamss) params ++= vs.map(_.symbol) case _ => @@ -525,6 +530,7 @@ trait TypeDiagnostics { case NoType | NoPrefix => case NullaryMethodType(_) => case MethodType(_, _) => + case SingleType(_, _) => case _ => log(s"$tp referenced from $currentOwner") treeTypes += tp @@ -547,6 +553,7 @@ trait TypeDiagnostics { def isSyntheticWarnable(sym: Symbol) = ( sym.isDefaultGetter ) + def isUnusedTerm(m: Symbol): Boolean = ( (m.isTerm) && (!m.isSynthetic || isSyntheticWarnable(m)) @@ -562,7 +569,7 @@ trait TypeDiagnostics { def treepos(t: Tree): Int = if (t.pos.isDefined) t.pos.point else sympos(t.symbol) - def unusedTypes = defnTrees.toList filter (t => isUnusedType(t.symbol)) sortBy treepos + def unusedTypes = defnTrees.toList.filter(t => isUnusedType(t.symbol)).sortBy(treepos) def unusedTerms = { val all = defnTrees.toList.filter(v => isUnusedTerm(v.symbol)) diff --git a/test/files/neg/t7860.check b/test/files/neg/t7860.check new file mode 100644 index 0000000000..9b9d86c89d --- /dev/null +++ b/test/files/neg/t7860.check @@ -0,0 +1,9 @@ +t7860.scala:5: warning: private class for your eyes only in object Test is never used + private implicit class `for your eyes only`(i: Int) { // warn + ^ +t7860.scala:31: warning: private class C in object Test3 is never used + private implicit class C(val i: Int) extends AnyVal { // warn + ^ +error: No warnings can be incurred under -Xfatal-warnings. +two warnings found +one error found diff --git a/test/files/neg/t7860.flags b/test/files/neg/t7860.flags new file mode 100644 index 0000000000..6ff0dea0b2 --- /dev/null +++ b/test/files/neg/t7860.flags @@ -0,0 +1 @@ +-Xfatal-warnings -Ywarn-unused:privates diff --git a/test/files/neg/t7860.scala b/test/files/neg/t7860.scala new file mode 100644 index 0000000000..6cc0d3e7f5 --- /dev/null +++ b/test/files/neg/t7860.scala @@ -0,0 +1,42 @@ + +class Test + +object Test { + private implicit class `for your eyes only`(i: Int) { // warn + def f = i + } +} + +class Test2 { + import Test2._ + println(5.toStr) +} + +object Test2 { + // was: warning: private object in object Test2 is never used + // i.e. synthetic object C + private implicit class C(val i: Int) extends AnyVal { // no warn + def toStr = i.toString + } +} + +class Test3 { + import Test3._ + //println(5.toStr) +} + +object Test3 { + // was: warning: private object in object Test2 is never used + // i.e. synthetic object C + private implicit class C(val i: Int) extends AnyVal { // warn + def toStr = i.toString + } +} + +object Test4 { + class A { class B } + + private val a: A = new A + + def b = (new a.B).## +} diff --git a/test/files/neg/warn-unused-privates.check b/test/files/neg/warn-unused-privates.check index 490e070794..d4853847fd 100644 --- a/test/files/neg/warn-unused-privates.check +++ b/test/files/neg/warn-unused-privates.check @@ -97,6 +97,9 @@ warn-unused-privates.scala:118: warning: local class DingDongDoobie is never use warn-unused-privates.scala:121: warning: local type OtherThing is never used type OtherThing = String // warn ^ +warn-unused-privates.scala:216: warning: private class for your eyes only in object not even using companion privates is never used + private implicit class `for your eyes only`(i: Int) { // warn + ^ warn-unused-privates.scala:201: warning: pattern var z in method f is never used; `z@_' suppresses this warning case z => "warn" ^ @@ -104,5 +107,5 @@ warn-unused-privates.scala:208: warning: pattern var z in method f is never used case Some(z) => "warn" ^ error: No warnings can be incurred under -Xfatal-warnings. -35 warnings found +36 warnings found one error found diff --git a/test/files/neg/warn-unused-privates.scala b/test/files/neg/warn-unused-privates.scala index 777e0f1579..6f16ab4138 100644 --- a/test/files/neg/warn-unused-privates.scala +++ b/test/files/neg/warn-unused-privates.scala @@ -209,3 +209,11 @@ trait CaseyAtTheBat { case None => "no warn" } } + +class `not even using companion privates` + +object `not even using companion privates` { + private implicit class `for your eyes only`(i: Int) { // warn + def f = i + } +} -- cgit v1.2.3 From d0ff0907e0cef7cd8defba12416e902e49e9f9e6 Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Sat, 10 Dec 2016 13:47:34 -0800 Subject: SI-8040 Avoid Null companion Don't warn on the private constructor of an abstract class. Instead, take it as a signal that the class must not be instantiated. --- src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala b/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala index b30972f931..042c58226a 100644 --- a/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala +++ b/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala @@ -557,7 +557,7 @@ trait TypeDiagnostics { def isUnusedTerm(m: Symbol): Boolean = ( (m.isTerm) && (!m.isSynthetic || isSyntheticWarnable(m)) - && (m.isPrivate || m.isLocalToBlock) + && ((m.isPrivate && !(m.isConstructor && m.owner.isAbstract))|| m.isLocalToBlock) && !targets(m) && !(m.name == nme.WILDCARD) // e.g. val _ = foo && (m.isValueParameter || !ignoreNames(m.name.toTermName)) // serialization methods @@ -594,7 +594,7 @@ trait TypeDiagnostics { def apply(unit: CompilationUnit): Unit = if (warningsEnabled) { val p = new UnusedPrivates - p traverse unit.body + p.traverse(unit.body) if (settings.warnUnusedLocals || settings.warnUnusedPrivates) { for (defn: DefTree <- p.unusedTerms) { val sym = defn.symbol -- cgit v1.2.3 From 6d7b81a1e47960fbbc469108a34414f76a706342 Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Fri, 16 Dec 2016 00:05:47 -0800 Subject: SI-8040 No warn args to super, main args `class B(x: X) extends A(x)` uses `x` in ctor, where it is detectable as an ordinary param. `implicit class C(val s: String)` may not use `s` in extension methods, so don't warn. Don't warn required args to main method. Don't warn about synthetic isDefinedAt in anonymous functions, or about defaultCase$. --- .../scala/tools/nsc/backend/jvm/opt/Inliner.scala | 1 - .../tools/nsc/typechecker/TypeDiagnostics.scala | 36 ++++++++++++++-------- src/repl/scala/tools/nsc/interpreter/IMain.scala | 10 +++--- test/files/neg/warn-unused-params.check | 4 +-- test/files/neg/warn-unused-params.scala | 8 +++++ test/files/neg/warn-unused-privates.check | 11 ++++++- test/files/neg/warn-unused-privates.flags | 2 +- test/files/neg/warn-unused-privates.scala | 7 +++++ 8 files changed, 56 insertions(+), 23 deletions(-) diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala index 0ae8347dc5..1c29859f46 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/Inliner.scala @@ -15,7 +15,6 @@ import scala.collection.JavaConverters._ import AsmUtils._ import BytecodeUtils._ import collection.mutable -import scala.tools.asm.tree.analysis.{Analyzer, SourceInterpreter} import BackendReporting._ import scala.tools.nsc.backend.jvm.BTypes.InternalName diff --git a/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala b/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala index 042c58226a..fa0407792e 100644 --- a/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala +++ b/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala @@ -500,26 +500,28 @@ trait TypeDiagnostics { ) override def traverse(t: Tree): Unit = { + val sym = t.symbol t match { case m: MemberDef if qualifies(t.symbol) => defnTrees += m t match { - case DefDef(mods@_, name@_, tparams@_, vparamss, tpt@_, rhs@_) if !t.symbol.isAbstract && !t.symbol.isDeprecated => - if (t.symbol.isPrimaryConstructor) - for (cpa <- t.symbol.owner.constrParamAccessors if cpa.isPrivateLocal) params += cpa - else if (t.symbol.isSynthetic && t.symbol.isImplicit) return - else if (!t.symbol.isConstructor) + case DefDef(mods@_, name@_, tparams@_, vparamss, tpt@_, rhs@_) if !sym.isAbstract && !sym.isDeprecated && !sym.isMacro => + if (sym.isPrimaryConstructor) + for (cpa <- sym.owner.constrParamAccessors if cpa.isPrivateLocal) params += cpa + else if (sym.isSynthetic && sym.isImplicit) return + else if (!sym.isConstructor) for (vs <- vparamss) params ++= vs.map(_.symbol) case _ => } case CaseDef(pat, guard@_, rhs@_) if settings.warnUnusedPatVars => pat.foreach { - case b @ Bind(_, _) if !atBounded(b) => patvars += b.symbol + // TODO don't warn in isDefinedAt of $anonfun + case b @ Bind(n, _) if !atBounded(b) && n != nme.DEFAULT_CASE => patvars += b.symbol case _ => } - case _: RefTree if t.symbol ne null => targets += t.symbol + case _: RefTree if sym ne null => targets += sym case Assign(lhs, _) if lhs.symbol != null => setVars += lhs.symbol - case Bind(n, _) if atBounded(t) => atBounds += t.symbol + case Bind(_, _) if atBounded(t) => atBounds += sym case _ => } // Only record type references which don't originate within the @@ -555,15 +557,20 @@ trait TypeDiagnostics { ) def isUnusedTerm(m: Symbol): Boolean = ( - (m.isTerm) + m.isTerm && (!m.isSynthetic || isSyntheticWarnable(m)) - && ((m.isPrivate && !(m.isConstructor && m.owner.isAbstract))|| m.isLocalToBlock) + && ((m.isPrivate && !(m.isConstructor && m.owner.isAbstract)) || m.isLocalToBlock) && !targets(m) && !(m.name == nme.WILDCARD) // e.g. val _ = foo && (m.isValueParameter || !ignoreNames(m.name.toTermName)) // serialization methods && !isConstantType(m.info.resultType) // subject to constant inlining && !treeTypes.exists(_ contains m) // e.g. val a = new Foo ; new a.Bar + //&& !(m.isVal && m.info.resultType =:= typeOf[Unit]) // Unit val is uninteresting ) + def isUnusedParam(m: Symbol): Boolean = isUnusedTerm(m) && !(m.isParamAccessor && ( + m.owner.isImplicit || + targets.exists(s => s.isParameter && s.name == m.name && s.owner.isConstructor && s.owner.owner == m.owner) // exclude ctor params + )) def sympos(s: Symbol): Int = if (s.pos.isDefined) s.pos.point else if (s.isTerm) s.asTerm.referenced.pos.point else -1 def treepos(t: Tree): Int = @@ -582,8 +589,9 @@ trait TypeDiagnostics { } // local vars which are never set, except those already returned in unused def unsetVars = localVars.filter(v => !setVars(v) && !isUnusedTerm(v)).sortBy(sympos) - def unusedParams = params.toList.filter(isUnusedTerm).sortBy(sympos) - def unusedPatVars = patvars.toList.filter(isUnusedTerm).sortBy(sympos) + def unusedParams = params.toList.filter(isUnusedParam).sortBy(sympos) + def inDefinedAt(p: Symbol) = p.owner.isMethod && p.owner.name == nme.isDefinedAt && p.owner.owner.isAnonymousFunction + def unusedPatVars = patvars.toList.filter(p => isUnusedTerm(p) && !inDefinedAt(p)).sortBy(sympos) } private def warningsEnabled: Boolean = { @@ -648,9 +656,13 @@ trait TypeDiagnostics { val opc = new overridingPairs.Cursor(classOf(m)) opc.iterator.exists(pair => pair.low == m) } + def isConvention(p: Symbol): Boolean = { + p.name.decoded == "args" && p.owner.name.decoded == "main" + } def warnable(s: Symbol) = ( (settings.warnUnusedParams || s.isImplicit) && !isImplementation(s.owner) + && !isConvention(s) ) for (s <- p.unusedParams if warnable(s)) reporter.warning(s.pos, s"parameter $s in ${s.owner} is never used") diff --git a/src/repl/scala/tools/nsc/interpreter/IMain.scala b/src/repl/scala/tools/nsc/interpreter/IMain.scala index a351d2da95..b977ab0939 100644 --- a/src/repl/scala/tools/nsc/interpreter/IMain.scala +++ b/src/repl/scala/tools/nsc/interpreter/IMain.scala @@ -751,11 +751,9 @@ class IMain(initialSettings: Settings, protected val out: JPrintWriter) extends lazy val evalClass = load(evalPath) def evalEither = callEither(resultName) match { - case Left(ex) => ex match { - case ex: NullPointerException => Right(null) - case ex => Left(unwrap(ex)) - } - case Right(result) => Right(result) + case Right(result) => Right(result) + case Left(_: NullPointerException) => Right(null) + case Left(e) => Left(unwrap(e)) } def compile(source: String): Boolean = compileAndSaveRun(label, source) @@ -789,7 +787,7 @@ class IMain(initialSettings: Settings, protected val out: JPrintWriter) extends } ((pos, msg)) :: loop(filtered) } - val warnings = loop(run.reporting.allConditionalWarnings.map{case (pos, (msg, since)) => (pos, msg)}) + val warnings = loop(run.reporting.allConditionalWarnings.map{ case (pos, (msg, since@_)) => (pos, msg) }) if (warnings.nonEmpty) mostRecentWarnings = warnings } diff --git a/test/files/neg/warn-unused-params.check b/test/files/neg/warn-unused-params.check index ca6320ccd9..373417ce08 100644 --- a/test/files/neg/warn-unused-params.check +++ b/test/files/neg/warn-unused-params.check @@ -7,10 +7,10 @@ warn-unused-params.scala:32: warning: parameter value s in method i is never use warn-unused-params.scala:49: warning: parameter value u in class Unusing is never used class Unusing(u: Int) { // warn ^ -warn-unused-params.scala:57: warning: parameter value s in class CaseyAtTheBat is never used +warn-unused-params.scala:59: warning: parameter value s in class CaseyAtTheBat is never used case class CaseyAtTheBat(k: Int)(s: String) // warn ^ -warn-unused-params.scala:60: warning: parameter value readResolve in method f is never used +warn-unused-params.scala:62: warning: parameter value readResolve in method f is never used def f(readResolve: Int) = 42 // warn ^ error: No warnings can be incurred under -Xfatal-warnings. diff --git a/test/files/neg/warn-unused-params.scala b/test/files/neg/warn-unused-params.scala index c7578e53a4..b166e8fae6 100644 --- a/test/files/neg/warn-unused-params.scala +++ b/test/files/neg/warn-unused-params.scala @@ -52,6 +52,8 @@ class Unusing(u: Int) { // warn class Valuing(val u: Int) // no warn +class Revaluing(u: Int) { def f = u } // no warn + case class CaseyKasem(k: Int) // no warn case class CaseyAtTheBat(k: Int)(s: String) // warn @@ -59,3 +61,9 @@ case class CaseyAtTheBat(k: Int)(s: String) // warn trait Ignorance { def f(readResolve: Int) = 42 // warn } + +class Reusing(u: Int) extends Unusing(u) // no warn + +class Main { + def main(args: Array[String]): Unit = println("hello, args") // no warn +} diff --git a/test/files/neg/warn-unused-privates.check b/test/files/neg/warn-unused-privates.check index d4853847fd..2a88d3e6c3 100644 --- a/test/files/neg/warn-unused-privates.check +++ b/test/files/neg/warn-unused-privates.check @@ -106,6 +106,15 @@ warn-unused-privates.scala:201: warning: pattern var z in method f is never used warn-unused-privates.scala:208: warning: pattern var z in method f is never used; `z@_' suppresses this warning case Some(z) => "warn" ^ +warn-unused-privates.scala:20: warning: parameter value msg0 in class B3 is never used +class B3(msg0: String) extends A("msg") + ^ +warn-unused-privates.scala:136: warning: parameter value i in method x_= is never used + private def x_=(i: Int): Unit = ??? + ^ +warn-unused-privates.scala:138: warning: parameter value i in method y_= is never used + private def y_=(i: Int): Unit = ??? + ^ error: No warnings can be incurred under -Xfatal-warnings. -36 warnings found +39 warnings found one error found diff --git a/test/files/neg/warn-unused-privates.flags b/test/files/neg/warn-unused-privates.flags index 5ab4f36371..25474aefb3 100644 --- a/test/files/neg/warn-unused-privates.flags +++ b/test/files/neg/warn-unused-privates.flags @@ -1 +1 @@ --Ywarn-unused:privates,locals,patvars -Xfatal-warnings +-Ywarn-unused -Xfatal-warnings diff --git a/test/files/neg/warn-unused-privates.scala b/test/files/neg/warn-unused-privates.scala index 6f16ab4138..f7640927fb 100644 --- a/test/files/neg/warn-unused-privates.scala +++ b/test/files/neg/warn-unused-privates.scala @@ -217,3 +217,10 @@ object `not even using companion privates` { def f = i } } + +class `no warn in patmat anonfun isDefinedAt` { + def f(pf: PartialFunction[String, Int]) = pf("42") + def g = f { + case s => s.length // no warn (used to warn case s => true in isDefinedAt) + } +} -- cgit v1.2.3 From 785f4fe224302bbddbbd6a198f1722b4c0ae17f7 Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Tue, 20 Dec 2016 14:35:43 -0800 Subject: SI-8040 Xlint enables unused warnings `-Ywarn-unused-import` is deprecated in favor of `-Ywarn-unused:imports`. `-Xlint` does not yet enable `-Ywarn-unused:patvars`. But the default for `-Ywarn-unused` is everything, including `patvars`. So `-Xlint:unused` is the populist option, `-Ywarn-unused` more exclusive. Tests are fixed by narrowing scope of `-Xlint` when specified. --- src/compiler/scala/tools/nsc/settings/Warnings.scala | 10 ++++------ test/files/neg/abstract-inaccessible.check | 2 +- test/files/neg/abstract-inaccessible.flags | 2 +- test/files/neg/abstract-inaccessible.scala | 2 +- test/files/neg/forgot-interpolator.flags | 2 +- test/files/neg/overloaded-implicit.flags | 2 +- test/files/neg/t1980.flags | 2 +- test/files/neg/t4877.flags | 1 - test/files/neg/t6567.flags | 2 +- test/files/neg/t6675.flags | 2 +- test/files/neg/warn-unused-imports.flags | 2 +- test/files/pos/t6091.scala | 4 ++-- test/files/pos/t8013.flags | 2 +- test/files/pos/t8040.flags | 1 + test/files/pos/t8040.scala | 6 ++++++ 15 files changed, 23 insertions(+), 19 deletions(-) delete mode 100644 test/files/neg/t4877.flags create mode 100644 test/files/pos/t8040.flags create mode 100644 test/files/pos/t8040.scala diff --git a/src/compiler/scala/tools/nsc/settings/Warnings.scala b/src/compiler/scala/tools/nsc/settings/Warnings.scala index 29138c78d1..eb78064155 100644 --- a/src/compiler/scala/tools/nsc/settings/Warnings.scala +++ b/src/compiler/scala/tools/nsc/settings/Warnings.scala @@ -48,7 +48,7 @@ trait Warnings { BooleanSetting("-Ywarn-unused-import", "Warn when imports are unused.") withPostSetHook { s => warnUnused.add(s"${if (s) "" else "-"}imports") - } // withDeprecationMessage s"Enable -Ywarn-unused:imports" + } withDeprecationMessage s"Enable -Ywarn-unused:imports" val warnExtraImplicit = BooleanSetting("-Ywarn-extra-implicit", "Warn when more than one implicit parameter section is defined.") @@ -85,8 +85,7 @@ trait Warnings { val UnsoundMatch = LintWarning("unsound-match", "Pattern match may not be typesafe.") val StarsAlign = LintWarning("stars-align", "Pattern sequence wildcard must align with sequence component.") val Constant = LintWarning("constant", "Evaluation of a constant arithmetic expression results in an error.") - //val Unused = LintWarning("unused", "Warn when private and local definitions are unused.") - val Unused = LintWarning("unused", "Use -Ywarn-unused to warn when private and local definitions are unused.") + val Unused = LintWarning("unused", "Enable -Ywarn-unused:-patvars,_.") def allLintWarnings = values.toSeq.asInstanceOf[Seq[LintWarning]] } @@ -129,9 +128,8 @@ trait Warnings { helpArg = "warning", descr = "Enable or disable specific warnings", domain = LintWarnings, - default = Some(List("_"))) //.withPostSetHook (s => if (s contains Unused) warnUnused.add("_")) - - // restore -Xlint:unused hook when SI-8040 is complete + default = Some(List("_")) + ).withPostSetHook { s => if (s contains Unused) List("-patvars","_").foreach(warnUnused.add) } allLintWarnings foreach { case w if w.yAliased => diff --git a/test/files/neg/abstract-inaccessible.check b/test/files/neg/abstract-inaccessible.check index d56f5691be..739620a4ce 100644 --- a/test/files/neg/abstract-inaccessible.check +++ b/test/files/neg/abstract-inaccessible.check @@ -8,7 +8,7 @@ Classes which cannot access Bippy may be unable to override overrideMe. ^ abstract-inaccessible.scala:7: warning: method overrideMeAlso in trait YourTrait references private[foo] trait Bippy. Classes which cannot access Bippy may be unable to override overrideMeAlso. - def overrideMeAlso(x: Map[Int, Set[Bippy]]) = 5 + def overrideMeAlso(x: Map[Int, Set[Bippy]]) = x.keys.head ^ error: No warnings can be incurred under -Xfatal-warnings. three warnings found diff --git a/test/files/neg/abstract-inaccessible.flags b/test/files/neg/abstract-inaccessible.flags index 6c1dd108ae..ea7773e255 100644 --- a/test/files/neg/abstract-inaccessible.flags +++ b/test/files/neg/abstract-inaccessible.flags @@ -1 +1 @@ --Xfatal-warnings -Xlint \ No newline at end of file +-Xfatal-warnings -Xlint:inaccessible diff --git a/test/files/neg/abstract-inaccessible.scala b/test/files/neg/abstract-inaccessible.scala index 3c80f30522..02b458016f 100644 --- a/test/files/neg/abstract-inaccessible.scala +++ b/test/files/neg/abstract-inaccessible.scala @@ -4,6 +4,6 @@ package foo { trait YourTrait { def implementMe(f: Int => (String, Bippy)): Unit def overrideMe[T <: Bippy](x: T): T = x - def overrideMeAlso(x: Map[Int, Set[Bippy]]) = 5 + def overrideMeAlso(x: Map[Int, Set[Bippy]]) = x.keys.head } } diff --git a/test/files/neg/forgot-interpolator.flags b/test/files/neg/forgot-interpolator.flags index 7949c2afa2..b0d7bc25cb 100644 --- a/test/files/neg/forgot-interpolator.flags +++ b/test/files/neg/forgot-interpolator.flags @@ -1 +1 @@ --Xlint -Xfatal-warnings +-Xlint:missing-interpolator -Xfatal-warnings diff --git a/test/files/neg/overloaded-implicit.flags b/test/files/neg/overloaded-implicit.flags index 9c1e74e4ef..e04a4228ba 100644 --- a/test/files/neg/overloaded-implicit.flags +++ b/test/files/neg/overloaded-implicit.flags @@ -1 +1 @@ --Xlint -Xfatal-warnings -Xdev +-Xlint:poly-implicit-overload -Xfatal-warnings -Xdev diff --git a/test/files/neg/t1980.flags b/test/files/neg/t1980.flags index 7949c2afa2..cdc464a47d 100644 --- a/test/files/neg/t1980.flags +++ b/test/files/neg/t1980.flags @@ -1 +1 @@ --Xlint -Xfatal-warnings +-Xlint:by-name-right-associative -Xfatal-warnings diff --git a/test/files/neg/t4877.flags b/test/files/neg/t4877.flags deleted file mode 100644 index 7ccd56103a..0000000000 --- a/test/files/neg/t4877.flags +++ /dev/null @@ -1 +0,0 @@ --Xlint \ No newline at end of file diff --git a/test/files/neg/t6567.flags b/test/files/neg/t6567.flags index e93641e931..076333a011 100644 --- a/test/files/neg/t6567.flags +++ b/test/files/neg/t6567.flags @@ -1 +1 @@ --Xlint -Xfatal-warnings \ No newline at end of file +-Xlint:option-implicit -Xfatal-warnings diff --git a/test/files/neg/t6675.flags b/test/files/neg/t6675.flags index 2843ea9efc..c6bfaf1f64 100644 --- a/test/files/neg/t6675.flags +++ b/test/files/neg/t6675.flags @@ -1 +1 @@ --deprecation -Xlint -Xfatal-warnings \ No newline at end of file +-deprecation -Xfatal-warnings diff --git a/test/files/neg/warn-unused-imports.flags b/test/files/neg/warn-unused-imports.flags index 24db705df1..c4e11e7fe7 100644 --- a/test/files/neg/warn-unused-imports.flags +++ b/test/files/neg/warn-unused-imports.flags @@ -1 +1 @@ --Xfatal-warnings -Ywarn-unused-import +-Xfatal-warnings -Ywarn-unused:imports diff --git a/test/files/pos/t6091.scala b/test/files/pos/t6091.scala index 72e663ec3b..0318640e7b 100644 --- a/test/files/pos/t6091.scala +++ b/test/files/pos/t6091.scala @@ -1,6 +1,6 @@ -object Foo { def eq(x:Int) = x } +object Foo { def eq(x: Int) = x } -class X { def ==(other: String) = true } +class X { def ==(other: String) = other.nonEmpty } object Test { def main(args: Array[String]): Unit = { diff --git a/test/files/pos/t8013.flags b/test/files/pos/t8013.flags index 3955bb6710..219723cec9 100644 --- a/test/files/pos/t8013.flags +++ b/test/files/pos/t8013.flags @@ -1 +1 @@ --Xfatal-warnings -Xlint:-infer-any,_ +-Xfatal-warnings -Xlint:missing-interpolator diff --git a/test/files/pos/t8040.flags b/test/files/pos/t8040.flags new file mode 100644 index 0000000000..3126c059f0 --- /dev/null +++ b/test/files/pos/t8040.flags @@ -0,0 +1 @@ +-Xfatal-warnings -Ywarn-unused:params diff --git a/test/files/pos/t8040.scala b/test/files/pos/t8040.scala new file mode 100644 index 0000000000..b067f36b0b --- /dev/null +++ b/test/files/pos/t8040.scala @@ -0,0 +1,6 @@ + +object Test { + implicit class C(val sc: StringContext) { // no warn unused sc + def c(args: Any*): String = "c?" + args.mkString(",") // would warn unused args + } +} -- cgit v1.2.3 From 47754ef531ec58bc11fe678b2b2aaab2cc20def9 Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Thu, 22 Dec 2016 00:37:21 -0800 Subject: SI-8040 No warn DummyImplicit It's just a dummy. --- src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala | 3 ++- test/files/pos/t8040.scala | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala b/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala index fa0407792e..7013c7da93 100644 --- a/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala +++ b/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala @@ -657,7 +657,8 @@ trait TypeDiagnostics { opc.iterator.exists(pair => pair.low == m) } def isConvention(p: Symbol): Boolean = { - p.name.decoded == "args" && p.owner.name.decoded == "main" + (p.name.decoded == "args" && p.owner.isMethod && p.owner.name.decoded == "main") || + (p.tpe =:= typeOf[scala.Predef.DummyImplicit]) } def warnable(s: Symbol) = ( (settings.warnUnusedParams || s.isImplicit) diff --git a/test/files/pos/t8040.scala b/test/files/pos/t8040.scala index b067f36b0b..1d1a770060 100644 --- a/test/files/pos/t8040.scala +++ b/test/files/pos/t8040.scala @@ -3,4 +3,6 @@ object Test { implicit class C(val sc: StringContext) { // no warn unused sc def c(args: Any*): String = "c?" + args.mkString(",") // would warn unused args } + + def f(implicit x: DummyImplicit) = 42 // no warn DummyImplicit } -- cgit v1.2.3 From 6614931197e788ffed2b8a6a959b9b5abfcc9142 Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Wed, 5 Apr 2017 12:27:40 -0700 Subject: SI-8040 Retreat on params Don't warn unused params when `-Xlint`. Don't disable under lint, so `-Ywarn-unused -Xlint` works. --- src/compiler/scala/tools/nsc/settings/Warnings.scala | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/compiler/scala/tools/nsc/settings/Warnings.scala b/src/compiler/scala/tools/nsc/settings/Warnings.scala index eb78064155..54678a5c12 100644 --- a/src/compiler/scala/tools/nsc/settings/Warnings.scala +++ b/src/compiler/scala/tools/nsc/settings/Warnings.scala @@ -85,7 +85,7 @@ trait Warnings { val UnsoundMatch = LintWarning("unsound-match", "Pattern match may not be typesafe.") val StarsAlign = LintWarning("stars-align", "Pattern sequence wildcard must align with sequence component.") val Constant = LintWarning("constant", "Evaluation of a constant arithmetic expression results in an error.") - val Unused = LintWarning("unused", "Enable -Ywarn-unused:-patvars,_.") + val Unused = LintWarning("unused", "Enable -Ywarn-unused:imports,privates,locals,implicits.") def allLintWarnings = values.toSeq.asInstanceOf[Seq[LintWarning]] } @@ -129,7 +129,10 @@ trait Warnings { descr = "Enable or disable specific warnings", domain = LintWarnings, default = Some(List("_")) - ).withPostSetHook { s => if (s contains Unused) List("-patvars","_").foreach(warnUnused.add) } + ).withPostSetHook { s => + val unused = List("imports", "privates", "locals", "implicits") + if (s contains Unused) unused.foreach(warnUnused.add) + } allLintWarnings foreach { case w if w.yAliased => -- cgit v1.2.3 From 1290fff26ea626f5d1f9f3c65bd5bd0a97939332 Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Thu, 6 Apr 2017 13:18:47 -0700 Subject: SI-8040 Defer deprecation of -Ywarn-unused-imports So as not to complicate established builds in the wild. --- src/compiler/scala/tools/nsc/settings/Warnings.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/scala/tools/nsc/settings/Warnings.scala b/src/compiler/scala/tools/nsc/settings/Warnings.scala index 54678a5c12..329a6aadd7 100644 --- a/src/compiler/scala/tools/nsc/settings/Warnings.scala +++ b/src/compiler/scala/tools/nsc/settings/Warnings.scala @@ -48,7 +48,7 @@ trait Warnings { BooleanSetting("-Ywarn-unused-import", "Warn when imports are unused.") withPostSetHook { s => warnUnused.add(s"${if (s) "" else "-"}imports") - } withDeprecationMessage s"Enable -Ywarn-unused:imports" + } //withDeprecationMessage s"Enable -Ywarn-unused:imports" val warnExtraImplicit = BooleanSetting("-Ywarn-extra-implicit", "Warn when more than one implicit parameter section is defined.") -- cgit v1.2.3 From bad61ce0ff9f460c2f8873c134a7f6bee0a53824 Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Thu, 6 Apr 2017 18:13:22 -0700 Subject: SD-363 Xlint no warn deprecated params, defaults Deprecation is an escape hatch for unused params. Since default arg getters receive values of previous args, don't warn when they are unused. --- .../scala/tools/nsc/typechecker/TypeDiagnostics.scala | 14 ++++++++++---- test/files/pos/t8040.scala | 5 +++++ 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala b/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala index 7013c7da93..a0139937f1 100644 --- a/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala +++ b/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala @@ -567,10 +567,16 @@ trait TypeDiagnostics { && !treeTypes.exists(_ contains m) // e.g. val a = new Foo ; new a.Bar //&& !(m.isVal && m.info.resultType =:= typeOf[Unit]) // Unit val is uninteresting ) - def isUnusedParam(m: Symbol): Boolean = isUnusedTerm(m) && !(m.isParamAccessor && ( - m.owner.isImplicit || - targets.exists(s => s.isParameter && s.name == m.name && s.owner.isConstructor && s.owner.owner == m.owner) // exclude ctor params - )) + def isUnusedParam(m: Symbol): Boolean = ( + isUnusedTerm(m) + && !m.isDeprecated + && !m.owner.isDefaultGetter + && !(m.isParamAccessor && ( + m.owner.isImplicit || + targets.exists(s => s.isParameter + && s.name == m.name && s.owner.isConstructor && s.owner.owner == m.owner) // exclude ctor params + )) + ) def sympos(s: Symbol): Int = if (s.pos.isDefined) s.pos.point else if (s.isTerm) s.asTerm.referenced.pos.point else -1 def treepos(t: Tree): Int = diff --git a/test/files/pos/t8040.scala b/test/files/pos/t8040.scala index 1d1a770060..3e01014ab4 100644 --- a/test/files/pos/t8040.scala +++ b/test/files/pos/t8040.scala @@ -5,4 +5,9 @@ object Test { } def f(implicit x: DummyImplicit) = 42 // no warn DummyImplicit + + + def f(x: Int)(y: Int = 1) = x + y // no warn default getter + + def g(@deprecated("","") x: Int) = 42 // no warn deprecated } -- cgit v1.2.3