diff options
author | Jason Zaugg <jzaugg@gmail.com> | 2014-05-16 23:35:43 +0200 |
---|---|---|
committer | Jason Zaugg <jzaugg@gmail.com> | 2014-11-09 11:15:58 +1000 |
commit | 32196749105c9f98a33712fddc21b1676250d046 (patch) | |
tree | 1a12ab0f8abdb36b2a9af4bbda6bbd957b3be835 /src | |
parent | b431a4bd83d3bfb2b95d0426d2905b34ce1265ad (diff) | |
download | scala-32196749105c9f98a33712fddc21b1676250d046.tar.gz scala-32196749105c9f98a33712fddc21b1676250d046.tar.bz2 scala-32196749105c9f98a33712fddc21b1676250d046.zip |
SI-8597 Improved pattern unchecked warnings
The spec says that `case _: List[Int]` should be always issue
an unchecked warning:
> Types which are not of one of the forms described above are
> also accepted as type patterns. However, such type patterns
> will be translated to their erasure (§3.7). The Scala compiler
> will issue an “unchecked” warning for these patterns to flag
> the possible loss of type-safety.
But the implementation goes a little further to omit warnings
based on the static type of the scrutinee. As a trivial example:
def foo(s: Seq[Int]) = s match { case _: List[Int] => }
need not issue this warning.
These discriminating unchecked warnings are domain of
`CheckabilityChecker`.
Let's deconstruct the reported bug:
def nowarn[T] = (null: Any) match { case _: Some[T] => }
We used to determine that if the first case matched, the scrutinee
type would be `Some[Any]` (`Some` is covariant). If this statically
matches `Some[T]` in a pattern context, we don't need to issue an
unchecked warning. But, our blanket use of `existentialAbstraction`
in `matchesPattern` loosened the pattern type to `Some[Any]`, and
the scrutinee type was deemed compatible.
I've added a new method, `scrutConformsToPatternType` which replaces
pattern type variables by wildcards, but leaves other abstract
types intact in the pattern type. We have to use this inside
`CheckabilityChecker` only. If we were to make `matchesPattern`
stricter in the same way, tests like `pos/t2486.scala` would fail.
I have introduced a new symbol test to (try to) identify pattern
type variables introduced by `typedBind`. Its not pretty, and it
might be cleaner to reserve a new flag for these.
I've also included a test variation exercising with nested matches.
The pattern type of the inner case can't, syntactically, refer to the
pattern type variable of the enclosing case. If it could, we would
have to be more selective in our wildcarding in `ptMatchesPatternType`
by restricting ourselves to type variables associated with the closest
enclosing `CaseDef`.
As some further validation of the correctness of this patch,
four stray warnings have been teased out of
neg/unchecked-abstract.scala
I also had to changes `typeArgsInTopLevelType` to extract the type
arguments of `Array[T]` if `T` is an abstract type. This avoids the
"Checkability checker says 'Uncheckable', but uncheckable type
cannot be found" warning and consequent overly lenient analysis.
Without this change, the warning was suppressed for:
def warnArray[T] = (null: Any) match { case _: Array[T] => }
Diffstat (limited to 'src')
-rw-r--r-- | src/compiler/scala/tools/nsc/typechecker/Checkable.scala | 25 | ||||
-rw-r--r-- | src/reflect/scala/reflect/internal/Symbols.scala | 4 |
2 files changed, 25 insertions, 4 deletions
diff --git a/src/compiler/scala/tools/nsc/typechecker/Checkable.scala b/src/compiler/scala/tools/nsc/typechecker/Checkable.scala index 3a77cab919..fcc3f6901c 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Checkable.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Checkable.scala @@ -100,7 +100,7 @@ trait Checkable { 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(_, ArrayClass, arg :: Nil) => if (arg.typeSymbol.isAbstractType) arg :: Nil else typeArgsInTopLevelType(arg) case TypeRef(pre, sym, args) => typeArgsInTopLevelType(pre) ++ args case ExistentialType(tparams, underlying) => tparams.map(_.tpe) ++ typeArgsInTopLevelType(underlying) case _ => Nil @@ -108,14 +108,31 @@ trait Checkable { tps filterNot isUnwarnableTypeArg } + private def scrutConformsToPatternType(scrut: Type, pattTp: Type): Boolean = { + def typeVarToWildcard(tp: Type) = { + // The need for typeSymbolDirect is demonstrated in neg/t8597b.scala + if (tp.typeSymbolDirect.isPatternTypeVariable) WildcardType else tp + } + val pattTpWild = pattTp.map(typeVarToWildcard) + scrut <:< pattTpWild + } + private class CheckabilityChecker(val X: Type, val P: Type) { def Xsym = X.typeSymbol def Psym = P.typeSymbol - def XR = if (Xsym == AnyClass) classExistentialType(Psym) else propagateKnownTypes(X, Psym) + def PErased = { + P match { + case erasure.GenericArray(n, core) => existentialAbstraction(core.typeSymbol :: Nil, P) + case _ => existentialAbstraction(Psym.typeParams, Psym.tpe_*) + } + } + def XR = if (Xsym == AnyClass) PErased else propagateKnownTypes(X, Psym) + + // sadly the spec says (new java.lang.Boolean(true)).isInstanceOf[scala.Boolean] - def P1 = X matchesPattern P + def P1 = scrutConformsToPatternType(X, P) def P2 = !Psym.isPrimitiveValueClass && isNeverSubType(X, P) - def P3 = isNonRefinementClassType(P) && (XR matchesPattern P) + def P3 = isNonRefinementClassType(P) && scrutConformsToPatternType(XR, P) def P4 = !(P1 || P2 || P3) def summaryString = f""" diff --git a/src/reflect/scala/reflect/internal/Symbols.scala b/src/reflect/scala/reflect/internal/Symbols.scala index 51f06b1d6d..b2f176e894 100644 --- a/src/reflect/scala/reflect/internal/Symbols.scala +++ b/src/reflect/scala/reflect/internal/Symbols.scala @@ -792,6 +792,10 @@ trait Symbols extends api.Symbols { self: SymbolTable => final def isDefinedInPackage = effectiveOwner.isPackageClass final def needsFlatClasses = phase.flatClasses && rawowner != NoSymbol && !rawowner.isPackageClass + // TODO introduce a flag for these? + final def isPatternTypeVariable: Boolean = + isAbstractType && !isExistential && !isTypeParameterOrSkolem && isLocalToBlock + /** change name by appending $$<fully-qualified-name-of-class `base`> * Do the same for any accessed symbols or setters/getters. * Implementation in TermSymbol. |