Skip to content

Commit 6a12e15

Browse files
committed
Add @experimental annotation
The `@exerimental` annotation marks definitions as _experimental_ feature. These can be used in the same situattions where `languange.experimental` can be used.
1 parent 090b1b1 commit 6a12e15

20 files changed

+239
-22
lines changed

compiler/src/dotty/tools/dotc/config/Feature.scala

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -99,20 +99,24 @@ object Feature:
9999

100100
private val assumeExperimentalIn = Set("dotty.tools.vulpix.ParallelTesting")
101101

102-
def checkExperimentalFeature(which: String, srcPos: SrcPos = NoSourcePosition)(using Context) =
103-
def hasSpecialPermission =
104-
new Exception().getStackTrace.exists(elem =>
105-
assumeExperimentalIn.exists(elem.getClassName().startsWith(_)))
106-
if !(Properties.experimental || hasSpecialPermission)
107-
|| ctx.settings.YnoExperimental.value
108-
then
109-
//println(i"${new Exception().getStackTrace.map(_.getClassName).toList}%\n%")
110-
report.error(i"Experimental feature$which may only be used with nightly or snapshot version of compiler", srcPos)
102+
def checkExperimentalFeature(which: String, srcPos: SrcPos)(using Context) =
103+
if !isExperimentalEnabled then
104+
report.error(i"Experimental $which may only be used with nightly or snapshot version of compiler", srcPos)
105+
106+
def checkExperimentalDef(sym: Symbol, srcPos: SrcPos)(using Context) =
107+
if !isExperimentalEnabled then
108+
report.error(i"Experimental $sym may only be used with nightly or snapshot version of compiler", srcPos)
111109

112110
/** Check that experimental compiler options are only set for snapshot or nightly compiler versions. */
113111
def checkExperimentalSettings(using Context): Unit =
114112
for setting <- ctx.settings.language.value
115113
if setting.startsWith("experimental.") && setting != "experimental.macros"
116-
do checkExperimentalFeature(s" $setting")
114+
do checkExperimentalFeature(s"feature $setting", NoSourcePosition)
115+
116+
def isExperimentalEnabled(using Context): Boolean =
117+
def hasSpecialPermission =
118+
Thread.currentThread.getStackTrace.exists(elem =>
119+
assumeExperimentalIn.exists(elem.getClassName().startsWith(_)))
120+
(Properties.experimental || hasSpecialPermission) && !ctx.settings.YnoExperimental.value
117121

118122
end Feature

