diff options
-rw-r--r-- | src/compiler/scala/tools/nsc/typechecker/Typers.scala | 69 | ||||
-rw-r--r-- | src/library/scala/reflect/ClassTag.scala | 10 | ||||
-rw-r--r-- | test/files/run/patmat_unapp_abstype-new.scala | 21 | ||||
-rw-r--r-- | test/files/run/virtpatmat_typetag.check | 10 | ||||
-rw-r--r-- | test/files/run/virtpatmat_typetag.scala | 36 |
5 files changed, 126 insertions, 20 deletions
diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index 95a2962199..9a27f3dd41 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -970,7 +970,7 @@ trait Typers extends Modes with Adaptations with Tags { val overloadedExtractorOfObject = tree.symbol filter (sym => hasUnapplyMember(sym.tpe)) // if the tree's symbol's type does not define an extractor, maybe the tree's type does // this is the case when we encounter an arbitrary tree as the target of an unapply call (rather than something that looks like a constructor call) - // (for now, this only happens due to maybeTypeTagExtractor, but when we support parameterized extractors, it will become more common place) + // (for now, this only happens due to maybeWrapClassTagUnapply, but when we support parameterized extractors, it will become more common place) val extractor = overloadedExtractorOfObject orElse unapplyMember(tree.tpe) if (extractor != NoSymbol) { // if we did some ad-hoc overloading resolution, update the tree's symbol @@ -3120,12 +3120,64 @@ trait Typers extends Modes with Adaptations with Tags { val itype = glb(List(pt1, arg.tpe)) arg.tpe = pt1 // restore type (arg is a dummy tree, just needs to pass typechecking) - UnApply(fun1, args1) setPos tree.pos setType itype + val unapply = UnApply(fun1, args1) setPos tree.pos setType itype + + // if the type that the unapply method expects for its argument is uncheckable, wrap in classtag extractor + // skip if the unapply's type is not a method type with (at least, but really it should be exactly) one argument + if (unappType.paramTypes.isEmpty) unapply + else maybeWrapClassTagUnapply(unapply, unappType.paramTypes.head) } else duplErrorTree(WrongNumberArgsPatternError(tree, fun)) } } + // TODO: disable when in unchecked match + def maybeWrapClassTagUnapply(uncheckedPattern: Tree, pt: Type): Tree = if (isPastTyper) uncheckedPattern else { + // only look at top-level type, can't (reliably) do anything about unchecked type args (in general) + pt.normalize.typeConstructor match { + // if at least one of the types in an intersection is checkable, use the checkable ones + // this avoids problems as in run/matchonseq.scala, where the expected type is `Coll with scala.collection.SeqLike` + // Coll is an abstract type, but SeqLike of course is not + case tp@RefinedType(parents, _) if (parents.length >= 2) && (parents.exists(tp => !infer.containsUnchecked(tp))) => + uncheckedPattern + + case ptCheckable if infer.containsUnchecked(ptCheckable) => + uncheckedPattern match { + // are we being called on a classtag extractor call? + // don't recurse forever, it's not *that* much fun + case UnApply(fun, _) if fun.symbol.owner.isNonBottomSubClass(ClassTagClass) => + uncheckedPattern + + case _ => + val typeTagExtractor = resolveClassTag(uncheckedPattern.pos, ptCheckable) + //orElse resolveTypeTag(uncheckedPattern.pos, ReflectMirrorPrefix.tpe, ptCheckable, concrete = true) + + // we don't create a new Context for a Match, so find the CaseDef, then go out one level and navigate back to the match that has this case + // val thisCase = context.nextEnclosing(_.tree.isInstanceOf[CaseDef]) + // val unchecked = thisCase.outer.tree.collect{case Match(selector, cases) if cases contains thisCase => selector} match { + // case List(Typed(_, tpt)) if treeInfo.isUncheckedAnnotation(tpt.tpe) => true + // case t => println("outer tree: "+ (t, thisCase, thisCase.outer.tree)); false + // } + // println("maybeWrapClassTagUnapply"+ (!isPastTyper && infer.containsUnchecked(pt), pt, uncheckedPattern)) + // println("typeTagExtractor"+ typeTagExtractor) + + + if (typeTagExtractor != EmptyTree && unapplyMember(typeTagExtractor.tpe) != NoSymbol) { + // println("maybeWrapClassTagUnapply "+ typeTagExtractor) + // println(util.Position.formatMessage(uncheckedPattern.pos, "made unchecked type test into a checked one", true)) + val args = List(uncheckedPattern) + // must call doTypedUnapply directly, as otherwise we get undesirable rewrites + // and re-typechecks of the target of the unapply call in PATTERNmode, + // this breaks down when the extractor (which defineds the unapply member) is not a simple reference to an object, + // but an arbitrary tree as is the case here + doTypedUnapply(Apply(typeTagExtractor, args), typeTagExtractor, typeTagExtractor, args, PATTERNmode, pt) + } else uncheckedPattern + } + + case _ => uncheckedPattern + } + } + /** * Convert an annotation constructor call into an AnnotationInfo. * @@ -4823,10 +4875,15 @@ trait Typers extends Modes with Adaptations with Tags { } case Typed(expr, tpt) => - val tpt1 = typedType(tpt, mode) - val expr1 = typed(expr, onlyStickyModes(mode), tpt1.tpe.deconst) - val ownType = if (isPatternMode) inferTypedPattern(tpt1, tpt1.tpe, pt) else tpt1.tpe - treeCopy.Typed(tree, expr1, tpt1) setType ownType + val tptTyped = typedType(tpt, mode) + val exprTyped = typed(expr, onlyStickyModes(mode), tptTyped.tpe.deconst) + val treeTyped = treeCopy.Typed(tree, exprTyped, tptTyped) + + if (isPatternMode) { + val ownType = inferTypedPattern(tptTyped, tptTyped.tpe, pt) + maybeWrapClassTagUnapply(treeTyped setType ownType, tptTyped.tpe) + } else + treeTyped setType tptTyped.tpe case TypeApply(fun, args) => // @M: kind-arity checking is done here and in adapt, full kind-checking is in checkKindBounds (in Infer) diff --git a/src/library/scala/reflect/ClassTag.scala b/src/library/scala/reflect/ClassTag.scala index 350003a8d8..f753dfbcbb 100644 --- a/src/library/scala/reflect/ClassTag.scala +++ b/src/library/scala/reflect/ClassTag.scala @@ -44,6 +44,16 @@ trait ClassTag[T] extends Equals with Serializable { case _ => java.lang.reflect.Array.newInstance(runtimeClass, len).asInstanceOf[Array[T]] } + /** A ClassTag[T] can serve as an extractor that matches only objects of type T. + * + * The compiler tries to turn unchecked type tests in pattern matches into checked ones + * by wrapping a `(_: T)` type pattern as `ct(_: T)`, where `ct` is the `ClassTag[T]` instance. + * Type tests necessary before calling other extractors are treated similarly. + * `SomeExtractor(...)` is turned into `ct(SomeExtractor(...))` if `T` in `SomeExtractor.unapply(x: T)` + * is uncheckable, but we have an instance of `ClassTag[T]`. + */ + def unapply(x: Any): Option[T] = if (runtimeClass.isAssignableFrom(x.getClass)) Some(x.asInstanceOf[T]) else None + /** case class accessories */ override def canEqual(x: Any) = x.isInstanceOf[ClassTag[_]] override def equals(x: Any) = x.isInstanceOf[ClassTag[_]] && this.runtimeClass == x.asInstanceOf[ClassTag[_]].runtimeClass diff --git a/test/files/run/patmat_unapp_abstype-new.scala b/test/files/run/patmat_unapp_abstype-new.scala index 45496f08a2..1141177504 100644 --- a/test/files/run/patmat_unapp_abstype-new.scala +++ b/test/files/run/patmat_unapp_abstype-new.scala @@ -1,10 +1,10 @@ +import reflect.{ClassTag, classTag} + // abstract types and extractors, oh my! trait TypesAPI { trait Type - // an alternative fix (implemented in the virtual pattern matcher, is to replace the isInstanceOf by a manifest-based run-time test) - // that's what typeRefMani is for - type TypeRef <: Type //; implicit def typeRefMani: Manifest[TypeRef] + type TypeRef <: Type val TypeRef: TypeRefExtractor; trait TypeRefExtractor { def apply(x: Int): TypeRef def unapply(x: TypeRef): Option[(Int)] @@ -19,11 +19,6 @@ trait TypesUser extends TypesAPI { def shouldNotCrash(tp: Type): Unit = { tp match { case TypeRef(x) => println("TypeRef") - // the above checks tp.isInstanceOf[TypeRef], which is erased to tp.isInstanceOf[Type] - // before calling TypeRef.unapply(tp), which will then crash unless tp.isInstanceOf[TypesImpl#TypeRef] (which is not implied by tp.isInstanceOf[Type]) - // tp.isInstanceOf[TypesImpl#TypeRef] is equivalent to classOf[TypesImpl#TypeRef].isAssignableFrom(tp.getClass) - // this is equivalent to manifest - // it is NOT equivalent to manifest[Type] <:< typeRefMani case MethodType(x) => println("MethodType") case _ => println("none of the above") } @@ -34,7 +29,6 @@ trait TypesImpl extends TypesAPI { object TypeRef extends TypeRefExtractor // this will have a bridged unapply(x: Type) = unapply(x.asInstanceOf[TypeRef]) case class TypeRef(n: Int) extends Type // this has a bridge from TypesAPI#Type to TypesImpl#TypeRef // --> the cast in the bridge will fail because the pattern matcher can't type test against the abstract types in TypesUser - //lazy val typeRefMani = manifest[TypeRef] } trait Foos { @@ -63,16 +57,15 @@ trait Intermed extends Foos { object TestUnappStaticallyKnownSynthetic extends TypesImpl with TypesUser { def test() = { - shouldNotCrash(TypeRef(10)) // should and does print "TypeRef" - // once #1697/#2337 are fixed, this should generate the correct output - shouldNotCrash(MethodType(10)) // should print "MethodType" but prints "none of the above" -- good one, pattern matcher! + shouldNotCrash(TypeRef(10)) // prints "TypeRef" + shouldNotCrash(MethodType(10)) // prints "MethodType" } } object TestUnappDynamicSynth extends RealFoos with Intermed { - case class FooToo(n: Int) extends Bar + case class NotAFoo(n: Int) extends Bar def test() = { - crash(FooToo(10)) + crash(NotAFoo(10)) crash(new Foo(5)) } } diff --git a/test/files/run/virtpatmat_typetag.check b/test/files/run/virtpatmat_typetag.check new file mode 100644 index 0000000000..f9800b84d0 --- /dev/null +++ b/test/files/run/virtpatmat_typetag.check @@ -0,0 +1,10 @@ +1 is not a ClassTag[int]; it's a class java.lang.Integer +1 is a ClassTag[class java.lang.Integer] +1 is not a ClassTag[class java.lang.String]; it's a class java.lang.Integer +true is a ClassTag[class java.lang.Object] +woele is a ClassTag[class java.lang.String] +1 is not a ClassTag[int]; it's a class java.lang.Integer +1 is a ClassTag[class java.lang.Integer] +1 is not a ClassTag[class java.lang.String]; it's a class java.lang.Integer +true is a ClassTag[class java.lang.Object] +woele is a ClassTag[class java.lang.String] diff --git a/test/files/run/virtpatmat_typetag.scala b/test/files/run/virtpatmat_typetag.scala new file mode 100644 index 0000000000..c1b1fd813a --- /dev/null +++ b/test/files/run/virtpatmat_typetag.scala @@ -0,0 +1,36 @@ +import reflect.{ClassTag, classTag} + +trait Extractors { + type T + implicit val tTag: ClassTag[T] + object ExtractT { + def unapply(x: T) = Some(x) + } + def apply(a: Any) = a match { + case ExtractT(x) => println(x +" is a "+ implicitly[ClassTag[T]]) + case _ => println(a+ " is not a "+ implicitly[ClassTag[T]] +"; it's a "+ a.getClass) + } +} + +object Test extends App { + def typeMatch[T: ClassTag](a: Any) = a match { + case x : T => println(x +" is a "+ implicitly[ClassTag[T]]) + case _ => println(a+ " is not a "+ implicitly[ClassTag[T]] +"; it's a "+ a.getClass) + } + + // the same match as typeMatch, but using an extractor + def extractorMatch[S: ClassTag](a: Any) = + (new Extractors { type T = S; val tTag = classTag[T] })(a) + + typeMatch[Int](1) + typeMatch[Integer](1) + typeMatch[String](1) + typeMatch[Any](true) + typeMatch[String]("woele") + + extractorMatch[Int](1) + extractorMatch[Integer](1) + extractorMatch[String](1) + extractorMatch[Any](true) + extractorMatch[String]("woele") +}
\ No newline at end of file |