From ea5acb56754e26585d2e17080c35a027988660e9 Mon Sep 17 00:00:00 2001 From: Dmitry Petrashko Date: Mon, 31 Mar 2014 14:24:43 +0200 Subject: Fix TypeTestCasts def p() = println().isInstanceOf[Long & Int] was rewritten to val ev$1: [T0]Boolean(x.isInstanceOf) = println().isInstanceOf println().$isInstanceOf[Long & Int].&&(println().$isInstanceOf[Long & Int]) --- .../tools/dotc/transform/TypeTestsCasts.scala | 26 +++++++++++----------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/dotty/tools/dotc/transform/TypeTestsCasts.scala b/src/dotty/tools/dotc/transform/TypeTestsCasts.scala index 54f72c20c..cbd521210 100644 --- a/src/dotty/tools/dotc/transform/TypeTestsCasts.scala +++ b/src/dotty/tools/dotc/transform/TypeTestsCasts.scala @@ -33,13 +33,13 @@ class TypeTestsCasts extends TreeTransform { def isPrimitive(tp: Type) = tp.classSymbol.isPrimitiveValueClass - def derivedTree(qual1: Tree, sym: Symbol) = - cpy.TypeApply(tree, Select(qual1, sym) withPos qual.pos, tree.args) + def derivedTree(qual1: Tree, sym: Symbol, tp: Type) = + cpy.TypeApply(tree, Select(qual1, sym) withPos qual.pos, List(TypeTree(tp))) def qualCls = qual.tpe.classSymbol - def transformIsInstanceOf(argType: Type): Tree = { - if (qual.tpe <:< argType) + def transformIsInstanceOf(expr:Tree, argType: Type): Tree = { + if (expr.tpe <:< argType) Literal(Constant(true)) withPos tree.pos else if (qualCls.isPrimitiveValueClass) { val argCls = argType.classSymbol @@ -49,11 +49,11 @@ class TypeTestsCasts extends TreeTransform { else argType.dealias match { case _: SingletonType => val cmpOp = if (argType derivesFrom defn.AnyValClass) defn.Any_equals else defn.Object_eq - Apply(Select(qual, cmpOp), singleton(argType) :: Nil) + Apply(Select(expr, cmpOp), singleton(argType) :: Nil) case AndType(tp1, tp2) => - evalOnce(fun) { fun => - val erased1 = transformIsInstanceOf(tp1) - val erased2 = transformIsInstanceOf(tp2) + evalOnce(expr) { fun => + val erased1 = transformIsInstanceOf(fun, tp1) + val erased2 = transformIsInstanceOf(fun, tp2) erased1 match { case Literal(Constant(true)) => erased2 case _ => @@ -68,10 +68,10 @@ class TypeTestsCasts extends TreeTransform { runtimeCall(nme.isArray, arg :: Literal(Constant(ndims)) :: Nil) if (ndims == 1) isArrayTest(qual) else evalOnce(qual) { qual1 => - mkAnd(derivedTree(qual1, defn.Object_isInstanceOf), isArrayTest(qual1)) + mkAnd(derivedTree(qual1, defn.Object_isInstanceOf, qual1.tpe), isArrayTest(qual1)) } case _ => - derivedTree(qual, defn.Object_isInstanceOf) + derivedTree(expr, defn.Object_isInstanceOf, argType) } } @@ -81,14 +81,14 @@ class TypeTestsCasts extends TreeTransform { else if (qualCls.isPrimitiveValueClass) { val argCls = argType.classSymbol if (argCls.isPrimitiveValueClass) primitiveConversion(qual, argCls) - else derivedTree(box(qual), defn.Object_asInstanceOf) + else derivedTree(box(qual), defn.Object_asInstanceOf, argType) } else - derivedTree(qual, defn.Object_asInstanceOf) + derivedTree(qual, defn.Object_asInstanceOf, argType) } if (sym eq defn.Any_isInstanceOf) - transformIsInstanceOf(tree.args.head.tpe) + transformIsInstanceOf(qual, tree.args.head.tpe) else if (defn.asInstanceOfMethods contains sym) transformAsInstanceOf(tree.args.head.tpe) else tree -- cgit v1.2.3 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 +- tests/pos/hashhash-overloads.scala | 8 ++ tests/untried/pos/hashhash-overloads.scala | 6 - 7 files changed, 171 insertions(+), 13 deletions(-) create mode 100644 src/dotty/tools/dotc/transform/InterceptedMethods.scala create mode 100644 tests/pos/hashhash-overloads.scala delete mode 100644 tests/untried/pos/hashhash-overloads.scala 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 diff --git a/tests/pos/hashhash-overloads.scala b/tests/pos/hashhash-overloads.scala new file mode 100644 index 000000000..a27cb1301 --- /dev/null +++ b/tests/pos/hashhash-overloads.scala @@ -0,0 +1,8 @@ +object Test { + def f = ().## + def g = 5f.## + def h = ({ 5 ; println("abc") }).## + def f2 = null.## + def l = 3L.## + def b(arg: Boolean) = arg.## +} diff --git a/tests/untried/pos/hashhash-overloads.scala b/tests/untried/pos/hashhash-overloads.scala deleted file mode 100644 index 40519bae0..000000000 --- a/tests/untried/pos/hashhash-overloads.scala +++ /dev/null @@ -1,6 +0,0 @@ -object Test { - def f = ().## - def g = 5f.## - def h = ({ 5 ; println("abc") }).## - def f2 = null.## -} -- cgit v1.2.3