compiler/src/dotty/tools/dotc/core/Definitions.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -910,6 +910,7 @@ class Definitions {
910910
@tu lazy val ConstructorOnlyAnnot: ClassSymbol = requiredClass("scala.annotation.constructorOnly")
911911
@tu lazy val CompileTimeOnlyAnnot: ClassSymbol = requiredClass("scala.annotation.compileTimeOnly")
912912
@tu lazy val SwitchAnnot: ClassSymbol = requiredClass("scala.annotation.switch")
913+
@tu lazy val ExperimentalAnnot: ClassSymbol = requiredClass("scala.annotation.experimental")
913914
@tu lazy val ThrowsAnnot: ClassSymbol = requiredClass("scala.throws")
914915
@tu lazy val TransientAnnot: ClassSymbol = requiredClass("scala.transient")
915916
@tu lazy val UncheckedAnnot: ClassSymbol = requiredClass("scala.unchecked")

compiler/src/dotty/tools/dotc/parsing/Parsers.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3083,7 +3083,7 @@ object Parsers {
30833083
if prefix == nme.experimental
30843084
&& selectors.exists(sel => Feature.experimental(sel.name) != Feature.scala2macros)
30853085
then
3086-
Feature.checkExperimentalFeature("s", imp.srcPos)
3086+
Feature.checkExperimentalFeature("features", imp.srcPos)
30873087
for
30883088
case ImportSelector(id @ Ident(imported), EmptyTree, _) <- selectors
30893089
if allSourceVersionNames.contains(imported)

compiler/src/dotty/tools/dotc/plugins/Plugins.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package plugins
33

44
import core._
55
import Contexts._
6-
import config.{ PathResolver, Properties }
6+
import config.{ PathResolver, Feature }
77
import dotty.tools.io._
88
import Phases._
99
import config.Printers.plugins.{ println => debug }
@@ -125,7 +125,7 @@ trait Plugins {
125125
val updatedPlan = Plugins.schedule(plan, pluginPhases)
126126

127127
// add research plugins
128-
if (Properties.experimental)
128+
if (Feature.isExperimentalEnabled)
129129
plugins.collect { case p: ResearchPlugin => p }.foldRight(updatedPlan) {
130130
(plug, plan) => plug.init(options(plug), plan)
131131
}

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

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import Symbols._, SymUtils._, NameOps._
1414
import ContextFunctionResults.annotateContextResults
1515
import config.Printers.typr
1616
import reporting._
17+
import util.Experimental
18+
1719

1820
object PostTyper {
1921
val name: String = "posttyper"
@@ -257,15 +259,19 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase
257259

258260
override def transform(tree: Tree)(using Context): Tree =
259261
try tree match {
260-
case tree: Ident if !tree.isType =>
261-
if tree.symbol.is(Inline) && !Inliner.inInlineMethod then
262-
ctx.compilationUnit.needsInlining = true
263-
checkNoConstructorProxy(tree)
264-
tree.tpe match {
265-
case tpe: ThisType => This(tpe.cls).withSpan(tree.span)
266-
case _ => tree
267-
}
262+
case tree: Ident =>
263+
Experimental.checkExperimental(tree)
264+
if tree.isType then super.transform(tree)
265+
else
266+
if tree.symbol.is(Inline) && !Inliner.inInlineMethod then
267+
ctx.compilationUnit.needsInlining = true
268+
checkNoConstructorProxy(tree)
269+
tree.tpe match {
270+
case tpe: ThisType => This(tpe.cls).withSpan(tree.span)
271+
case _ => tree
272+
}
268273
case tree @ Select(qual, name) =>
274+
Experimental.checkExperimental(tree)
269275
if tree.symbol.is(Inline) then
270276
ctx.compilationUnit.needsInlining = true
271277
if (name.isTypeName) {
@@ -382,6 +388,7 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase
382388
Checking.checkRealizable(ref.tpe, ref.srcPos)
383389
super.transform(tree)
384390
case tree: TypeTree =>
391+
Experimental.checkExperimental(tree)
385392
tree.withType(
386393
tree.tpe match {
387394
case AnnotatedType(tpe, annot) => AnnotatedType(tpe, transformAnnot(annot))

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,13 @@ object SymUtils:
259259
&& self.owner.linkedClass.is(Case)
260260
&& self.owner.linkedClass.isDeclaredInfix
261261

262+
/** Is symbol declared experimental? */
263+
def isExperimental(using Context): Boolean =
264+
(self eq defn.ExperimentalAnnot)
265+
|| self.hasAnnotation(defn.ExperimentalAnnot)
266+
|| self.allOverriddenSymbols.nonEmpty && self.allOverriddenSymbols.forall(_.hasAnnotation(defn.ExperimentalAnnot)) // TODO infer @experimental?
267+
|| (self.maybeOwner.exists && self.owner.isClass && self.owner.hasAnnotation(defn.ExperimentalAnnot)) // TODO infer @experimental?
268+
262269
/** The declared self type of this class, as seen from `site`, stripping
263270
* all refinements for opaque types.
264271
*/

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -422,6 +422,14 @@ object Checking {
422422
}
423423
}
424424

425+
/** Check that classes extending experimental classes have the @experimental annotation */
426+
def checkExperimentalInheritance(cls: ClassSymbol, parents: List[Type], srcPos: SrcPos)(using Context): Unit =
427+
if !cls.hasAnnotation(defn.ExperimentalAnnot) then
428+
parents.find(_.typeSymbol.isExperimental) match
429+
case Some(parent) =>
430+
report.error(em"extension of experimental ${parent.typeSymbol} must have @experimental annotation", srcPos)
431+
case _ =>
432+
425433
/** Check that symbol's definition is well-formed. */
426434
def checkWellFormed(sym: Symbol)(using Context): Unit = {
427435
def fail(msg: Message) = report.error(msg, sym.srcPos)

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import Annotations.Annotation
2121
import SymDenotations.SymDenotation
2222
import Inferencing.isFullyDefined
2323
import config.Printers.inlining
24+
import config.Feature
2425
import ErrorReporting.errorTree
2526
import dotty.tools.dotc.util.{SimpleIdentityMap, SimpleIdentitySet, EqHashMap, SourceFile, SourcePosition, SrcPos}
2627
import dotty.tools.dotc.parsing.Parsers.Parser
@@ -92,6 +93,7 @@ object Inliner {
9293
if (tree.symbol == defn.CompiletimeTesting_typeChecks) return Intrinsics.typeChecks(tree)
9394
if (tree.symbol == defn.CompiletimeTesting_typeCheckErrors) return Intrinsics.typeCheckErrors(tree)
9495

96+
Feature.checkExperimentalDef(tree.symbol, tree)
9597

9698
/** Set the position of all trees logically contained in the expansion of
9799
* inlined call `call` to the position of `call`. This transform is necessary

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,7 @@ object RefChecks {
210210
* 1.9. If M is erased, O is erased. If O is erased, M is erased or inline.
211211
* 1.10. If O is inline (and deferred, otherwise O would be final), M must be inline
212212
* 1.11. If O is a Scala-2 macro, M must be a Scala-2 macro.
213+
* 1.12. If O is non-experimental, M must be non-experimental.
213214
* 2. Check that only abstract classes have deferred members
214215
* 3. Check that concrete classes do not have deferred definitions
215216
* that are not implemented in a subclass.
@@ -475,6 +476,8 @@ object RefChecks {
475476
overrideError(i"needs to be declared with @targetName(${"\""}${other.targetName}${"\""}) so that external names match")
476477
else
477478
overrideError("cannot have a @targetName annotation since external names would be different")
479+
else if !other.isExperimental && member.hasAnnotation(defn.ExperimentalAnnot) then // (1.12)
480+
overrideError("may not override non-experimental member")
478481
else
479482
checkOverrideDeprecated()
480483
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2352,6 +2352,7 @@ class Typer extends Namer
23522352
}
23532353

23542354
checkNonCyclicInherited(cls.thisType, cls.info.parents, cls.info.decls, cdef.srcPos)
2355+
checkExperimentalInheritance(cls, cls.info.parents, cdef.srcPos)
23552356

23562357
// check value class constraints
23572358
checkDerivedValueClass(cls, body1)
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package dotty.tools.dotc
2+
package util
3+
4+
import dotty.tools.dotc.ast.tpd
5+
import dotty.tools.dotc.ast.Trees._
6+
import dotty.tools.dotc.config.Feature
7+
import dotty.tools.dotc.core.Contexts._
8+
import dotty.tools.dotc.core.Symbols._
9+
import dotty.tools.dotc.core.Types._
10+
import dotty.tools.dotc.core.Flags._
11+
import dotty.tools.dotc.transform.SymUtils._
12+
13+
object Experimental:
14+
import tpd._
15+
16+
def checkExperimental(tree: Tree)(using Context): Unit =
17+
if tree.symbol.isExperimental
18+
&& !tree.symbol.isConstructor // already reported on the class
19+
&& !tree.symbol.is(ModuleClass) // already reported on the module
20+
&& (tree.span.exists || tree.symbol != defn.ExperimentalAnnot) // already reported on inferred annotations
21+
then
22+
Feature.checkExperimentalDef(tree.symbol, tree)
23+
24+
def checkExperimentalTypes(tree: Tree)(using Context): Unit =
25+
val checker = new TypeTraverser:
26+
def traverse(tp: Type): Unit =
27+
if tp.typeSymbol.isExperimental then
28+
Feature.checkExperimentalDef(tp.typeSymbol, tree)
29+
else
30+
traverseChildren(tp)
31+
if !tree.span.isSynthetic then // avoid double errors
32+
checker.traverse(tree.tpe)

compiler/test/dotty/tools/dotc/CompilationTests.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ class CompilationTests {
240240
Properties.compilerInterface, Properties.scalaLibrary, Properties.scalaAsm,
241241
Properties.dottyInterfaces, Properties.jlineTerminal, Properties.jlineReader,
242242
).mkString(File.pathSeparator),
243-
Array("-Ycheck-reentrant", "-language:postfixOps", "-Xsemanticdb", "-Yno-experimental")
243+
Array("-Ycheck-reentrant", "-language:postfixOps", "-Xsemanticdb")
244244
)
245245

246246
val libraryDirs = List(Paths.get("library/src"), Paths.get("library/src-bootstrapped"))
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package scala.annotation
2+
3+
/** An annotation that can be used to mark a definition as experimental.
4+
*
5+
* This class is experimental as well as if it was defined as
6+
* ```scala
7+
* @experimental
8+
* class experimental extends StaticAnnotation
9+
* ```
10+
*
11+
* @syntax markdown
12+
*/
13+
// @experimental
14+
class experimental extends StaticAnnotation

library/src/scala/runtime/stdLibPatches/language.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ object language:
1818
*
1919
* @group experimental
2020
*/
21+
@scala.annotation.experimental
2122
object experimental:
2223

2324
/* Experimental support for richer dependent types (disabled for now)

library/src/scala/util/FromDigits.scala

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@ package scala.util
22
import scala.math.{BigInt}
33
import quoted._
44
import annotation.internal.sharable
5+
import annotation.experimental
6+
57

68
/** A type class for types that admit numeric literals.
79
*/
10+
@experimental
811
trait FromDigits[T] {
912

1013
/** Convert `digits` string to value of type `T`
@@ -20,11 +23,13 @@ trait FromDigits[T] {
2023
def fromDigits(digits: String): T
2124
}
2225

26+
@experimental
2327
object FromDigits {
2428

2529
/** A subclass of `FromDigits` that also allows to convert whole number literals
2630
* with a radix other than 10
2731
*/
32+
@experimental
2833
trait WithRadix[T] extends FromDigits[T] {
2934
def fromDigits(digits: String): T = fromDigits(digits, 10)
3035

@@ -37,28 +42,34 @@ object FromDigits {
3742
/** A subclass of `FromDigits` that also allows to convert number
3843
* literals containing a decimal point ".".
3944
*/
45+
@experimental
4046
trait Decimal[T] extends FromDigits[T]
4147

4248
/** A subclass of `FromDigits`that allows also to convert number
4349
* literals containing a decimal point "." or an
4450
* exponent `('e' | 'E')['+' | '-']digit digit*`.
4551
*/
52+
@experimental
4653
trait Floating[T] extends Decimal[T]
4754

4855
/** The base type for exceptions that can be thrown from
4956
* `fromDigits` conversions
5057
*/
58+
@experimental
5159
abstract class FromDigitsException(msg: String) extends NumberFormatException(msg)
5260

5361
/** Thrown if value of result does not fit into result type's range */
62+
@experimental
5463
class NumberTooLarge(msg: String = "number too large") extends FromDigitsException(msg)
5564

5665
/** Thrown in case of numeric underflow (e.g. a non-zero
5766
* floating point literal that produces a zero value)
5867
*/
68+
@experimental
5969
class NumberTooSmall(msg: String = "number too small") extends FromDigitsException(msg)
6070

6171
/** Thrown if digit string is not legal for the given type */
72+
@experimental
6273
class MalformedNumber(msg: String = "malformed number literal") extends FromDigitsException(msg)
6374

6475
/** Convert digits and radix to integer value (either int or Long)
@@ -156,10 +167,12 @@ object FromDigits {
156167
x
157168
}
158169

170+
@experimental
159171
given BigIntFromDigits: WithRadix[BigInt] with {
160172
def fromDigits(digits: String, radix: Int): BigInt = BigInt(digits, radix)
161173
}
162174

175+
@experimental
163176
given BigDecimalFromDigits: Floating[BigDecimal] with {
164177
def fromDigits(digits: String): BigDecimal = BigDecimal(digits)
165178
}

0 commit comments

Comments
 (0)