summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/compiler/scala/tools/nsc/matching/MatchSupport.scala22
-rw-r--r--src/compiler/scala/tools/nsc/symtab/Types.scala28
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/Infer.scala30
-rw-r--r--test/files/neg/bug1878.check7
-rw-r--r--test/files/neg/patmat-type-check.check21
-rw-r--r--test/files/neg/patmat-type-check.scala28
-rw-r--r--test/files/pos/bug0646.scala2
-rw-r--r--test/files/run/withIndex.scala2
8 files changed, 99 insertions, 41 deletions
diff --git a/src/compiler/scala/tools/nsc/matching/MatchSupport.scala b/src/compiler/scala/tools/nsc/matching/MatchSupport.scala
index 117973865b..374e974170 100644
--- a/src/compiler/scala/tools/nsc/matching/MatchSupport.scala
+++ b/src/compiler/scala/tools/nsc/matching/MatchSupport.scala
@@ -28,24 +28,10 @@ trait MatchSupport extends ast.TreeDSL { self: ParallelMatching =>
import definitions._
implicit def enrichType(x: Type): RichType = new RichType(x)
- // see bug1434.scala for an illustration of why "x <:< y" is insufficient.
- // this code is inadequate but slowly improving... Inherited comment:
- //
- // an approximation of _tp1 <:< tp2 that ignores _ types. this code is wrong,
- // ideally there is a better way to do it, and ideally defined in Types.scala
- private[matching] def matches(arg1: Type, arg2: Type) = {
- val List(t1, t2) = List(arg1, arg2) map decodedEqualsType
-
- def matchesIgnoringBounds(tp1: Type, tp2: Type) = tp2 match {
- case TypeRef(pre, sym, args) => tp1 <:< TypeRef(pre, sym, args map (_ => AnyClass.tpe))
- case _ => false
- }
- def okPrefix = t1.prefix =:= t2.prefix
- def okSubtype = t1.baseTypeSeq exists (_.typeSymbol eq t2.typeSymbol)
- def okBounds = matchesIgnoringBounds(t1, t2)
-
- (t1 <:< t2) || (okPrefix && okSubtype && okBounds)
- }
+ // A subtype test which creates fresh existentials for type
+ // parameters on the right hand side.
+ private[matching] def matches(arg1: Type, arg2: Type) =
+ decodedEqualsType(arg1) matchesPattern decodedEqualsType(arg2)
class RichType(undecodedTpe: Type) {
def tpe = decodedEqualsType(undecodedTpe)
diff --git a/src/compiler/scala/tools/nsc/symtab/Types.scala b/src/compiler/scala/tools/nsc/symtab/Types.scala
index e46d7bea84..aa88996876 100644
--- a/src/compiler/scala/tools/nsc/symtab/Types.scala
+++ b/src/compiler/scala/tools/nsc/symtab/Types.scala
@@ -587,6 +587,34 @@ trait Types extends reflect.generic.Types { self: SymbolTable =>
}
}
+ /** Can this type only be subtyped by bottom types?
+ * This is assessed to be the case if the class is final,
+ * and all type parameters (if any) are invariant.
+ */
+ def isFinalType = (
+ typeSymbol.isFinal &&
+ (typeSymbol.typeParams forall (_.variance == 0))
+ )
+
+ /** Is this type a subtype of that type in a pattern context?
+ * Any type arguments on the right hand side are replaced with
+ * fresh existentials, except for Arrays.
+ *
+ * See bug1434.scala for an example of code which would fail
+ * if only a <:< test were applied.
+ */
+ def matchesPattern(that: Type): Boolean = {
+ (this <:< that) || ((this, that) match {
+ case (TypeRef(_, ArrayClass, List(arg1)), TypeRef(_, ArrayClass, List(arg2))) if arg2.typeSymbol.typeParams.nonEmpty =>
+ arg1 matchesPattern arg2
+ case (_, TypeRef(_, _, args)) =>
+ val newtp = existentialAbstraction(args map (_.typeSymbol), that)
+ !(that =:= newtp) && (this <:< newtp)
+ case _ =>
+ false
+ })
+ }
+
def stat_<:<(that: Type): Boolean = {
incCounter(subtypeCount)
val start = startTimer(subtypeNanos)
diff --git a/src/compiler/scala/tools/nsc/typechecker/Infer.scala b/src/compiler/scala/tools/nsc/typechecker/Infer.scala
index 8761420778..3738e9a5ec 100644
--- a/src/compiler/scala/tools/nsc/typechecker/Infer.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/Infer.scala
@@ -394,8 +394,6 @@ trait Infer {
}
}
- def isPlausiblyPopulated(tp1: Type, tp2: Type): Boolean = true
-
def isPlausiblyCompatible(tp: Type, pt: Type): Boolean = tp match {
case PolyType(_, restpe) =>
isPlausiblyCompatible(restpe, pt)
@@ -1496,6 +1494,11 @@ trait Infer {
def inferTypedPattern(pos: Position, pattp: Type, pt0: Type): Type = {
val pt = widen(pt0)
+
+ /** If we can absolutely rule out a match we can fail fast. */
+ if (pt.isFinalType && !(pt matchesPattern pattp))
+ error(pos, "scrutinee is incompatible with pattern type"+foundReqMsg(pattp, pt))
+
checkCheckable(pos, pattp, "pattern ")
if (!(pattp <:< pt)) {
val tpparams = freeTypeParamsOfTerms.collect(pattp)
@@ -1509,24 +1512,11 @@ trait Infer {
if (settings.debug.value) log("free type params (2) = " + ptparams)
val ptvars = ptparams map freshVar
val pt1 = pt.instantiateTypeParams(ptparams, ptvars)
- if (!(isPopulated(tp, pt1) && isInstantiatable(tvars ::: ptvars))) {
- // In ticket #2486 we have this example of code which would fail
- // here without a change:
- //
- // class A[T]
- // class B extends A[Int]
- // class C[T] extends A[T] { def f(t: A[T]) = t match { case x: B => () } }
- //
- // This reports error: pattern type is incompatible with expected type;
- // found : B
- // required: A[T]
- //
- // I am not sure what is the ideal fix, but for the moment I am intercepting
- // it at the last minute and applying a looser check before failing.
- if (!isPlausiblyCompatible(pattp, pt)) {
- error(pos, "pattern type is incompatible with expected type"+foundReqMsg(pattp, pt))
- return pattp
- }
+ // See ticket #2486 we have this example of code which would incorrectly
+ // fail without verifying that !(pattp matchesPattern pt)
+ if (!(isPopulated(tp, pt1) && isInstantiatable(tvars ::: ptvars)) && !(pattp matchesPattern pt)) {
+ error(pos, "pattern type is incompatible with expected type"+foundReqMsg(pattp, pt))
+ return pattp
}
ptvars foreach instantiateTypeVar
}
diff --git a/test/files/neg/bug1878.check b/test/files/neg/bug1878.check
index 5484d675af..f760781fa0 100644
--- a/test/files/neg/bug1878.check
+++ b/test/files/neg/bug1878.check
@@ -1,10 +1,15 @@
bug1878.scala:3: error: _* may only come last
val err1 = "" match { case Seq(f @ _*, ',') => f }
^
+bug1878.scala:3: error: scrutinee is incompatible with pattern type;
+ found : Seq[A]
+ required: java.lang.String
+ val err1 = "" match { case Seq(f @ _*, ',') => f }
+ ^
bug1878.scala:9: error: _* may only come last
val List(List(_*, arg2), _) = List(List(1,2,3), List(4,5,6))
^
bug1878.scala:13: error: _* may only come last
case <p> { _* } </p> =>
^
-three errors found
+four errors found
diff --git a/test/files/neg/patmat-type-check.check b/test/files/neg/patmat-type-check.check
new file mode 100644
index 0000000000..ab638b616d
--- /dev/null
+++ b/test/files/neg/patmat-type-check.check
@@ -0,0 +1,21 @@
+patmat-type-check.scala:18: error: scrutinee is incompatible with pattern type;
+ found : Seq[A]
+ required: java.lang.String
+ def f1 = "bob".reverse match { case Seq('b', 'o', 'b') => true } // fail
+ ^
+patmat-type-check.scala:19: error: scrutinee is incompatible with pattern type;
+ found : Seq[A]
+ required: Array[Char]
+ def f2 = "bob".toArray match { case Seq('b', 'o', 'b') => true } // fail
+ ^
+patmat-type-check.scala:23: error: scrutinee is incompatible with pattern type;
+ found : Seq[A]
+ required: Test.Bop2
+ def f3(x: Bop2) = x match { case Seq('b', 'o', 'b') => true } // fail
+ ^
+patmat-type-check.scala:27: error: scrutinee is incompatible with pattern type;
+ found : Seq[A]
+ required: Test.Bop3[T]
+ def f4[T](x: Bop3[T]) = x match { case Seq('b', 'o', 'b') => true } // fail
+ ^
+four errors found
diff --git a/test/files/neg/patmat-type-check.scala b/test/files/neg/patmat-type-check.scala
new file mode 100644
index 0000000000..c6c689b256
--- /dev/null
+++ b/test/files/neg/patmat-type-check.scala
@@ -0,0 +1,28 @@
+object Test
+{
+ def s1 = "bob".toList match { case Seq('b', 'o', 'b') => true } // list ok
+
+ // not final, allowed
+ class Bop
+ def s2(x: Bop) = x match { case Seq('b', 'o', 'b') => true }
+
+ // covariance, allowed
+ final class Bop4[+T]
+ def s3[T](x: Bop4[T]) = x match { case Seq('b', 'o', 'b') => true }
+
+ // contravariance, allowed
+ final class Bop5[T, U, -V]
+ def s4[T1, T2](x: Bop5[_, T1, T2]) = x match { case Seq('b', 'o', 'b') => true }
+
+ // String and Array are final/invariant, disallowed
+ def f1 = "bob".reverse match { case Seq('b', 'o', 'b') => true } // fail
+ def f2 = "bob".toArray match { case Seq('b', 'o', 'b') => true } // fail
+
+ // final, no type parameters, should be disallowed
+ final class Bop2
+ def f3(x: Bop2) = x match { case Seq('b', 'o', 'b') => true } // fail
+
+ // final, invariant type parameter, should be disallowed
+ final class Bop3[T]
+ def f4[T](x: Bop3[T]) = x match { case Seq('b', 'o', 'b') => true } // fail
+}
diff --git a/test/files/pos/bug0646.scala b/test/files/pos/bug0646.scala
index 64214f65b1..a56e857223 100644
--- a/test/files/pos/bug0646.scala
+++ b/test/files/pos/bug0646.scala
@@ -10,7 +10,7 @@ object xfor {
</bks>;
new NodeSeq { val theSeq = books.child } match {
- case t @ <title>Blabla</title> => t
+ case t @ Seq(<title>Blabla</title>) => t
}
//val n: NodeSeq = new NodeSeq { val theSeq = books.child }
diff --git a/test/files/run/withIndex.scala b/test/files/run/withIndex.scala
index 3b9c9e84e5..910b1f1f9e 100644
--- a/test/files/run/withIndex.scala
+++ b/test/files/run/withIndex.scala
@@ -3,7 +3,7 @@ object Test {
val ary: Array[String] = Array("a", "b", "c")
val lst: List[String] = List("a", "b", "c")
val itr: Iterator[String] = lst.iterator
- val str: Stream[String] = Stream.fromIterator(lst.iterator)
+ val str: Stream[String] = lst.iterator.toStream
Console.println(ary.zipWithIndex.toList)
Console.println(lst.zipWithIndex.toList)