diff options
-rw-r--r-- | src/compiler/scala/tools/nsc/matching/MatchSupport.scala | 22 | ||||
-rw-r--r-- | src/compiler/scala/tools/nsc/symtab/Types.scala | 28 | ||||
-rw-r--r-- | src/compiler/scala/tools/nsc/typechecker/Infer.scala | 30 | ||||
-rw-r--r-- | test/files/neg/bug1878.check | 7 | ||||
-rw-r--r-- | test/files/neg/patmat-type-check.check | 21 | ||||
-rw-r--r-- | test/files/neg/patmat-type-check.scala | 28 | ||||
-rw-r--r-- | test/files/pos/bug0646.scala | 2 | ||||
-rw-r--r-- | test/files/run/withIndex.scala | 2 |
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) |