Skip to content

Commit e52fa50

Browse files
committed
Merge pull request #103 from DarkDimius/transform/erasure-transforms
TypeTestCasts fixes and InterceptedMethods transformer
2 parents 2033b56 + 959c8d0 commit e52fa50

File tree

6 files changed

+178
-20
lines changed

6 files changed

+178
-20
lines changed

src/dotty/tools/dotc/Compiler.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ class Compiler {
2121
List(
2222
List(new FrontEnd),
2323
List(new LazyValsCreateCompanionObjects, new PatternMatcher), //force separataion between lazyVals and LVCreateCO
24-
List(new LazyValTranformContext().transformer, new Splitter, new TypeTestsCasts),
24+
List(new LazyValTranformContext().transformer, new Splitter, new TypeTestsCasts, new InterceptedMethods),
2525
List(new Erasure),
2626
List(new UncurryTreeTransform)
2727
)

src/dotty/tools/dotc/core/Contexts.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,8 @@ object Contexts {
205205

206206
final def withPhase(phase: Phase): Context =
207207
withPhase(phase.id)
208-
/** If -Ydebug is on, the top of the stack trace where this context
208+
209+
/** If -Ydebug is on, the top of the stack trace where this context
209210
* was created, otherwise `null`.
210211
*/
211212
private var creationTrace: Array[StackTraceElement] = _
@@ -298,6 +299,7 @@ object Contexts {
298299
setCreationTrace()
299300
this
300301
}
302+
301303
/** A fresh clone of this context. */
302304
def fresh: FreshContext = clone.asInstanceOf[FreshContext].init(this)
303305

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

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ class Definitions {
126126

127127
lazy val AnyValClass: ClassSymbol = ctx.requiredClass("scala.AnyVal")
128128

129+
lazy val AnyVal_getClass = AnyValClass.requiredMethod(nme.getClass_)
129130
lazy val Any_== = newMethod(AnyClass, nme.EQ, methOfAny(BooleanType), Final)
130131
lazy val Any_!= = newMethod(AnyClass, nme.NE, methOfAny(BooleanType), Final)
131132
lazy val Any_equals = newMethod(AnyClass, nme.equals_, methOfAny(BooleanType))
@@ -154,6 +155,7 @@ class Definitions {
154155
ScalaPackageClass, tpnme.Null, AbstractFinal, List(ObjectClass.typeRef))
155156

156157
lazy val ScalaPredefModule = ctx.requiredModule("scala.Predef")
158+
lazy val ScalaRuntimeModule = ctx.requiredModule("scala.runtime.ScalaRunTime")
157159
lazy val DottyPredefModule = ctx.requiredModule("dotty.DottyPredef")
158160
lazy val NilModule = ctx.requiredModule("scala.collection.immutable.Nil")
159161

@@ -170,14 +172,20 @@ class Definitions {
170172

171173
lazy val UnitClass = valueClassSymbol("scala.Unit", BoxedUnitClass, java.lang.Void.TYPE, UnitEnc)
172174
lazy val BooleanClass = valueClassSymbol("scala.Boolean", BoxedBooleanClass, java.lang.Boolean.TYPE, BooleanEnc)
173-
175+
lazy val Boolean_! = BooleanClass.requiredMethod(nme.UNARY_!)
174176
lazy val Boolean_and = BooleanClass.requiredMethod(nme.ZAND)
175177

176178
lazy val ByteClass = valueClassSymbol("scala.Byte", BoxedByteClass, java.lang.Byte.TYPE, ByteEnc)
177179
lazy val ShortClass = valueClassSymbol("scala.Short", BoxedShortClass, java.lang.Short.TYPE, ShortEnc)
178180
lazy val CharClass = valueClassSymbol("scala.Char", BoxedCharClass, java.lang.Character.TYPE, CharEnc)
179181
lazy val IntClass = valueClassSymbol("scala.Int", BoxedIntClass, java.lang.Integer.TYPE, IntEnc)
180182
lazy val LongClass = valueClassSymbol("scala.Long", BoxedLongClass, java.lang.Long.TYPE, LongEnc)
183+
lazy val Long_XOR_Long = LongClass.info.member(nme.XOR).requiredSymbol(
184+
x => (x is Method) && (x.info.firstParamTypes.head isRef defn.LongClass)
185+
)
186+
lazy val Long_LSR_Int = LongClass.info.member(nme.LSR).requiredSymbol(
187+
x => (x is Method) && (x.info.firstParamTypes.head isRef defn.IntClass)
188+
)
181189
lazy val FloatClass = valueClassSymbol("scala.Float", BoxedFloatClass, java.lang.Float.TYPE, FloatEnc)
182190
lazy val DoubleClass = valueClassSymbol("scala.Double", BoxedDoubleClass, java.lang.Double.TYPE, DoubleEnc)
183191

@@ -421,16 +429,16 @@ class Definitions {
421429

422430
// ----- primitive value class machinery ------------------------------------------
423431

424-
lazy val ScalaValueClasses: collection.Set[Symbol] = Set(
425-
UnitClass,
426-
BooleanClass,
432+
lazy val ScalaNumericValueClasses: collection.Set[Symbol] = Set(
427433
ByteClass,
428434
ShortClass,
429435
CharClass,
430436
IntClass,
431437
LongClass,
432438
FloatClass,
433439
DoubleClass)
440+
441+
lazy val ScalaValueClasses: collection.Set[Symbol] = ScalaNumericValueClasses + UnitClass + BooleanClass
434442

435443
lazy val ScalaBoxedClasses = ScalaValueClasses map boxedClass
436444

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
package dotty.tools.dotc
2+
package transform
3+
4+
import TreeTransforms._
5+
import core.DenotTransformers._
6+
import core.Denotations._
7+
import core.SymDenotations._
8+
import core.Contexts._
9+
import core.Types._
10+
import ast.Trees._
11+
import ast.tpd.{Apply, Tree, cpy}
12+
import dotty.tools.dotc.ast.tpd
13+
import scala.collection.mutable
14+
import dotty.tools.dotc._
15+
import core._
16+
import Contexts._
17+
import Symbols._
18+
import Decorators._
19+
import NameOps._
20+
import dotty.tools.dotc.transform.TreeTransforms.{TransformerInfo, TreeTransformer, TreeTransform}
21+
import dotty.tools.dotc.ast.Trees._
22+
import dotty.tools.dotc.ast.{untpd, tpd}
23+
import dotty.tools.dotc.core.Constants.Constant
24+
import dotty.tools.dotc.core.Types.MethodType
25+
import dotty.tools.dotc.core.Names.Name
26+
import dotty.runtime.LazyVals
27+
import scala.collection.mutable.ListBuffer
28+
import dotty.tools.dotc.core.Denotations.SingleDenotation
29+
import dotty.tools.dotc.core.SymDenotations.SymDenotation
30+
import dotty.tools.dotc.core.DenotTransformers.DenotTransformer
31+
import StdNames._
32+
33+
/** Replace member references as follows:
34+
*
35+
* - `x == y` for == in class Any becomes `x equals y` with equals in class Object.
36+
* - `x != y` for != in class Any becomes `!(x equals y)` with equals in class Object.
37+
* - `x.##` for ## in other classes becomes calls to ScalaRunTime.hash,
38+
* using the most precise overload available
39+
* - `x.getClass` for getClass in primitives becomes `x.getClass` with getClass in class Object.
40+
*/
41+
class InterceptedMethods extends TreeTransform {
42+
43+
import tpd._
44+
45+
override def name: String = "intercepted"
46+
47+
private var getClassMethods: Set[Symbol] = _
48+
private var poundPoundMethods: Set[Symbol] = _
49+
private var Any_comparisons: Set[Symbol] = _
50+
private var interceptedMethods: Set[Symbol] = _
51+
private var primitiveGetClassMethods: Set[Symbol] = _
52+
53+
/** perform context-dependant initialization */
54+
override def init(implicit ctx: Context, info: TransformerInfo): Unit = {
55+
getClassMethods = Set(defn.Any_getClass, defn.AnyVal_getClass)
56+
poundPoundMethods = Set(defn.Any_##, defn.Object_##)
57+
Any_comparisons = Set(defn.Any_==, defn.Any_!=)
58+
interceptedMethods = getClassMethods ++ poundPoundMethods ++ Any_comparisons
59+
primitiveGetClassMethods = Set[Symbol](defn.Any_getClass, defn.AnyVal_getClass) ++
60+
defn.ScalaValueClasses.map(x => x.requiredMethod(nme.getClass_))
61+
}
62+
63+
// this should be removed if we have guarantee that ## will get Apply node
64+
override def transformSelect(tree: tpd.Select)(implicit ctx: Context, info: TransformerInfo): Tree = {
65+
if (tree.symbol.isTerm && poundPoundMethods.contains(tree.symbol.asTerm)) {
66+
val rewrite = PoundPoundValue(tree.qualifier)
67+
ctx.log(s"$name rewrote $tree to $rewrite")
68+
rewrite
69+
}
70+
else tree
71+
}
72+
73+
private def PoundPoundValue(tree: Tree)(implicit ctx: Context) = {
74+
val s = tree.tpe.widen.typeSymbol
75+
if (s == defn.NullClass) Literal(Constant(0))
76+
else {
77+
// Since we are past typer, we need to avoid creating trees carrying
78+
// overloaded types. This logic is custom (and technically incomplete,
79+
// although serviceable) for def hash. What is really needed is for
80+
// the overloading logic presently hidden away in a few different
81+
// places to be properly exposed so we can just call "resolveOverload"
82+
// after typer. Until then:
83+
84+
def alts = defn.ScalaRuntimeModule.info.member(nme.hash_)
85+
86+
// if tpe is a primitive value type, alt1 will match on the exact value,
87+
// taking in account that null.asInstanceOf[Int] == 0
88+
def alt1 = alts.suchThat(_.info.firstParamTypes.head =:= tree.tpe.widen)
89+
90+
// otherwise alt2 will match. alt2 also knows how to handle 'null' runtime value
91+
def alt2 = defn.ScalaRuntimeModule.info.member(nme.hash_)
92+
.suchThat(_.info.firstParamTypes.head.typeSymbol == defn.AnyClass)
93+
94+
if (defn.ScalaNumericValueClasses contains s) {
95+
tpd.Apply(Ident(alt1.termRef), List(tree))
96+
} else tpd.Apply(Ident(alt2.termRef), List(tree))
97+
}
98+
}
99+
100+
override def transformApply(tree: Apply)(implicit ctx: Context, info: TransformerInfo): Tree = {
101+
def unknown = {
102+
assert(false, s"The symbol '${tree.fun.symbol}' was interecepted but didn't match any cases, " +
103+
s"that means the intercepted methods set doesn't match the code")
104+
tree
105+
}
106+
if (tree.fun.symbol.isTerm && tree.args.isEmpty &&
107+
(interceptedMethods contains tree.fun.symbol.asTerm)) {
108+
val rewrite: Tree = tree.fun match {
109+
case Select(qual, name) =>
110+
if (poundPoundMethods contains tree.fun.symbol.asTerm) {
111+
PoundPoundValue(qual)
112+
} else if (Any_comparisons contains tree.fun.symbol.asTerm) {
113+
if (tree.fun.symbol eq defn.Any_==) {
114+
Apply(Select(qual, defn.Object_equals.termRef), tree.args)
115+
} else if (tree.fun.symbol eq defn.Any_!=) {
116+
Select(Apply(Select(qual, defn.Object_equals.termRef), tree.args), defn.Boolean_!.termRef)
117+
} else unknown
118+
} /* else if (isPrimitiveValueClass(qual.tpe.typeSymbol)) {
119+
// todo: this is needed to support value classes
120+
// Rewrite 5.getClass to ScalaRunTime.anyValClass(5)
121+
global.typer.typed(gen.mkRuntimeCall(nme.anyValClass,
122+
List(qual, typer.resolveClassTag(tree.pos, qual.tpe.widen))))
123+
}*/
124+
else if (primitiveGetClassMethods.contains(tree.fun.symbol)) {
125+
// if we got here then we're trying to send a primitive getClass method to either
126+
// a) an Any, in which cage Object_getClass works because Any erases to object. Or
127+
//
128+
// b) a non-primitive, e.g. because the qualifier's type is a refinement type where one parent
129+
// of the refinement is a primitive and another is AnyRef. In that case
130+
// we get a primitive form of _getClass trying to target a boxed value
131+
// so we need replace that method name with Object_getClass to get correct behavior.
132+
// See SI-5568.
133+
Apply(Select(qual, defn.Object_getClass.termRef), Nil)
134+
} else {
135+
unknown
136+
}
137+
case _ =>
138+
unknown
139+
}
140+
ctx.log(s"$name rewrote $tree to $rewrite")
141+
rewrite
142+
}
143+
else tree
144+
}
145+
}

src/dotty/tools/dotc/transform/TypeTestsCasts.scala

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ import typer.ErrorReporting._
1515
import ast.Trees._
1616
import Erasure.Boxing.box
1717

18-
/** This transform normalizes type tests and type casts.
18+
/** This transform normalizes type tests and type casts,
19+
* also replacing type tests with singleton argument type with refference equality check
1920
* Any remaining type tests
2021
* - use the object methods $isInstanceOf and $asInstanceOf
2122
* - have a reference type as receiver
@@ -33,13 +34,13 @@ class TypeTestsCasts extends TreeTransform {
3334

3435
def isPrimitive(tp: Type) = tp.classSymbol.isPrimitiveValueClass
3536

36-
def derivedTree(qual1: Tree, sym: Symbol) =
37-
cpy.TypeApply(tree, Select(qual1, sym) withPos qual.pos, tree.args)
37+
def derivedTree(qual1: Tree, sym: Symbol, tp: Type) =
38+
cpy.TypeApply(tree, Select(qual1, sym) withPos qual.pos, List(TypeTree(tp)))
3839

3940
def qualCls = qual.tpe.classSymbol
4041

41-
def transformIsInstanceOf(argType: Type): Tree = {
42-
if (qual.tpe <:< argType)
42+
def transformIsInstanceOf(expr:Tree, argType: Type): Tree = {
43+
if (expr.tpe <:< argType)
4344
Literal(Constant(true)) withPos tree.pos
4445
else if (qualCls.isPrimitiveValueClass) {
4546
val argCls = argType.classSymbol
@@ -49,11 +50,11 @@ class TypeTestsCasts extends TreeTransform {
4950
else argType.dealias match {
5051
case _: SingletonType =>
5152
val cmpOp = if (argType derivesFrom defn.AnyValClass) defn.Any_equals else defn.Object_eq
52-
Apply(Select(qual, cmpOp), singleton(argType) :: Nil)
53+
Apply(Select(expr, cmpOp), singleton(argType) :: Nil)
5354
case AndType(tp1, tp2) =>
54-
evalOnce(fun) { fun =>
55-
val erased1 = transformIsInstanceOf(tp1)
56-
val erased2 = transformIsInstanceOf(tp2)
55+
evalOnce(expr) { fun =>
56+
val erased1 = transformIsInstanceOf(fun, tp1)
57+
val erased2 = transformIsInstanceOf(fun, tp2)
5758
erased1 match {
5859
case Literal(Constant(true)) => erased2
5960
case _ =>
@@ -68,10 +69,10 @@ class TypeTestsCasts extends TreeTransform {
6869
runtimeCall(nme.isArray, arg :: Literal(Constant(ndims)) :: Nil)
6970
if (ndims == 1) isArrayTest(qual)
7071
else evalOnce(qual) { qual1 =>
71-
mkAnd(derivedTree(qual1, defn.Object_isInstanceOf), isArrayTest(qual1))
72+
mkAnd(derivedTree(qual1, defn.Object_isInstanceOf, qual1.tpe), isArrayTest(qual1))
7273
}
7374
case _ =>
74-
derivedTree(qual, defn.Object_isInstanceOf)
75+
derivedTree(expr, defn.Object_isInstanceOf, argType)
7576
}
7677
}
7778

@@ -81,14 +82,14 @@ class TypeTestsCasts extends TreeTransform {
8182
else if (qualCls.isPrimitiveValueClass) {
8283
val argCls = argType.classSymbol
8384
if (argCls.isPrimitiveValueClass) primitiveConversion(qual, argCls)
84-
else derivedTree(box(qual), defn.Object_asInstanceOf)
85+
else derivedTree(box(qual), defn.Object_asInstanceOf, argType)
8586
}
8687
else
87-
derivedTree(qual, defn.Object_asInstanceOf)
88+
derivedTree(qual, defn.Object_asInstanceOf, argType)
8889
}
8990

9091
if (sym eq defn.Any_isInstanceOf)
91-
transformIsInstanceOf(tree.args.head.tpe)
92+
transformIsInstanceOf(qual, tree.args.head.tpe)
9293
else if (defn.asInstanceOfMethods contains sym)
9394
transformAsInstanceOf(tree.args.head.tpe)
9495
else tree

tests/untried/pos/hashhash-overloads.scala renamed to tests/pos/hashhash-overloads.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,6 @@ object Test {
33
def g = 5f.##
44
def h = ({ 5 ; println("abc") }).##
55
def f2 = null.##
6+
def l = 3L.##
7+
def b(arg: Boolean) = arg.##
68
}

0 commit comments

Comments
 (0)