Skip to content

Commit ba1af02

Browse files
committed
Erasure: properly erase value classes
There are three ways to erase a value class: - In most case, it should be erased to an ErasedValueType, which will be erased in the next phase to its underlying type by ElimErasedValueType (not yet present in this commit). This corresponds to isSemi = 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 In these cases, we set isSemi = 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 3ad9459 commit ba1af02

File tree

5 files changed

+53
-20
lines changed

5 files changed

+53
-20
lines changed

src/dotty/tools/dotc/TypeErasure.scala

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ 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

@@ -32,6 +33,8 @@ object TypeErasure {
3233
* isInstanceOf may have types that do not satisfy the predicate.
3334
*/
3435
def isErasedType(tp: Type)(implicit ctx: Context): Boolean = tp match {
36+
case _: ErasedValueType =>
37+
true
3538
case tp: TypeRef =>
3639
tp.symbol.isClass && tp.symbol != defn.AnyClass
3740
case _: TermRef =>
@@ -270,10 +273,12 @@ class TypeErasure(isJava: Boolean, isSemi: Boolean, isConstructor: Boolean, wild
270273
* - For any other type, exception.
271274
*/
272275
private def apply(tp: Type)(implicit ctx: Context): Type = tp match {
276+
case _: ErasedValueType =>
277+
tp
273278
case tp: TypeRef =>
274279
val sym = tp.symbol
275280
if (!sym.isClass) this(tp.info)
276-
else if (sym.isDerivedValueClass) eraseDerivedValueClassRef(tp)
281+
else if (isSemi && isDerivedValueClass(sym)) eraseDerivedValueClassRef(tp)
277282
else eraseNormalClassRef(tp)
278283
case tp: RefinedType =>
279284
val parent = tp.parent
@@ -282,7 +287,8 @@ class TypeErasure(isJava: Boolean, isSemi: Boolean, isConstructor: Boolean, wild
282287
case tp: TermRef =>
283288
this(tp.widen)
284289
case tp: ThisType =>
285-
this(tp.cls.typeRef)
290+
val thisTypeErasure = erasureFn(isJava, isSemi = false, isConstructor, wildcardOK)(_)
291+
thisTypeErasure(tp.cls.typeRef)
286292
case SuperType(thistpe, supertpe) =>
287293
SuperType(this(thistpe), this(supertpe))
288294
case ExprType(rt) =>
@@ -352,8 +358,12 @@ class TypeErasure(isJava: Boolean, isSemi: Boolean, isConstructor: Boolean, wild
352358
case tp => this(tp)
353359
}
354360

355-
private def eraseDerivedValueClassRef(tref: TypeRef)(implicit ctx: Context): Type =
356-
unsupported("eraseDerivedValueClass")
361+
private def eraseDerivedValueClassRef(tref: TypeRef)(implicit ctx: Context): Type = {
362+
val cls = tref.symbol.asClass
363+
val underlying = underlyingOfValueClass(cls)
364+
ErasedValueType(cls, erasure(underlying, isSemi = false))
365+
}
366+
357367

358368
private def eraseNormalClassRef(tref: TypeRef)(implicit ctx: Context): Type = {
359369
val cls = tref.symbol.asClass
@@ -365,7 +375,7 @@ class TypeErasure(isJava: Boolean, isSemi: Boolean, isConstructor: Boolean, wild
365375
case tp: TypeRef =>
366376
val sym = tp.typeSymbol
367377
if (sym eq defn.UnitClass) sym.typeRef
368-
else if (sym.isDerivedValueClass) eraseNormalClassRef(tp)
378+
else if (isConstructor && isDerivedValueClass(sym)) eraseNormalClassRef(tp)
369379
else this(tp)
370380
case RefinedType(parent, _) if !(parent isRef defn.ArrayClass) =>
371381
eraseResult(parent)
@@ -387,10 +397,12 @@ class TypeErasure(isJava: Boolean, isSemi: Boolean, isConstructor: Boolean, wild
387397
* Need to ensure correspondence with erasure!
388398
*/
389399
private def sigName(tp: Type)(implicit ctx: Context): TypeName = tp match {
400+
case ErasedValueType(_, underlying) =>
401+
sigName(underlying)
390402
case tp: TypeRef =>
391403
val sym = tp.symbol
392404
if (!sym.isClass) sigName(tp.info)
393-
else if (sym.isDerivedValueClass) sigName(eraseDerivedValueClassRef(tp))
405+
else if (isDerivedValueClass(sym)) sigName(eraseDerivedValueClassRef(tp))
394406
else normalizeClass(sym.asClass).fullName.asTypeName
395407
case defn.ArrayType(elem) =>
396408
sigName(this(tp))

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

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -417,11 +417,6 @@ object Symbols {
417417
/** If this symbol satisfies predicate `p` this symbol, otherwise `NoSymbol` */
418418
def filter(p: Symbol => Boolean): Symbol = if (p(this)) this else NoSymbol
419419

420-
/** Is this symbol a user-defined value class? */
421-
final def isDerivedValueClass(implicit ctx: Context): Boolean =
422-
false // will migrate to ValueClasses.isDerivedValueClass;
423-
// unsupported value class code will continue to use this stub while it exists
424-
425420
/** The current name of this symbol */
426421
final def name(implicit ctx: Context): ThisName = denot.name.asInstanceOf[ThisName]
427422

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,19 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling wi
139139
}
140140

141141
private def firstTry(tp1: Type, tp2: Type): Boolean = tp2 match {
142+
// FIXME: This is needed because sometimes (underlying1 ne underlying2)
143+
// but (underyling1 =:= underlying2). I don't know if there's a better
144+
// solution.
145+
case TypeErasure.ErasedValueType(cls2, underlying2) =>
146+
def compareErasedValueType = {
147+
tp1 match {
148+
case TypeErasure.ErasedValueType(cls1, underlying1) =>
149+
(cls1 eq cls2) && (underlying1 =:= underlying2)
150+
case _ =>
151+
secondTry(tp1, tp2)
152+
}
153+
}
154+
compareErasedValueType
142155
case tp2: NamedType =>
143156
def compareAlias(info1: Type) = tp2.info match {
144157
case info2: TypeAlias => isSubType(tp1, info2.alias)

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

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -247,18 +247,31 @@ object Erasure extends TypeTestsCasts{
247247
class Typer extends typer.ReTyper with NoChecking {
248248
import Boxing._
249249

250-
def erasedType(tree: untpd.Tree)(implicit ctx: Context): Type = tree.typeOpt match {
251-
case tp: TermRef if tree.isTerm => erasedRef(tp)
252-
case tp => erasure(tp)
253-
}
250+
def erasedType(tree: untpd.Tree, isSemi: Boolean = true)(implicit ctx: Context): Type =
251+
tree.typeOpt match {
252+
case tp: TermRef if tree.isTerm => erasedRef(tp)
253+
case tp => erasure(tp, isSemi)
254+
}
254255

255-
override def promote(tree: untpd.Tree)(implicit ctx: Context): tree.ThisTree[Type] = {
256+
def promote(tree: untpd.Tree, isSemi: Boolean)(implicit ctx: Context): tree.ThisTree[Type] = {
256257
assert(tree.hasType)
257-
val erased = erasedType(tree)
258+
val erased = erasedType(tree, isSemi)
258259
ctx.log(s"promoting ${tree.show}: ${erased.showWithUnderlying()}")
259260
tree.withType(erased)
260261
}
261262

263+
override def promote(tree: untpd.Tree)(implicit ctx: Context): tree.ThisTree[Type] = {
264+
promote(tree, true)
265+
}
266+
267+
override def typedTypeTree(tree: untpd.TypeTree, pt: Type)(implicit ctx: Context): TypeTree = {
268+
// TypeTree inside New nodes should not semi-erase value types.
269+
// This is not the case for DefDef.tpt and ValDef.tpt but they are already
270+
// handled separately inside typedDefDef and typedValDef.
271+
// FIXME: What about other TypeTree? Is it OK to not semi-erase them?
272+
promote(tree, isSemi = false)
273+
}
274+
262275
override def typedLiteral(tree: untpd.Literal)(implicit ctc: Context): Literal =
263276
if (tree.typeOpt.isRef(defn.UnitClass)) tree.withType(tree.typeOpt)
264277
else super.typedLiteral(tree)
@@ -323,7 +336,7 @@ object Erasure extends TypeTestsCasts{
323336
assert(sym.isConstructor, s"${sym.showLocated}")
324337
select(qual, defn.ObjectClass.info.decl(sym.name).symbol)
325338
}
326-
else if (qualIsPrimitive && !symIsPrimitive || qual.tpe.isErasedValueType)
339+
else if (qualIsPrimitive && !symIsPrimitive || qual.tpe.widen.isErasedValueType)
327340
recur(box(qual))
328341
else if (!qualIsPrimitive && symIsPrimitive)
329342
recur(unbox(qual, sym.owner.typeRef))
@@ -342,7 +355,7 @@ object Erasure extends TypeTestsCasts{
342355
}
343356

344357
override def typedSelectFromTypeTree(tree: untpd.SelectFromTypeTree, pt: Type)(implicit ctx: Context) =
345-
untpd.Ident(tree.name).withPos(tree.pos).withType(erasedType(tree))
358+
untpd.Ident(tree.name).withPos(tree.pos).withType(erasedType(tree, isSemi = false))
346359

347360
override def typedThis(tree: untpd.This)(implicit ctx: Context): Tree =
348361
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, isSemi = 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)