From 51f60b6e47b705e0457407c7d2d9bedfb6853e85 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Fri, 14 Jan 2022 10:10:30 +0100 Subject: [PATCH] Disallow bottom types in erased implementations --- .../src/dotty/tools/dotc/transform/PostTyper.scala | 10 ++++++++++ docs/docs/reference/experimental/canthrow.md | 4 ++-- library/src/scala/CanThrow.scala | 2 +- library/src/scala/compiletime/package.scala | 2 +- .../run-custom-args/erased/erased-2.check | 0 .../run-custom-args/erased/erased-2.scala | 0 tests/invalid/neg/typelevel-erased-leak.scala | 2 +- tests/invalid/run/Tuple.scala | 2 +- tests/neg-custom-args/typeclass-derivation2.scala | 2 +- tests/neg/erased-class.scala | 9 +++++---- tests/neg/safeThrowsStrawman.scala | 2 +- tests/neg/safeThrowsStrawman2.scala | 2 +- tests/pos-custom-args/inline-match-gadt.scala | 2 +- tests/pos-custom-args/phantom-Eq.scala | 12 ++++++------ tests/pos-custom-args/phantom-Evidence.scala | 2 +- tests/pos/i11864.scala | 2 +- tests/pos/i13392.scala | 2 +- tests/run-custom-args/generic-tuples.scala | 2 +- tests/run-custom-args/phantom-OnHList.scala | 4 ++-- tests/run-custom-args/typeclass-derivation2.scala | 2 +- tests/run-custom-args/typeclass-derivation2c.scala | 4 ++-- tests/run-custom-args/typelevel-defaultValue.scala | 2 +- tests/run/i13691.scala | 4 ++-- tests/run/safeThrowsStrawman.scala | 4 ++-- tests/run/safeThrowsStrawman2.scala | 2 +- 25 files changed, 46 insertions(+), 35 deletions(-) rename tests/{ => disabled}/run-custom-args/erased/erased-2.check (100%) rename tests/{ => disabled}/run-custom-args/erased/erased-2.scala (100%) diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index 5d08921f1ea6..54bb72275921 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -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])) @@ -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) diff --git a/docs/docs/reference/experimental/canthrow.md b/docs/docs/reference/experimental/canthrow.md index b46cd8cc4876..222bc63b6739 100644 --- a/docs/docs/reference/experimental/canthrow.md +++ b/docs/docs/reference/experimental/canthrow.md @@ -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 ... ``` @@ -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") ``` diff --git a/library/src/scala/CanThrow.scala b/library/src/scala/CanThrow.scala index 3e66e42514e4..fcfd11fc9197 100644 --- a/library/src/scala/CanThrow.scala +++ b/library/src/scala/CanThrow.scala @@ -12,5 +12,5 @@ erased class CanThrow[-E <: Exception] @experimental object unsafeExceptions: - given canThrowAny: CanThrow[Exception] = ??? + given canThrowAny: CanThrow[Exception] = compiletime.erasedValue diff --git a/library/src/scala/compiletime/package.scala b/library/src/scala/compiletime/package.scala index 754ce4d0b9d1..ff00b83bcb79 100644 --- a/library/src/scala/compiletime/package.scala +++ b/library/src/scala/compiletime/package.scala @@ -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: * diff --git a/tests/run-custom-args/erased/erased-2.check b/tests/disabled/run-custom-args/erased/erased-2.check similarity index 100% rename from tests/run-custom-args/erased/erased-2.check rename to tests/disabled/run-custom-args/erased/erased-2.check diff --git a/tests/run-custom-args/erased/erased-2.scala b/tests/disabled/run-custom-args/erased/erased-2.scala similarity index 100% rename from tests/run-custom-args/erased/erased-2.scala rename to tests/disabled/run-custom-args/erased/erased-2.scala diff --git a/tests/invalid/neg/typelevel-erased-leak.scala b/tests/invalid/neg/typelevel-erased-leak.scala index ce44137109f6..4875678dbdcc 100644 --- a/tests/invalid/neg/typelevel-erased-leak.scala +++ b/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 { diff --git a/tests/invalid/run/Tuple.scala b/tests/invalid/run/Tuple.scala index 9da7e941b098..5d9f013d1f5a 100644 --- a/tests/invalid/run/Tuple.scala +++ b/tests/invalid/run/Tuple.scala @@ -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 } } diff --git a/tests/neg-custom-args/typeclass-derivation2.scala b/tests/neg-custom-args/typeclass-derivation2.scala index 75e549413027..be54d7697994 100644 --- a/tests/neg-custom-args/typeclass-derivation2.scala +++ b/tests/neg-custom-args/typeclass-derivation2.scala @@ -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 diff --git a/tests/neg/erased-class.scala b/tests/neg/erased-class.scala index 29b28d7d5275..96a1c8769bb1 100644 --- a/tests/neg/erased-class.scala +++ b/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 diff --git a/tests/neg/safeThrowsStrawman.scala b/tests/neg/safeThrowsStrawman.scala index f9b7fda118f9..bc07eb0bb3f9 100644 --- a/tests/neg/safeThrowsStrawman.scala +++ b/tests/neg/safeThrowsStrawman.scala @@ -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 diff --git a/tests/neg/safeThrowsStrawman2.scala b/tests/neg/safeThrowsStrawman2.scala index 55e5ef4aea2f..7d87baad6fa4 100644 --- a/tests/neg/safeThrowsStrawman2.scala +++ b/tests/neg/safeThrowsStrawman2.scala @@ -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 diff --git a/tests/pos-custom-args/inline-match-gadt.scala b/tests/pos-custom-args/inline-match-gadt.scala index 0f22f7b96c22..f63d0fb5b68c 100644 --- a/tests/pos-custom-args/inline-match-gadt.scala +++ b/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 { diff --git a/tests/pos-custom-args/phantom-Eq.scala b/tests/pos-custom-args/phantom-Eq.scala index 3bd16323524f..6ec5f77676ce 100644 --- a/tests/pos-custom-args/phantom-Eq.scala +++ b/tests/pos-custom-args/phantom-Eq.scala @@ -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 } diff --git a/tests/pos-custom-args/phantom-Evidence.scala b/tests/pos-custom-args/phantom-Evidence.scala index 414f96f3ef33..3a82cfe0c6e8 100644 --- a/tests/pos-custom-args/phantom-Evidence.scala +++ b/tests/pos-custom-args/phantom-Evidence.scala @@ -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 } diff --git a/tests/pos/i11864.scala b/tests/pos/i11864.scala index da7140e57b8d..4f7735f1c8c5 100644 --- a/tests/pos/i11864.scala +++ b/tests/pos/i11864.scala @@ -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]] = ??? diff --git a/tests/pos/i13392.scala b/tests/pos/i13392.scala index de489b9aa75a..614f711eebb5 100644 --- a/tests/pos/i13392.scala +++ b/tests/pos/i13392.scala @@ -8,4 +8,4 @@ erased class CanThrow[-E <: Exception] @experimental object unsafeExceptions: - given canThrowAny: CanThrow[Exception] = ??? + given canThrowAny: CanThrow[Exception] = new CanThrow diff --git a/tests/run-custom-args/generic-tuples.scala b/tests/run-custom-args/generic-tuples.scala index f134b9d864bb..71aa13427493 100644 --- a/tests/run-custom-args/generic-tuples.scala +++ b/tests/run-custom-args/generic-tuples.scala @@ -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 } } diff --git a/tests/run-custom-args/phantom-OnHList.scala b/tests/run-custom-args/phantom-OnHList.scala index 7c1ba18bbec4..81eddfe0d4a3 100644 --- a/tests/run-custom-args/phantom-OnHList.scala +++ b/tests/run-custom-args/phantom-OnHList.scala @@ -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 } diff --git a/tests/run-custom-args/typeclass-derivation2.scala b/tests/run-custom-args/typeclass-derivation2.scala index 228547bcccc1..90744d2f7ab6 100644 --- a/tests/run-custom-args/typeclass-derivation2.scala +++ b/tests/run-custom-args/typeclass-derivation2.scala @@ -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 diff --git a/tests/run-custom-args/typeclass-derivation2c.scala b/tests/run-custom-args/typeclass-derivation2c.scala index ddf4d62e0498..f7eee2a3fafe 100644 --- a/tests/run-custom-args/typeclass-derivation2c.scala +++ b/tests/run-custom-args/typeclass-derivation2c.scala @@ -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 */ diff --git a/tests/run-custom-args/typelevel-defaultValue.scala b/tests/run-custom-args/typelevel-defaultValue.scala index 7c3b0af46d25..a171bf8a153c 100644 --- a/tests/run-custom-args/typelevel-defaultValue.scala +++ b/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 { diff --git a/tests/run/i13691.scala b/tests/run/i13691.scala index 6db01ee0de35..224656d87923 100644 --- a/tests/run/i13691.scala +++ b/tests/run/i13691.scala @@ -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]: @@ -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 diff --git a/tests/run/safeThrowsStrawman.scala b/tests/run/safeThrowsStrawman.scala index d9bd4dc227e6..973c9d8f5137 100644 --- a/tests/run/safeThrowsStrawman.scala +++ b/tests/run/safeThrowsStrawman.scala @@ -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") diff --git a/tests/run/safeThrowsStrawman2.scala b/tests/run/safeThrowsStrawman2.scala index d7af087a1690..1c84d84babc7 100644 --- a/tests/run/safeThrowsStrawman2.scala +++ b/tests/run/safeThrowsStrawman2.scala @@ -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