Skip to content

Commit

Permalink
Disallow bottom types in erased implementations
Browse files Browse the repository at this point in the history
  • Loading branch information
nicolasstucki committed Jan 14, 2022
1 parent b4eaa1b commit fcf93db
Show file tree
Hide file tree
Showing 23 changed files with 43 additions and 33 deletions.
9 changes: 9 additions & 0 deletions compiler/src/dotty/tools/dotc/transform/PostTyper.scala
Expand Up @@ -352,9 +352,11 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase
)
}
case tree: ValDef =>
checkErasedDef(tree)
val tree1 = cpy.ValDef(tree)(rhs = normalizeErasedRhs(tree.rhs, tree.symbol))
processValOrDefDef(super.transform(tree1))
case tree: DefDef =>
checkErasedDef(tree)
annotateContextResults(tree)
val tree1 = cpy.DefDef(tree)(rhs = normalizeErasedRhs(tree.rhs, tree.symbol))
processValOrDefDef(superAcc.wrapDefDef(tree1)(super.transform(tree1).asInstanceOf[DefDef]))
Expand Down Expand Up @@ -464,6 +466,13 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase
private def normalizeErasedRhs(rhs: Tree, sym: Symbol)(using Context) =
if (sym.isEffectivelyErased) dropInlines.transform(rhs) else rhs

private def checkErasedDef(tree: ValOrDefDef)(using Context): Unit =
if tree.symbol.is(Erased, butNot = Macro) then
if tree.rhs.tpe.derivesFrom(defn.NothingClass) then
report.error("`erased` definition cannot be implemented with en expression of type Nothing", tree.srcPos)
if tree.rhs.tpe.derivesFrom(defn.NullClass) then
report.error("`erased` definition cannot be implemented with en expression of type Null", tree.srcPos)

