Skip to content

Commit a8f2787

Browse files
authored
Merge pull request #7345 from dotty-staging/fix-#3495
Fix #3495: Improve handling of getClass
2 parents 02e954a + 2c80291 commit a8f2787

File tree

9 files changed

+49
-67
lines changed

9 files changed

+49
-67
lines changed

compiler/src/dotty/tools/dotc/core/Definitions.scala

+20-22
Original file line numberDiff line numberDiff line change
@@ -153,10 +153,12 @@ class Definitions {
153153
tl => op(tl.paramRefs(0), tl.paramRefs(1))))
154154

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

264-
@tu lazy val Any_== : TermSymbol = enterMethod(AnyClass, nme.EQ, methOfAny(BooleanType), Final)
265-
@tu lazy val Any_!= : TermSymbol = enterMethod(AnyClass, nme.NE, methOfAny(BooleanType), Final)
266-
@tu lazy val Any_equals: TermSymbol = enterMethod(AnyClass, nme.equals_, methOfAny(BooleanType))
267-
@tu lazy val Any_hashCode: TermSymbol = enterMethod(AnyClass, nme.hashCode_, MethodType(Nil, IntType))
268-
@tu lazy val Any_toString: TermSymbol = enterMethod(AnyClass, nme.toString_, MethodType(Nil, StringType))
269-
@tu lazy val Any_## : TermSymbol = enterMethod(AnyClass, nme.HASHHASH, ExprType(IntType), Final)
270-
@tu lazy val Any_getClass: TermSymbol = enterMethod(AnyClass, nme.getClass_, MethodType(Nil, ClassClass.typeRef.appliedTo(TypeBounds.empty)), Final)
255+
@tu lazy val Any_== : TermSymbol = enterMethod(AnyClass, nme.EQ, methOfAny(BooleanType), Final)
256+
@tu lazy val Any_!= : TermSymbol = enterMethod(AnyClass, nme.NE, methOfAny(BooleanType), Final)
257+
@tu lazy val Any_equals: TermSymbol = enterMethod(AnyClass, nme.equals_, methOfAny(BooleanType))
258+
@tu lazy val Any_hashCode: TermSymbol = enterMethod(AnyClass, nme.hashCode_, MethodType(Nil, IntType))
259+
@tu lazy val Any_toString: TermSymbol = enterMethod(AnyClass, nme.toString_, MethodType(Nil, StringType))
260+
@tu lazy val Any_## : TermSymbol = enterMethod(AnyClass, nme.HASHHASH, ExprType(IntType), Final)
271261
@tu lazy val Any_isInstanceOf: TermSymbol = enterT1ParameterlessMethod(AnyClass, nme.isInstanceOf_, _ => BooleanType, Final)
272262
@tu lazy val Any_asInstanceOf: TermSymbol = enterT1ParameterlessMethod(AnyClass, nme.asInstanceOf_, _.paramRefs(0), Final)
273-
@tu lazy val Any_typeTest: TermSymbol = enterT1ParameterlessMethod(AnyClass, nme.isInstanceOfPM, _ => BooleanType, Final | Synthetic | Artifact)
274-
@tu lazy val Any_typeCast: TermSymbol = enterT1ParameterlessMethod(AnyClass, nme.asInstanceOfPM, _.paramRefs(0), Final | Synthetic | Artifact | StableRealizable)
263+
@tu lazy val Any_typeTest: TermSymbol = enterT1ParameterlessMethod(AnyClass, nme.isInstanceOfPM, _ => BooleanType, Final | Synthetic | Artifact)
264+
@tu lazy val Any_typeCast: TermSymbol = enterT1ParameterlessMethod(AnyClass, nme.asInstanceOfPM, _.paramRefs(0), Final | Synthetic | Artifact | StableRealizable)
275265
// generated by pattern matcher, eliminated by erasure
276266

