From 959c8d0cd1d4003ab28ba88bf05854682d32d17d Mon Sep 17 00:00:00 2001 From: Dmitry Petrashko Date: Mon, 31 Mar 2014 15:04:41 +0200 Subject: Intercepted methods transformer Replace member references for: methods inside Any( == and !=) ## on primitives .getClass on primitives --- src/dotty/tools/dotc/Compiler.scala | 2 +- src/dotty/tools/dotc/core/Contexts.scala | 4 +- src/dotty/tools/dotc/core/Definitions.scala | 16 ++- .../tools/dotc/transform/InterceptedMethods.scala | 145 +++++++++++++++++++++ .../tools/dotc/transform/TypeTestsCasts.scala | 3 +- 5 files changed, 163 insertions(+), 7 deletions(-) create mode 100644 src/dotty/tools/dotc/transform/InterceptedMethods.scala (limited to 'src/dotty/tools/dotc') diff --git a/src/dotty/tools/dotc/Compiler.scala b/src/dotty/tools/dotc/Compiler.scala index 1e8f13578..8bcd919c2 100644 --- a/src/dotty/tools/dotc/Compiler.scala +++ b/src/dotty/tools/dotc/Compiler.scala @@ -21,7 +21,7 @@ class Compiler { List( List(new FrontEnd), List(new LazyValsCreateCompanionObjects, new PatternMatcher), //force separataion between lazyVals and LVCreateCO - List(new LazyValTranformContext().transformer, new Splitter, new TypeTestsCasts), + List(new LazyValTranformContext().transformer, new Splitter, new TypeTestsCasts, new InterceptedMethods), List(new Erasure), List(new UncurryTreeTransform) ) diff --git a/src/dotty/tools/dotc/core/Contexts.scala b/src/dotty/tools/dotc/core/Contexts.scala index b0214a631..d6852fe13 100644 --- a/src/dotty/tools/dotc/core/Contexts.scala +++ b/src/dotty/tools/dotc/core/Contexts.scala @@ -205,7 +205,8 @@ object Contexts { final def withPhase(phase: Phase): Context = withPhase(phase.id) - /** If -Ydebug is on, the top of the stack trace where this context + + /** If -Ydebug is on, the top of the stack trace where this context * was created, otherwise `null`. */ private var creationTrace: Array[StackTraceElement] = _ @@ -298,6 +299,7 @@ object Contexts { setCreationTrace() this } + /** A fresh clone of this context. */ def fresh: FreshContext = clone.asInstanceOf[FreshContext].init(this) diff --git a/src/dotty/tools/dotc/core/Definitions.scala b/src/dotty/tools/dotc/core/Definitions.scala index 46878d3ca..40fd33671 100644 --- a/src/dotty/tools/dotc/core/Definitions.scala +++ b/src/dotty/tools/dotc/core/Definitions.scala @@ -126,6 +126,7 @@ class Definitions { lazy val AnyValClass: ClassSymbol = ctx.requiredClass("scala.AnyVal") + lazy val AnyVal_getClass = AnyValClass.requiredMethod(nme.getClass_) lazy val Any_== = newMethod(AnyClass, nme.EQ, methOfAny(BooleanType), Final) lazy val Any_!= = newMethod(AnyClass, nme.NE, methOfAny(BooleanType), Final) lazy val Any_equals = newMethod(AnyClass, nme.equals_, methOfAny(BooleanType)) @@ -154,6 +155,7 @@ class Definitions { ScalaPackageClass, tpnme.Null, AbstractFinal, List(ObjectClass.typeRef)) lazy val ScalaPredefModule = ctx.requiredModule("scala.Predef") + lazy val ScalaRuntimeModule = ctx.requiredModule("scala.runtime.ScalaRunTime") lazy val DottyPredefModule = ctx.requiredModule("dotty.DottyPredef") lazy val NilModule = ctx.requiredModule("scala.collection.immutable.Nil") @@ -170,7 +172,7 @@ class Definitions { lazy val UnitClass = valueClassSymbol("scala.Unit", BoxedUnitClass, java.lang.Void.TYPE, UnitEnc) lazy val BooleanClass = valueClassSymbol("scala.Boolean", BoxedBooleanClass, java.lang.Boolean.TYPE, BooleanEnc) - + lazy val Boolean_! = BooleanClass.requiredMethod(nme.UNARY_!) lazy val Boolean_and = BooleanClass.requiredMethod(nme.ZAND) lazy val ByteClass = valueClassSymbol("scala.Byte", BoxedByteClass, java.lang.Byte.TYPE, ByteEnc) @@ -178,6 +180,12 @@ class Definitions { lazy val CharClass = valueClassSymbol("scala.Char", BoxedCharClass, java.lang.Character.TYPE, CharEnc) lazy val IntClass = valueClassSymbol("scala.Int", BoxedIntClass, java.lang.Integer.TYPE, IntEnc) lazy val LongClass = valueClassSymbol("scala.Long", BoxedLongClass, java.lang.Long.TYPE, LongEnc) + lazy val Long_XOR_Long = LongClass.info.member(nme.XOR).requiredSymbol( + x => (x is Method) && (x.info.firstParamTypes.head isRef defn.LongClass) + ) + lazy val Long_LSR_Int = LongClass.info.member(nme.LSR).requiredSymbol( + x => (x is Method) && (x.info.firstParamTypes.head isRef defn.IntClass) + ) lazy val FloatClass = valueClassSymbol("scala.Float", BoxedFloatClass, java.lang.Float.TYPE, FloatEnc) lazy val DoubleClass = valueClassSymbol("scala.Double", BoxedDoubleClass, java.lang.Double.TYPE, DoubleEnc) @@ -421,9 +429,7 @@ class Definitions { // ----- primitive value class machinery ------------------------------------------ - lazy val ScalaValueClasses: collection.Set[Symbol] = Set( - UnitClass, - BooleanClass, + lazy val ScalaNumericValueClasses: collection.Set[Symbol] = Set( ByteClass, ShortClass, CharClass, @@ -431,6 +437,8 @@ class Definitions { LongClass, FloatClass, DoubleClass) + + lazy val ScalaValueClasses: collection.Set[Symbol] = ScalaNumericValueClasses + UnitClass + BooleanClass lazy val ScalaBoxedClasses = ScalaValueClasses map boxedClass diff --git a/src/dotty/tools/dotc/transform/InterceptedMethods.scala b/src/dotty/tools/dotc/transform/InterceptedMethods.scala new file mode 100644 index 000000000..b56985ffe --- /dev/null +++ b/src/dotty/tools/dotc/transform/InterceptedMethods.scala @@ -0,0 +1,145 @@ +package dotty.tools.dotc +package transform + +import TreeTransforms._ +import core.DenotTransformers._ +import core.Denotations._ +import core.SymDenotations._ +import core.Contexts._ +import core.Types._ +import ast.Trees._ +import ast.tpd.{Apply, Tree, cpy} +import dotty.tools.dotc.ast.tpd +import scala.collection.mutable +import dotty.tools.dotc._ +import core._ +import Contexts._ +import Symbols._ +import Decorators._ +import NameOps._ +import dotty.tools.dotc.transform.TreeTransforms.{TransformerInfo, TreeTransformer, TreeTransform} +import dotty.tools.dotc.ast.Trees._ +import dotty.tools.dotc.ast.{untpd, tpd} +import dotty.tools.dotc.core.Constants.Constant +import dotty.tools.dotc.core.Types.MethodType +import dotty.tools.dotc.core.Names.Name +import dotty.runtime.LazyVals +import scala.collection.mutable.ListBuffer +import dotty.tools.dotc.core.Denotations.SingleDenotation +import dotty.tools.dotc.core.SymDenotations.SymDenotation +import dotty.tools.dotc.core.DenotTransformers.DenotTransformer +import StdNames._ + +/** Replace member references as follows: + * + * - `x == y` for == in class Any becomes `x equals y` with equals in class Object. + * - `x != y` for != in class Any becomes `!(x equals y)` with equals in class Object. + * - `x.##` for ## in other classes 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 TreeTransform { + + import tpd._ + + override def name: String = "intercepted" + + private var getClassMethods: Set[Symbol] = _ + private var poundPoundMethods: Set[Symbol] = _ + private var Any_comparisons: Set[Symbol] = _ + private var interceptedMethods: Set[Symbol] = _ + private var primitiveGetClassMethods: Set[Symbol] = _ + + /** perform context-dependant initialization */ + override def init(implicit ctx: Context, info: TransformerInfo): Unit = { + getClassMethods = Set(defn.Any_getClass, defn.AnyVal_getClass) + poundPoundMethods = Set(defn.Any_##, defn.Object_##) + Any_comparisons = Set(defn.Any_==, defn.Any_!=) + interceptedMethods = getClassMethods ++ poundPoundMethods ++ Any_comparisons + primitiveGetClassMethods = Set[Symbol](defn.Any_getClass, defn.AnyVal_getClass) ++ + defn.ScalaValueClasses.map(x => x.requiredMethod(nme.getClass_)) + } + + // this should be removed if we have guarantee that ## will get Apply node + override def transformSelect(tree: tpd.Select)(implicit ctx: Context, info: TransformerInfo): Tree = { + if (tree.symbol.isTerm && poundPoundMethods.contains(tree.symbol.asTerm)) { + val rewrite = PoundPoundValue(tree.qualifier) + ctx.log(s"$name rewrote $tree to $rewrite") + rewrite + } + else tree + } + + private def PoundPoundValue(tree: Tree)(implicit ctx: Context) = { + val s = tree.tpe.widen.typeSymbol + if (s == defn.NullClass) Literal(Constant(0)) + else { + // Since we are past typer, we need to avoid creating trees carrying + // overloaded types. This logic is custom (and technically incomplete, + // although serviceable) for def hash. What is really needed is for + // the overloading logic presently hidden away in a few different + // places to be properly exposed so we can just call "resolveOverload" + // after typer. Until then: + + def alts = defn.ScalaRuntimeModule.info.member(nme.hash_) + + // if tpe is a primitive value type, alt1 will match on the exact value, + // taking in account that null.asInstanceOf[Int] == 0 + def alt1 = alts.suchThat(_.info.firstParamTypes.head =:= tree.tpe.widen) + + // otherwise alt2 will match. alt2 also knows how to handle 'null' runtime value + def alt2 = defn.ScalaRuntimeModule.info.member(nme.hash_) + .suchThat(_.info.firstParamTypes.head.typeSymbol == defn.AnyClass) + + if (defn.ScalaNumericValueClasses contains s) { + tpd.Apply(Ident(alt1.termRef), List(tree)) + } else tpd.Apply(Ident(alt2.termRef), List(tree)) + } + } + + override def transformApply(tree: Apply)(implicit ctx: Context, info: TransformerInfo): Tree = { + def unknown = { + assert(false, s"The symbol '${tree.fun.symbol}' was interecepted but didn't match any cases, " + + s"that means the intercepted methods set doesn't match the code") + tree + } + if (tree.fun.symbol.isTerm && tree.args.isEmpty && + (interceptedMethods contains tree.fun.symbol.asTerm)) { + val rewrite: Tree = tree.fun match { + case Select(qual, name) => + if (poundPoundMethods contains tree.fun.symbol.asTerm) { + PoundPoundValue(qual) + } else if (Any_comparisons contains tree.fun.symbol.asTerm) { + if (tree.fun.symbol eq defn.Any_==) { + Apply(Select(qual, defn.Object_equals.termRef), tree.args) + } else if (tree.fun.symbol eq defn.Any_!=) { + Select(Apply(Select(qual, defn.Object_equals.termRef), tree.args), defn.Boolean_!.termRef) + } else unknown + } /* 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)))) + }*/ + else if (primitiveGetClassMethods.contains(tree.fun.symbol)) { + // 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. + Apply(Select(qual, defn.Object_getClass.termRef), Nil) + } else { + unknown + } + case _ => + unknown + } + ctx.log(s"$name rewrote $tree to $rewrite") + rewrite + } + else tree + } +} diff --git a/src/dotty/tools/dotc/transform/TypeTestsCasts.scala b/src/dotty/tools/dotc/transform/TypeTestsCasts.scala index cbd521210..aba674d1c 100644 --- a/src/dotty/tools/dotc/transform/TypeTestsCasts.scala +++ b/src/dotty/tools/dotc/transform/TypeTestsCasts.scala @@ -15,7 +15,8 @@ import typer.ErrorReporting._ import ast.Trees._ import Erasure.Boxing.box -/** This transform normalizes type tests and type casts. +/** This transform normalizes type tests and type casts, + * also replacing type tests with singleton argument type with refference equality check * Any remaining type tests * - use the object methods $isInstanceOf and $asInstanceOf * - have a reference type as receiver -- cgit v1.2.3