Skip to content

Commit 1558591

Browse files
committed
Semi-erase classes
Map erased classes to empty interfaces.
1 parent 799f6e4 commit 1558591

File tree

5 files changed

+68
-10
lines changed

5 files changed

+68
-10
lines changed

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

Lines changed: 42 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import core.Names._
1313
import core.StdNames._
1414
import core.NameOps._
1515
import core.NameKinds.{AdaptedClosureName, BodyRetainerName}
16+
import core.Scopes.newScopeWith
1617
import core.Decorators._
1718
import core.Constants._
1819
import core.Definitions._
@@ -81,16 +82,22 @@ class Erasure extends Phase with DenotTransformer {
8182
val oldName = ref.name
8283
val newName = ref.targetName
8384
val oldInfo = ref.info
84-
val newInfo = transformInfo(oldSymbol, oldInfo)
85+
var newInfo = transformInfo(oldSymbol, oldInfo)
8586
val oldFlags = ref.flags
8687
var newFlags =
87-
if (oldSymbol.is(Flags.TermParam) && isCompacted(oldSymbol.owner.denot)) oldFlags &~ Flags.Param
88+
if oldSymbol.is(Flags.TermParam) && isCompacted(oldSymbol.owner.denot) then oldFlags &~ Flags.Param
8889
else oldFlags
8990
val oldAnnotations = ref.annotations
9091
var newAnnotations = oldAnnotations
9192
if oldSymbol.isRetainedInlineMethod then
9293
newFlags = newFlags &~ Flags.Inline
9394
newAnnotations = newAnnotations.filterConserve(!_.isInstanceOf[BodyAnnotation])
95+
oldSymbol match
96+
case cls: ClassSymbol if cls.is(Flags.Erased) =>
97+
newFlags = newFlags | Flags.Trait | Flags.JavaInterface
98+
newAnnotations = Nil
99+
newInfo = erasedClassInfo(cls)
100+
case _ =>
94101
// TODO: define derivedSymDenotation?
95102
if ref.is(Flags.PackageClass)
96103
|| !ref.isClass // non-package classes are always copied since their base types change
@@ -125,6 +132,12 @@ class Erasure extends Phase with DenotTransformer {
125132
unit.tpdTree = eraser.typedExpr(unit.tpdTree)(using ctx.fresh.setTyper(eraser).setPhase(this.next))
126133
}
127134

135+
/** erased classes get erased to empty traits with Object as parent and an empty constructor */
136+
private def erasedClassInfo(cls: ClassSymbol)(using Context) =
137+
cls.classInfo.derivedClassInfo(
138+
declaredParents = defn.ObjectClass.typeRef :: Nil,
139+
decls = newScopeWith(newConstructor(cls, Flags.EmptyFlags, Nil, Nil)))
140+
128141
override def checkPostCondition(tree: tpd.Tree)(using Context): Unit = {
129142
assertErased(tree)
130143
tree match {
@@ -587,15 +600,30 @@ object Erasure {
587600
checkNotErasedClass(tree)
588601
}
589602

603+
private def checkNotErasedClass(tp: Type, tree: untpd.Tree)(using Context): Unit = tp match
604+
case JavaArrayType(et) =>
605+
checkNotErasedClass(et, tree)
606+
case _ =>
607+
if tp.isErasedClass then
608+
val (kind, tree1) = tree match
609+
case tree: untpd.ValOrDefDef => ("definition", tree.tpt)
610+
case tree: untpd.DefTree => ("definition", tree)
611+
case _ => ("expression", tree)
612+
report.error(em"illegal reference to erased ${tp.typeSymbol} in $kind that is not itself erased", tree1.srcPos)
613+
590614
private def checkNotErasedClass(tree: Tree)(using Context): tree.type =
591-
if tree.tpe.widen.isErasedClass then
592-
report.error(em"illegal reference to erased ${tree.tpe.widen.typeSymbol} in expression that is not itself erased", tree.srcPos)
615+
checkNotErasedClass(tree.tpe.widen.finalResultType, tree)
593616
tree
594617

595-
def erasedDef(sym: Symbol)(using Context): Thicket = {
596-
if (sym.owner.isClass) sym.dropAfter(erasurePhase)
597-
tpd.EmptyTree
598-
}
618+
def erasedDef(sym: Symbol)(using Context): Tree =
619+
if sym.isClass then
620+
// We cannot simply drop erased classes, since then they would not generate classfiles
621+
// and would not be visible under separate compilation. So we transform them to
622+
// empty interfaces instead.
623+
tpd.ClassDef(sym.asClass, DefDef(sym.primaryConstructor.asTerm), Nil)
624+
else
625+
if sym.owner.isClass then sym.dropAfter(erasurePhase)
626+
tpd.EmptyTree
599627

600628
def erasedType(tree: untpd.Tree)(using Context): Type = {
601629
val tp = tree.typeOpt
@@ -874,6 +902,7 @@ object Erasure {
874902
override def typedValDef(vdef: untpd.ValDef, sym: Symbol)(using Context): Tree =
875903
if (sym.isEffectivelyErased) erasedDef(sym)
876904
else
905+
checkNotErasedClass(sym.info, vdef)
877906
super.typedValDef(untpd.cpy.ValDef(vdef)(
878907
tpt = untpd.TypedSplice(TypeTree(sym.info).withSpan(vdef.tpt.span))), sym)
879908

@@ -885,6 +914,7 @@ object Erasure {
885914
if sym.isEffectivelyErased || sym.name.is(BodyRetainerName) then
886915
erasedDef(sym)
887916
else
917+
checkNotErasedClass(sym.info.finalResultType, ddef)
888918
val restpe = if sym.isConstructor then defn.UnitType else sym.info.resultType
889919
var vparams = outerParamDefs(sym)
890920
::: ddef.paramss.collect {
@@ -1021,8 +1051,10 @@ object Erasure {
10211051
if mbr.is(ConstructorProxy) then mbr.dropAfter(erasurePhase)
10221052

10231053
override def typedClassDef(cdef: untpd.TypeDef, cls: ClassSymbol)(using Context): Tree =
1024-
try super.typedClassDef(cdef, cls)
1025-
finally dropConstructorProxies(cls)
1054+
if cls.is(Flags.Erased) then erasedDef(cls)
1055+
else
1056+
try super.typedClassDef(cdef, cls)
1057+
finally dropConstructorProxies(cls)
10261058

10271059
override def typedAnnotated(tree: untpd.Annotated, pt: Type)(using Context): Tree =
10281060
typed(tree.arg, pt)

tests/neg/erased-class.scala

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import language.experimental.erasedTerms
2+
erased class AA
3+
erased class BB extends AA // ok
4+
5+
@main def Test =
6+
val f1: Array[AA] = ??? // error
7+
def f2(x: Int): Array[AA] = ??? // error
8+
def bar: AA = ??? // ok
9+
val baz: AA = ??? // ok

tests/neg/erased-inheritance.scala

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import language.experimental.erasedTerms
2+
erased class A
3+
erased class B extends A // ok
4+
5+
class C extends A // error
6+
7+
erased trait D
8+
9+
val x = new A{} // ok, x is erased
10+
val y = new C with D{} // error
11+
12+
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import language.experimental.erasedTerms
2+
erased class A
3+
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
import language.experimental.erasedTerms
2+
erased val x: A = A()

0 commit comments

Comments
 (0)