Skip to content

Commit 290f36d

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 290f36d

File tree

16 files changed

+172
-20
lines changed

16 files changed

+172
-20
lines changed

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

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -100,19 +100,20 @@ object Feature:
100100
private val assumeExperimentalIn = Set("dotty.tools.vulpix.ParallelTesting")
101101

102102
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
103+
if !isExperimentalEnabled then
109104
//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)
105+
report.error(i"Experimental $which may only be used with nightly or snapshot version of compiler", srcPos)
111106

112107
/** Check that experimental compiler options are only set for snapshot or nightly compiler versions. */
113108
def checkExperimentalSettings(using Context): Unit =
114109
for setting <- ctx.settings.language.value
115110
if setting.startsWith("experimental.") && setting != "experimental.macros"
116-
do checkExperimentalFeature(s" $setting")
111+
do checkExperimentalFeature(s"feature $setting")
112+
113+
def isExperimentalEnabled(using Context): Boolean =
114+
def hasSpecialPermission =
115+
new Exception().getStackTrace.exists(elem =>
116+
assumeExperimentalIn.exists(elem.getClassName().startsWith(_)))
117+
(Properties.experimental || hasSpecialPermission) && !ctx.settings.YnoExperimental.value
117118

118119
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.exists(_.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/Inliner.scala

Lines changed: 3 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,8 @@ 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+
if tree.symbol.isExperimental then
97+
Feature.checkExperimentalFeature(tree.symbol.show, tree)
9598

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

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2351,6 +2351,15 @@ class Typer extends Namer
23512351
report.featureWarning(nme.dynamics.toString, "extension of type scala.Dynamic", cls, isRequired, cdef.srcPos)
23522352
}
23532353

2354+
// @experimental inference
2355+
parents1.find(_.symbol.isExperimental) match
2356+
case Some(parent) =>
2357+
cls.addAnnotation(Annotation(defn.ExperimentalAnnot))
2358+
// var sym = parent.symbol
2359+
// if sym.isConstructor then sym = sym.owner
2360+
// report.warning(em"extension of experimental $sym should have @experimental annotation", cdef.srcPos)
2361+
case _ =>
2362+
23542363
checkNonCyclicInherited(cls.thisType, cls.info.parents, cls.info.decls, cdef.srcPos)
23552364

23562365
// check value class constraints
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.checkExperimentalFeature(tree.symbol.show, 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.checkExperimentalFeature(tp.typeSymbol.show, 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: 4 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,6 +23,7 @@ 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
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import scala.annotation.experimental
2+
3+
@experimental // error
4+
val x = ()
5+
6+
@experimental // error
7+
def f() = ()
8+
9+
@experimental // error
10+
class A:
11+
def f() = 1
12+
13+
class B extends A: // error
14+
override def f() = 2
15+
16+
@experimental // error
17+
type X
18+
19+
@experimental // error
20+
object X:
21+
def fx() = 1 // error
22+
23+
class C:
24+
@experimental // error
25+
def f() = 1
26+
27+
class D extends C:
28+
override def f() = 2
29+
30+
object Extractor1:
31+
def unapply(s: Any): Option[A] = ??? // error
32+
33+
object Extractor2:
34+
@experimental // error
35+
def unapply(s: Any): Option[Int] = ???
36+
37+
def test(
38+
p1: A, // error
39+
p2: X, // error
40+
p3: List[A], // error
41+
): Unit =
42+
f() // error
43+
x // error
44+
new A // error
45+
new B // error
46+
X.fx() // error
47+
import X.fx // error
48+
fx() // error
49+
val i1 = identity[X] // error // error
50+
val i2 = identity[A] // error // error
51+
val a: A = ??? // error
52+
val b: B = ??? // error
53+
val c: C = ???
54+
val d: D = ???
55+
a.f() // error
56+
b.f() // error
57+
c.f() // error
58+
d.f() // error
59+
()
60+
61+
??? match
62+
case _: A => // error // error
63+
case Extractor1(_) => // error
64+
case Extractor2(_) => // error
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
class MyExperimentalAnnot extends scala.annotation.experimental // error
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import scala.annotation.experimental
2+
3+
@experimental // error
4+
inline def g() = ()
5+
6+
def test: Unit =
7+
g() // errors
8+
()

0 commit comments

Comments
 (0)