Skip to content

TypeTestCasts fixes and InterceptedMethods transformer #103

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 2 commits into from
Apr 2, 2014
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
2 changes: 1 addition & 1 deletion src/dotty/tools/dotc/Compiler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
)
Expand Down
4 changes: 3 additions & 1 deletion src/dotty/tools/dotc/core/Contexts.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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] = _
Expand Down Expand Up @@ -298,6 +299,7 @@ object Contexts {
setCreationTrace()
this
}

/** A fresh clone of this context. */
def fresh: FreshContext = clone.asInstanceOf[FreshContext].init(this)

Expand Down
16 changes: 12 additions & 4 deletions src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down Expand Up @@ -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")

Expand All @@ -170,14 +172,20 @@ 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)
lazy val ShortClass = valueClassSymbol("scala.Short", BoxedShortClass, java.lang.Short.TYPE, ShortEnc)
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)

Expand Down Expand Up @@ -421,16 +429,16 @@ 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,
IntClass,
LongClass,
FloatClass,
DoubleClass)

lazy val ScalaValueClasses: collection.Set[Symbol] = ScalaNumericValueClasses + UnitClass + BooleanClass

lazy val ScalaBoxedClasses = ScalaValueClasses map boxedClass

Expand Down
145 changes: 145 additions & 0 deletions src/dotty/tools/dotc/transform/InterceptedMethods.scala
Original file line number Diff line number Diff line change
@@ -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:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You might want to drop a short line, why this is the proper resolution. E.g.:

  • if tpe is a primitive value type, alt1 will match on the exact value.
  • if tpe is a Number, alt1 will match
  • otherwise alt2 will match

It's currently unclear to me if this does the correct thing when tpe is java.lang.Integer. (It should call the number overload, but will choose the Any overload).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it's an java.lang.Integer Any overload still needs to be called, as Number overload doesn't support null values.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I see. Could you put this into a comment, please?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.


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
}
}
29 changes: 15 additions & 14 deletions src/dotty/tools/dotc/transform/TypeTestsCasts.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -33,13 +34,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
Expand All @@ -49,11 +50,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 _ =>
Expand All @@ -68,10 +69,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)
}
}

Expand All @@ -81,14 +82,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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,6 @@ object Test {
def g = 5f.##
def h = ({ 5 ; println("abc") }).##
def f2 = null.##
def l = 3L.##
def b(arg: Boolean) = arg.##
}