Skip to content

Commit a9b7637

Browse files
committed
Add synthetic casts to and from ErasedValueType
For a value class V, let U be the underlying type after erasure. We add to the companion object of V two cast methods: def underlying2evt$(x0: U): ErasedValueType(V, U) def evt2underlying$(x0: V): ErasedValueType(U, V) The casts are used in Erasure to make it typecheck, they are then removed in ElimErasedValueType (not yet present in this commit). This is different from the implementation of value classes in Scala 2 (see SIP-15) which used `asInstanceOf` which does not typecheck.
1 parent 61c5149 commit a9b7637

File tree

4 files changed

+49
-7
lines changed

4 files changed

+49
-7
lines changed

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,7 @@ object StdNames {
223223
val ANYname: N = "<anyname>"
224224
val CONSTRUCTOR: N = Names.CONSTRUCTOR.toString
225225
val DEFAULT_CASE: N = "defaultCase$"
226+
val EVT2UNDERLYING: N = "evt2underlying$"
226227
val EQEQ_LOCAL_VAR: N = "eqEqTemp$"
227228
val FAKE_LOCAL_THIS: N = "this$"
228229
val IMPLCLASS_CONSTRUCTOR: N = "$init$"
@@ -254,6 +255,7 @@ object StdNames {
254255
val SKOLEM: N = "<skolem>"
255256
val SPECIALIZED_INSTANCE: N = "specInstance$"
256257
val THIS: N = "_$this"
258+
val UNDERLYING2EVT: N = "underlying2evt$"
257259

258260
final val Nil: N = "Nil"
259261
final val Predef: N = "Predef"

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

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,8 @@ object Erasure extends TypeTestsCasts{
192192

193193
/** Generate a synthetic cast operation from tree.tpe to pt.
194194
* Does not do any boxing/unboxing (this is handled upstream).
195+
* Casts from and to ErasedValueType are special, see the explanation
196+
* in ExtensionMethods#transform.
195197
*/
196198
def cast(tree: Tree, pt: Type)(implicit ctx: Context): Tree = {
197199
// TODO: The commented out assertion fails for tailcall/t6574.scala
@@ -203,9 +205,18 @@ object Erasure extends TypeTestsCasts{
203205
if treeElem.widen.isPrimitiveValueType && !ptElem.isPrimitiveValueType =>
204206
// See SI-2386 for one example of when this might be necessary.
205207
cast(ref(defn.runtimeMethod(nme.toObjectArray)).appliedTo(tree), pt)
208+
case (_, ErasedValueType(cls, _)) =>
209+
ref(underlying2evtSym(cls)).appliedTo(tree)
206210
case _ =>
207-
if (pt.isPrimitiveValueType) primitiveConversion(tree, pt.classSymbol)
208-
else tree.asInstance(pt)
211+
tree.tpe.widen match {
212+
case ErasedValueType(cls, _) =>
213+
ref(evt2underlyingSym(cls)).appliedTo(tree)
214+
case _ =>
215+
if (pt.isPrimitiveValueType)
216+
primitiveConversion(tree, pt.classSymbol)
217+
else
218+
tree.asInstance(pt)
219+
}
209220
}
210221
}
211222

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

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,11 @@ import core._
1414
import Phases.Phase
1515
import Types._, Contexts._, Constants._, Names._, NameOps._, Flags._, DenotTransformers._
1616
import SymDenotations._, Symbols._, StdNames._, Annotations._, Trees._, Scopes._, Denotations._
17+
import TypeErasure.{ erasure, ErasedValueType }
1718
import TypeUtils._
1819
import util.Positions._
1920
import Decorators._
21+
import SymUtils._
2022

2123
/**
2224
* Perform Step 1 and 2 in the value classes SIP: Creates extension methods for all
@@ -34,15 +36,36 @@ class ExtensionMethods extends MiniPhaseTransform with DenotTransformer with Ful
3436
override def transform(ref: SingleDenotation)(implicit ctx: Context): SingleDenotation = ref match {
3537
case ref: ClassDenotation if ref is ModuleClass =>
3638
ref.linkedClass match {
37-
// In Scala 2, extension methods are added before pickling so we should not generate them again
38-
case origClass: ClassSymbol if isDerivedValueClass(origClass) && !(origClass is Scala2x) =>
39+
case origClass: ClassSymbol if isDerivedValueClass(origClass) =>
3940
val cinfo = ref.classInfo
4041
val decls1 = cinfo.decls.cloneScope
4142
ctx.atPhase(thisTransformer.next) { implicit ctx =>
42-
for (decl <- origClass.classInfo.decls) {
43-
if (isMethodWithExtension(decl))
44-
decls1.enter(createExtensionMethod(decl, ref.symbol))
43+
// In Scala 2, extension methods are added before pickling so we should
44+
// not generate them again.
45+
if (!(origClass is Scala2x)) {
46+
for (decl <- origClass.classInfo.decls) {
47+
if (isMethodWithExtension(decl))
48+
decls1.enter(createExtensionMethod(decl, ref.symbol))
49+
}
4550
}
51+
52+
// For a value class V, let U be the underlying type after erasure. We add
53+
// to the companion object of V two cast methods:
54+
// def underlying2evt$(x0: U): ErasedValueType(V, U)
55+
// def evt2underlying$(x0: V): ErasedValueType(U, V)
56+
// The casts are used in Erasure to make it typecheck, they are then removed
57+
// in ElimErasedValueType.
58+
// This is different from the implementation of value classes in Scala 2
59+
// (see SIP-15) which used `asInstanceOf` which does not typecheck.
60+
val sym = ref.symbol
61+
val underlying = erasure(underlyingOfValueClass(origClass))
62+
val evt = ErasedValueType(origClass, underlying)
63+
val underlying2evtSym = ctx.newSymbol(sym, nme.UNDERLYING2EVT, Method,
64+
MethodType(List(nme.x_0), List(underlying), evt))
65+
val evt2underlyingSym = ctx.newSymbol(sym, nme.EVT2UNDERLYING, Method,
66+
MethodType(List(nme.x_0), List(evt), underlying))
67+
decls1.enter(underlying2evtSym)
68+
decls1.enter(evt2underlyingSym)
4669
}
4770
if (decls1.isEmpty) ref
4871
else ref.copySymDenotation(info = cinfo.derivedClassInfo(decls = decls1))

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import Symbols._
77
import SymDenotations._
88
import Contexts._
99
import Flags._
10+
import StdNames._
1011

1112
/** Methods that apply to user-defined value classes */
1213
object ValueClasses {
@@ -32,6 +33,11 @@ object ValueClasses {
3233
.map(_.symbol)
3334
.getOrElse(NoSymbol)
3435

36+
def underlying2evtSym(d: ClassDenotation)(implicit ctx: Context): Symbol =
37+
d.linkedClass.info.decl(nme.UNDERLYING2EVT).symbol
38+
def evt2underlyingSym(d: ClassDenotation)(implicit ctx: Context): Symbol =
39+
d.linkedClass.info.decl(nme.EVT2UNDERLYING).symbol
40+
3541
/** The unboxed type that underlies a derived value class */
3642
def underlyingOfValueClass(d: ClassDenotation)(implicit ctx: Context): Type =
3743
valueClassUnbox(d).info.resultType

0 commit comments

Comments
 (0)