diff options
Diffstat (limited to 'src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala')
-rw-r--r-- | src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala | 185 |
1 files changed, 143 insertions, 42 deletions
diff --git a/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala b/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala index 35e6e0099e..cd1dd18768 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 @@ -353,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() @@ -464,13 +469,18 @@ 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]() val targets = mutable.Set[Symbol]() val setVars = mutable.Set[Symbol]() 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) @@ -490,20 +500,39 @@ trait TypeDiagnostics { ) override def traverse(t: Tree): Unit = { + val sym = t.symbol 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 !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 { + // 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 sym ne null => targets += sym case Assign(lhs, _) if lhs.symbol != null => setVars += lhs.symbol + case Bind(_, _) if atBounded(t) => atBounds += sym case _ => } // 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(_) => case MethodType(_, _) => + case SingleType(_, _) => case _ => log(s"$tp referenced from $currentOwner") treeTypes += tp @@ -523,55 +552,127 @@ 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.isPrivate || m.isLocalToBlock) + m.isTerm + && (!m.isSynthetic || isSyntheticWarnable(m)) + && ((m.isPrivate && !(m.isConstructor && m.owner.isAbstract)) || 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 + //&& !(m.isVal && m.info.resultType =:= typeOf[Unit]) // Unit val is uninteresting + ) + 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 unusedTypes = defnTrees.toList filter (t => isUnusedType(t.symbol)) - def unusedTerms = defnTrees.toList filter (v => isUnusedTerm(v.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)) + + // 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) + ).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(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 = { + val ss = settings + import ss._ + warnUnusedPatVars || warnUnusedPrivates || warnUnusedLocals || warnUnusedParams || warnUnusedImplicits } - def apply(unit: CompilationUnit) = { + 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 - 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 + p.traverse(unit.body) + 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: consider using immutable 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)))) "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 "term" - ) - reporter.warning(pos, s"$why $what in ${sym.owner} is never used") + } } - p.unsetVars foreach { v => - reporter.warning(v.pos, s"local var ${v.name} in ${v.owner} is never set - it could be a val") + 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") } - p.unusedTypes foreach { t => - 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 isConvention(p: Symbol): Boolean = { + (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) + && !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") } } } |