diff options
author | Paul Phillips <paulp@improving.org> | 2011-06-22 21:10:33 +0000 |
---|---|---|
committer | Paul Phillips <paulp@improving.org> | 2011-06-22 21:10:33 +0000 |
commit | 9853b5b8292650d8e192b500e636dc441550eef7 (patch) | |
tree | ec3a85b9b7af29c9fbd3bba17389791f083394cc | |
parent | e49ec10e93eeec1af775096de07dd46a7d0a1fad (diff) | |
download | scala-9853b5b8292650d8e192b500e636dc441550eef7.tar.gz scala-9853b5b8292650d8e192b500e636dc441550eef7.tar.bz2 scala-9853b5b8292650d8e192b500e636dc441550eef7.zip |
A total rewrite of "runtimeClass", discarding t...
A total rewrite of "runtimeClass", discarding the user-space approach in
favor of simply fixing getClass.
def f1 = 5.getClass // Class[Int]
def f2 = (5: AnyVal).getClass // Class[_ <: AnyVal]
def f3 = (5: java.lang.Integer).getClass // Class[_ <: java.lang.Integer]
class A
class B extends A
def f1 = (new B: Any).getClass().newInstance() // Any
def f2 = (new B: AnyRef).getClass().newInstance() // AnyRef
def f3 = (new B: A).getClass().newInstance() // A
def f4 = (new B: B).getClass().newInstance() // B
But that's not all!
def f0[T >: B] = (new B: T).getClass().newInstance()
def f5 = f0[Any] // Any
def f6 = f0[AnyRef] // AnyRef
def f7 = f0[A] // A
def f8 = f0[B] // B
Closes #490, #896, #4696. Review by moors. (Note: I think this is pretty
good, but picky review requested.)
-rw-r--r-- | src/compiler/scala/reflect/internal/Definitions.scala | 21 | ||||
-rw-r--r-- | src/compiler/scala/tools/cmd/gen/AnyVals.scala | 23 | ||||
-rw-r--r-- | src/compiler/scala/tools/nsc/transform/Erasure.scala | 66 | ||||
-rw-r--r-- | src/compiler/scala/tools/nsc/typechecker/Typers.scala | 17 | ||||
-rw-r--r-- | src/library-aux/scala/Any.scala | 6 | ||||
-rwxr-xr-x | src/library/scala/Boolean.scala | 2 | ||||
-rw-r--r-- | src/library/scala/Byte.scala | 1 | ||||
-rw-r--r-- | src/library/scala/Char.scala | 1 | ||||
-rw-r--r-- | src/library/scala/Double.scala | 1 | ||||
-rw-r--r-- | src/library/scala/Float.scala | 1 | ||||
-rw-r--r-- | src/library/scala/Int.scala | 1 | ||||
-rw-r--r-- | src/library/scala/Long.scala | 1 | ||||
-rw-r--r-- | src/library/scala/Short.scala | 1 | ||||
-rwxr-xr-x | src/library/scala/Unit.scala | 5 | ||||
-rw-r--r-- | src/library/scala/runtime/ScalaRunTime.scala | 16 | ||||
-rw-r--r-- | test/files/run/getClassTest.check | 18 | ||||
-rw-r--r-- | test/files/run/getClassTest.scala | 66 |
17 files changed, 223 insertions, 24 deletions
diff --git a/src/compiler/scala/reflect/internal/Definitions.scala b/src/compiler/scala/reflect/internal/Definitions.scala index 967bea38b4..ce869cfd3d 100644 --- a/src/compiler/scala/reflect/internal/Definitions.scala +++ b/src/compiler/scala/reflect/internal/Definitions.scala @@ -248,6 +248,7 @@ trait Definitions /*extends reflect.generic.StandardDefinitions*/ { def arrayCloneMethod = getMember(ScalaRunTimeModule, "array_clone") def ensureAccessibleMethod = getMember(ScalaRunTimeModule, "ensureAccessible") def scalaRuntimeHash = getMember(ScalaRunTimeModule, "hash") + def scalaRuntimeAnyValClass = getMember(ScalaRunTimeModule, "anyValClass") def scalaRuntimeSameElements = getMember(ScalaRunTimeModule, nme.sameElements) // classes with special meanings @@ -526,6 +527,7 @@ trait Definitions /*extends reflect.generic.StandardDefinitions*/ { var Any_equals : Symbol = _ var Any_hashCode : Symbol = _ var Any_toString : Symbol = _ + var Any_getClass : Symbol = _ var Any_isInstanceOf: Symbol = _ var Any_asInstanceOf: Symbol = _ var Any_## : Symbol = _ @@ -816,11 +818,24 @@ trait Definitions /*extends reflect.generic.StandardDefinitions*/ { // members of class scala.Any Any_== = newMethod(AnyClass, nme.EQ, anyparam, booltype) setFlag FINAL Any_!= = newMethod(AnyClass, nme.NE, anyparam, booltype) setFlag FINAL - Any_equals = newMethod(AnyClass, nme.equals_, anyparam, booltype) + Any_equals = newMethod(AnyClass, nme.equals_, anyparam, booltype) Any_hashCode = newMethod(AnyClass, nme.hashCode_, Nil, inttype) Any_toString = newMethod(AnyClass, nme.toString_, Nil, stringtype) - Any_## = newMethod(AnyClass, nme.HASHHASH, Nil, inttype) setFlag FINAL - + Any_## = newMethod(AnyClass, nme.HASHHASH, Nil, inttype) setFlag FINAL + + // Any_getClass requires special handling. The return type is determined on + // a per-call-site basis as if the function being called were actually: + // + // // Assuming `target.getClass()` + // def getClass[T](target: T): Class[_ <: T] + // + // Since getClass is not actually a polymorphic method, this requires compiler + // participation. At the "Any" level, the return type is Class[_] as it is in + // java.lang.Object. Java also special cases the return type. + Any_getClass = ( + newMethod(AnyClass, nme.getClass_, Nil, getMember(ObjectClass, nme.getClass_).tpe.resultType) + setFlag DEFERRED + ) Any_isInstanceOf = newPolyMethod( AnyClass, nme.isInstanceOf_, tparam => NullaryMethodType(booltype)) setFlag FINAL Any_asInstanceOf = newPolyMethod( diff --git a/src/compiler/scala/tools/cmd/gen/AnyVals.scala b/src/compiler/scala/tools/cmd/gen/AnyVals.scala index 9f8e488c43..31b44744da 100644 --- a/src/compiler/scala/tools/cmd/gen/AnyVals.scala +++ b/src/compiler/scala/tools/cmd/gen/AnyVals.scala @@ -12,12 +12,12 @@ trait AnyValReps { self: AnyVals => sealed abstract class AnyValNum(name: String) extends AnyValRep(name) { - def isCardinal: Boolean = isIntegerType(this) - def unaryOps = if (isCardinal) List("+", "-", "~") else List("+", "-") - def bitwiseOps = if (isCardinal) List("|", "&", "^") else Nil - def shiftOps = if (isCardinal) List("<<", ">>>", ">>") else Nil - def comparisonOps = List("==", "!=", "<", "<=", ">", ">=") - def otherOps = List("+", "-" ,"*", "/", "%") + def isCardinal: Boolean = isIntegerType(this) + def unaryOps = if (isCardinal) List("+", "-", "~") else List("+", "-") + def bitwiseOps = if (isCardinal) List("|", "&", "^") else Nil + def shiftOps = if (isCardinal) List("<<", ">>>", ">>") else Nil + def comparisonOps = List("==", "!=", "<", "<=", ">", ">=") + def otherOps = List("+", "-" ,"*", "/", "%") // Given two numeric value types S and T , the operation type of S and T is defined as follows: // If both S and T are subrange types then the operation type of S and T is Int. @@ -49,7 +49,7 @@ trait AnyValReps { ) xs1 ++ xs2 } - def classLines = clumps.foldLeft(List[String]()) { + def classLines = (clumps :+ commonClassLines).foldLeft(List[String]()) { case (res, Nil) => res case (res, lines) => val xs = lines map { @@ -80,6 +80,9 @@ trait AnyValReps { sealed abstract class AnyValRep(val name: String) { def classLines: List[String] def objectLines: List[String] + def commonClassLines = List( + "def getClass(): Class[@name@]" + ) def lcname = name.toLowerCase def boxedName = this match { @@ -243,6 +246,8 @@ def &&(x: Boolean): Boolean = sys.error("stub") def |(x: Boolean): Boolean = sys.error("stub") def &(x: Boolean): Boolean = sys.error("stub") def ^(x: Boolean): Boolean = sys.error("stub") + +def getClass(): Class[Boolean] = sys.error("stub") """.trim.lines.toList def objectLines = interpolate(allCompanions).lines.toList @@ -254,7 +259,9 @@ def ^(x: Boolean): Boolean = sys.error("stub") * only one value of type Unit: `()`. */ """ - def classLines = Nil + def classLines = List( + """def getClass(): Class[Unit] = sys.error("stub")""" + ) def objectLines = interpolate(allCompanions).lines.toList override def boxUnboxImpls = Map( diff --git a/src/compiler/scala/tools/nsc/transform/Erasure.scala b/src/compiler/scala/tools/nsc/transform/Erasure.scala index 1dc36633db..a2fd3ebbb4 100644 --- a/src/compiler/scala/tools/nsc/transform/Erasure.scala +++ b/src/compiler/scala/tools/nsc/transform/Erasure.scala @@ -66,6 +66,39 @@ abstract class Erasure extends AddInterfaces } } + // A type function from T => Class[U], used to determine the return + // type of getClass calls. The returned type is: + // + // 1. If T is a value type, Class[T]. + // 2. If T is anonymous or a refinement type, calculate the intersection + // dominator of the parents T', and Class[_ <: T']. + // 3. If T is a phantom type (Any or AnyVal), Class[_]. + // 4. Otherwise, Class[_ <: T]. + // + // Note: AnyVal cannot be Class[_ <: AnyVal] because if the static type of the + // receiver is AnyVal, it implies the receiver is boxed, so the correct + // class object is that of java.lang.Integer, not Int. + // + // TODO: If T is final, return type could be Class[T]. Should it? + def getClassReturnType(tp: Type): Type = { + def mkClass(targs: List[Type]) = typeRef(ClassClass.tpe.prefix, ClassClass, targs) + val tparams = ClassClass.typeParams + val sym = tp.typeSymbol + + if (tparams.isEmpty) mkClass(Nil) // call must be coming post-erasure + else if (isValueClass(sym)) mkClass(List(tp.widen)) + else if (sym.isLocalClass) getClassReturnType(erasure.intersectionDominator(tp.parents)) + else { + val eparams = typeParamsToExistentials(ClassClass, tparams) + val upperBound = if (isPhantomClass(sym)) AnyClass.tpe else tp.widen + + existentialAbstraction( + eparams, + mkClass(List(eparams.head setInfo TypeBounds.upper(upperBound) tpe)) + ) + } + } + private def unboundedGenericArrayLevel(tp: Type): Int = tp match { case GenericArray(level, core) if !(core <:< AnyRefClass.tpe) => level case _ => 0 @@ -491,6 +524,12 @@ abstract class Erasure extends AddInterfaces case _ => tp.deconst } } + // Methods on Any/Object which we rewrite here while we still know what + // is a primitive and what arrived boxed. + private lazy val interceptedMethods = Set[Symbol](Any_##, Object_##, Any_getClass) ++ ( + // Each value class has its own getClass for ultra-precise class object typing. + ScalaValueClasses map (_.tpe member nme.getClass_) + ) // -------- erasure on trees ------------------------------------------ @@ -974,17 +1013,24 @@ abstract class Erasure extends AddInterfaces SelectFromArray(qual, name, erasure(qual.tpe)).copyAttrs(fn), args) - case Apply(fn @ Select(qual, _), Nil) if fn.symbol == Any_## || fn.symbol == Object_## => - // This is unattractive, but without it we crash here on ().## because after - // erasure the ScalaRunTime.hash overload goes from Unit => Int to BoxedUnit => Int. - // This must be because some earlier transformation is being skipped on ##, but so - // far I don't know what. For null we now define null.## == 0. - val arg = qual.tpe.typeSymbolDirect match { - case UnitClass => BLOCK(qual, REF(BoxedUnit_UNIT)) // ({ expr; UNIT }).## - case NullClass => LIT(0) // (null: Object).## - case _ => qual + case Apply(fn @ Select(qual, _), Nil) if interceptedMethods(fn.symbol) => + if (fn.symbol == Any_## || fn.symbol == Object_##) { + // This is unattractive, but without it we crash here on ().## because after + // erasure the ScalaRunTime.hash overload goes from Unit => Int to BoxedUnit => Int. + // This must be because some earlier transformation is being skipped on ##, but so + // far I don't know what. For null we now define null.## == 0. + val arg = qual.tpe.typeSymbolDirect match { + case UnitClass => BLOCK(qual, REF(BoxedUnit_UNIT)) // ({ expr; UNIT }).## + case NullClass => LIT(0) // (null: Object).## + case _ => qual + } + Apply(gen.mkAttributedRef(scalaRuntimeHash), List(arg)) } - Apply(gen.mkAttributedRef(scalaRuntimeHash), List(arg)) + // Rewrite 5.getClass to ScalaRunTime.anyValClass(5) + else if (isValueClass(qual.tpe.typeSymbol)) + Apply(gen.mkAttributedRef(scalaRuntimeAnyValClass), List(qual)) + else + tree case Apply(fn, args) => if (fn.symbol == Any_asInstanceOf) diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index 7edb624b8e..dd92418af7 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -3611,7 +3611,7 @@ trait Typers extends Modes { if (settings.Xchecknull.value && isPotentialNullDeference && unit != null) unit.warning(tree.pos, "potential null pointer dereference: "+tree) - result match { + val selection = result match { // could checkAccessible (called by makeAccessible) potentially have skipped checking a type application in qual? case SelectFromTypeTree(qual@TypeTree(), name) if qual.tpe.typeArgs nonEmpty => // TODO: somehow the new qual is not checked in refchecks treeCopy.SelectFromTypeTree( @@ -3631,6 +3631,21 @@ trait Typers extends Modes { case _ => result } + // To fully benefit from special casing the return type of + // getClass, we have to catch it immediately so expressions + // like x.getClass().newInstance() are typed with the type of x. + val isRefinableGetClass = ( + selection.symbol.name == nme.getClass_ + && selection.tpe.params.isEmpty + // TODO: If the type of the qualifier is inaccessible, we can cause private types + // to escape scope here, e.g. pos/t1107. I'm not sure how to properly handle this + // so for now it requires the type symbol be public. + && qual.tpe.typeSymbol.isPublic + ) + if (isRefinableGetClass) + selection setType MethodType(Nil, erasure.getClassReturnType(qual.tpe)) + else + selection } } diff --git a/src/library-aux/scala/Any.scala b/src/library-aux/scala/Any.scala index 2feab8f836..490c546bbf 100644 --- a/src/library-aux/scala/Any.scala +++ b/src/library-aux/scala/Any.scala @@ -54,6 +54,12 @@ abstract class Any { */ def toString: String + /** Returns the runtime class representation of the object. + * + * @return a class object corresponding to the static type of the receiver + */ + def getClass(): Class[_] + /** Test two objects for equality. * * @param that the object to compare against this object for equality. diff --git a/src/library/scala/Boolean.scala b/src/library/scala/Boolean.scala index 7719065ae7..f77bdd2ea0 100755 --- a/src/library/scala/Boolean.scala +++ b/src/library/scala/Boolean.scala @@ -29,6 +29,8 @@ final class Boolean extends AnyVal { def |(x: Boolean): Boolean = sys.error("stub") def &(x: Boolean): Boolean = sys.error("stub") def ^(x: Boolean): Boolean = sys.error("stub") + + def getClass(): Class[Boolean] = sys.error("stub") } object Boolean extends AnyValCompanion { diff --git a/src/library/scala/Byte.scala b/src/library/scala/Byte.scala index 540e05c19f..8c598e044a 100644 --- a/src/library/scala/Byte.scala +++ b/src/library/scala/Byte.scala @@ -144,6 +144,7 @@ final class Byte extends AnyVal { def %(x: Float): Float = sys.error("stub") def %(x: Double): Double = sys.error("stub") + def getClass(): Class[Byte] = sys.error("stub") } object Byte extends AnyValCompanion { diff --git a/src/library/scala/Char.scala b/src/library/scala/Char.scala index a5d1cb019b..a8f15125bf 100644 --- a/src/library/scala/Char.scala +++ b/src/library/scala/Char.scala @@ -144,6 +144,7 @@ final class Char extends AnyVal { def %(x: Float): Float = sys.error("stub") def %(x: Double): Double = sys.error("stub") + def getClass(): Class[Char] = sys.error("stub") } object Char extends AnyValCompanion { diff --git a/src/library/scala/Double.scala b/src/library/scala/Double.scala index 5f2e01063f..108c6207bb 100644 --- a/src/library/scala/Double.scala +++ b/src/library/scala/Double.scala @@ -118,6 +118,7 @@ final class Double extends AnyVal { def %(x: Float): Double = sys.error("stub") def %(x: Double): Double = sys.error("stub") + def getClass(): Class[Double] = sys.error("stub") } object Double extends AnyValCompanion { diff --git a/src/library/scala/Float.scala b/src/library/scala/Float.scala index f403e5ccab..9ef7181806 100644 --- a/src/library/scala/Float.scala +++ b/src/library/scala/Float.scala @@ -118,6 +118,7 @@ final class Float extends AnyVal { def %(x: Float): Float = sys.error("stub") def %(x: Double): Double = sys.error("stub") + def getClass(): Class[Float] = sys.error("stub") } object Float extends AnyValCompanion { diff --git a/src/library/scala/Int.scala b/src/library/scala/Int.scala index c53be1241d..4546934149 100644 --- a/src/library/scala/Int.scala +++ b/src/library/scala/Int.scala @@ -144,6 +144,7 @@ final class Int extends AnyVal { def %(x: Float): Float = sys.error("stub") def %(x: Double): Double = sys.error("stub") + def getClass(): Class[Int] = sys.error("stub") } object Int extends AnyValCompanion { diff --git a/src/library/scala/Long.scala b/src/library/scala/Long.scala index 3124130663..12b8a25b8a 100644 --- a/src/library/scala/Long.scala +++ b/src/library/scala/Long.scala @@ -144,6 +144,7 @@ final class Long extends AnyVal { def %(x: Float): Float = sys.error("stub") def %(x: Double): Double = sys.error("stub") + def getClass(): Class[Long] = sys.error("stub") } object Long extends AnyValCompanion { diff --git a/src/library/scala/Short.scala b/src/library/scala/Short.scala index 8bf6f09154..81953505b7 100644 --- a/src/library/scala/Short.scala +++ b/src/library/scala/Short.scala @@ -144,6 +144,7 @@ final class Short extends AnyVal { def %(x: Float): Float = sys.error("stub") def %(x: Double): Double = sys.error("stub") + def getClass(): Class[Short] = sys.error("stub") } object Short extends AnyValCompanion { diff --git a/src/library/scala/Unit.scala b/src/library/scala/Unit.scala index 58b024273b..c5d12afeba 100755 --- a/src/library/scala/Unit.scala +++ b/src/library/scala/Unit.scala @@ -15,8 +15,9 @@ package scala * not represented as objects by the underlying host system. There is * only one value of type Unit: `()`. */ -final class Unit extends AnyVal { } - +final class Unit extends AnyVal { + def getClass(): Class[Unit] = sys.error("stub") +} object Unit extends AnyValCompanion { diff --git a/src/library/scala/runtime/ScalaRunTime.scala b/src/library/scala/runtime/ScalaRunTime.scala index 7a8224298b..031f15f10a 100644 --- a/src/library/scala/runtime/ScalaRunTime.scala +++ b/src/library/scala/runtime/ScalaRunTime.scala @@ -32,6 +32,22 @@ object ScalaRunTime { def isValueClass(clazz: Class[_]) = clazz.isPrimitive() + /** Return the class object representing an unboxed value type, + * e.g. classOf[int], not classOf[java.lang.Integer]. The compiler + * rewrites expressions like 5.getClass to come here. + */ + def anyValClass[T <: AnyVal](value: T): Class[T] = (value match { + case x: Byte => java.lang.Byte.TYPE + case x: Short => java.lang.Short.TYPE + case x: Char => java.lang.Character.TYPE + case x: Int => java.lang.Integer.TYPE + case x: Long => java.lang.Long.TYPE + case x: Float => java.lang.Float.TYPE + case x: Double => java.lang.Double.TYPE + case x: Boolean => java.lang.Boolean.TYPE + case x: Unit => java.lang.Void.TYPE + }).asInstanceOf[Class[T]] + /** Retrieve generic array element */ def array_apply(xs: AnyRef, idx: Int): Any = xs match { case x: Array[AnyRef] => x(idx).asInstanceOf[Any] diff --git a/test/files/run/getClassTest.check b/test/files/run/getClassTest.check new file mode 100644 index 0000000000..94e86c3889 --- /dev/null +++ b/test/files/run/getClassTest.check @@ -0,0 +1,18 @@ +f1: java.lang.Class<?> +f2: java.lang.Class<?> +f3: java.lang.Class<java.lang.Object> +f4: java.lang.Class<? extends java.lang.Integer> +f5: java.lang.Class<?> +f0: T +f1: class java.lang.Object +f2: class java.lang.Object +f3: class AnyRefs$A +f4: class AnyRefs$B +f5: class java.lang.Object +f6: class java.lang.Object +f7: class AnyRefs$A +f8: class AnyRefs$B +f1: java.lang.Class<? extends MoreAnyRefs$A> +f2: java.lang.Class<? extends MoreAnyRefs$B> +f3: java.lang.Class<?> +f4: java.lang.Class<? extends MoreAnyRefs$A> diff --git a/test/files/run/getClassTest.scala b/test/files/run/getClassTest.scala new file mode 100644 index 0000000000..951cc8d931 --- /dev/null +++ b/test/files/run/getClassTest.scala @@ -0,0 +1,66 @@ +class AnyVals { + def f1 = (5: Any).getClass + def f2 = (5: AnyVal).getClass + def f3 = 5.getClass + def f4 = (5: java.lang.Integer).getClass + def f5 = (5.asInstanceOf[AnyRef]).getClass + + // scalap says: + // + // def f1 : java.lang.Class[?0] forSome {type ?0} = { /* compiled code */ } + // def f2 : java.lang.Class[?0] forSome {type ?0} = { /* compiled code */ } + // def f3 : java.lang.Class[scala.Int] = { /* compiled code */ } + // def f4 : java.lang.Class[?0] forSome {type ?0 <: java.lang.Integer} = { /* compiled code */ } + // def f5 : java.lang.Class[?0] forSome {type ?0 <: scala.AnyRef} = { /* compiled code */ } + // + // java generic signature says: + // + // f1: java.lang.Class<?> + // f2: java.lang.Class<?> + // f3: java.lang.Class<java.lang.Object> + // f4: java.lang.Class<? extends java.lang.Integer> + // f5: java.lang.Class<?> +} + +class AnyRefs { + class A + class B extends A + + def f1 = (new B: Any).getClass().newInstance() + def f2 = (new B: AnyRef).getClass().newInstance() + def f3 = (new B: A).getClass().newInstance() + def f4 = (new B: B).getClass().newInstance() + + def f0[T >: B] = (new B: T).getClass().newInstance() + + def f5 = f0[Any] + def f6 = f0[AnyRef] + def f7 = f0[A] + def f8 = f0[B] +} + +class MoreAnyRefs { + trait A + trait B + + // don't leak anon/refinements + def f1 = (new A with B { }).getClass() + def f2 = (new B with A { }).getClass() + def f3 = (new { def bippy() = 5 }).getClass() + def f4 = (new A { def bippy() = 5 }).getClass() +} + +object Test { + def returnTypes[T: Manifest] = ( + manifest[T].erasure.getMethods.toList + filter (_.getName startsWith "f") + sortBy (_.getName) + map (m => m.getName + ": " + m.getGenericReturnType.toString) + ) + + def main(args: Array[String]): Unit = { + returnTypes[AnyVals] foreach println + returnTypes[AnyRefs] foreach println + returnTypes[MoreAnyRefs] foreach println + } +} |