267+
/** def getClass[A >: this.type](): Class[? <: A] */
268+
@tu lazy val Any_getClass: TermSymbol =
269+
enterPolyMethod(
270+
AnyClass, nme.getClass_, 1,
271+
pt => MethodType(Nil, ClassClass.typeRef.appliedTo(TypeBounds.upper(pt.paramRefs(0)))),
272+
Final,
273+
bounds = TypeBounds.lower(AnyClass.thisType))
274+
277275
def AnyMethods: List[TermSymbol] = List(Any_==, Any_!=, Any_equals, Any_hashCode,
278276
Any_toString, Any_##, Any_getClass, Any_isInstanceOf, Any_asInstanceOf, Any_typeTest, Any_typeCast)
279277

compiler/src/dotty/tools/dotc/transform/InterceptedMethods.scala

+4-33
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ object InterceptedMethods {
2121
* - `x.##` for ## in NullClass becomes `0`
2222
* - `x.##` for ## in Any becomes calls to ScalaRunTime.hash,
2323
* using the most precise overload available
24-
* - `x.getClass` for getClass in primitives becomes `x.getClass` with getClass in class Object.
2524
*/
2625
class InterceptedMethods extends MiniPhase {
2726
import tpd._
@@ -62,11 +61,6 @@ class InterceptedMethods extends MiniPhase {
6261
}
6362

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

81-
val Any_!= = defn.Any_!=
82-
val rewritten: Tree = tree.fun.symbol match {
83-
case Any_!= =>
84-
qual.select(defn.Any_==).appliedToArgs(tree.args).select(defn.Boolean_!).withSpan(tree.span)
85-
/*
86-
/* else if (isPrimitiveValueClass(qual.tpe.typeSymbol)) {
87-
// todo: this is needed to support value classes
88-
// Rewrite 5.getClass to ScalaRunTime.anyValClass(5)
89-
global.typer.typed(gen.mkRuntimeCall(nme.anyValClass,
90-
List(qual, typer.resolveClassTag(tree.pos, qual.tpe.widen))))
91-
}*/
92-
*/
93-
case t if t.name == nme.getClass_ && defn.ScalaValueClasses().contains(t.owner) =>
94-
// if we got here then we're trying to send a primitive getClass method to either
95-
// a) an Any, in which cage Object_getClass works because Any erases to object. Or
96-
//
97-
// b) a non-primitive, e.g. because the qualifier's type is a refinement type where one parent
98-
// of the refinement is a primitive and another is AnyRef. In that case
99-
// we get a primitive form of _getClass trying to target a boxed value
100-
// so we need replace that method name with Object_getClass to get correct behavior.
101-
// See SI-5568.
102-
qual.selectWithSig(defn.Any_getClass).appliedToNone.withSpan(tree.span)
103-
case _ =>
104-
tree
105-
}
106-
ctx.log(s"$phaseName rewrote $tree to $rewritten")
107-
rewritten
75+
if tree.fun.symbol == defn.Any_!= then
76+
qual.select(defn.Any_==).appliedToArgs(tree.args).select(defn.Boolean_!).withSpan(tree.span)
77+
else
78+
tree
10879
}
10980
}

compiler/test-resources/repl/getClass

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
scala> val xs = List(1)
22
val xs: List[Int] = List(1)
33
scala> xs.getClass
4-
val res0: Class[?] = class scala.collection.immutable.$colon$colon
4+
val res0: Class[? <: List[Int]] = class scala.collection.immutable.$colon$colon
5+

tests/pending/run/t5568.check

-9
This file was deleted.

tests/pending/run/t5568.flags

-1
This file was deleted.

tests/pos/i3495.scala

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
class A
2+
3+
object Test {
4+
val x: A = new A()
5+
6+
def y = x.getClass
7+
8+
val z: Class[? <: A] = y
9+
10+
1.getClass
11+
12+
}
13+

tests/pos/t1107b/T.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@ sealed trait Top
22
sealed trait Sub extends Top
33
trait C {
44
private object P extends Sub
5-
def bob() = P.getClass
5+
def bob(): Class[_] = P.getClass
66
def bob2() = O.d(P)
77
}

tests/run/t5568.check

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
void
2+
int
3+
void
4+
void
5+
int
6+
int
7+
5
8+
5
9+
5
File renamed without changes.

0 commit comments

Comments
 (0)