Permalink
Browse files

turn unchecked type patterns into checked ones

the pattern `(_: T)` is made checkable using (ct: ClassTag[T]).unapply
by rewriting it to `ct(_: T)` (if there's a ClassTag[T] available)

similarly for extractors: if the formal type of the unapply method
is an uncheckable type, wrap in the corresponding classtag extractor (if available)

don't trigger rewrite on non-toplevel unchecked types
(i.e., only look at type constructor part of T when looking for unchecked types)

TODO: find outer match to figure out if we're supposed to be unchecked

would like to give users a chance to opt-out from the wrapping,
but finding the match to which this pattern belongs turned out to be tricky...
  • Loading branch information...
adriaanm authored and xeno-by committed Jun 4, 2012
1 parent 10292e4 commit 6b3ef4f1676adcbe6dbdbf59a3bd359f339b0626
@@ -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)
@@ -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
@@ -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))
}
}
@@ -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]
@@ -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")
+}

0 comments on commit 6b3ef4f

Please sign in to comment.