diff options
author | Paul Phillips <paulp@improving.org> | 2012-09-25 18:11:29 -0700 |
---|---|---|
committer | Paul Phillips <paulp@improving.org> | 2012-09-25 18:21:33 -0700 |
commit | 9d423c9bb76dddcd080d98f4a05c02856708fc06 (patch) | |
tree | 764110f7277097e6acdc6efe5ce44040decd5fcc /src | |
parent | 5499e41157cdb6a81772728525e9c1f44401e7cc (diff) | |
download | scala-9d423c9bb76dddcd080d98f4a05c02856708fc06.tar.gz scala-9d423c9bb76dddcd080d98f4a05c02856708fc06.tar.bz2 scala-9d423c9bb76dddcd080d98f4a05c02856708fc06.zip |
Improvements to unchecked warnings.
Closes SI-6275, SI-5762.
The comment says is better than I can.
/** On pattern matcher checkability:
*
* Consider a pattern match of this form: (x: X) match { case _: P => }
*
* There are four possibilities to consider:
* [P1] X will always conform to P
* [P2] x will never conform to P
* [P3] X <: P if some runtime test is true
* [P4] X cannot be checked against P
*
* The first two cases correspond to those when there is enough static
* information to say X <: P or that !(X <: P) for all X and P.
* The fourth case includes unknown abstract types or structural
* refinements appearing within a pattern.
*
* The third case is the interesting one. We designate another type, XR,
* which is essentially the intersection of X and |P|, where |P| is
* the erasure of P. If XR <: P, then no warning is emitted.
*
* Examples of how this info is put to use:
* sealed trait A[T] ; class B[T] extends A[T]
* def f(x: B[Int]) = x match { case _: A[Int] if true => }
* def g(x: A[Int]) = x match { case _: B[Int] => }
*
* `f` requires no warning because X=B[Int], P=A[Int], and B[Int] <:< A[Int].
* `g` requires no warning because X=A[Int], P=B[Int], XR=B[Int], and B[Int] <:< B[Int].
* XR=B[Int] because a value of type A[Int] which is tested to be a B can
* only be a B[Int], due to the definition of B (B[T] extends A[T].)
*
* This is something like asSeenFrom, only rather than asking what a type looks
* like from the point of view of one of its base classes, we ask what it looks
* like from the point of view of one of its subclasses.
*/
Diffstat (limited to 'src')
-rw-r--r-- | src/compiler/scala/tools/nsc/typechecker/Checkable.scala | 254 |
1 files changed, 166 insertions, 88 deletions
diff --git a/src/compiler/scala/tools/nsc/typechecker/Checkable.scala b/src/compiler/scala/tools/nsc/typechecker/Checkable.scala index 13dcfaacfe..5363abbae9 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Checkable.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Checkable.scala @@ -11,6 +11,7 @@ import scala.collection.mutable.ListBuffer import scala.util.control.ControlThrowable import symtab.Flags._ import scala.annotation.tailrec +import Checkability._ /** On pattern matcher checkability: * @@ -51,104 +52,181 @@ trait Checkable { import global._ import definitions._ - trait InferCheckable { - self: Inferencer => + /** The applied type of class 'to' after inferring anything + * possible from the knowledge that 'to' must also be of the + * type given in 'from'. + */ + def propagateKnownTypes(from: Type, to: Symbol): Type = { + def tparams = to.typeParams + val tvars = tparams map (p => TypeVar(p)) + val tvarType = appliedType(to, tvars: _*) + val bases = from.baseClasses filter (to.baseClasses contains _) - import InferErrorGen._ - private def context = getContext + bases foreach { bc => + val tps1 = (from baseType bc).typeArgs + val tps2 = (tvarType baseType bc).typeArgs + (tps1, tps2).zipped foreach (_ =:= _) + } - // if top-level abstract types can be checked using a classtag extractor, don't warn about them - def checkCheckable(tree: Tree, typeToTest: Type, typeEnsured: Type, inPattern: Boolean, canRemedy: Boolean = false) = { - log(s"checkCheckable($tree, $typeToTest, $typeEnsured, inPattern = $inPattern, canRemedy = $canRemedy") + val resArgs = tparams zip tvars map { + case (_, tvar) if tvar.instValid => tvar.constr.inst + case (tparam, _) => tparam.tpe + } + appliedType(to, resArgs: _*) + } - sealed abstract class TypeConformance(check: (Type, Type) => Boolean) { - def apply(t1: Type, t2: Type): Boolean = check(t1, t2) && { - log(s"Skipping unchecked for statically verifiable condition $t1 ${this} $t2") - true - } + private def isUnwarnableTypeArgSymbol(sym: Symbol) = ( + sym.isTypeParameterOrSkolem // dummy + || (sym.name.toTermName == nme.WILDCARD) // _ + || nme.isVariableName(sym.name) // type variable + || (sym == NonLocalReturnControlClass) // synthetic + ) + private def isUnwarnableTypeArg(arg: Type) = ( + isUnwarnableTypeArgSymbol(arg.typeSymbolDirect) // has to be direct: see pos/t1439 + || (arg hasAnnotation UncheckedClass) // @unchecked T + ) + + private def typeArgsInTopLevelType(tp: Type): List[Type] = { + val tps = tp match { + case RefinedType(parents, _) => parents flatMap typeArgsInTopLevelType + case TypeRef(_, ArrayClass, arg :: Nil) => typeArgsInTopLevelType(arg) + case TypeRef(pre, sym, args) => typeArgsInTopLevelType(pre) ++ args + case ExistentialType(tparams, underlying) => tparams.map(_.tpe) ++ typeArgsInTopLevelType(underlying) + case _ => Nil + } + tps filterNot isUnwarnableTypeArg + } + + private def isReifiableArray(tp: Type): Boolean = tp match { + case TypeRef(_, ArrayClass, arg :: Nil) => isReifiableArray(arg) + case TypeRef(_, sym, args) => isUnwarnableTypeArg(tp) || (!sym.isAbstractType && args.isEmpty) + case _ => false + } + + private class CheckabilityChecker(val X: Type, val P: Type) { + def Xsym = X.typeSymbol + def Psym = P.typeSymbol + def XR = propagateKnownTypes(X, Psym) + def P1 = X matchesPattern P + def P2 = CheckabilityChecker.isNeverSubType(X, P) + def P3 = Psym.isClass && ((XR matchesPattern P) || isReifiableArray(P) || P.typeArgs.forall(isUnwarnableTypeArg)) + def P4 = !(P1 || P2 || P3) + + def summaryString = f""" + |Checking checkability of (x: $X) against pattern $P + |[P1] $P1%-6s X <: P // $X <: $P + |[P2] $P2%-6s X !<: P // $X !<: $P for all X, P + |[P3] $P3%-6s XR <: P // $XR <: $P + |[P4] $P4%-6s None of the above // !(P1 || P2 || P3) + """.stripMargin.trim + + val result = ( + if (X.isErroneous || P.isErroneous) CheckabilityError + else if (P1) StaticallyTrue + else if (P2) StaticallyFalse + else if (P3) RuntimeCheckable + else if (uncheckableType == NoType) { + // Avoid warning (except ourselves) if we can't pinpoint the uncheckable type + debugwarn("Checkability checker says 'Uncheckable', but uncheckable type cannot be found:\n" + summaryString) + CheckabilityError } - // I tried to use varianceInType to track the variance implications - // but I could not make it work. - case object =:= extends TypeConformance(_ =:= _) - case object <:< extends TypeConformance(_ <:< _) - case object >:> extends TypeConformance((t1, t2) => t2 <:< t1) - case object =!= extends TypeConformance((t1, t2) => false) - - var bound: List[Symbol] = Nil - var warningMessages: List[String] = Nil - - def isLocalBinding(sym: Symbol) = ( - sym.isAbstractType && ( - (bound contains sym) - || (sym.name == tpnme.WILDCARD) - || { - val e = context.scope.lookupEntry(sym.name) - (e ne null) && e.sym == sym && !e.sym.isTypeParameterOrSkolem && e.owner == context.scope - } - ) - ) - def check(tp0: Type, pt: Type, conformance: TypeConformance): Boolean = { - val tp = tp0.normalize - // Set the warning message to be issued when the top-level call fails. - def warn(what: String): Boolean = { - warningMessages ::= what - false - } - def checkArg(param: Symbol, arg: Type) = { - def conforms = ( - if (param.isCovariant) <:< - else if (param.isContravariant) >:> - else =:= - ) - (arg hasAnnotation UncheckedClass) || { - arg.withoutAnnotations match { - case TypeRef(_, sym, args) => - ( isLocalBinding(sym) - || arg.typeSymbol.isTypeParameterOrSkolem - || (sym.name == tpnme.WILDCARD) // avoid spurious warnings on HK types - || check(arg, param.tpeHK, conforms) - || warn("non-variable type argument " + arg) - ) - case _ => - warn("non-variable type argument " + arg) - } + else Uncheckable + ) + lazy val uncheckableType = if (Psym.isAbstractType) P else { + val possibles = typeArgsInTopLevelType(P).toSet + val opt = possibles find { targ => + // Create a derived type with every possibly uncheckable type replaced + // with a WildcardType, except for 'targ'. If !(XR <: derived) then + // 'targ' is uncheckable. + val derived = P map (tp => if (possibles(tp) && !(tp =:= targ)) WildcardType else tp) + !(XR <:< derived) + } + opt getOrElse NoType + } + + def isUncheckable = result == Uncheckable + def uncheckableMessage = uncheckableType match { + case NoType => "something" + case tp @ RefinedType(_, _) => "refinement " + tp + case TypeRef(_, sym, _) if sym.isAbstractType => "abstract type " + sym.name + case tp => "non-variable type argument " + tp + } + } + + /** X, P, [P1], etc. are all explained at the top of the file. + */ + private object CheckabilityChecker { + private def isNeverSubClass(sym1: Symbol, sym2: Symbol) = ( + sym1.isClass + && sym2.isClass + && sym1.isEffectivelyFinal + && !(sym1 isSubClass sym2) + ) + private def isNeverSubArgs(tps1: List[Type], tps2: List[Type], tparams: List[Symbol]): Boolean = { + def isNeverSubArg(t1: Type, t2: Type, variance: Int) = { + if (variance > 0) isNeverSubType(t2, t1) + else if (variance < 0) isNeverSubType(t1, t2) + else isNeverSameType(t1, t2) + } + exists3(tps1, tps2, tparams map (_.variance))(isNeverSubArg) + } + private def isNeverSameType(tp1: Type, tp2: Type): Boolean = (tp1, tp2) match { + case (TypeRef(_, sym1, args1), TypeRef(_, sym2, args2)) => + sym1.isClass && sym2.isClass && (sym1 ne sym2) && (sym1.isEffectivelyFinal || sym2.isEffectivelyFinal) + case _ => + false + } + private def isNeverSubType(tp1: Type, tp2: Type): Boolean = (tp1, tp2) match { + case (TypeRef(_, sym1, args1), TypeRef(_, sym2, args2)) => + isNeverSubClass(sym1, sym2) || { + (sym1 isSubClass sym2) && { + val tp1seen = tp1 baseType sym2 + + isNeverSubArgs(tp1seen.typeArgs, args2, sym2.typeParams) } } + case _ => false + } + } - // Checking if pt (the expected type of the pattern, and the type - // we are guaranteed) conforms to tp (the type expressed in the pattern's - // type test.) If it does, then even if the type being checked for appears - // to be uncheckable, it is not a warning situation, because it is indeed - // checked: not at runtime, but statically. - conformance.apply(pt, tp) || (tp match { - case SingleType(pre, _) => check(pre, pt, =:=) - case ExistentialType(quantified, tp1) => bound :::= quantified ; check(tp1, pt, <:<) - case ThisType(_) | NoPrefix => true - case RefinedType(parents, decls) if decls.isEmpty => parents forall (p => check(p, pt, <:<)) - case RefinedType(_, _) => warn("refinement " + tp) - case TypeRef(_, ArrayClass, arg :: Nil) => check(arg, NoType, =!=) - case TypeRef(_, NonLocalReturnControlClass, _) => true // no way to suppress unchecked warnings on try/catch - // we only use the extractor for top-level type tests, type arguments remain unchecked - case TypeRef(_, sym, _) if sym.isAbstractType => isLocalBinding(sym) || canRemedy || warn("abstract type " + tp) - case TypeRef(_, _, Nil) => false // leaf node - case TypeRef(pre, sym, args) => forall2(sym.typeParams, args)(checkArg) && check(pre, pt.prefix, =:=) - case _ => warn("type " + tp) - }) - } - typeToTest match { - // Prohibit top-level type tests for these, but they are - // acceptable nested (e.g. case Foldable[Nothing] => ... ) + trait InferCheckable { + self: Inferencer => + + /** TODO: much better error positions. + * Kind of stuck right now because they just pass us the one tree. + * TODO: Eliminate inPattern, canRemedy, which have no place here. + */ + def checkCheckable(tree: Tree, P0: Type, X: Type, inPattern: Boolean, canRemedy: Boolean = false) { + // singleton types not considered here + val P = P0.widen + P match { + // Prohibit top-level type tests for these, but they are ok nested (e.g. case Foldable[Nothing] => ... ) case TypeRef(_, NothingClass | NullClass | AnyValClass, _) => - TypePatternOrIsInstanceTestError(tree, typeToTest) + InferErrorGen.TypePatternOrIsInstanceTestError(tree, P) + // If top-level abstract types can be checked using a classtag extractor, don't warn about them + case TypeRef(_, sym, _) if sym.isAbstractType && canRemedy => + ; case _ => - def where = ( if (inPattern) "pattern " else "" ) + typeToTest - if (check(typeToTest, typeEnsured, =:=)) () - // Note that this is a regular warning, not an uncheckedWarning, - // which is now the province of such notifications as "pattern matcher - // exceeded its analysis budget." - else warningMessages foreach (m => - context.unit.warning(tree.pos, s"$m in type $where is unchecked since it is eliminated by erasure")) + val checker = new CheckabilityChecker(X.widen, P) + log(checker.summaryString) + if (checker.isUncheckable) { + def where = if (inPattern) "pattern " else "" + val msg = ( + if (checker.uncheckableType =:= P) s"abstract type $where$P" + else s"${checker.uncheckableMessage} in type $where$P" + ) + getContext.unit.warning(tree.pos, s"$msg is unchecked since it is eliminated by erasure") + } } } } } + +private[typechecker] final class Checkability(val value: Int) extends AnyVal { } +private[typechecker] object Checkability { + val StaticallyTrue = new Checkability(0) + val StaticallyFalse = new Checkability(1) + val RuntimeCheckable = new Checkability(2) + val Uncheckable = new Checkability(3) + val CheckabilityError = new Checkability(4) +} |