Skip to content

Commit 78f8154

Browse files
committed
Check that no references to erased classes survive
1 parent 2fd5991 commit 78f8154

File tree

5 files changed

+50
-9
lines changed

5 files changed

+50
-9
lines changed

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

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -584,9 +584,14 @@ object Erasure {
584584
case _ => // OK
585585
}
586586
}
587-
tree
587+
checkNotErasedClass(tree)
588588
}
589589

590+
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)
593+
tree
594+
590595
def erasedDef(sym: Symbol)(using Context): Thicket = {
591596
if (sym.owner.isClass) sym.dropAfter(erasurePhase)
592597
tpd.EmptyTree
@@ -609,7 +614,7 @@ object Erasure {
609614
* are handled separately by [[typedDefDef]], [[typedValDef]] and [[typedTyped]].
610615
*/
611616
override def typedTypeTree(tree: untpd.TypeTree, pt: Type)(using Context): TypeTree =
612-
tree.withType(erasure(tree.tpe))
617+
checkNotErasedClass(tree.withType(erasure(tree.tpe)))
613618

614619
/** This override is only needed to semi-erase type ascriptions */
615620
override def typedTyped(tree: untpd.Typed, pt: Type)(using Context): Tree =
@@ -628,7 +633,7 @@ object Erasure {
628633
if (tree.typeOpt.isRef(defn.UnitClass))
629634
tree.withType(tree.typeOpt)
630635
else if (tree.const.tag == Constants.ClazzTag)
631-
clsOf(tree.const.typeValue)
636+
checkNotErasedClass(clsOf(tree.const.typeValue))
632637
else
633638
super.typedLiteral(tree)
634639

@@ -996,6 +1001,9 @@ object Erasure {
9961001
adaptClosure(implClosure)
9971002
}
9981003

1004+
override def typedNew(tree: untpd.New, pt: Type)(using Context): Tree =
1005+
checkNotErasedClass(super.typedNew(tree, pt))
1006+
9991007
override def typedTypeDef(tdef: untpd.TypeDef, sym: Symbol)(using Context): Tree =
10001008
EmptyTree
10011009

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ object TypeUtils {
2121
def isPrimitiveValueType(using Context): Boolean =
2222
self.classSymbol.isPrimitiveValueClass
2323

24+
def isErasedClass(using Context): Boolean =
25+
self.underlyingClassRef(refinementOK = true).typeSymbol.is(Flags.Erased)
26+
2427
def isByName: Boolean =
2528
self.isInstanceOf[ExprType]
2629

compiler/src/dotty/tools/dotc/typer/Typer.scala

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1156,7 +1156,7 @@ class Typer extends Namer
11561156
case AppliedTypeTree(tycon: TypeTree, args)
11571157
if !isErased
11581158
&& numArgs > 0
1159-
&& args.indexWhere(arg => !isErasedClass(arg.tpe)) == numArgs =>
1159+
&& args.indexWhere(!_.tpe.isErasedClass) == numArgs =>
11601160
val tycon1 = TypeTree(defn.FunctionClass(numArgs, isContextual, isErased = true).typeRef)
11611161
.withSpan(tycon.span)
11621162
assignType(cpy.AppliedTypeTree(app)(tycon1, args), tycon1, args)
@@ -2172,16 +2172,13 @@ class Typer extends Namer
21722172
//todo: make sure dependent method types do not depend on implicits or by-name params
21732173
}
21742174

2175-
private def isErasedClass(tpe: Type)(using Context): Boolean =
2176-
tpe.underlyingClassRef(refinementOK = true).typeSymbol.is(Erased)
2177-
21782175
/** (1) Check that the signature of the class mamber does not return a repeated parameter type
21792176
* (2) If info is an erased class, set erased flag of member
21802177
*/
21812178
private def postProcessInfo(sym: Symbol)(using Context): Unit =
21822179
if (!sym.isOneOf(Synthetic | InlineProxy | Param) && sym.info.finalResultType.isRepeatedParam)
21832180
report.error(em"Cannot return repeated parameter type ${sym.info.finalResultType}", sym.srcPos)
2184-
if !sym.is(Module) && isErasedClass(sym.info) then
2181+
if !sym.is(Module) && sym.info.isErasedClass then
21852182
sym.setFlag(Erased)
21862183

21872184
def typedTypeDef(tdef: untpd.TypeDef, sym: Symbol)(using Context): Tree = {

tests/neg/safeThrowsStrawman2.scala

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import language.experimental.erasedTerms
2+
3+
object scalax:
4+
erased class CanThrow[E <: Exception]
5+
type CTF = CanThrow[Fail]
6+
7+
infix type throws[R, E <: Exception] = CanThrow[E] ?=> R
8+
9+
class Fail extends Exception
10+
11+
def raise[E <: Exception](e: E): Nothing throws E = throw e
12+
13+
import scalax._
14+
15+
def foo(x: Boolean, y: CanThrow[Fail]): Int throws Fail =
16+
if x then 1 else raise(Fail())
17+
18+
def bar(x: Boolean)(using CanThrow[Fail]): Int =
19+
if x then 1 else raise(Fail())
20+
21+
@main def Test =
22+
try
23+
given ctf: CanThrow[Fail] = ???
24+
val x = CanThrow[Fail]() // OK, x is erased
25+
val y: Any = CanThrow[Fail]() // error: illegal reference to erased class CanThrow
26+
val y2: Any = new CTF() // error: illegal reference to erased class CanThrow
27+
println(foo(true, ctf)) // error: ctf is declared as erased, but is in fact used
28+
val a = (1, CanThrow[Fail]()) // error: illegal reference to erased class CanThrow
29+
def b: (Int, CanThrow[Fail]) = ???
30+
def c = b._2 // ok; we only check creation sites
31+
bar(true)(using ctf)
32+
catch case ex: Fail =>
33+
println("failed")

tests/run/safeThrowsStrawman.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ def bar(x: Boolean)(using CanThrow[Fail]): Int =
1919

2020
@main def Test =
2121
try
22-
erased given CanThrow[Fail] = ???
22+
given CanThrow[Fail] = ???
2323
println(foo(true))
2424
println(foo(false))
2525
catch case ex: Fail =>

0 commit comments

Comments
 (0)