Skip to content

Disallow bottom types in erased implementations #14267

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions compiler/src/dotty/tools/dotc/transform/PostTyper.scala
Original file line number Diff line number Diff line change
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,14 @@ 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
val tpe = tree.rhs.tpe
if tpe.derivesFrom(defn.NothingClass) then
report.error("`erased` definition cannot be implemented with en expression of type Nothing", tree.srcPos)
else if 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 compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1765,8 +1765,8 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
untpd.ValDef(
EvidenceParamName.fresh(),
untpd.TypeTree(defn.CanThrowClass.typeRef.appliedTo(tp)),
untpd.ref(defn.Predef_undefined))
.withFlags(Given | Final | Lazy | Erased)
untpd.ref(defn.Compiletime_erasedValue))
.withFlags(Given | Final | Erased)
.withSpan(expr.span)
val caughtExceptions =
if Feature.enabled(Feature.saferExceptions) then
Expand Down
4 changes: 2 additions & 2 deletions docs/docs/reference/experimental/canthrow.md
Original file line number Diff line number Diff line change
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
2 changes: 1 addition & 1 deletion library/src/scala/CanThrow.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@ erased class CanThrow[-E <: Exception]

@experimental
object unsafeExceptions:
given canThrowAny: CanThrow[Exception] = ???
given canThrowAny: CanThrow[Exception] = compiletime.erasedValue

2 changes: 1 addition & 1 deletion library/src/scala/compiletime/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import annotation.compileTimeOnly
* @syntax markdown
*/
// TODO add `erased` once it is not an experimental feature anymore
def erasedValue[T]: T = ???
def erasedValue[T]: T = erasedValue[T]

/** Used as the initializer of a mutable class or object field, like this:
*
Expand Down
2 changes: 1 addition & 1 deletion tests/invalid/neg/typelevel-erased-leak.scala
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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