private def annotateExperimental(sym: Symbol)(using Context): Unit =
if sym.is(Module) && sym.companionClass.hasAnnotation(defn.ExperimentalAnnot) then
sym.addAnnotation(defn.ExperimentalAnnot)
Expand Down
4 changes: 2 additions & 2 deletions docs/docs/reference/experimental/canthrow.md
Expand Up @@ -120,7 +120,7 @@ catch
the compiler generates an accumulated capability of type `CanThrow[Ex1 | ... | Ex2]` that is available as a given in the scope of `body`. It does this by augmenting the `try` roughly as follows:
```scala
try
erased given CanThrow[Ex1 | ... | ExN] = ???
erased given CanThrow[Ex1 | ... | ExN] = compiletime.erasedValue
body
catch ...
```
Expand Down Expand Up @@ -196,7 +196,7 @@ Everything typechecks and works as expected. But wait - we have called `map` wit
// compiler-generated code
@main def test(xs: Double*) =
try
erased given ctl: CanThrow[LimitExceeded] = ???
erased given ctl: CanThrow[LimitExceeded] = compiletime.erasedValue
println(xs.map(x => f(x)(using ctl)).sum)
catch case ex: LimitExceeded => println("too large")
```
Expand Down
File renamed without changes.
File renamed without changes.
2 changes: 1 addition & 1 deletion tests/invalid/neg/typelevel-erased-leak.scala
@@ -1,6 +1,6 @@

object typelevel {
erased def erasedValue[T]: T = ???
erased def erasedValue[T]: T = compiletime.erasedValue
}

object Test {
Expand Down
2 changes: 1 addition & 1 deletion tests/invalid/run/Tuple.scala
Expand Up @@ -2,7 +2,7 @@ import annotation.showAsInfix

// This version of Tuple requires full retyping of untyped trees on inlining
object typelevel {
erased def erasedValue[T]: T = ???
erased def erasedValue[T]: T = compiletime.erasedValue
class Typed[T](val value: T) { type Type = T }
}

Expand Down
2 changes: 1 addition & 1 deletion tests/neg-custom-args/typeclass-derivation2.scala
Expand Up @@ -117,7 +117,7 @@ object TypeLevel {
type Subtype[t] = Type[_, t]
type Supertype[t] = Type[t, _]
type Exactly[t] = Type[t, t]
erased def typeOf[T]: Type[T, T] = ???
erased def typeOf[T]: Type[T, T] = compiletime.erasedValue
}

// An algebraic datatype
Expand Down
9 changes: 5 additions & 4 deletions tests/neg/erased-class.scala
@@ -1,9 +1,10 @@
import language.experimental.erasedDefinitions
import scala.annotation.compileTimeOnly
erased class AA
erased class BB extends AA // ok

@main def Test =
val f1: Array[AA] = ??? // error
def f2(x: Int): Array[AA] = ??? // error
def bar: AA = ??? // ok
val baz: AA = ??? // ok
val f1: Array[AA] = compiletime.erasedValue // error // error
def f2(x: Int): Array[AA] = compiletime.erasedValue // error // error
def bar: AA = compiletime.erasedValue // ok
val baz: AA = compiletime.erasedValue // ok
2 changes: 1 addition & 1 deletion tests/neg/safeThrowsStrawman.scala
Expand Up @@ -21,7 +21,7 @@ def bar: Int raises Exception =

@main def Test =
try
erased given CanThrow[Fail] = ???
erased given CanThrow[Fail] = compiletime.erasedValue
println(foo(true))
println(foo(false))
println(bar) // error
Expand Down
2 changes: 1 addition & 1 deletion tests/neg/safeThrowsStrawman2.scala
Expand Up @@ -20,7 +20,7 @@ def bar(x: Boolean)(using CanThrow[Fail]): Int =

@main def Test =
try
given ctf: CanThrow[Fail] = ???
given ctf: CanThrow[Fail] = new CanThrow[Fail]
val x = new CanThrow[Fail]() // OK, x is erased
val y: Any = new CanThrow[Fail]() // error: illegal reference to erased class CanThrow
val y2: Any = new CTF() // error: illegal reference to erased class CanThrow
Expand Down
2 changes: 1 addition & 1 deletion tests/pos-custom-args/inline-match-gadt.scala
@@ -1,6 +1,6 @@
object `inline-match-gadt` {
class Exactly[T]
erased def exactType[T]: Exactly[T] = ???
erased def exactType[T]: Exactly[T] = compiletime.erasedValue

inline def foo[T](t: T): T =
inline exactType[T] match {
Expand Down
12 changes: 6 additions & 6 deletions tests/pos-custom-args/phantom-Eq.scala
Expand Up @@ -20,12 +20,12 @@ object EqUtil {
extension [T](x: T)
def ===[U](y: U)(using erased PhantomEq[T, U]) = x.equals(y)

erased given eqString: PhantomEqEq[String] = ???
erased given eqInt: PhantomEqEq[Int] = ???
erased given eqDouble: PhantomEqEq[Double] = ???
erased given eqString: PhantomEqEq[String] = compiletime.erasedValue
erased given eqInt: PhantomEqEq[Int] = compiletime.erasedValue
erased given eqDouble: PhantomEqEq[Double] = compiletime.erasedValue

erased given eqByteNum: PhantomEq[Byte, Number] = ???
erased given eqNumByte: PhantomEq[Number, Byte] = ???
erased given eqByteNum: PhantomEq[Byte, Number] = compiletime.erasedValue
erased given eqNumByte: PhantomEq[Number, Byte] = compiletime.erasedValue

erased given eqSeq[T, U](using erased PhantomEq[T, U]): PhantomEq[Seq[T], Seq[U]] = ???
erased given eqSeq[T, U](using erased PhantomEq[T, U]): PhantomEq[Seq[T], Seq[U]] = compiletime.erasedValue
}
2 changes: 1 addition & 1 deletion tests/pos-custom-args/phantom-Evidence.scala
Expand Up @@ -24,5 +24,5 @@ object WithNormalState {

object Utils {
type =::=[From, To]
erased given tpEquals[A]: A =::= A = ???
erased given tpEquals[A]: A =::= A = compiletime.erasedValue
}
2 changes: 1 addition & 1 deletion tests/pos/i11864.scala
Expand Up @@ -40,7 +40,7 @@ final class CallbackTo[+A] {
object CallbackTo {

type MapGuard[A] = { type Out = A }
erased given MapGuard[A]: MapGuard[A] = ???
erased given MapGuard[A]: MapGuard[A] = compiletime.erasedValue

def traverse[A, B](ta: List[A]): CallbackTo[List[B]] =
val x: CallbackTo[List[A] => List[B]] = ???
Expand Down
2 changes: 1 addition & 1 deletion tests/pos/i13392.scala
Expand Up @@ -8,4 +8,4 @@ erased class CanThrow[-E <: Exception]

@experimental
object unsafeExceptions:
given canThrowAny: CanThrow[Exception] = ???
given canThrowAny: CanThrow[Exception] = new CanThrow
2 changes: 1 addition & 1 deletion tests/run-custom-args/generic-tuples.scala
Expand Up @@ -7,7 +7,7 @@ class HNil extends Tuple
case object HNil extends HNil

trait Pair[H, T <: Tuple] {
erased inline def size = ???
erased inline def size = compiletime.erasedValue
}
}

Expand Down
4 changes: 2 additions & 2 deletions tests/run-custom-args/phantom-OnHList.scala
Expand Up @@ -88,6 +88,6 @@ object Appender {

object PhantomAppender {
type Aux[L1 <: HList, L2 <: HList, O <: HList]
implicit erased def caseHNil[L <: HList]: Aux[HNil, L, L] = ???
implicit erased def caseHCons[H, T <: HList, L <: HList, O <: HList] (using erased p: Aux[T, L, O]): Aux[H :: T, L, H :: O] = ???
implicit erased def caseHNil[L <: HList]: Aux[HNil, L, L] = compiletime.erasedValue
implicit erased def caseHCons[H, T <: HList, L <: HList, O <: HList] (using erased p: Aux[T, L, O]): Aux[H :: T, L, H :: O] = compiletime.erasedValue
}
2 changes: 1 addition & 1 deletion tests/run-custom-args/typeclass-derivation2.scala
Expand Up @@ -119,7 +119,7 @@ object TypeLevel {
type Subtype[t] = Type[_, t]
type Supertype[t] = Type[t, _]
type Exactly[t] = Type[t, t]
erased def typeOf[T]: Type[T, T] = ???
erased def typeOf[T]: Type[T, T] = compiletime.erasedValue
}

// An algebraic datatype
Expand Down
4 changes: 2 additions & 2 deletions tests/run-custom-args/typeclass-derivation2c.scala
Expand Up @@ -24,12 +24,12 @@ object Deriving {
/** The number of cases in the sum.
* Implemented by an inline method in concrete subclasses.
*/
erased def numberOfCases: Int = ???
erased def numberOfCases: Int = compiletime.erasedValue

/** The Generic representations of the sum's alternatives.
* Implemented by an inline method in concrete subclasses.
*/
erased def alternative(n: Int): Generic[_ <: T] = ???
erased def alternative(n: Int): Generic[_ <: T] = compiletime.erasedValue
}

/** The Generic for a product type */
Expand Down
2 changes: 1 addition & 1 deletion tests/run-custom-args/typelevel-defaultValue.scala
@@ -1,6 +1,6 @@

object compiletime {
erased def erasedValue[T]: T = ???
erased def erasedValue[T]: T = compiletime.erasedValue
}

object Test extends App {
Expand Down
4 changes: 2 additions & 2 deletions tests/run/i13691.scala
Expand Up @@ -5,7 +5,7 @@ erased class Foo
class Bar

object unsafeExceptions:
given canThrowAny: CanThrow[Exception] = null
given canThrowAny: CanThrow[Exception] = new CanThrow

object test1:
trait Decoder[+T]:
Expand Down Expand Up @@ -44,7 +44,7 @@ object test5:

@main def Test(): Unit =
import unsafeExceptions.canThrowAny
given Foo = ???
given Foo = Foo()
given Bar = Bar()
test1.deco.apply().apply
test2.deco.apply().apply
Expand Down
4 changes: 2 additions & 2 deletions tests/run/safeThrowsStrawman.scala
Expand Up @@ -19,13 +19,13 @@ def baz: Int raises Exception = foo(false)

@main def Test =
try
given CanThrow[Fail] = ???
given CanThrow[Fail] = new CanThrow
println(foo(true))
println(foo(false))
catch case ex: Fail =>
println("failed")
try
given CanThrow[Exception] = ???
given CanThrow[Exception] = new CanThrow
println(baz)
catch case ex: Fail =>
println("failed")
2 changes: 1 addition & 1 deletion tests/run/safeThrowsStrawman2.scala
Expand Up @@ -18,7 +18,7 @@ object scalax:
def try2[R, E <: Exception](body: => R raises E)(c: E => Unit)(f: => Unit): R =
val res = new Result[R]
try
given CanThrow[E] = ???
given CanThrow[E] = new CanThrow
res.value = body
catch c.asInstanceOf[Throwable => Unit]
finally f
Expand Down

0 comments on commit fcf93db

Please sign in to comment.