diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index a61e4a2c6372..9e205b6a8c01 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -3115,7 +3115,7 @@ object Parsers { case Some(prefix) => in.languageImportContext = in.languageImportContext.importContext(imp, NoSymbol) if prefix == nme.experimental - && selectors.exists(sel => Feature.experimental(sel.name) != Feature.scala2macros) + && selectors.exists(sel => Feature.experimental(sel.name) != Feature.scala2macros && Feature.experimental(sel.name) != Feature.erasedDefinitions) then Feature.checkExperimentalFeature("features", imp.srcPos) for diff --git a/compiler/src/dotty/tools/dotc/transform/PruneErasedDefs.scala b/compiler/src/dotty/tools/dotc/transform/PruneErasedDefs.scala index b8e7a7924a79..12c6477262f8 100644 --- a/compiler/src/dotty/tools/dotc/transform/PruneErasedDefs.scala +++ b/compiler/src/dotty/tools/dotc/transform/PruneErasedDefs.scala @@ -12,6 +12,8 @@ import typer.RefChecks import MegaPhase.MiniPhase import StdNames.nme import ast.tpd +import SymUtils._ +import config.Feature /** This phase makes all erased term members of classes private so that they cannot * conflict with non-erased members. This is needed so that subsequent phases like @@ -39,13 +41,22 @@ class PruneErasedDefs extends MiniPhase with SymTransformer { thisTransform => else cpy.Apply(tree)(tree.fun, tree.args.map(trivialErasedTree)) override def transformValDef(tree: ValDef)(using Context): Tree = + checkErasedInExperimental(tree.symbol) if !tree.symbol.isEffectivelyErased || tree.rhs.isEmpty then tree else cpy.ValDef(tree)(rhs = trivialErasedTree(tree.rhs)) override def transformDefDef(tree: DefDef)(using Context): Tree = + checkErasedInExperimental(tree.symbol) if !tree.symbol.isEffectivelyErased || tree.rhs.isEmpty then tree else cpy.DefDef(tree)(rhs = trivialErasedTree(tree.rhs)) + override def transformTypeDef(tree: TypeDef)(using Context): Tree = + checkErasedInExperimental(tree.symbol) + tree + + def checkErasedInExperimental(sym: Symbol)(using Context): Unit = + if sym.is(Erased) && sym != defn.Compiletime_erasedValue && !sym.isInExperimentalScope then + Feature.checkExperimentalFeature("erased", sym.sourcePos) } object PruneErasedDefs { diff --git a/docs/docs/reference/experimental/erased-defs.md b/docs/docs/reference/experimental/erased-defs.md index b2d57584a1a7..fa39f6df59dc 100644 --- a/docs/docs/reference/experimental/erased-defs.md +++ b/docs/docs/reference/experimental/erased-defs.md @@ -9,6 +9,8 @@ title: "Erased Definitions" import scala.language.experimental.erasedDefinitions ``` or by setting the command line option `-language:experimental.erasedDefinitions`. +Erased definitions must be in an experimental scope (see [../other-new-features/experimental-defs.md]). + ## Why erased terms? Let's describe the motivation behind erased terms with an example. In the diff --git a/tests/neg-custom-args/no-experimental/experimental-erased.scala b/tests/neg-custom-args/no-experimental/experimental-erased.scala index 91f84ba3f85f..c80c3e0d4b49 100644 --- a/tests/neg-custom-args/no-experimental/experimental-erased.scala +++ b/tests/neg-custom-args/no-experimental/experimental-erased.scala @@ -1,7 +1,9 @@ -import language.experimental.erasedDefinitions // error +import language.experimental.erasedDefinitions import annotation.experimental @experimental erased class CanThrow[-E <: Exception] +erased class CanThrow2[-E <: Exception] // error + def other = 1 diff --git a/tests/neg-custom-args/no-experimental/experimental.scala b/tests/neg-custom-args/no-experimental/experimental.scala index 26f9ba3d21c7..c1fb43db8455 100644 --- a/tests/neg-custom-args/no-experimental/experimental.scala +++ b/tests/neg-custom-args/no-experimental/experimental.scala @@ -7,7 +7,7 @@ class Test0 { } class Test1 { - import scala.language.experimental.erasedDefinitions // error + import scala.language.experimental.erasedDefinitions import scala.compiletime.erasedValue type UnivEq[A] object UnivEq: diff --git a/tests/neg-custom-args/no-experimental/experimentalErased.scala b/tests/neg-custom-args/no-experimental/experimentalErased.scala new file mode 100644 index 000000000000..6fcb11a3cc2f --- /dev/null +++ b/tests/neg-custom-args/no-experimental/experimentalErased.scala @@ -0,0 +1,22 @@ +import language.experimental.erasedDefinitions +import annotation.experimental + +@experimental +erased class Foo + +erased class Bar // error + +@experimental +erased def foo = 2 + +erased def bar = 2 // error + +@experimental +erased val foo2 = 2 + +erased val bar2 = 2 // error + +@experimental +def foo3(erased a: Int) = 2 + +def bar3(erased a: Int) = 2 // error diff --git a/tests/pos/experimentalErased.scala b/tests/pos/experimentalErased.scala new file mode 100644 index 000000000000..358c134c714a --- /dev/null +++ b/tests/pos/experimentalErased.scala @@ -0,0 +1,22 @@ +import language.experimental.erasedDefinitions +import annotation.experimental + +@experimental +erased class Foo + +erased class Bar + +@experimental +erased def foo = 2 + +erased def bar = 2 + +@experimental +erased val foo2 = 2 + +erased val bar2 = 2 + +@experimental +def foo3(erased a: Int) = 2 + +def bar3(erased a: Int) = 2 diff --git a/tests/pos/i13392.scala b/tests/pos/i13392.scala new file mode 100644 index 000000000000..de489b9aa75a --- /dev/null +++ b/tests/pos/i13392.scala @@ -0,0 +1,11 @@ +package scala +import language.experimental.erasedDefinitions +import annotation.{implicitNotFound, experimental} + +@experimental +@implicitNotFound("The capability to throw exception ${E} is missing.\nThe capability can be provided by one of the following:\n - A using clause `(using CanThrow[${E}])`\n - A `throws` clause in a result type such as `X throws ${E}`\n - an enclosing `try` that catches ${E}") +erased class CanThrow[-E <: Exception] + +@experimental +object unsafeExceptions: + given canThrowAny: CanThrow[Exception] = ???