Skip to content

Commit b736261

Browse files
committed
Erasure: properly erase value classes
There are three ways to erase a value class: - In most case, it should be semi-erased to an ErasedValueType, which will be fully erased to its underlying type in ElimErasedValueType. This corresponds to semiEraseVCs = true in TypeErasure. - In a few cases, it should be erased like a normal class, so far this seems to be necessary for: * The return type of a constructor * The underlying type of a ThisType * TypeTree nodes inside New nodes * TypeApply nodes * Arrays In these cases, we set semiEraseVCs = false - When calling `sigName` it should be erased to its underlying type. This commit implements all these cases. Note that this breaks most tests because ElimErasedValueType has not been implemented yet, it is part of the next commit.
1 parent 754e9c3 commit b736261

File tree

5 files changed

+68
-27
lines changed

5 files changed

+68
-27
lines changed

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

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1441,10 +1441,16 @@ object SymDenotations {
14411441

14421442
def inCache(tp: Type) = baseTypeRefCache.containsKey(tp)
14431443

1444-
/** Can't cache types containing type variables which are uninstantiated
1445-
* or whose instances can change, depending on typerstate.
1444+
/** We cannot cache:
1445+
* - type variables which are uninstantiated or whose instances can
1446+
* change, depending on typerstate.
1447+
* - types where the underlying type is an ErasedValueType, because
1448+
* this underlying type will change after ElimErasedValueType,
1449+
* and this changes subtyping relations. As a shortcut, we do not
1450+
* cache ErasedValueType at all.
14461451
*/
14471452
def isCachable(tp: Type): Boolean = tp match {
1453+
case _: TypeErasure.ErasedValueType => false
14481454
case tp: TypeVar => tp.inst.exists && inCache(tp.inst)
14491455
case tp: TypeProxy => inCache(tp.underlying)
14501456
case tp: AndOrType => inCache(tp.tp1) && inCache(tp.tp2)

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

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -434,14 +434,6 @@ object Symbols {
434434
/** If this symbol satisfies predicate `p` this symbol, otherwise `NoSymbol` */
435435
def filter(p: Symbol => Boolean): Symbol = if (p(this)) this else NoSymbol
436436

437-
/** Is this symbol a user-defined value class? */
438-
final def isDerivedValueClass(implicit ctx: Context): Boolean = {
439-
this.derivesFrom(defn.AnyValClass)(ctx.withPhase(denot.validFor.firstPhaseId))
440-
// Simulate ValueClasses.isDerivedValueClass
441-
false // will migrate to ValueClasses.isDerivedValueClass;
442-
// unsupported value class code will continue to use this stub while it exists
443-
}
444-
445437
/** The current name of this symbol */
446438
final def name(implicit ctx: Context): ThisName = denot.name.asInstanceOf[ThisName]
447439

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

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@ package core
55
import Symbols._, Types._, Contexts._, Flags._, Names._, StdNames._, Decorators._, Flags.JavaDefined
66
import Uniques.unique
77
import dotc.transform.ExplicitOuter._
8+
import dotc.transform.ValueClasses._
89
import typer.Mode
910
import util.DotClass
1011

1112
/** Erased types are:
1213
*
14+
* ErasedValueType
1315
* TypeRef(prefix is ignored, denot is ClassDenotation)
1416
* TermRef(prefix is ignored, denot is SymDenotation)
1517
* JavaArrayType
@@ -30,8 +32,12 @@ object TypeErasure {
3032

3133
/** A predicate that tests whether a type is a legal erased type. Only asInstanceOf and
3234
* isInstanceOf may have types that do not satisfy the predicate.
35+
* ErasedValueType is considered an erased type because it is valid after Erasure (it is
36+
* eliminated by ElimErasedValueType).
3337
*/
3438
def isErasedType(tp: Type)(implicit ctx: Context): Boolean = tp match {
39+
case _: ErasedValueType =>
40+
true
3541
case tp: TypeRef =>
3642
tp.symbol.isClass && tp.symbol != defn.AnyClass
3743
case _: TermRef =>
@@ -283,10 +289,12 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean
283289
* - For any other type, exception.
284290
*/
285291
private def apply(tp: Type)(implicit ctx: Context): Type = tp match {
292+
case _: ErasedValueType =>
293+
tp
286294
case tp: TypeRef =>
287295
val sym = tp.symbol
288296
if (!sym.isClass) this(tp.info)
289-
else if (sym.isDerivedValueClass) eraseDerivedValueClassRef(tp)
297+
else if (semiEraseVCs && isDerivedValueClass(sym)) eraseDerivedValueClassRef(tp)
290298
else eraseNormalClassRef(tp)
291299
case tp: RefinedType =>
292300
val parent = tp.parent
@@ -295,7 +303,9 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean
295303
case tp: TermRef =>
296304
this(tp.widen)
297305
case tp: ThisType =>
298-
this(tp.cls.typeRef)
306+
def thisTypeErasure(tpToErase: Type) =
307+
erasureFn(isJava, semiEraseVCs = false, isConstructor, wildcardOK)(tpToErase)
308+
thisTypeErasure(tp.cls.typeRef)
299309
case SuperType(thistpe, supertpe) =>
300310
SuperType(this(thistpe), this(supertpe))
301311
case ExprType(rt) =>
@@ -307,7 +317,8 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean
307317
case OrType(tp1, tp2) =>
308318
ctx.typeComparer.orType(this(tp1), this(tp2), erased = true)
309319
case tp: MethodType =>
310-
val paramErasure = erasureFn(tp.isJava, semiEraseVCs, isConstructor, wildcardOK)(_)
320+
def paramErasure(tpToErase: Type) =
321+
erasureFn(tp.isJava, semiEraseVCs, isConstructor, wildcardOK)(tpToErase)
311322
val formals = tp.paramTypes.mapConserve(paramErasure)
312323
eraseResult(tp.resultType) match {
313324
case rt: MethodType =>
@@ -345,9 +356,11 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean
345356

346357
private def eraseArray(tp: RefinedType)(implicit ctx: Context) = {
347358
val defn.ArrayType(elemtp) = tp
359+
def arrayErasure(tpToErase: Type) =
360+
erasureFn(isJava, semiEraseVCs = false, isConstructor, wildcardOK)(tpToErase)
348361
if (elemtp derivesFrom defn.NullClass) JavaArrayType(defn.ObjectType)
349362
else if (isUnboundedGeneric(elemtp)) defn.ObjectType
350-
else JavaArrayType(this(elemtp))
363+
else JavaArrayType(arrayErasure(elemtp))
351364
}
352365

353366
/** The erasure of a symbol's info. This is different from `apply` in the way `ExprType`s are
@@ -365,8 +378,12 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean
365378
case tp => this(tp)
366379
}
367380

368-
private def eraseDerivedValueClassRef(tref: TypeRef)(implicit ctx: Context): Type =
369-
unsupported("eraseDerivedValueClass")
381+
private def eraseDerivedValueClassRef(tref: TypeRef)(implicit ctx: Context): Type = {
382+
val cls = tref.symbol.asClass
383+
val underlying = underlyingOfValueClass(cls)
384+
ErasedValueType(cls, erasure(underlying))
385+
}
386+
370387

371388
private def eraseNormalClassRef(tref: TypeRef)(implicit ctx: Context): Type = {
372389
val cls = tref.symbol.asClass
@@ -378,7 +395,10 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean
378395
case tp: TypeRef =>
379396
val sym = tp.typeSymbol
380397
if (sym eq defn.UnitClass) sym.typeRef
381-
else if (sym.isDerivedValueClass) eraseNormalClassRef(tp)
398+
// For a value class V, "new V(x)" should have type V for type adaptation to work
399+
// correctly (see SIP-15 and [[Erasure.Boxing.adaptToType]]), so the return type of a
400+
// constructor method should not be semi-erased.
401+
else if (isConstructor && isDerivedValueClass(sym)) eraseNormalClassRef(tp)
382402
else this(tp)
383403
case RefinedType(parent, _) if !(parent isRef defn.ArrayClass) =>
384404
eraseResult(parent)
@@ -400,10 +420,12 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean
400420
* Need to ensure correspondence with erasure!
401421
*/
402422
private def sigName(tp: Type)(implicit ctx: Context): TypeName = tp match {
423+
case ErasedValueType(_, underlying) =>
424+
sigName(underlying)
403425
case tp: TypeRef =>
404426
val sym = tp.symbol
405427
if (!sym.isClass) sigName(tp.info)
406-
else if (sym.isDerivedValueClass) sigName(eraseDerivedValueClassRef(tp))
428+
else if (isDerivedValueClass(sym)) sigName(eraseDerivedValueClassRef(tp))
407429
else normalizeClass(sym.asClass).fullName.asTypeName
408430
case defn.ArrayType(elem) =>
409431
sigName(this(tp))

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

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -254,18 +254,39 @@ object Erasure extends TypeTestsCasts{
254254
class Typer extends typer.ReTyper with NoChecking {
255255
import Boxing._
256256

257-
def erasedType(tree: untpd.Tree)(implicit ctx: Context): Type = tree.typeOpt match {
258-
case tp: TermRef if tree.isTerm => erasedRef(tp)
259-
case tp => erasure(tp)
260-
}
257+
def erasedType(tree: untpd.Tree, semiEraseVCs: Boolean = true)(implicit ctx: Context): Type =
258+
tree.typeOpt match {
259+
case tp: TermRef if tree.isTerm => erasedRef(tp)
260+
case tp => erasure(tp, semiEraseVCs)
261+
}
261262

262-
override def promote(tree: untpd.Tree)(implicit ctx: Context): tree.ThisTree[Type] = {
263+
def promote(tree: untpd.Tree, semiEraseVCs: Boolean)(implicit ctx: Context): tree.ThisTree[Type] = {
263264
assert(tree.hasType)
264-
val erased = erasedType(tree)
265+
val erased = erasedType(tree, semiEraseVCs)
265266
ctx.log(s"promoting ${tree.show}: ${erased.showWithUnderlying()}")
266267
tree.withType(erased)
267268
}
268269

270+
override def promote(tree: untpd.Tree)(implicit ctx: Context): tree.ThisTree[Type] = {
271+
promote(tree, true)
272+
}
273+
274+
/** When erasing most TypeTrees we should not semi-erase value types.
275+
* This is not the case for [[DefDef#tpt]], [[ValDef#tpt]] and [[Typed#tpt]], they
276+
* are handled separately by [[typedDefDef]], [[typedValDef]] and [[typedTyped]].
277+
*/
278+
override def typedTypeTree(tree: untpd.TypeTree, pt: Type)(implicit ctx: Context): TypeTree = {
279+
promote(tree, semiEraseVCs = false)
280+
}
281+
282+
/** This override is only needed to semi-erase type ascriptions */
283+
override def typedTyped(tree: untpd.Typed, pt: Type)(implicit ctx: Context): Tree = {
284+
val Typed(expr, tpt) = tree
285+
val tpt1 = promote(tpt)
286+
val expr1 = typed(expr, tpt1.tpe)
287+
assignType(untpd.cpy.Typed(tree)(expr1, tpt1), tpt1)
288+
}
289+
269290
override def typedLiteral(tree: untpd.Literal)(implicit ctc: Context): Literal =
270291
if (tree.typeOpt.isRef(defn.UnitClass)) tree.withType(tree.typeOpt)
271292
else super.typedLiteral(tree)
@@ -330,7 +351,7 @@ object Erasure extends TypeTestsCasts{
330351
assert(sym.isConstructor, s"${sym.showLocated}")
331352
select(qual, defn.ObjectClass.info.decl(sym.name).symbol)
332353
}
333-
else if (qualIsPrimitive && !symIsPrimitive || qual.tpe.isErasedValueType)
354+
else if (qualIsPrimitive && !symIsPrimitive || qual.tpe.widenDealias.isErasedValueType)
334355
recur(box(qual))
335356
else if (!qualIsPrimitive && symIsPrimitive)
336357
recur(unbox(qual, sym.owner.typeRef))
@@ -349,7 +370,7 @@ object Erasure extends TypeTestsCasts{
349370
}
350371

351372
override def typedSelectFromTypeTree(tree: untpd.SelectFromTypeTree, pt: Type)(implicit ctx: Context) =
352-
untpd.Ident(tree.name).withPos(tree.pos).withType(erasedType(tree))
373+
untpd.Ident(tree.name).withPos(tree.pos).withType(erasedType(tree, semiEraseVCs = false))
353374

354375
override def typedThis(tree: untpd.This)(implicit ctx: Context): Tree =
355376
if (tree.symbol == ctx.owner.enclosingClass || tree.symbol.isStaticOwner) promote(tree)

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ trait TypeTestsCasts {
9393
else
9494
derivedTree(qual, defn.Any_asInstanceOf, argType)
9595
}
96-
def erasedArg = erasure(tree.args.head.tpe)
96+
def erasedArg = erasure(tree.args.head.tpe, semiEraseVCs = false)
9797
if (sym eq defn.Any_isInstanceOf)
9898
transformIsInstanceOf(qual, erasedArg)
9999
else if (sym eq defn.Any_asInstanceOf)

0 commit comments

Comments
 (0)