Skip to content

Commit f9d36a4

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 * Arrays 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 a9b7637 commit f9d36a4

File tree

6 files changed

+56
-24
lines changed

6 files changed

+56
-24
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1400,6 +1400,7 @@ object SymDenotations {
14001400
* or whose instances can change, depending on typerstate.
14011401
*/
14021402
def isCachable(tp: Type): Boolean = tp match {
1403+
case _: TypeErasure.ErasedValueType => false
14031404
case tp: TypeVar => tp.inst.exists && inCache(tp.inst)
14041405
case tp: TypeProxy => inCache(tp.underlying)
14051406
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
@@ -417,14 +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-
this.derivesFrom(defn.AnyValClass)(ctx.withPhase(denot.validFor.firstPhaseId))
423-
// Simulate ValueClasses.isDerivedValueClass
424-
false // will migrate to ValueClasses.isDerivedValueClass;
425-
// unsupported value class code will continue to use this stub while it exists
426-
}
427-
428420
/** The current name of this symbol */
429421
final def name(implicit ctx: Context): ThisName = denot.name.asInstanceOf[ThisName]
430422

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/core/TypeErasure.scala

Lines changed: 20 additions & 7 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) =>
@@ -332,9 +338,10 @@ class TypeErasure(isJava: Boolean, isSemi: Boolean, isConstructor: Boolean, wild
332338

333339
private def eraseArray(tp: RefinedType)(implicit ctx: Context) = {
334340
val defn.ArrayType(elemtp) = tp
341+
val arrayErasure = erasureFn(isJava, isSemi = false, isConstructor, wildcardOK)(_)
335342
if (elemtp derivesFrom defn.NullClass) JavaArrayType(defn.ObjectType)
336343
else if (isUnboundedGeneric(elemtp)) defn.ObjectType
337-
else JavaArrayType(this(elemtp))
344+
else JavaArrayType(arrayErasure(elemtp))
338345
}
339346

340347
/** The erasure of a symbol's info. This is different from `apply` in the way `ExprType`s are
@@ -352,8 +359,12 @@ class TypeErasure(isJava: Boolean, isSemi: Boolean, isConstructor: Boolean, wild
352359
case tp => this(tp)
353360
}
354361

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

358369
private def eraseNormalClassRef(tref: TypeRef)(implicit ctx: Context): Type = {
359370
val cls = tref.symbol.asClass
@@ -365,7 +376,7 @@ class TypeErasure(isJava: Boolean, isSemi: Boolean, isConstructor: Boolean, wild
365376
case tp: TypeRef =>
366377
val sym = tp.typeSymbol
367378
if (sym eq defn.UnitClass) sym.typeRef
368-
else if (sym.isDerivedValueClass) eraseNormalClassRef(tp)
379+
else if (isConstructor && isDerivedValueClass(sym)) eraseNormalClassRef(tp)
369380
else this(tp)
370381
case RefinedType(parent, _) if !(parent isRef defn.ArrayClass) =>
371382
eraseResult(parent)
@@ -387,10 +398,12 @@ class TypeErasure(isJava: Boolean, isSemi: Boolean, isConstructor: Boolean, wild
387398
* Need to ensure correspondence with erasure!
388399
*/
389400
private def sigName(tp: Type)(implicit ctx: Context): TypeName = tp match {
401+
case ErasedValueType(_, underlying) =>
402+
sigName(underlying)
390403
case tp: TypeRef =>
391404
val sym = tp.symbol
392405
if (!sym.isClass) sigName(tp.info)
393-
else if (sym.isDerivedValueClass) sigName(eraseDerivedValueClassRef(tp))
406+
else if (isDerivedValueClass(sym)) sigName(eraseDerivedValueClassRef(tp))
394407
else normalizeClass(sym.asClass).fullName.asTypeName
395408
case defn.ArrayType(elem) =>
396409
sigName(this(tp))

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

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -254,18 +254,31 @@ 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, isSemi: Boolean = true)(implicit ctx: Context): Type =
258+
tree.typeOpt match {
259+
case tp: TermRef if tree.isTerm => erasedRef(tp)
260+
case tp => erasure(tp, isSemi)
261+
}
261262

262-
override def promote(tree: untpd.Tree)(implicit ctx: Context): tree.ThisTree[Type] = {
263+
def promote(tree: untpd.Tree, isSemi: Boolean)(implicit ctx: Context): tree.ThisTree[Type] = {
263264
assert(tree.hasType)
264-
val erased = erasedType(tree)
265+
val erased = erasedType(tree, isSemi)
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+
override def typedTypeTree(tree: untpd.TypeTree, pt: Type)(implicit ctx: Context): TypeTree = {
275+
// TypeTree inside New nodes should not semi-erase value types.
276+
// This is not the case for DefDef.tpt and ValDef.tpt but they are already
277+
// handled separately inside typedDefDef and typedValDef.
278+
// FIXME: What about other TypeTree? Is it OK to not semi-erase them?
279+
promote(tree, isSemi = false)
280+
}
281+
269282
override def typedLiteral(tree: untpd.Literal)(implicit ctc: Context): Literal =
270283
if (tree.typeOpt.isRef(defn.UnitClass)) tree.withType(tree.typeOpt)
271284
else super.typedLiteral(tree)
@@ -330,7 +343,7 @@ object Erasure extends TypeTestsCasts{
330343
assert(sym.isConstructor, s"${sym.showLocated}")
331344
select(qual, defn.ObjectClass.info.decl(sym.name).symbol)
332345
}
333-
else if (qualIsPrimitive && !symIsPrimitive || qual.tpe.isErasedValueType)
346+
else if (qualIsPrimitive && !symIsPrimitive || qual.tpe.widen.isErasedValueType)
334347
recur(box(qual))
335348
else if (!qualIsPrimitive && symIsPrimitive)
336349
recur(unbox(qual, sym.owner.typeRef))
@@ -349,7 +362,7 @@ object Erasure extends TypeTestsCasts{
349362
}
350363

351364
override def typedSelectFromTypeTree(tree: untpd.SelectFromTypeTree, pt: Type)(implicit ctx: Context) =
352-
untpd.Ident(tree.name).withPos(tree.pos).withType(erasedType(tree))
365+
untpd.Ident(tree.name).withPos(tree.pos).withType(erasedType(tree, isSemi = false))
353366

354367
override def typedThis(tree: untpd.This)(implicit ctx: Context): Tree =
355368
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)