From 23e5428008fc88377e59a1a5c20d5476c586d62e Mon Sep 17 00:00:00 2001 From: Paul Phillips Date: Thu, 18 Feb 2010 05:40:27 +0000 Subject: Tighter pattern matching hits the street. is final and does not conform to the pattern, it will no longer compile. See all the exciting things you can no longer do: "bob".reverse match { case Seq('b', 'o', 'b') => true } // denied! "bob".toArray match { case Seq('b', 'o', 'b') => true } // rejected! final class Dunk def f3(x: Dunk) = x match { case Seq('b', 'o', 'b') => true } // uh-uh! And so forth. Review by odersky. --- .../scala/tools/nsc/matching/MatchSupport.scala | 22 +++------------- src/compiler/scala/tools/nsc/symtab/Types.scala | 28 ++++++++++++++++++++ .../scala/tools/nsc/typechecker/Infer.scala | 30 ++++++++-------------- 3 files changed, 42 insertions(+), 38 deletions(-) (limited to 'src/compiler') 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 } -- cgit v1.2.3