From 6a740332c7bfd56b20993be6ecd0bf818104f56c Mon Sep 17 00:00:00 2001 From: Eugene Burmako Date: Thu, 6 Sep 2012 21:05:17 +0200 Subject: [PATCH] SI-6318 fixes ClassTag.unapply for primitives ClassTag.unapply now has overloads for primitive value classes so that it can preserve boxiness when performing subtyping tests. First I wanted to annotate ClassTag.unapply with a ClassTag itself, i.e. to transform its signature from "def unapply(x: Any): Option[T]" to "def unapply[U: ClassTag](x: U): Option[T]". But then virtpatmat_typetag.scala exhibited a nasty problem. When pattern matching with this unapply, patmat first infers U as something and then tries to pattern match against this inferred type. And if U gets inferred as an abstract type itself, bad things happen: warning: The outer reference in this type test cannot be checked at run time. That's why I decided to drop the ClassTag idea and go with 9 extra overloads. Not very beautiful, but definitely robust. --- src/library/scala/reflect/ClassTag.scala | 21 ++++++- test/files/run/t6318_derived.check | 3 + test/files/run/t6318_derived.scala | 15 +++++ test/files/run/t6318_primitives.check | 36 ++++++++++++ test/files/run/t6318_primitives.scala | 71 ++++++++++++++++++++++++ 5 files changed, 145 insertions(+), 1 deletion(-) create mode 100644 test/files/run/t6318_derived.check create mode 100644 test/files/run/t6318_derived.scala create mode 100644 test/files/run/t6318_primitives.check create mode 100644 test/files/run/t6318_primitives.scala diff --git a/src/library/scala/reflect/ClassTag.scala b/src/library/scala/reflect/ClassTag.scala index 5255c44f10e1..8ce06d611eaa 100644 --- a/src/library/scala/reflect/ClassTag.scala +++ b/src/library/scala/reflect/ClassTag.scala @@ -54,7 +54,26 @@ trait ClassTag[T] extends ClassManifestDeprecatedApis[T] with Equals with Serial * `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 (x != null && runtimeClass.isAssignableFrom(x.getClass)) Some(x.asInstanceOf[T]) else None + def unapply(x: Any): Option[T] = unapply_impl(x) + def unapply(x: Byte): Option[T] = unapply_impl(x) + def unapply(x: Short): Option[T] = unapply_impl(x) + def unapply(x: Char): Option[T] = unapply_impl(x) + def unapply(x: Int): Option[T] = unapply_impl(x) + def unapply(x: Long): Option[T] = unapply_impl(x) + def unapply(x: Float): Option[T] = unapply_impl(x) + def unapply(x: Double): Option[T] = unapply_impl(x) + def unapply(x: Boolean): Option[T] = unapply_impl(x) + def unapply(x: Unit): Option[T] = unapply_impl(x) + + private def unapply_impl[U: ClassTag](x: U): Option[T] = + if (x == null) None + else { + val staticClass = classTag[U].runtimeClass + val dynamicClass = x.getClass + val effectiveClass = if (staticClass.isPrimitive) staticClass else dynamicClass + val conforms = runtimeClass.isAssignableFrom(effectiveClass) + if (conforms) Some(x.asInstanceOf[T]) else None + } /** case class accessories */ override def canEqual(x: Any) = x.isInstanceOf[ClassTag[_]] diff --git a/test/files/run/t6318_derived.check b/test/files/run/t6318_derived.check new file mode 100644 index 000000000000..ad43b6579bbb --- /dev/null +++ b/test/files/run/t6318_derived.check @@ -0,0 +1,3 @@ +Some(X) +true +Some(X) diff --git a/test/files/run/t6318_derived.scala b/test/files/run/t6318_derived.scala new file mode 100644 index 000000000000..ccdc18daee6f --- /dev/null +++ b/test/files/run/t6318_derived.scala @@ -0,0 +1,15 @@ +import scala.reflect.{ClassTag, classTag} + +object Test extends App { + def test[T: ClassTag](x: T) { + println(classTag[T].runtimeClass.isAssignableFrom(x.getClass)) + println(classTag[T].unapply(x)) + } + + class X(val x: Int) extends AnyVal { override def toString = "X" } + val x = new X(1) + // the commented line crashes because of SI-6326 + //println(classTag[X].runtimeClass.isAssignableFrom(x.getClass)) + println(classTag[X].unapply(x)) + test(x) +} \ No newline at end of file diff --git a/test/files/run/t6318_primitives.check b/test/files/run/t6318_primitives.check new file mode 100644 index 000000000000..bb474c3bdc06 --- /dev/null +++ b/test/files/run/t6318_primitives.check @@ -0,0 +1,36 @@ +true +Some(1) +false +None +true +Some(1) +false +None +true +Some() +false +None +true +Some(1) +false +None +true +Some(1) +false +None +true +Some(1.0) +false +None +true +Some(1.0) +false +None +true +Some(true) +false +None +true +Some(()) +false +None diff --git a/test/files/run/t6318_primitives.scala b/test/files/run/t6318_primitives.scala new file mode 100644 index 000000000000..30f27120b360 --- /dev/null +++ b/test/files/run/t6318_primitives.scala @@ -0,0 +1,71 @@ +import scala.reflect.{ClassTag, classTag} + +object Test extends App { + def test[T: ClassTag](x: T) { + println(classTag[T].runtimeClass.isAssignableFrom(x.getClass)) + println(classTag[T].unapply(x)) + } + + { + val x = 1.toByte + println(ClassTag.Byte.runtimeClass.isAssignableFrom(x.getClass)) + println(ClassTag.Byte.unapply(x)) + test(x) + } + + { + val x = 1.toShort + println(ClassTag.Short.runtimeClass.isAssignableFrom(x.getClass)) + println(ClassTag.Short.unapply(x)) + test(x) + } + + { + val x = 1.toChar + println(ClassTag.Char.runtimeClass.isAssignableFrom(x.getClass)) + println(ClassTag.Char.unapply(x)) + test(x) + } + + { + val x = 1.toInt + println(ClassTag.Int.runtimeClass.isAssignableFrom(x.getClass)) + println(ClassTag.Int.unapply(x)) + test(x) + } + + { + val x = 1.toLong + println(ClassTag.Long.runtimeClass.isAssignableFrom(x.getClass)) + println(ClassTag.Long.unapply(x)) + test(x) + } + + { + val x = 1.toFloat + println(ClassTag.Float.runtimeClass.isAssignableFrom(x.getClass)) + println(ClassTag.Float.unapply(x)) + test(x) + } + + { + val x = 1.toDouble + println(ClassTag.Double.runtimeClass.isAssignableFrom(x.getClass)) + println(ClassTag.Double.unapply(x)) + test(x) + } + + { + val x = true + println(ClassTag.Boolean.runtimeClass.isAssignableFrom(x.getClass)) + println(ClassTag.Boolean.unapply(x)) + test(x) + } + + { + val x = () + println(ClassTag.Unit.runtimeClass.isAssignableFrom(x.getClass)) + println(ClassTag.Unit.unapply(x)) + test(x) + } +} \ No newline at end of file