Skip to content

Commit c23db64

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 1f503e7 commit c23db64

File tree

20 files changed

+239
-22
lines changed

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)

0 commit comments

Comments
 (0)