Skip to content

Fix #3495: Improve handling of getClass #7345

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Oct 2, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 20 additions & 22 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -153,10 +153,12 @@ class Definitions {
tl => op(tl.paramRefs(0), tl.paramRefs(1))))

private def enterPolyMethod(cls: ClassSymbol, name: TermName, typeParamCount: Int,
resultTypeFn: PolyType => Type, flags: FlagSet = EmptyFlags,
resultTypeFn: PolyType => Type,
flags: FlagSet = EmptyFlags,
bounds: TypeBounds = TypeBounds.empty,
useCompleter: Boolean = false) = {
val tparamNames = PolyType.syntheticParamNames(typeParamCount)
val tparamInfos = tparamNames map (_ => TypeBounds.empty)
val tparamInfos = tparamNames map (_ => bounds)
def ptype = PolyType(tparamNames)(_ => tparamInfos, resultTypeFn)
val info =
if (useCompleter)
Expand Down Expand Up @@ -244,36 +246,32 @@ class Definitions {
* - Have other methods exist only in Object.
* To achieve this, we synthesize all Any and Object methods; Object methods no longer get
* loaded from a classfile.
*
* There's a remaining question about `getClass`. In Scala2.x `getClass` was handled by compiler magic.
* This is deemed too cumersome for Dotty and therefore right now `getClass` gets no special treatment;
* it's just a method on `Any` which returns the raw type `java.lang.Class`. An alternative
* way to get better `getClass` typing would be to treat `getClass` as a method of a generic
* decorator which gets remapped in a later phase to Object#getClass. Then we could give it
* the right type without changing the typechecker:
*
* implicit class AnyGetClass[T](val x: T) extends AnyVal {
* def getClass: java.lang.Class[T] = ???
* }
*/
@tu lazy val AnyClass: ClassSymbol = completeClass(enterCompleteClassSymbol(ScalaPackageClass, tpnme.Any, Abstract, Nil), ensureCtor = false)
def AnyType: TypeRef = AnyClass.typeRef
@tu lazy val AnyValClass: ClassSymbol = completeClass(enterCompleteClassSymbol(ScalaPackageClass, tpnme.AnyVal, Abstract, List(AnyClass.typeRef)))
def AnyValType: TypeRef = AnyValClass.typeRef

@tu lazy val Any_== : TermSymbol = enterMethod(AnyClass, nme.EQ, methOfAny(BooleanType), Final)
@tu lazy val Any_!= : TermSymbol = enterMethod(AnyClass, nme.NE, methOfAny(BooleanType), Final)
@tu lazy val Any_equals: TermSymbol = enterMethod(AnyClass, nme.equals_, methOfAny(BooleanType))
@tu lazy val Any_hashCode: TermSymbol = enterMethod(AnyClass, nme.hashCode_, MethodType(Nil, IntType))
@tu lazy val Any_toString: TermSymbol = enterMethod(AnyClass, nme.toString_, MethodType(Nil, StringType))
@tu lazy val Any_## : TermSymbol = enterMethod(AnyClass, nme.HASHHASH, ExprType(IntType), Final)
@tu lazy val Any_getClass: TermSymbol = enterMethod(AnyClass, nme.getClass_, MethodType(Nil, ClassClass.typeRef.appliedTo(TypeBounds.empty)), Final)
@tu lazy val Any_== : TermSymbol = enterMethod(AnyClass, nme.EQ, methOfAny(BooleanType), Final)
@tu lazy val Any_!= : TermSymbol = enterMethod(AnyClass, nme.NE, methOfAny(BooleanType), Final)
@tu lazy val Any_equals: TermSymbol = enterMethod(AnyClass, nme.equals_, methOfAny(BooleanType))
@tu lazy val Any_hashCode: TermSymbol = enterMethod(AnyClass, nme.hashCode_, MethodType(Nil, IntType))
@tu lazy val Any_toString: TermSymbol = enterMethod(AnyClass, nme.toString_, MethodType(Nil, StringType))
@tu lazy val Any_## : TermSymbol = enterMethod(AnyClass, nme.HASHHASH, ExprType(IntType), Final)
@tu lazy val Any_isInstanceOf: TermSymbol = enterT1ParameterlessMethod(AnyClass, nme.isInstanceOf_, _ => BooleanType, Final)
@tu lazy val Any_asInstanceOf: TermSymbol = enterT1ParameterlessMethod(AnyClass, nme.asInstanceOf_, _.paramRefs(0), Final)
@tu lazy val Any_typeTest: TermSymbol = enterT1ParameterlessMethod(AnyClass, nme.isInstanceOfPM, _ => BooleanType, Final | Synthetic | Artifact)
@tu lazy val Any_typeCast: TermSymbol = enterT1ParameterlessMethod(AnyClass, nme.asInstanceOfPM, _.paramRefs(0), Final | Synthetic | Artifact | StableRealizable)
@tu lazy val Any_typeTest: TermSymbol = enterT1ParameterlessMethod(AnyClass, nme.isInstanceOfPM, _ => BooleanType, Final | Synthetic | Artifact)
@tu lazy val Any_typeCast: TermSymbol = enterT1ParameterlessMethod(AnyClass, nme.asInstanceOfPM, _.paramRefs(0), Final | Synthetic | Artifact | StableRealizable)
// generated by pattern matcher, eliminated by erasure

/** def getClass[A >: this.type](): Class[? <: A] */
@tu lazy val Any_getClass: TermSymbol =
enterPolyMethod(
AnyClass, nme.getClass_, 1,
pt => MethodType(Nil, ClassClass.typeRef.appliedTo(TypeBounds.upper(pt.paramRefs(0)))),
Final,
bounds = TypeBounds.lower(AnyClass.thisType))

def AnyMethods: List[TermSymbol] = List(Any_==, Any_!=, Any_equals, Any_hashCode,
Any_toString, Any_##, Any_getClass, Any_isInstanceOf, Any_asInstanceOf, Any_typeTest, Any_typeCast)

Expand Down
37 changes: 4 additions & 33 deletions compiler/src/dotty/tools/dotc/transform/InterceptedMethods.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ object InterceptedMethods {
* - `x.##` for ## in NullClass becomes `0`
* - `x.##` for ## in Any becomes calls to ScalaRunTime.hash,
* using the most precise overload available
* - `x.getClass` for getClass in primitives becomes `x.getClass` with getClass in class Object.
*/
class InterceptedMethods extends MiniPhase {
import tpd._
Expand Down Expand Up @@ -62,11 +61,6 @@ class InterceptedMethods extends MiniPhase {
}

override def transformApply(tree: Apply)(implicit ctx: Context): Tree = {
def unknown = {
assert(false, s"The symbol '${tree.fun.symbol.showLocated}' was intercepted but didn't match any cases, " +
s"that means the intercepted methods set doesn't match the code")
tree
}
lazy val qual = tree.fun match {
case Select(qual, _) => qual
case ident @ Ident(_) =>
Expand All @@ -78,32 +72,9 @@ class InterceptedMethods extends MiniPhase {
}
}

val Any_!= = defn.Any_!=
val rewritten: Tree = tree.fun.symbol match {
case Any_!= =>
qual.select(defn.Any_==).appliedToArgs(tree.args).select(defn.Boolean_!).withSpan(tree.span)
/*
/* else if (isPrimitiveValueClass(qual.tpe.typeSymbol)) {
// todo: this is needed to support value classes
// Rewrite 5.getClass to ScalaRunTime.anyValClass(5)
global.typer.typed(gen.mkRuntimeCall(nme.anyValClass,
List(qual, typer.resolveClassTag(tree.pos, qual.tpe.widen))))
}*/
*/
case t if t.name == nme.getClass_ && defn.ScalaValueClasses().contains(t.owner) =>
// if we got here then we're trying to send a primitive getClass method to either
// a) an Any, in which cage Object_getClass works because Any erases to object. Or
//
// b) a non-primitive, e.g. because the qualifier's type is a refinement type where one parent
// of the refinement is a primitive and another is AnyRef. In that case
// we get a primitive form of _getClass trying to target a boxed value
// so we need replace that method name with Object_getClass to get correct behavior.
// See SI-5568.
qual.selectWithSig(defn.Any_getClass).appliedToNone.withSpan(tree.span)
case _ =>
tree
}
ctx.log(s"$phaseName rewrote $tree to $rewritten")
rewritten
if tree.fun.symbol == defn.Any_!= then
qual.select(defn.Any_==).appliedToArgs(tree.args).select(defn.Boolean_!).withSpan(tree.span)
else
tree
}
}
3 changes: 2 additions & 1 deletion compiler/test-resources/repl/getClass
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
scala> val xs = List(1)
val xs: List[Int] = List(1)
scala> xs.getClass
val res0: Class[?] = class scala.collection.immutable.$colon$colon
val res0: Class[? <: List[Int]] = class scala.collection.immutable.$colon$colon

9 changes: 0 additions & 9 deletions tests/pending/run/t5568.check

This file was deleted.

1 change: 0 additions & 1 deletion tests/pending/run/t5568.flags

This file was deleted.

13 changes: 13 additions & 0 deletions tests/pos/i3495.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
class A

object Test {
val x: A = new A()

def y = x.getClass

val z: Class[? <: A] = y

1.getClass

}

2 changes: 1 addition & 1 deletion tests/pos/t1107b/T.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ sealed trait Top
sealed trait Sub extends Top
trait C {
private object P extends Sub
def bob() = P.getClass
def bob(): Class[_] = P.getClass
def bob2() = O.d(P)
}
9 changes: 9 additions & 0 deletions tests/run/t5568.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
void
int
void
void
int
int
5
5
5
File renamed without changes.