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 /src/compiler/scala/tools | |
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.)
Diffstat (limited to 'src/compiler/scala/tools')
-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 |
3 files changed, 87 insertions, 19 deletions
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 } } |