From 9d423c9bb76dddcd080d98f4a05c02856708fc06 Mon Sep 17 00:00:00 2001 From: Paul Phillips Date: Tue, 25 Sep 2012 18:11:29 -0700 Subject: 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. */ --- test/files/neg/t4302.check | 2 +- test/files/neg/unchecked.check | 2 +- test/files/neg/unchecked.scala | 4 +-- test/files/neg/unchecked2.check | 60 ++++++++++++++++++++++++++++------------- test/files/neg/unchecked2.scala | 37 ++++++++++++++++++++----- 5 files changed, 77 insertions(+), 28 deletions(-) (limited to 'test/files/neg') diff --git a/test/files/neg/t4302.check b/test/files/neg/t4302.check index 327425acb0..450d28bbc5 100644 --- a/test/files/neg/t4302.check +++ b/test/files/neg/t4302.check @@ -1,4 +1,4 @@ -t4302.scala:2: error: abstract type T in type T is unchecked since it is eliminated by erasure +t4302.scala:2: error: abstract type T is unchecked since it is eliminated by erasure def hasMatch[T](x: AnyRef) = x.isInstanceOf[T] ^ one error found diff --git a/test/files/neg/unchecked.check b/test/files/neg/unchecked.check index 34a11db1a0..2883b716c9 100644 --- a/test/files/neg/unchecked.check +++ b/test/files/neg/unchecked.check @@ -2,7 +2,7 @@ unchecked.scala:18: error: non-variable type argument String in type pattern Ite case xs: Iterable[String] => xs.head // unchecked ^ unchecked.scala:22: error: non-variable type argument Any in type pattern Set[Any] is unchecked since it is eliminated by erasure - case xs: Set[Any] => xs.head // unchecked + case xs: Set[Any] => xs.head // unchecked ^ unchecked.scala:26: error: non-variable type argument Any in type pattern Map[Any,Any] is unchecked since it is eliminated by erasure case xs: Map[Any, Any] => xs.head // unchecked diff --git a/test/files/neg/unchecked.scala b/test/files/neg/unchecked.scala index b50cdf9d7a..e491b253ba 100644 --- a/test/files/neg/unchecked.scala +++ b/test/files/neg/unchecked.scala @@ -19,8 +19,8 @@ object Test { case _ => 0 } def f3(x: Any) = x match { - case xs: Set[Any] => xs.head // unchecked - case _ => 0 + case xs: Set[Any] => xs.head // unchecked + case _ => 0 } def f4(x: Any) = x match { case xs: Map[Any, Any] => xs.head // unchecked diff --git a/test/files/neg/unchecked2.check b/test/files/neg/unchecked2.check index e37865928e..0ff2a249a8 100644 --- a/test/files/neg/unchecked2.check +++ b/test/files/neg/unchecked2.check @@ -1,19 +1,43 @@ -unchecked2.scala:2: error: non-variable type argument Int in type Option[Int] is unchecked since it is eliminated by erasure - Some(123).isInstanceOf[Option[Int]] - ^ -unchecked2.scala:3: error: non-variable type argument String in type Option[String] is unchecked since it is eliminated by erasure - Some(123).isInstanceOf[Option[String]] - ^ unchecked2.scala:4: error: non-variable type argument List[String] in type Option[List[String]] is unchecked since it is eliminated by erasure - Some(123).isInstanceOf[Option[List[String]]] - ^ -unchecked2.scala:5: error: non-variable type argument List[Int => String] in type Option[List[Int => String]] is unchecked since it is eliminated by erasure - Some(123).isInstanceOf[Option[List[Int => String]]] - ^ -unchecked2.scala:6: error: non-variable type argument (String, Double) in type Option[(String, Double)] is unchecked since it is eliminated by erasure - Some(123).isInstanceOf[Option[(String, Double)]] - ^ -unchecked2.scala:7: error: non-variable type argument String => Double in type Option[String => Double] is unchecked since it is eliminated by erasure - Some(123).isInstanceOf[Option[String => Double]] - ^ -6 errors found + /* warn */ Some(List(1)).isInstanceOf[Option[List[String]]] + ^ +unchecked2.scala:5: error: non-variable type argument Option[_] in type Option[Option[_]] is unchecked since it is eliminated by erasure + /* warn */ Some(123).isInstanceOf[Option[Option[_]]] + ^ +unchecked2.scala:6: error: non-variable type argument String in type Option[String] is unchecked since it is eliminated by erasure + /* warn */ Some(123).isInstanceOf[Option[String]] + ^ +unchecked2.scala:7: error: non-variable type argument List[String] in type Option[List[String]] is unchecked since it is eliminated by erasure + /* warn */ Some(123).isInstanceOf[Option[List[String]]] + ^ +unchecked2.scala:8: error: non-variable type argument List[Int => String] in type Option[List[Int => String]] is unchecked since it is eliminated by erasure + /* warn */ Some(123).isInstanceOf[Option[List[Int => String]]] + ^ +unchecked2.scala:9: error: non-variable type argument (String, Double) in type Option[(String, Double)] is unchecked since it is eliminated by erasure + /* warn */ Some(123).isInstanceOf[Option[(String, Double)]] + ^ +unchecked2.scala:10: error: non-variable type argument String => Double in type Option[String => Double] is unchecked since it is eliminated by erasure + /* warn */ Some(123).isInstanceOf[Option[String => Double]] + ^ +unchecked2.scala:14: error: non-variable type argument List[String] in type Option[List[String]] is unchecked since it is eliminated by erasure + /* warn */ (Some(List(1)): Any).isInstanceOf[Option[List[String]]] + ^ +unchecked2.scala:15: error: non-variable type argument Int in type Option[Int] is unchecked since it is eliminated by erasure + /* warn */ (Some(123): Any).isInstanceOf[Option[Int]] + ^ +unchecked2.scala:16: error: non-variable type argument String in type Option[String] is unchecked since it is eliminated by erasure + /* warn */ (Some(123): Any).isInstanceOf[Option[String]] + ^ +unchecked2.scala:17: error: non-variable type argument List[String] in type Option[List[String]] is unchecked since it is eliminated by erasure + /* warn */ (Some(123): Any).isInstanceOf[Option[List[String]]] + ^ +unchecked2.scala:18: error: non-variable type argument List[Int => String] in type Option[List[Int => String]] is unchecked since it is eliminated by erasure + /* warn */ (Some(123): Any).isInstanceOf[Option[List[Int => String]]] + ^ +unchecked2.scala:19: error: non-variable type argument (String, Double) in type Option[(String, Double)] is unchecked since it is eliminated by erasure + /* warn */ (Some(123): Any).isInstanceOf[Option[(String, Double)]] + ^ +unchecked2.scala:20: error: non-variable type argument String => Double in type Option[String => Double] is unchecked since it is eliminated by erasure + /* warn */ (Some(123): Any).isInstanceOf[Option[String => Double]] + ^ +14 errors found diff --git a/test/files/neg/unchecked2.scala b/test/files/neg/unchecked2.scala index a2e757e1dc..616b05aad8 100644 --- a/test/files/neg/unchecked2.scala +++ b/test/files/neg/unchecked2.scala @@ -1,8 +1,33 @@ object Test { - Some(123).isInstanceOf[Option[Int]] - Some(123).isInstanceOf[Option[String]] - Some(123).isInstanceOf[Option[List[String]]] - Some(123).isInstanceOf[Option[List[Int => String]]] - Some(123).isInstanceOf[Option[(String, Double)]] - Some(123).isInstanceOf[Option[String => Double]] + // These warn because it can be statically shown they won't match. + + /* warn */ Some(List(1)).isInstanceOf[Option[List[String]]] + /* warn */ Some(123).isInstanceOf[Option[Option[_]]] + /* warn */ Some(123).isInstanceOf[Option[String]] + /* warn */ Some(123).isInstanceOf[Option[List[String]]] + /* warn */ Some(123).isInstanceOf[Option[List[Int => String]]] + /* warn */ Some(123).isInstanceOf[Option[(String, Double)]] + /* warn */ Some(123).isInstanceOf[Option[String => Double]] + + // These warn because you can't check at runtime. + + /* warn */ (Some(List(1)): Any).isInstanceOf[Option[List[String]]] + /* warn */ (Some(123): Any).isInstanceOf[Option[Int]] + /* warn */ (Some(123): Any).isInstanceOf[Option[String]] + /* warn */ (Some(123): Any).isInstanceOf[Option[List[String]]] + /* warn */ (Some(123): Any).isInstanceOf[Option[List[Int => String]]] + /* warn */ (Some(123): Any).isInstanceOf[Option[(String, Double)]] + /* warn */ (Some(123): Any).isInstanceOf[Option[String => Double]] + + // These don't warn. + + /* nowarn */ Some(List(1)).isInstanceOf[Option[List[Int]]] + /* nowarn */ Some(123).isInstanceOf[Option[Int]] + /* nowarn */ Some(123).isInstanceOf[Some[Int]] + /* nowarn */ Some(123).isInstanceOf[AnyRef] + + /* nowarn */ (Some(List(1)): Any).isInstanceOf[Option[_]] + /* nowarn */ (Some(123): Any).isInstanceOf[Option[_]] + /* nowarn */ (Some(123): Any).isInstanceOf[Some[_]] + /* nowarn */ (Some(123): Any).isInstanceOf[AnyRef] } -- cgit v1.2.3 From 9904301752c2aa8c8509f1bcd6108f626220524a Mon Sep 17 00:00:00 2001 From: Paul Phillips Date: Tue, 25 Sep 2012 18:14:49 -0700 Subject: Additional new tests for unchecked warnings. --- test/files/neg/t5762.check | 13 ++++++++++ test/files/neg/t5762.flags | 1 + test/files/neg/t5762.scala | 24 ++++++++++++++++++ test/files/neg/unchecked3.check | 13 ++++++++++ test/files/neg/unchecked3.flags | 1 + test/files/neg/unchecked3.scala | 33 ++++++++++++++++++++++++ test/files/pos/t6275.flags | 1 + test/files/pos/t6275.scala | 11 ++++++++ test/pending/pos/exhaust_2.scala | 54 ++++++++++++++++++++++++++++++++++++++++ 9 files changed, 151 insertions(+) create mode 100644 test/files/neg/t5762.check create mode 100644 test/files/neg/t5762.flags create mode 100644 test/files/neg/t5762.scala create mode 100644 test/files/neg/unchecked3.check create mode 100644 test/files/neg/unchecked3.flags create mode 100644 test/files/neg/unchecked3.scala create mode 100644 test/files/pos/t6275.flags create mode 100644 test/files/pos/t6275.scala create mode 100644 test/pending/pos/exhaust_2.scala (limited to 'test/files/neg') diff --git a/test/files/neg/t5762.check b/test/files/neg/t5762.check new file mode 100644 index 0000000000..10064032aa --- /dev/null +++ b/test/files/neg/t5762.check @@ -0,0 +1,13 @@ +t5762.scala:6: error: non-variable type argument Int in type pattern D[Int] is unchecked since it is eliminated by erasure + case _: D[Int] if bippy => 1 + ^ +t5762.scala:7: error: non-variable type argument String in type pattern D[String] is unchecked since it is eliminated by erasure + case _: D[String] => 2 + ^ +t5762.scala:20: error: non-variable type argument D[Int] in type pattern D[D[Int]] is unchecked since it is eliminated by erasure + case _: D[D[Int]] if bippy => 1 + ^ +t5762.scala:21: error: non-variable type argument D[String] in type pattern D[D[String]] is unchecked since it is eliminated by erasure + case _: D[D[String]] => 2 + ^ +four errors found diff --git a/test/files/neg/t5762.flags b/test/files/neg/t5762.flags new file mode 100644 index 0000000000..85d8eb2ba2 --- /dev/null +++ b/test/files/neg/t5762.flags @@ -0,0 +1 @@ +-Xfatal-warnings diff --git a/test/files/neg/t5762.scala b/test/files/neg/t5762.scala new file mode 100644 index 0000000000..fb73552b12 --- /dev/null +++ b/test/files/neg/t5762.scala @@ -0,0 +1,24 @@ +class D[-A] + +object Test { + var bippy: Boolean = true + def f1(x: D[Int with String]) = x match { + case _: D[Int] if bippy => 1 + case _: D[String] => 2 + } + // Correctly warns: + // + // a.scala:5: warning: non variable type-argument Int in type pattern D[Int] is unchecked since it is eliminated by erasure + // case _: D[Int] => 1 + // ^ + // a.scala:6: warning: non variable type-argument String in type pattern D[String] is unchecked since it is eliminated by erasure + // case _: D[String] => 2 + // ^ + // two warnings found + + def f2(x: D[D[Int] with D[String]]) = x match { + case _: D[D[Int]] if bippy => 1 + case _: D[D[String]] => 2 + } + // No warnings! +} diff --git a/test/files/neg/unchecked3.check b/test/files/neg/unchecked3.check new file mode 100644 index 0000000000..f2905124f2 --- /dev/null +++ b/test/files/neg/unchecked3.check @@ -0,0 +1,13 @@ +unchecked3.scala:24: error: non-variable type argument Double in type pattern E1[Double] is unchecked since it is eliminated by erasure + /* warn */ def peerTypes2(x: B1[Int]) = x match { case _: E1[Double] => true } + ^ +unchecked3.scala:25: error: non-variable type argument Double in type pattern F1[Double] is unchecked since it is eliminated by erasure + /* warn */ def peerTypes3(x: B1[_]) = x match { case _: F1[Double] => true } + ^ +unchecked3.scala:28: error: non-variable type argument Int in type pattern A2[Int] is unchecked since it is eliminated by erasure + /* warn */ def twotypes1[T](x: B2[T, Int]) = x match { case _: A2[Int] => true } + ^ +unchecked3.scala:32: error: non-variable type argument Int in type pattern B2[_,Int] is unchecked since it is eliminated by erasure + /* warn */ def twotypes5[T](x: A2[T]) = x match { case _: B2[_, Int] => true } + ^ +four errors found diff --git a/test/files/neg/unchecked3.flags b/test/files/neg/unchecked3.flags new file mode 100644 index 0000000000..85d8eb2ba2 --- /dev/null +++ b/test/files/neg/unchecked3.flags @@ -0,0 +1 @@ +-Xfatal-warnings diff --git a/test/files/neg/unchecked3.scala b/test/files/neg/unchecked3.scala new file mode 100644 index 0000000000..cf01acd4ee --- /dev/null +++ b/test/files/neg/unchecked3.scala @@ -0,0 +1,33 @@ +sealed trait A2[T1] +final class B2[T1, T2] extends A2[T1] + +sealed trait A[T] +final class B[T] extends A[T] + +sealed trait A1[T] +trait B1[T] extends A1[T] +trait C1[T] extends A1[T] +trait D1[T] extends A1[Int] +trait E1[T] extends B1[Int] +trait F1[T] extends B1[T] + +object MiscUnchecked { + /* nowarn */ def knownType1(x: A[Int]) = x match { case _: B[Int] if true => 1 } + /* nowarn */ def knownType2(x: B[Int]) = x match { case _: A[Int] if true => 1 } + /* nowarn */ def tparamLeakage1(x: Any) = x match { case Array() => 1 } + /* nowarn */ def tparamLeakage2(x: Any) = x match { case List() => 1 } + + // E1[Double] implies B1[Int], but B1[Int] does not imply E1[Double], even if .isInstanceOf[E1[_]] + // F1[Int] implies B1[Int], and B1[Int] implies F1[Int] + + /* nowarn */ def peerTypes1(x: B1[Int]) = x match { case _: C1[Int] => true } + /* warn */ def peerTypes2(x: B1[Int]) = x match { case _: E1[Double] => true } + /* warn */ def peerTypes3(x: B1[_]) = x match { case _: F1[Double] => true } + /* nowarn */ def peerTypes4(x: B1[Int]) = x match { case _: F1[Int] => true } + + /* warn */ def twotypes1[T](x: B2[T, Int]) = x match { case _: A2[Int] => true } + /* nowarn */ def twotypes2[T](x: B2[Int, T]) = x match { case _: A2[Int] => true } + /* nowarn */ def twotypes3(x: A2[Int]) = x match { case _: B2[Int, _] => true } + /* nowarn */ def twotypes4[T](x: A2[T]) = x match { case _: B2[T, _] => true } + /* warn */ def twotypes5[T](x: A2[T]) = x match { case _: B2[_, Int] => true } +} diff --git a/test/files/pos/t6275.flags b/test/files/pos/t6275.flags new file mode 100644 index 0000000000..85d8eb2ba2 --- /dev/null +++ b/test/files/pos/t6275.flags @@ -0,0 +1 @@ +-Xfatal-warnings diff --git a/test/files/pos/t6275.scala b/test/files/pos/t6275.scala new file mode 100644 index 0000000000..6b5ec7dceb --- /dev/null +++ b/test/files/pos/t6275.scala @@ -0,0 +1,11 @@ + +sealed trait A[T] +final class B[T] extends A[T] + +object ParsedAxis { + type BI = B[Int] + + def f1(a: A[Int]) = a match { case b: B[Int] => 3 } + def f2(a: A[Int]) = a match { case b: BI => 3 } + def f3(a: A[Int]) = a match { case b: B[t] => 3 } +} diff --git a/test/pending/pos/exhaust_2.scala b/test/pending/pos/exhaust_2.scala new file mode 100644 index 0000000000..4f4e47c43b --- /dev/null +++ b/test/pending/pos/exhaust_2.scala @@ -0,0 +1,54 @@ +object ExhaustivityWarnBugReportMinimal { + //sealed is needed for the warning + sealed trait FoundNode[T]/*presence of parameters is irrelevant*/ + // This also causes a warning: + // sealed abstract class FoundNode[T]/*presence of parameters is irrelevant*/ + case class FoundFilter[T](/*presence of parameters is irrelevant*/) extends FoundNode[T] + case class FoundTypeCase[T](/*presence of parameters is irrelevant*/) extends FoundNode[T] + val f: Some[_] = ??? + f match { + case x: Some[t] => //no warning + } + //With these variants, no warnings: + //val v: (Some[Int], FoundNode[_]) = (???, ???) + //val v: (Some[AnyRef], FoundNode[_]) = (???, ???) + //val v: (Some[String], FoundNode[_]) = (???, ???) + + val v: (Some[_], FoundNode[_]) = (???, ???) + //Warning here: + v match { + case (x: Some[t], _: FoundNode[_]) => + } + v match { + case (x: Some[t], _) => + } + + v match { + case (x: Some[_], _) => + } + case class Foo[T]() + + val vp: (Foo[_], FoundNode[_]) = (???, ???) + vp match { + case (x: Foo[_], _) => + } + + //No warning here: + v match { + case (Some(y), _) => + } + + v match { + case (x, _) => + } + + val v2: (Some[_], Int) = (???, ???) + v2 match { + case (x: Some[t], _) => + } + + val v3: (Option[_], FoundNode[_]) = (???, ???) + v match { + case (x: Option[_], _) => + } +} -- cgit v1.2.3 From 43bf1ea07ccf4ccf5a63743e322d4b64320fc1c1 Mon Sep 17 00:00:00 2001 From: Paul Phillips Date: Wed, 26 Sep 2012 08:07:34 -0700 Subject: Yet more tests for unchecked warnings. --- test/files/neg/unchecked3.check | 26 ++++++++++++++++++++- test/files/neg/unchecked3.scala | 50 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+), 1 deletion(-) (limited to 'test/files/neg') diff --git a/test/files/neg/unchecked3.check b/test/files/neg/unchecked3.check index f2905124f2..f4f0c74257 100644 --- a/test/files/neg/unchecked3.check +++ b/test/files/neg/unchecked3.check @@ -10,4 +10,28 @@ unchecked3.scala:28: error: non-variable type argument Int in type pattern A2[In unchecked3.scala:32: error: non-variable type argument Int in type pattern B2[_,Int] is unchecked since it is eliminated by erasure /* warn */ def twotypes5[T](x: A2[T]) = x match { case _: B2[_, Int] => true } ^ -four errors found +unchecked3.scala:40: error: non-variable type argument String in type pattern Array[List[String]] is unchecked since it is eliminated by erasure + /* warn */ case _: Array[List[String]] => () + ^ +unchecked3.scala:43: error: non-variable type argument String in type pattern Array[Array[List[String]]] is unchecked since it is eliminated by erasure + /* warn */ case _: Array[Array[List[String]]] => () + ^ +unchecked3.scala:50: error: non-variable type argument String in type pattern Array[List[String]] is unchecked since it is eliminated by erasure + /* warn */ case _: Array[List[String]] => () + ^ +unchecked3.scala:53: error: non-variable type argument String in type pattern Array[Array[List[String]]] is unchecked since it is eliminated by erasure + /* warn */ case _: Array[Array[List[String]]] => () + ^ +unchecked3.scala:60: error: non-variable type argument String in type pattern Array[List[String]] is unchecked since it is eliminated by erasure + /* warn */ case _: Array[List[String]] => () + ^ +unchecked3.scala:62: error: non-variable type argument Array[String] in type pattern Array[List[Array[String]]] is unchecked since it is eliminated by erasure + /* warn */ case _: Array[List[Array[String]]] => () + ^ +unchecked3.scala:63: error: non-variable type argument String in type pattern Array[Array[List[String]]] is unchecked since it is eliminated by erasure + /* warn */ case _: Array[Array[List[String]]] => () + ^ +unchecked3.scala:75: error: abstract type A in type pattern Set[Q.this.A] is unchecked since it is eliminated by erasure + /* warn */ case xs: Set[A] => xs.head + ^ +12 errors found diff --git a/test/files/neg/unchecked3.scala b/test/files/neg/unchecked3.scala index cf01acd4ee..7b8c13e8f8 100644 --- a/test/files/neg/unchecked3.scala +++ b/test/files/neg/unchecked3.scala @@ -31,3 +31,53 @@ object MiscUnchecked { /* nowarn */ def twotypes4[T](x: A2[T]) = x match { case _: B2[T, _] => true } /* warn */ def twotypes5[T](x: A2[T]) = x match { case _: B2[_, Int] => true } } + +object Arrays { + def f1(x: Any) = x match { + /* nowarn */ case _: Array[Int] => () + /* nowarn */ case _: Array[Boolean] => () + /* nowarn */ case _: Array[String] => () + /* warn */ case _: Array[List[String]] => () + /* nowarn */ case _: Array[Array[String]] => () + /* nowarn */ case _: Array[Array[Array[String]]] => () + /* warn */ case _: Array[Array[List[String]]] => () + } + + def f2(x: Array[_]) = x match { + /* nowarn */ case _: Array[Int] => () + /* nowarn */ case _: Array[Boolean] => () + /* nowarn */ case _: Array[String] => () + /* warn */ case _: Array[List[String]] => () + /* nowarn */ case _: Array[Array[String]] => () + /* nowarn */ case _: Array[Array[Array[String]]] => () + /* warn */ case _: Array[Array[List[String]]] => () + } + + def f3[T](x: Array[T]) = x match { + /* nowarn */ case _: Array[Int] => () + /* nowarn */ case _: Array[Boolean] => () + /* nowarn */ case _: Array[String] => () + /* warn */ case _: Array[List[String]] => () + /* nowarn */ case _: Array[Array[String]] => () + /* warn */ case _: Array[List[Array[String]]] => () + /* warn */ case _: Array[Array[List[String]]] => () + } +} + +object Matching { + class Q { + type A + type B <: A + + def f(xs: Traversable[B]) = xs match { + /* nowarn */ case xs: List[A] => xs.head + /* nowarn */ case xs: Seq[B] => xs.head + /* warn */ case xs: Set[A] => xs.head + } + def f2[T <: B](xs: Traversable[T]) = xs match { + /* nowarn */ case xs: List[B with T] => xs.head + /* nowarn */ case xs: Seq[A] => xs.head + /* nowarn */ case xs: Set[T] => xs.head + } + } +} -- cgit v1.2.3 From 17b409b7832f541e3d52d2776c8ff3c47574ae0f Mon Sep 17 00:00:00 2001 From: Paul Phillips Date: Wed, 26 Sep 2012 08:38:00 -0700 Subject: Restored warning for impossible type tests. I had this in before, then removed it since it is sometimes redundant with an error message later issued by the pattern matcher (e.g. scrutinee is incompatible with pattern type.) However it also catches a lot of cases which are not errors, so I think the modest redundancy is tolerable for now. I also enhanced the logic for recognizing impossible type tests, taking sealedness into account. --- .../scala/tools/nsc/typechecker/Checkable.scala | 40 +++++++++++++++++----- test/files/neg/unchecked2.check | 12 +++---- 2 files changed, 37 insertions(+), 15 deletions(-) (limited to 'test/files/neg') diff --git a/src/compiler/scala/tools/nsc/typechecker/Checkable.scala b/src/compiler/scala/tools/nsc/typechecker/Checkable.scala index 763d209dd0..3a1803038c 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Checkable.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Checkable.scala @@ -137,6 +137,7 @@ trait Checkable { opt getOrElse NoType } + def neverMatches = result == StaticallyFalse def isUncheckable = result == Uncheckable def uncheckableMessage = uncheckableType match { case NoType => "something" @@ -149,13 +150,28 @@ trait Checkable { /** X, P, [P1], etc. are all explained at the top of the file. */ private object CheckabilityChecker { - private def isNeverSubClass(sym1: Symbol, sym2: Symbol) = ( + /** Given classes A and B, can it be shown A is never a subclass of B? + */ + private def isNeverSubClass(sym1: Symbol, sym2: Symbol) = /*logResult(s"isNeverSubClass($sym1, $sym2)")*/( sym1.isClass && sym2.isClass - && sym1.isEffectivelyFinal && !(sym1 isSubClass sym2) + && ( + // If B is final, A can only be a subclass of B if it is B itself. + // Therefore, A <: B is impossible unless B <: A. + if (sym2.isEffectivelyFinal) + !(sym2 isSubClass sym1) + // If A is final but B is not, a subclass relationship can + // still be ruled out if B is sealed and A is not a subclass of + // any of B's sealed children. + else ( + sym1.isEffectivelyFinal + && sym2.isSealed + && sym2.children.forall(child => !(sym1 isSubClass child)) + ) + ) ) - private def isNeverSubArgs(tps1: List[Type], tps2: List[Type], tparams: List[Symbol]): Boolean = { + private def isNeverSubArgs(tps1: List[Type], tps2: List[Type], tparams: List[Symbol]): Boolean = /*logResult(s"isNeverSubArgs($tps1, $tps2, $tparams)")*/{ def isNeverSubArg(t1: Type, t2: Type, variance: Int) = { if (variance > 0) isNeverSubType(t2, t1) else if (variance < 0) isNeverSubType(t1, t2) @@ -165,21 +181,24 @@ trait Checkable { } 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) + ( isNeverSubClass(sym1, sym2) + || isNeverSubClass(sym2, sym1) + || ((sym1 == sym2) && isNeverSubArgs(args1, args2, sym1.typeParams)) + ) case _ => false } - private def isNeverSubType(tp1: Type, tp2: Type): Boolean = (tp1, tp2) match { + // Important to dealias at any entry point (this is the only one at this writing.) + private def isNeverSubType(tp1: Type, tp2: Type): Boolean = /*logResult(s"isNeverSubType($tp1, $tp2)")*/((tp1.dealias, tp2.dealias) 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 - } + }) } trait InferCheckable { @@ -190,6 +209,8 @@ trait Checkable { * TODO: Eliminate inPattern, canRemedy, which have no place here. */ def checkCheckable(tree: Tree, P0: Type, X: Type, inPattern: Boolean, canRemedy: Boolean = false) { + def where = if (inPattern) "pattern " else "" + // singleton types not considered here val P = P0.widen P match { @@ -202,8 +223,9 @@ trait Checkable { case _ => val checker = new CheckabilityChecker(X.widen, P) log(checker.summaryString) - if (checker.isUncheckable) { - def where = if (inPattern) "pattern " else "" + if (checker.neverMatches) + getContext.unit.warning(tree.pos, s"fruitless type test: a $X can never be a $P (but still might match its erasure)") + else if (checker.isUncheckable) { val msg = ( if (checker.uncheckableType =:= P) s"abstract type $where$P" else s"${checker.uncheckableMessage} in type $where$P" diff --git a/test/files/neg/unchecked2.check b/test/files/neg/unchecked2.check index 0ff2a249a8..b4a14358c7 100644 --- a/test/files/neg/unchecked2.check +++ b/test/files/neg/unchecked2.check @@ -1,22 +1,22 @@ -unchecked2.scala:4: error: non-variable type argument List[String] in type Option[List[String]] is unchecked since it is eliminated by erasure +unchecked2.scala:4: error: fruitless type test: a Some[List[Int]] can never be a Option[List[String]] (but still might match its erasure) /* warn */ Some(List(1)).isInstanceOf[Option[List[String]]] ^ unchecked2.scala:5: error: non-variable type argument Option[_] in type Option[Option[_]] is unchecked since it is eliminated by erasure /* warn */ Some(123).isInstanceOf[Option[Option[_]]] ^ -unchecked2.scala:6: error: non-variable type argument String in type Option[String] is unchecked since it is eliminated by erasure +unchecked2.scala:6: error: fruitless type test: a Some[Int] can never be a Option[String] (but still might match its erasure) /* warn */ Some(123).isInstanceOf[Option[String]] ^ -unchecked2.scala:7: error: non-variable type argument List[String] in type Option[List[String]] is unchecked since it is eliminated by erasure +unchecked2.scala:7: error: fruitless type test: a Some[Int] can never be a Option[List[String]] (but still might match its erasure) /* warn */ Some(123).isInstanceOf[Option[List[String]]] ^ -unchecked2.scala:8: error: non-variable type argument List[Int => String] in type Option[List[Int => String]] is unchecked since it is eliminated by erasure +unchecked2.scala:8: error: fruitless type test: a Some[Int] can never be a Option[List[Int => String]] (but still might match its erasure) /* warn */ Some(123).isInstanceOf[Option[List[Int => String]]] ^ -unchecked2.scala:9: error: non-variable type argument (String, Double) in type Option[(String, Double)] is unchecked since it is eliminated by erasure +unchecked2.scala:9: error: fruitless type test: a Some[Int] can never be a Option[(String, Double)] (but still might match its erasure) /* warn */ Some(123).isInstanceOf[Option[(String, Double)]] ^ -unchecked2.scala:10: error: non-variable type argument String => Double in type Option[String => Double] is unchecked since it is eliminated by erasure +unchecked2.scala:10: error: fruitless type test: a Some[Int] can never be a Option[String => Double] (but still might match its erasure) /* warn */ Some(123).isInstanceOf[Option[String => Double]] ^ unchecked2.scala:14: error: non-variable type argument List[String] in type Option[List[String]] is unchecked since it is eliminated by erasure -- cgit v1.2.3 From 96d4a8646b1962fac2f2fc443b56c6619221b43c Mon Sep 17 00:00:00 2001 From: Paul Phillips Date: Thu, 27 Sep 2012 06:41:17 -0700 Subject: Nailed down the "impossible match" logic. I will again defer to a comment. /** Given classes A and B, can it be shown that nothing which is * an A will ever be a subclass of something which is a B? This * entails not only showing that !(A isSubClass B) but that the * same is true of all their subclasses. Restated for symmetry: * the same value cannot be a member of both A and B. * * 1) A must not be a subclass of B, nor B of A (the trivial check) * 2) One of A or B must be completely knowable (see isKnowable) * 3) Assuming A is knowable, the proposition is true if * !(A' isSubClass B) for all A', where A' is a subclass of A. * * Due to symmetry, the last condition applies as well in reverse. */ --- .../scala/tools/nsc/typechecker/Checkable.scala | 77 +++++++++++------- test/files/neg/patmat-type-check.check | 10 +++ test/files/neg/t1872.check | 4 + test/files/neg/unchecked-abstract.check | 25 ++++++ test/files/neg/unchecked-abstract.flags | 1 + test/files/neg/unchecked-abstract.scala | 93 ++++++++++++++++++++++ test/files/neg/unchecked-impossible.check | 4 + test/files/neg/unchecked-impossible.flags | 1 + test/files/neg/unchecked-impossible.scala | 16 ++++ test/files/neg/unchecked-knowable.check | 4 + test/files/neg/unchecked-knowable.flags | 1 + test/files/neg/unchecked-knowable.scala | 20 +++++ test/files/neg/unchecked2.check | 12 +-- 13 files changed, 234 insertions(+), 34 deletions(-) create mode 100644 test/files/neg/unchecked-abstract.check create mode 100644 test/files/neg/unchecked-abstract.flags create mode 100644 test/files/neg/unchecked-abstract.scala create mode 100644 test/files/neg/unchecked-impossible.check create mode 100644 test/files/neg/unchecked-impossible.flags create mode 100644 test/files/neg/unchecked-impossible.scala create mode 100644 test/files/neg/unchecked-knowable.check create mode 100644 test/files/neg/unchecked-knowable.flags create mode 100644 test/files/neg/unchecked-knowable.scala (limited to 'test/files/neg') diff --git a/src/compiler/scala/tools/nsc/typechecker/Checkable.scala b/src/compiler/scala/tools/nsc/typechecker/Checkable.scala index 3a1803038c..2be08d12ea 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Checkable.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Checkable.scala @@ -23,8 +23,8 @@ import Checkability._ * [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 first two cases correspond to those when there is enough + * static information to say X <: P or that (x ∈ X) ⇒ (x ∉ P). * The fourth case includes unknown abstract types or structural * refinements appearing within a pattern. * @@ -51,6 +51,7 @@ trait Checkable { import global._ import definitions._ + import CheckabilityChecker.{ isNeverSubType, isNeverSubClass } /** The applied type of class 'to' after inferring anything * possible from the knowledge that 'to' must also be of the @@ -100,15 +101,16 @@ trait Checkable { def Xsym = X.typeSymbol def Psym = P.typeSymbol def XR = propagateKnownTypes(X, Psym) + // sadly the spec says (new java.lang.Boolean(true)).isInstanceOf[scala.Boolean] def P1 = X matchesPattern P - def P2 = CheckabilityChecker.isNeverSubType(X, P) + def P2 = !Psym.isPrimitiveValueClass && isNeverSubType(X, P) def P3 = Psym.isClass && (XR matchesPattern P) 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 + |[P2] $P2%-6s x ∉ P // (x ∈ $X) ⇒ (x ∉ $P) |[P3] $P3%-6s XR <: P // $XR <: $P |[P4] $P4%-6s None of the above // !(P1 || P2 || P3) """.stripMargin.trim @@ -137,7 +139,8 @@ trait Checkable { opt getOrElse NoType } - def neverMatches = result == StaticallyFalse + def neverSubClass = isNeverSubClass(Xsym, Psym) + def neverMatches = result == StaticallyFalse def isUncheckable = result == Uncheckable def uncheckableMessage = uncheckableType match { case NoType => "something" @@ -150,28 +153,42 @@ trait Checkable { /** X, P, [P1], etc. are all explained at the top of the file. */ private object CheckabilityChecker { - /** Given classes A and B, can it be shown A is never a subclass of B? + /** A knowable class is one which is either effectively final + * itself, or sealed with only knowable children. */ - private def isNeverSubClass(sym1: Symbol, sym2: Symbol) = /*logResult(s"isNeverSubClass($sym1, $sym2)")*/( + def isKnowable(sym: Symbol): Boolean = /*logResult(s"isKnowable($sym)")*/( + sym.initialize.isEffectivelyFinal // pesky .initialize requirement, or we receive lies about isSealed + || sym.isSealed && (sym.children forall isKnowable) + ) + def knownSubclasses(sym: Symbol): List[Symbol] = /*logResult(s"knownSubclasses($sym)")*/(sym :: { + if (sym.isSealed) sym.children.toList flatMap knownSubclasses + else Nil + }) + def excludable(s1: Symbol, s2: Symbol) = /*logResult(s"excludable($s1, $s2)")*/( + isKnowable(s1) + && !(s2 isSubClass s1) + && knownSubclasses(s1).forall(child => !(child isSubClass s2)) + ) + + /** Given classes A and B, can it be shown that nothing which is + * an A will ever be a subclass of something which is a B? This + * entails not only showing that !(A isSubClass B) but that the + * same is true of all their subclasses. Restated for symmetry: + * the same value cannot be a member of both A and B. + * + * 1) A must not be a subclass of B, nor B of A (the trivial check) + * 2) One of A or B must be completely knowable (see isKnowable) + * 3) Assuming A is knowable, the proposition is true if + * !(A' isSubClass B) for all A', where A' is a subclass of A. + * + * Due to symmetry, the last condition applies as well in reverse. + */ + def isNeverSubClass(sym1: Symbol, sym2: Symbol) = /*logResult(s"isNeverSubClass($sym1, $sym2)")*/( sym1.isClass && sym2.isClass - && !(sym1 isSubClass sym2) - && ( - // If B is final, A can only be a subclass of B if it is B itself. - // Therefore, A <: B is impossible unless B <: A. - if (sym2.isEffectivelyFinal) - !(sym2 isSubClass sym1) - // If A is final but B is not, a subclass relationship can - // still be ruled out if B is sealed and A is not a subclass of - // any of B's sealed children. - else ( - sym1.isEffectivelyFinal - && sym2.isSealed - && sym2.children.forall(child => !(sym1 isSubClass child)) - ) - ) + && (excludable(sym1, sym2) || excludable(sym2, sym1)) ) - private def isNeverSubArgs(tps1: List[Type], tps2: List[Type], tparams: List[Symbol]): Boolean = /*logResult(s"isNeverSubArgs($tps1, $tps2, $tparams)")*/{ + private def isNeverSubArgs(tps1: List[Type], tps2: List[Type], tparams: List[Symbol]): Boolean = /*logResult(s"isNeverSubArgs($tps1, $tps2, $tparams)")*/ { def isNeverSubArg(t1: Type, t2: Type, variance: Int) = { if (variance > 0) isNeverSubType(t2, t1) else if (variance < 0) isNeverSubType(t1, t2) @@ -189,7 +206,7 @@ trait Checkable { false } // Important to dealias at any entry point (this is the only one at this writing.) - private def isNeverSubType(tp1: Type, tp2: Type): Boolean = /*logResult(s"isNeverSubType($tp1, $tp2)")*/((tp1.dealias, tp2.dealias) match { + def isNeverSubType(tp1: Type, tp2: Type): Boolean = /*logResult(s"isNeverSubType($tp1, $tp2)")*/((tp1.dealias, tp2.dealias) match { case (TypeRef(_, sym1, args1), TypeRef(_, sym2, args2)) => isNeverSubClass(sym1, sym2) || { (sym1 isSubClass sym2) && { @@ -208,11 +225,13 @@ trait Checkable { * 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) { + def checkCheckable(tree: Tree, P0: Type, X0: Type, inPattern: Boolean, canRemedy: Boolean = false) { def where = if (inPattern) "pattern " else "" // singleton types not considered here val P = P0.widen + val X = X0.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, _) => @@ -221,10 +240,12 @@ trait Checkable { case TypeRef(_, sym, _) if sym.isAbstractType && canRemedy => ; case _ => - val checker = new CheckabilityChecker(X.widen, P) + val checker = new CheckabilityChecker(X, P) log(checker.summaryString) - if (checker.neverMatches) - getContext.unit.warning(tree.pos, s"fruitless type test: a $X can never be a $P (but still might match its erasure)") + if (checker.neverMatches) { + val addendum = if (checker.neverSubClass) "" else " (but still might match its erasure)" + getContext.unit.warning(tree.pos, s"fruitless type test: a value of type $X cannot also be a $P$addendum") + } else if (checker.isUncheckable) { val msg = ( if (checker.uncheckableType =:= P) s"abstract type $where$P" diff --git a/test/files/neg/patmat-type-check.check b/test/files/neg/patmat-type-check.check index e045841ce1..721217c314 100644 --- a/test/files/neg/patmat-type-check.check +++ b/test/files/neg/patmat-type-check.check @@ -1,3 +1,12 @@ +patmat-type-check.scala:11: warning: fruitless type test: a value of type Test.Bop4[T] cannot also be a Seq[A] + def s3[T](x: Bop4[T]) = x match { case Seq('b', 'o', 'b') => true } + ^ +patmat-type-check.scala:15: warning: fruitless type test: a value of type Test.Bop5[_$1,T1,T2] cannot also be a Seq[A] + def s4[T1, T2](x: Bop5[_, T1, T2]) = x match { case Seq('b', 'o', 'b') => true } + ^ +patmat-type-check.scala:19: warning: fruitless type test: a value of type Test.Bop3[T] cannot also be a Seq[A] + def f4[T](x: Bop3[T]) = x match { case Seq('b', 'o', 'b') => true } + ^ patmat-type-check.scala:22: error: scrutinee is incompatible with pattern type; found : Seq[A] required: String @@ -18,4 +27,5 @@ patmat-type-check.scala:30: error: scrutinee is incompatible with pattern type; required: Test.Bop3[Char] def f4[T](x: Bop3[Char]) = x match { case Seq('b', 'o', 'b') => true } // fail ^ +three warnings found four errors found diff --git a/test/files/neg/t1872.check b/test/files/neg/t1872.check index ef84ef79e0..c5dc2a8080 100644 --- a/test/files/neg/t1872.check +++ b/test/files/neg/t1872.check @@ -1,4 +1,8 @@ +t1872.scala:3: warning: fruitless type test: a value of type Int cannot also be a scala.util.Random + def f(x: Int) = x.isInstanceOf[util.Random] + ^ t1872.scala:3: error: isInstanceOf cannot test if value types are references. def f(x: Int) = x.isInstanceOf[util.Random] ^ +one warning found one error found diff --git a/test/files/neg/unchecked-abstract.check b/test/files/neg/unchecked-abstract.check new file mode 100644 index 0000000000..dc7a8d93d0 --- /dev/null +++ b/test/files/neg/unchecked-abstract.check @@ -0,0 +1,25 @@ +unchecked-abstract.scala:16: error: abstract type H in type Con[M.this.H] is unchecked since it is eliminated by erasure + /* warn */ println(x.isInstanceOf[Con[H]]) + ^ +unchecked-abstract.scala:21: error: abstract type H in type Con[M.this.H] is unchecked since it is eliminated by erasure + /* warn */ println(x.isInstanceOf[Con[H]]) + ^ +unchecked-abstract.scala:27: error: abstract type T in type Inv[M.this.T] is unchecked since it is eliminated by erasure + /* warn */ println(x.isInstanceOf[Inv[T]]) + ^ +unchecked-abstract.scala:28: error: abstract type L in type Inv[M.this.L] is unchecked since it is eliminated by erasure + /* warn */ println(x.isInstanceOf[Inv[L]]) + ^ +unchecked-abstract.scala:31: error: abstract type H in type Inv[M.this.H] is unchecked since it is eliminated by erasure + /* warn */ println(x.isInstanceOf[Inv[H]]) + ^ +unchecked-abstract.scala:33: error: abstract type L in type Inv[M.this.L] is unchecked since it is eliminated by erasure + /* warn */ println(x.isInstanceOf[Inv[L]]) + ^ +unchecked-abstract.scala:36: error: abstract type H in type Inv[M.this.H] is unchecked since it is eliminated by erasure + /* warn */ println(x.isInstanceOf[Inv[H]]) + ^ +unchecked-abstract.scala:37: error: abstract type T in type Inv[M.this.T] is unchecked since it is eliminated by erasure + /* warn */ println(x.isInstanceOf[Inv[T]]) + ^ +8 errors found diff --git a/test/files/neg/unchecked-abstract.flags b/test/files/neg/unchecked-abstract.flags new file mode 100644 index 0000000000..85d8eb2ba2 --- /dev/null +++ b/test/files/neg/unchecked-abstract.flags @@ -0,0 +1 @@ +-Xfatal-warnings diff --git a/test/files/neg/unchecked-abstract.scala b/test/files/neg/unchecked-abstract.scala new file mode 100644 index 0000000000..5b915755f4 --- /dev/null +++ b/test/files/neg/unchecked-abstract.scala @@ -0,0 +1,93 @@ +trait Con[-X] +trait Inv[X] +trait Cov[+X] + +abstract class M { + type H + type L <: H + type T >: L <: H + + def h1(x: Con[H]) = { + /* nowarn */ println(x.isInstanceOf[Con[H]]) + /* nowarn */ println(x.isInstanceOf[Con[T]]) + /* nowarn */ println(x.isInstanceOf[Con[L]]) + } + def h2(x: Con[T]) = { + /* warn */ println(x.isInstanceOf[Con[H]]) + /* nowarn */ println(x.isInstanceOf[Con[T]]) + /* nowarn */ println(x.isInstanceOf[Con[L]]) + } + def h3(x: Con[L]) = { + /* warn */ println(x.isInstanceOf[Con[H]]) + /* warn */ println(x.isInstanceOf[Con[T]]) + /* nowarn */ println(x.isInstanceOf[Con[L]]) + } + def h4(x: Inv[H]) = { + /* nowarn */ println(x.isInstanceOf[Inv[H]]) + /* warn */ println(x.isInstanceOf[Inv[T]]) + /* warn */ println(x.isInstanceOf[Inv[L]]) + } + def h5(x: Inv[T]) = { + /* warn */ println(x.isInstanceOf[Inv[H]]) + /* nowarn */ println(x.isInstanceOf[Inv[T]]) + /* warn */ println(x.isInstanceOf[Inv[L]]) + } + def h6(x: Inv[L]) = { + /* warn */ println(x.isInstanceOf[Inv[H]]) + /* warn */ println(x.isInstanceOf[Inv[T]]) + /* nowarn */ println(x.isInstanceOf[Inv[L]]) + } + def h7(x: Cov[H]) = { + /* nowarn */ println(x.isInstanceOf[Cov[H]]) + /* warn */ println(x.isInstanceOf[Cov[T]]) + /* warn */ println(x.isInstanceOf[Cov[L]]) + } + def h8(x: Cov[T]) = { + /* nowarn */ println(x.isInstanceOf[Cov[H]]) + /* nowarn */ println(x.isInstanceOf[Cov[T]]) + /* warn */ println(x.isInstanceOf[Cov[L]]) + } + def h9(x: Cov[L]) = { + /* nowarn */ println(x.isInstanceOf[Cov[H]]) + /* nowarn */ println(x.isInstanceOf[Cov[T]]) + /* nowarn */ println(x.isInstanceOf[Cov[L]]) + } +} + +object Test extends M { + type H = Any + type T = Int + type L = Nothing + + val conh = new Con[H] { } + val cont = new Con[T] { } + val conl = new Con[L] { } + + val invh = new Inv[H] { } + val invt = new Inv[T] { } + val invl = new Inv[L] { } + + val covh = new Cov[H] { } + val covt = new Cov[T] { } + val covl = new Cov[L] { } + + def main(args: Array[String]): Unit = { + h1(conh) + h2(conh) + h2(cont) + h3(conh) + h3(cont) + h3(conl) + + h4(invh) + h5(invt) + h6(invl) + + h7(covh) + h7(covt) + h7(covl) + h8(covt) + h8(covl) + h9(covl) + } +} diff --git a/test/files/neg/unchecked-impossible.check b/test/files/neg/unchecked-impossible.check new file mode 100644 index 0000000000..0ab371dbaa --- /dev/null +++ b/test/files/neg/unchecked-impossible.check @@ -0,0 +1,4 @@ +unchecked-impossible.scala:5: error: fruitless type test: a value of type T2[Int,Int] cannot also be a Seq[A] + case Seq(x) => + ^ +one error found diff --git a/test/files/neg/unchecked-impossible.flags b/test/files/neg/unchecked-impossible.flags new file mode 100644 index 0000000000..85d8eb2ba2 --- /dev/null +++ b/test/files/neg/unchecked-impossible.flags @@ -0,0 +1 @@ +-Xfatal-warnings diff --git a/test/files/neg/unchecked-impossible.scala b/test/files/neg/unchecked-impossible.scala new file mode 100644 index 0000000000..985a2d0b08 --- /dev/null +++ b/test/files/neg/unchecked-impossible.scala @@ -0,0 +1,16 @@ +final case class T2[+A, +B](a: A, b: B) + +class A { + def f1 = T2(1, 2) match { + case Seq(x) => + case _ => + } + def f2 = T2(1, 2) match { + case _: T2[Int, Int] => /* nowarn */ + case _ => + } + def f3 = T2(1, 2) match { + case _: T2[_, Int] => /* nowarn */ + case _ => + } +} diff --git a/test/files/neg/unchecked-knowable.check b/test/files/neg/unchecked-knowable.check new file mode 100644 index 0000000000..3a6ef994b5 --- /dev/null +++ b/test/files/neg/unchecked-knowable.check @@ -0,0 +1,4 @@ +unchecked-knowable.scala:17: error: fruitless type test: a value of type Bippy cannot also be a A1 + /* warn */ (new Bippy).isInstanceOf[A1] + ^ +one error found diff --git a/test/files/neg/unchecked-knowable.flags b/test/files/neg/unchecked-knowable.flags new file mode 100644 index 0000000000..85d8eb2ba2 --- /dev/null +++ b/test/files/neg/unchecked-knowable.flags @@ -0,0 +1 @@ +-Xfatal-warnings diff --git a/test/files/neg/unchecked-knowable.scala b/test/files/neg/unchecked-knowable.scala new file mode 100644 index 0000000000..667b47f504 --- /dev/null +++ b/test/files/neg/unchecked-knowable.scala @@ -0,0 +1,20 @@ +/** Knowable - only final leaves */ +sealed abstract class A1 +sealed abstract class A2 extends A1 +final class A3 extends A1 +final class A4 extends A2 + +/** Unknowable */ +sealed abstract class B1 +sealed abstract class B2 extends B1 +final class B3 extends B1 +trait B4 extends B2 + +class Bippy +trait Dingus + +class A { + /* warn */ (new Bippy).isInstanceOf[A1] + /* nowarn */ (new Bippy).isInstanceOf[B1] + /* nowarn */ ((new Bippy): Any).isInstanceOf[A1] +} diff --git a/test/files/neg/unchecked2.check b/test/files/neg/unchecked2.check index b4a14358c7..68fdfa82ac 100644 --- a/test/files/neg/unchecked2.check +++ b/test/files/neg/unchecked2.check @@ -1,22 +1,22 @@ -unchecked2.scala:4: error: fruitless type test: a Some[List[Int]] can never be a Option[List[String]] (but still might match its erasure) +unchecked2.scala:4: error: fruitless type test: a value of type Some[List[Int]] cannot also be a Option[List[String]] (but still might match its erasure) /* warn */ Some(List(1)).isInstanceOf[Option[List[String]]] ^ unchecked2.scala:5: error: non-variable type argument Option[_] in type Option[Option[_]] is unchecked since it is eliminated by erasure /* warn */ Some(123).isInstanceOf[Option[Option[_]]] ^ -unchecked2.scala:6: error: fruitless type test: a Some[Int] can never be a Option[String] (but still might match its erasure) +unchecked2.scala:6: error: fruitless type test: a value of type Some[Int] cannot also be a Option[String] (but still might match its erasure) /* warn */ Some(123).isInstanceOf[Option[String]] ^ -unchecked2.scala:7: error: fruitless type test: a Some[Int] can never be a Option[List[String]] (but still might match its erasure) +unchecked2.scala:7: error: fruitless type test: a value of type Some[Int] cannot also be a Option[List[String]] (but still might match its erasure) /* warn */ Some(123).isInstanceOf[Option[List[String]]] ^ -unchecked2.scala:8: error: fruitless type test: a Some[Int] can never be a Option[List[Int => String]] (but still might match its erasure) +unchecked2.scala:8: error: fruitless type test: a value of type Some[Int] cannot also be a Option[List[Int => String]] (but still might match its erasure) /* warn */ Some(123).isInstanceOf[Option[List[Int => String]]] ^ -unchecked2.scala:9: error: fruitless type test: a Some[Int] can never be a Option[(String, Double)] (but still might match its erasure) +unchecked2.scala:9: error: fruitless type test: a value of type Some[Int] cannot also be a Option[(String, Double)] (but still might match its erasure) /* warn */ Some(123).isInstanceOf[Option[(String, Double)]] ^ -unchecked2.scala:10: error: fruitless type test: a Some[Int] can never be a Option[String => Double] (but still might match its erasure) +unchecked2.scala:10: error: fruitless type test: a value of type Some[Int] cannot also be a Option[String => Double] (but still might match its erasure) /* warn */ Some(123).isInstanceOf[Option[String => Double]] ^ unchecked2.scala:14: error: non-variable type argument List[String] in type Option[List[String]] is unchecked since it is eliminated by erasure -- cgit v1.2.3 From 211c9620ba83de143ea4776f55a3e0c4de11d002 Mon Sep 17 00:00:00 2001 From: Paul Phillips Date: Thu, 27 Sep 2012 10:20:45 -0700 Subject: Added logic and tests for unchecked refinements. --- .../scala/tools/nsc/typechecker/Checkable.scala | 24 +++++++++++++++---- test/files/neg/unchecked-refinement.check | 13 +++++++++++ test/files/neg/unchecked-refinement.flags | 1 + test/files/neg/unchecked-refinement.scala | 27 ++++++++++++++++++++++ 4 files changed, 61 insertions(+), 4 deletions(-) create mode 100644 test/files/neg/unchecked-refinement.check create mode 100644 test/files/neg/unchecked-refinement.flags create mode 100644 test/files/neg/unchecked-refinement.scala (limited to 'test/files/neg') diff --git a/src/compiler/scala/tools/nsc/typechecker/Checkable.scala b/src/compiler/scala/tools/nsc/typechecker/Checkable.scala index 2be08d12ea..7e15cf91a7 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Checkable.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Checkable.scala @@ -67,6 +67,17 @@ trait Checkable { val tps1 = (from baseType bc).typeArgs val tps2 = (tvarType baseType bc).typeArgs (tps1, tps2).zipped foreach (_ =:= _) + // Alternate, variance respecting formulation causes + // neg/unchecked3.scala to fail (abstract types). TODO - + // figure it out. It seems there is more work to do if I + // allow for variance, because the constraints accumulate + // as bounds and "tvar.instValid" is false. + // + // foreach3(tps1, tps2, bc.typeParams)((tp1, tp2, tparam) => + // if (tparam.initialize.isCovariant) tp1 <:< tp2 + // else if (tparam.isContravariant) tp2 <:< tp1 + // else tp1 =:= tp2 + // ) } val resArgs = tparams zip tvars map { @@ -77,14 +88,15 @@ trait Checkable { } private def isUnwarnableTypeArgSymbol(sym: Symbol) = ( - sym.isTypeParameterOrSkolem // dummy + sym.isTypeParameter // dummy || (sym.name.toTermName == nme.WILDCARD) // _ || nme.isVariableName(sym.name) // type variable ) private def isUnwarnableTypeArg(arg: Type) = ( - isUnwarnableTypeArgSymbol(arg.typeSymbolDirect) // has to be direct: see pos/t1439 - || (arg hasAnnotation UncheckedClass) // @unchecked T + uncheckedOk(arg) // @unchecked T + || isUnwarnableTypeArgSymbol(arg.typeSymbolDirect) // has to be direct: see pos/t1439 ) + private def uncheckedOk(tp: Type) = tp hasAnnotation UncheckedClass private def typeArgsInTopLevelType(tp: Type): List[Type] = { val tps = tp match { @@ -104,7 +116,7 @@ trait Checkable { // sadly the spec says (new java.lang.Boolean(true)).isInstanceOf[scala.Boolean] def P1 = X matchesPattern P def P2 = !Psym.isPrimitiveValueClass && isNeverSubType(X, P) - def P3 = Psym.isClass && (XR matchesPattern P) + def P3 = isNonRefinementClassType(P) && (XR matchesPattern P) def P4 = !(P1 || P2 || P3) def summaryString = f""" @@ -226,6 +238,7 @@ trait Checkable { * TODO: Eliminate inPattern, canRemedy, which have no place here. */ def checkCheckable(tree: Tree, P0: Type, X0: Type, inPattern: Boolean, canRemedy: Boolean = false) { + if (uncheckedOk(P0)) return def where = if (inPattern) "pattern " else "" // singleton types not considered here @@ -239,6 +252,9 @@ trait Checkable { // If top-level abstract types can be checked using a classtag extractor, don't warn about them case TypeRef(_, sym, _) if sym.isAbstractType && canRemedy => ; + // Matching on types like case _: AnyRef { def bippy: Int } => doesn't work -- yet. + case RefinedType(_, decls) if !decls.isEmpty => + getContext.unit.warning(tree.pos, s"a pattern match on a refinement type is unchecked") case _ => val checker = new CheckabilityChecker(X, P) log(checker.summaryString) diff --git a/test/files/neg/unchecked-refinement.check b/test/files/neg/unchecked-refinement.check new file mode 100644 index 0000000000..d81517464f --- /dev/null +++ b/test/files/neg/unchecked-refinement.check @@ -0,0 +1,13 @@ +unchecked-refinement.scala:17: error: abstract type U in type pattern Foo[U,U,V] is unchecked since it is eliminated by erasure + /* warn */ case _: Foo[U, U, V] if b => () + ^ +unchecked-refinement.scala:19: error: non-variable type argument Any in type pattern Foo[Any,U,V] is unchecked since it is eliminated by erasure + /* warn */ case _: Foo[Any, U, V] if b => () + ^ +unchecked-refinement.scala:23: error: a pattern match on a refinement type is unchecked + /* nowarn - todo */ case x: AnyRef { def bippy: Int } if b => x.bippy // this could/should do an instance check and not warn + ^ +unchecked-refinement.scala:24: error: a pattern match on a refinement type is unchecked + /* nowarn - todo */ case x: AnyRef { def size: Int } if b => x.size // this could/should do a static conformance test and not warn + ^ +four errors found diff --git a/test/files/neg/unchecked-refinement.flags b/test/files/neg/unchecked-refinement.flags new file mode 100644 index 0000000000..85d8eb2ba2 --- /dev/null +++ b/test/files/neg/unchecked-refinement.flags @@ -0,0 +1 @@ +-Xfatal-warnings diff --git a/test/files/neg/unchecked-refinement.scala b/test/files/neg/unchecked-refinement.scala new file mode 100644 index 0000000000..79ed7f13c1 --- /dev/null +++ b/test/files/neg/unchecked-refinement.scala @@ -0,0 +1,27 @@ +// a.scala +// Thu Sep 27 09:42:16 PDT 2012 + +trait Bar[-T1, T2, +T3] { } +trait Foo[-T1, T2, +T3] extends Bar[T1, T2, T3] + +class A { + var b = true + + def f1(x: Foo[Int, Int, Int]) = x match { + /* nowarn */ case _: Foo[Nothing, Int, Any] => true + } + def f2[T, U, V](x: Foo[T, U, V]) = x match { + /* nowarn */ case _: Foo[Nothing, U, Any] => true + } + def f3[T, U, V](x: Foo[T, U, V]) = x match { + /* warn */ case _: Foo[U, U, V] if b => () + /* nowarn */ case _: Foo[Nothing, U, V] if b => () + /* warn */ case _: Foo[Any, U, V] if b => () + } + + def f4(xs: List[Int]) = xs match { + /* nowarn - todo */ case x: AnyRef { def bippy: Int } if b => x.bippy // this could/should do an instance check and not warn + /* nowarn - todo */ case x: AnyRef { def size: Int } if b => x.size // this could/should do a static conformance test and not warn + /* nowarn */ case x: ((AnyRef { def size: Int }) @unchecked) if b => x.size + } +} -- cgit v1.2.3