Skip to content

Commit 63243ab

Browse files
committed
New representation for arrays of value classes
For a value class V whose underlying type is U, instead of representing an array of V as V[] on the JVM, we use our own class VCXArray where X is "U" if U is a primitive type and is "Object" otherwise. This avoids boxing when creating arrays but we still need to box and unbox when using such an array in a generic position, this also breaks reflection and makes it pretty hard to use an array of a value class from Java or Scala 2. See also the FIXMEs in tests/run/valueclasses-array.scala for the current implementation restrictions.
1 parent 2051769 commit 63243ab

File tree

6 files changed

+174
-1
lines changed

6 files changed

+174
-1
lines changed

src/dotty/tools/dotc/Compiler.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ class Compiler {
6262
new AugmentScala2Traits,
6363
new ResolveSuper),
6464
List(new Erasure),
65+
List(new VCArrays), // Separate group for easier debugging for now
6566
List(new ElimErasedValueType,
6667
new VCInline,
6768
new Mixin,

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,7 @@ object StdNames {
227227

228228
// Compiler-internal
229229
val ANYname: N = "<anyname>"
230+
val ARR: N = "arr"
230231
val CONSTRUCTOR: N = Names.CONSTRUCTOR.toString
231232
val DEFAULT_CASE: N = "defaultCase$"
232233
val EVT2U: N = "evt2u$"

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -372,8 +372,10 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean
372372

373373
private def eraseArray(tp: RefinedType)(implicit ctx: Context) = {
374374
val defn.ArrayType(elemtp) = tp
375+
// Arrays are semi-erased to ErasedValueType(V, U)[] and fully erased in
376+
// VCArrays
375377
def arrayErasure(tpToErase: Type) =
376-
erasureFn(isJava, semiEraseVCs = false, isConstructor, wildcardOK)(tpToErase)
378+
erasureFn(isJava, semiEraseVCs = true, isConstructor, wildcardOK)(tpToErase)
377379
if (elemtp derivesFrom defn.NullClass) JavaArrayType(defn.ObjectType)
378380
else if (isUnboundedGeneric(elemtp)) defn.ObjectType
379381
else JavaArrayType(arrayErasure(elemtp))
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
package dotty.tools.dotc
2+
package transform
3+
4+
import ast.{Trees, tpd}
5+
import core._, core.Decorators._
6+
import Contexts._, Trees._, Types._, StdNames._, Symbols._
7+
import DenotTransformers._, TreeTransforms._, Phases.Phase
8+
import TypeErasure.ErasedValueType, ValueClasses._
9+
10+
/** This phase erases arrays of value classes to their runtime representation.
11+
*
12+
* For a value class V whose erased underlying type is U, an array of V has type
13+
* Array[V] before Erasure and Array[ErasedValueType(V, U)] afterwards. This phase
14+
* replaces this type by VCXArray where X is "U" if U is a primitive type and is "Object"
15+
* otherwise.
16+
*/
17+
class VCArrays extends MiniPhaseTransform with InfoTransformer {
18+
import tpd._
19+
20+
override def phaseName: String = "vcArrays"
21+
22+
override def transformInfo(tp: Type, sym: Symbol)(implicit ctx: Context): Type =
23+
eraseVCArrays(tp)
24+
25+
private def eraseVCArrays(tp: Type)(implicit ctx: Context): Type = tp match {
26+
case JavaArrayType(ErasedValueType(cls, _)) =>
27+
defn.vcArrayOf(cls).typeRef
28+
case tp: MethodType =>
29+
val paramTypes = tp.paramTypes.mapConserve(eraseVCArrays)
30+
val retType = eraseVCArrays(tp.resultType)
31+
tp.derivedMethodType(tp.paramNames, paramTypes, retType)
32+
case _ =>
33+
tp
34+
}
35+
36+
private def transformTypeOfTree(tree: Tree)(implicit ctx: Context): Tree =
37+
tree.withType(eraseVCArrays(tree.tpe))
38+
39+
override def transformValDef(tree: ValDef)(implicit ctx: Context, info: TransformerInfo): Tree = {
40+
val tpt1 = transformTypeOfTree(tree.tpt)
41+
cpy.ValDef(tree)(tpt = tpt1)
42+
}
43+
override def transformDefDef(tree: DefDef)(implicit ctx: Context, info: TransformerInfo): Tree = {
44+
val tpt1 = transformTypeOfTree(tree.tpt)
45+
cpy.DefDef(tree)(tpt = tpt1)
46+
}
47+
48+
override def transformTypeApply(tree: TypeApply)(implicit ctx: Context, info: TransformerInfo): Tree =
49+
tree match {
50+
case TypeApply(sel @ Select(_, _), _) if (sel.symbol == defn.newRefArrayMethod) =>
51+
// Preserve the semi-erased type of the array so that we can properly transform
52+
// it in transformApply
53+
tree
54+
case TypeApply(fun, args) =>
55+
val tree1 = cpy.TypeApply(tree)(fun, args.map(transformTypeOfTree(_)))
56+
transformTypeOfTree(tree1)
57+
}
58+
59+
override def transformSeqLiteral(tree: SeqLiteral)(implicit ctx: Context, info: TransformerInfo): Tree =
60+
tree.tpe match {
61+
// [arg1, arg2, ...] => new VCXArray([V.evt2u$(arg1), V.evt2u$(arg2), ...])
62+
case JavaArrayType(ErasedValueType(cls, _)) =>
63+
val evt2uMethod = ref(evt2u(cls))
64+
val underlyingArray = JavaSeqLiteral(tree.elems.map(evt2uMethod.appliedTo(_)))
65+
val mod = cls.companionModule
66+
New(defn.vcArrayOf(cls).typeRef, List(underlyingArray, ref(mod)))
67+
case _ =>
68+
tree
69+
}
70+
71+
override def transformApply(tree: Apply)(implicit ctx: Context, info: TransformerInfo): Tree = {
72+
tree match {
73+
// newRefArray[ErasedValueType(V, U)[]](args) => New VCXArray(newXArray(args), V)
74+
case Apply(ta @ TypeApply(sel @ Select(_,_), List(targ)), args)
75+
if (sel.symbol == defn.newRefArrayMethod) =>
76+
targ.tpe match {
77+
case JavaArrayType(ErasedValueType(cls, underlying)) =>
78+
val mod = cls.companionModule
79+
New(defn.vcArrayOf(cls).typeRef,
80+
List(newArray(TypeTree(underlying), tree.pos).appliedToArgs(args),
81+
ref(mod)))
82+
case _ =>
83+
tree
84+
}
85+
// array.[]update(idx, elem) => array.arr().[]update(idx, elem)
86+
case Apply(Select(array, nme.primitive.arrayUpdate), List(idx, elem)) =>
87+
elem.tpe.widen match {
88+
case ErasedValueType(cls, _) =>
89+
array.select(nme.ARR).appliedToNone
90+
.select(nme.primitive.arrayUpdate).appliedTo(idx, ref(evt2u(cls)).appliedTo(elem))
91+
case _ =>
92+
tree
93+
}
94+
// array.[]apply(idx) => array.arr().[]apply(idx)
95+
case Apply(Select(array, nme.primitive.arrayApply), List(idx)) =>
96+
tree.tpe.widen match {
97+
case ErasedValueType(cls, _) =>
98+
ref(u2evt(cls)).appliedTo(array.select(nme.ARR).appliedToNone
99+
.select(nme.primitive.arrayApply).appliedTo(idx))
100+
case _ =>
101+
tree
102+
}
103+
// array.[]length() => array.arr().[]length()
104+
case Apply(Select(array, nme.primitive.arrayLength), Nil)
105+
if (array.tpe <:< defn.VCArrayPrototypeType) =>
106+
array.select(nme.ARR).appliedToNone
107+
.select(nme.primitive.arrayLength).appliedToNone
108+
case _ =>
109+
tree
110+
}
111+
}
112+
}

tests/run/valueclasses-array.check

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
[Meter
2+
Meter(1)
3+
1
4+
Meter(2)
5+
2
6+
Meter(3)
7+
1
8+
Meter(4)
9+
2

tests/run/valueclasses-array.scala

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
class Meter(val underlying: Int) extends AnyVal {
2+
def plus(other: Meter): Meter =
3+
new Meter(underlying + other.underlying)
4+
5+
override def toString = "Meter(" + underlying + ")"
6+
}
7+
8+
object Meter {
9+
// FIXME: generate these methods in VCParents
10+
def box(underlying: Int) = new Meter(underlying)
11+
def runtimeClass = classOf[Meter]
12+
}
13+
14+
object Test {
15+
def genAccess[T](genArray: Array[T]) = genArray(0)
16+
def genUpdated[T](genArray: Array[T], elem: T) = genArray(0) = elem
17+
def genLength[T](genArray: Array[T]) = genArray.length
18+
19+
def foo(myArray: Array[Meter], elem: Meter) = {
20+
val update = myArray(0) = elem
21+
val access = myArray(0)
22+
val length: Int = myArray.length
23+
24+
println(access)
25+
println(length)
26+
}
27+
28+
def genFoo[T](genArray: Array[T], elem: T) = {
29+
val update = genArray(0) = elem
30+
val access = genArray(0)
31+
val length: Int = genArray.length
32+
33+
println(access)
34+
println(length)
35+
}
36+
37+
def main(args: Array[String]) = {
38+
val myArray = new Array[Meter](1)
39+
val myArray2 = Array[Meter](new Meter(1), new Meter(2))
40+
assert(myArray.getClass eq myArray2.getClass)
41+
42+
println(myArray.toString)
43+
foo(myArray, new Meter(1))
44+
foo(myArray2, new Meter(2))
45+
genFoo(myArray, new Meter(3))
46+
genFoo(myArray2, new Meter(4))
47+
}
48+
}

0 commit comments

Comments
 (0)