From 79fb64e92e85d162a0d6656dfa100cfe50b0a38f Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 30 Sep 2019 23:04:22 +0200 Subject: [PATCH 1/4] Drop special treatment of getClass in intercepted methods It was not clear what this was supposed accomplish. The referred to test case, t5568, compiles and runs with reasonable output. It was moved from pending to run. --- .../dotc/transform/InterceptedMethods.scala | 37 ++----------------- tests/pending/run/t5568.check | 9 ----- tests/pending/run/t5568.flags | 1 - tests/run/t5568.check | 9 +++++ tests/{pending => }/run/t5568.scala | 0 5 files changed, 13 insertions(+), 43 deletions(-) delete mode 100644 tests/pending/run/t5568.check delete mode 100644 tests/pending/run/t5568.flags create mode 100644 tests/run/t5568.check rename tests/{pending => }/run/t5568.scala (100%) diff --git a/compiler/src/dotty/tools/dotc/transform/InterceptedMethods.scala b/compiler/src/dotty/tools/dotc/transform/InterceptedMethods.scala index 5b3ba45e1850..be456b9b8e27 100644 --- a/compiler/src/dotty/tools/dotc/transform/InterceptedMethods.scala +++ b/compiler/src/dotty/tools/dotc/transform/InterceptedMethods.scala @@ -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._ @@ -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(_) => @@ -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 } } diff --git a/tests/pending/run/t5568.check b/tests/pending/run/t5568.check deleted file mode 100644 index 67aaf16e079f..000000000000 --- a/tests/pending/run/t5568.check +++ /dev/null @@ -1,9 +0,0 @@ -void -int -class scala.runtime.BoxedUnit -class scala.runtime.BoxedUnit -class java.lang.Integer -class java.lang.Integer -5 -5 -5 diff --git a/tests/pending/run/t5568.flags b/tests/pending/run/t5568.flags deleted file mode 100644 index ad51758c3932..000000000000 --- a/tests/pending/run/t5568.flags +++ /dev/null @@ -1 +0,0 @@ --nowarn diff --git a/tests/run/t5568.check b/tests/run/t5568.check new file mode 100644 index 000000000000..90c75aafaa43 --- /dev/null +++ b/tests/run/t5568.check @@ -0,0 +1,9 @@ +void +int +void +void +int +int +5 +5 +5 diff --git a/tests/pending/run/t5568.scala b/tests/run/t5568.scala similarity index 100% rename from tests/pending/run/t5568.scala rename to tests/run/t5568.scala From e460be0cb595fd203f5471b22755351eeb3acd89 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 30 Sep 2019 23:10:14 +0200 Subject: [PATCH 2/4] Improve type of getClass The new assumed signature is def getClass[A >: this.type](): Class[? <: A] This matches what scalac does, without requiring special machinery in the type checker and inferencer. --- .../dotty/tools/dotc/core/Definitions.scala | 31 ++++++++++++------- tests/pos/i3495.scala | 13 ++++++++ tests/pos/t1107b/T.scala | 2 +- 3 files changed, 34 insertions(+), 12 deletions(-) create mode 100644 tests/pos/i3495.scala diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 4886bfe0eb32..8b7510519747 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -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) @@ -261,19 +263,26 @@ class Definitions { @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) diff --git a/tests/pos/i3495.scala b/tests/pos/i3495.scala new file mode 100644 index 000000000000..e77bcc5e1630 --- /dev/null +++ b/tests/pos/i3495.scala @@ -0,0 +1,13 @@ +class A + +object Test { + val x: A = new A() + + def y = x.getClass + + val z: Class[? <: A] = y + + 1.getClass + +} + diff --git a/tests/pos/t1107b/T.scala b/tests/pos/t1107b/T.scala index 0dff0b94fdc3..5889d43548a0 100644 --- a/tests/pos/t1107b/T.scala +++ b/tests/pos/t1107b/T.scala @@ -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) } From 8b9cf8c90d1886bb5c24dcf8361f9782d53a17f8 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 30 Sep 2019 23:33:30 +0200 Subject: [PATCH 3/4] Fix repl test --- compiler/test-resources/repl/getClass | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/compiler/test-resources/repl/getClass b/compiler/test-resources/repl/getClass index 7b3d0bcedd09..6591129a0be9 100644 --- a/compiler/test-resources/repl/getClass +++ b/compiler/test-resources/repl/getClass @@ -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 + From 2c802914611a8e37fe8841a0730c6c6bebe64125 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 1 Oct 2019 00:00:15 +0200 Subject: [PATCH 4/4] Drop outdated comment --- .../src/dotty/tools/dotc/core/Definitions.scala | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 8b7510519747..c95201086200 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -246,17 +246,6 @@ 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 @@ -275,7 +264,7 @@ class Definitions { @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] + /** def getClass[A >: this.type](): Class[? <: A] */ @tu lazy val Any_getClass: TermSymbol = enterPolyMethod( AnyClass, nme.getClass_, 1,