diff options
Diffstat (limited to 'src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala')
-rw-r--r-- | src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala | 269 |
1 files changed, 197 insertions, 72 deletions
diff --git a/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala b/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala index 5f2643cb25..cd1dd18768 100644 --- a/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala +++ b/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala @@ -11,6 +11,7 @@ import scala.collection.mutable.ListBuffer import scala.util.control.Exception.ultimately import symtab.Flags._ import PartialFunction._ +import scala.annotation.tailrec /** An interface to enable higher configurability of diagnostic messages * regarding type errors. This is barely a beginning as error messages are @@ -32,7 +33,7 @@ import PartialFunction._ * @version 1.0 */ trait TypeDiagnostics { - self: Analyzer => + self: Analyzer with StdAttachments => import global._ import definitions._ @@ -78,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 @@ -97,7 +100,7 @@ trait TypeDiagnostics { /** An explanatory note to be added to error messages * when there's a problem with abstract var defs */ def abstractVarMessage(sym: Symbol): String = - if (underlyingSymbol(sym).isVariable) + if (sym.isSetter || sym.isGetter && sym.setterIn(sym.owner).exists) "\n(Note that variables need to be initialized to be defined)" else "" @@ -116,13 +119,13 @@ trait TypeDiagnostics { */ final def exampleTuplePattern(names: List[Name]): String = { val arity = names.length - val varPatterNames: Option[List[String]] = sequence(names map { + val varPatternNames: Option[List[String]] = sequence(names map { case name if nme.isVariableName(name) => Some(name.decode) case _ => None }) def parenthesize(a: String) = s"($a)" def genericParams = (Seq("param1") ++ (if (arity > 2) Seq("...") else Nil) ++ Seq(s"param$arity")) - parenthesize(varPatterNames.getOrElse(genericParams).mkString(", ")) + parenthesize(varPatternNames.getOrElse(genericParams).mkString(", ")) } def alternatives(tree: Tree): List[Type] = tree.tpe match { @@ -133,12 +136,14 @@ trait TypeDiagnostics { alternatives(tree) map (x => " " + methodTypeErrorString(x)) mkString ("", " <and>\n", "\n") /** The symbol which the given accessor represents (possibly in part). - * This is used for error messages, where we want to speak in terms - * of the actual declaration or definition, not in terms of the generated setters - * and getters. - */ + * This is used for error messages, where we want to speak in terms + * of the actual declaration or definition, not in terms of the generated setters + * and getters. + * + * TODO: is it wise to create new symbols simply to generate error message? is this safe in interactive/resident mode? + */ def underlyingSymbol(member: Symbol): Symbol = - if (!member.hasAccessorFlag) member + if (!member.hasAccessorFlag || member.accessed == NoSymbol) member else if (!member.isDeferred) member.accessed else { val getter = if (member.isSetter) member.getterIn(member.owner) else member @@ -272,19 +277,54 @@ trait TypeDiagnostics { if (AnyRefTpe <:< req) notAnyRefMessage(found) else "" } + def finalOwners(tpe: Type): Boolean = (tpe.prefix == NoPrefix) || recursivelyFinal(tpe) + + @tailrec + final def recursivelyFinal(tpe: Type): Boolean = { + val prefix = tpe.prefix + if (prefix != NoPrefix) { + if (prefix.typeSymbol.isFinal) { + recursivelyFinal(prefix) + } else { + false + } + } else { + true + } + } + // TODO - figure out how to avoid doing any work at all // when the message will never be seen. I though context.reportErrors // being false would do that, but if I return "<suppressed>" under // that condition, I see it. def foundReqMsg(found: Type, req: Type): String = { - def baseMessage = ( - ";\n found : " + found.toLongString + existentialContext(found) + explainAlias(found) + - "\n required: " + req + existentialContext(req) + explainAlias(req) - ) - ( withDisambiguation(Nil, found, req)(baseMessage) - + explainVariance(found, req) - + explainAnyVsAnyRef(found, req) - ) + val foundWiden = found.widen + val reqWiden = req.widen + val sameNamesDifferentPrefixes = + foundWiden.typeSymbol.name == reqWiden.typeSymbol.name && + foundWiden.prefix.typeSymbol != reqWiden.prefix.typeSymbol + val easilyMistakable = + sameNamesDifferentPrefixes && + !req.typeSymbol.isConstant && + finalOwners(foundWiden) && finalOwners(reqWiden) && + !found.typeSymbol.isTypeParameterOrSkolem && !req.typeSymbol.isTypeParameterOrSkolem + + if (easilyMistakable) { + val longestNameLength = foundWiden.nameAndArgsString.length max reqWiden.nameAndArgsString.length + val paddedFoundName = foundWiden.nameAndArgsString.padTo(longestNameLength, ' ') + val paddedReqName = reqWiden.nameAndArgsString.padTo(longestNameLength, ' ') + ";\n found : " + (paddedFoundName + s" (in ${found.prefix.typeSymbol.fullNameString}) ") + explainAlias(found) + + "\n required: " + (paddedReqName + s" (in ${req.prefix.typeSymbol.fullNameString}) ") + explainAlias(req) + } else { + def baseMessage = { + ";\n found : " + found.toLongString + existentialContext(found) + explainAlias(found) + + "\n required: " + req + existentialContext(req) + explainAlias(req) + } + (withDisambiguation(Nil, found, req)(baseMessage) + + explainVariance(found, req) + + explainAnyVsAnyRef(found, req) + ) + } } def typePatternAdvice(sym: Symbol, ptSym: Symbol) = { @@ -313,17 +353,12 @@ trait TypeDiagnostics { def restoreName() = sym.name = savedName def modifyName(f: String => String) = sym setName newTypeName(f(sym.name.toString)) - /** Prepend java.lang, scala., or Predef. if this type originated - * in one of those. - */ - def qualifyDefaultNamespaces() = { - val intersect = Set(trueOwner, aliasOwner) intersect UnqualifiedOwners - if (intersect.nonEmpty && tp.typeSymbolDirect.name == tp.typeSymbol.name) preQualify() - } - // 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() @@ -412,12 +447,6 @@ trait TypeDiagnostics { if (td1 string_== td2) tds foreach (_.nameQualify()) - // If they have the same simple name, and either of them is in the - // scala package or predef, qualify with scala so it is not confusing why - // e.g. java.util.Iterator and Iterator are different types. - if (td1 name_== td2) - tds foreach (_.qualifyDefaultNamespaces()) - // If they still print identically: // a) If they are type parameters with different owners, append (in <owner>) // b) Failing that, the best we can do is append "(some other)" to the latter. @@ -440,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) @@ -466,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 @@ -499,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 unusedTypes = defnTrees.toList filter (t => isUnusedType(t.symbol)) - def unusedTerms = defnTrees.toList filter (v => isUnusedTerm(v.symbol)) + 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 = + 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) } - 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 - 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) "var" - else if (sym.isVal || sym.isGetter && sym.accessed.isVal || 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") } } } |