Skip to content

Commit 4aaca69

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 183ec08 commit 4aaca69

File tree

12 files changed

+135
-11
lines changed

12 files changed

+135
-11
lines changed

compiler/src/dotty/tools/dotc/Compiler.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ class Compiler {
5959
protected def transformPhases: List[List[Phase]] =
6060
List(new FirstTransform, // Some transformations to put trees into a canonical form
6161
new CheckReentrant, // Internal use only: Check that compiled program has no data races involving global vars
62+
new CheckExperimental, // Check that no @experimental APIs are used
6263
new ElimPackagePrefixes, // Eliminate references to package prefixes in Select nodes
6364
new CookComments, // Cook the comments: expand variables, doc, etc.
6465
new CheckStatic, // Check restrictions that apply to @static members

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
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package dotty.tools.dotc
2+
package transform
3+
4+
import core._
5+
import dotty.tools.dotc.transform.MegaPhase._
6+
import Flags._
7+
import Contexts._
8+
import Types._
9+
import Symbols._
10+
import SymUtils._
11+
import Decorators._
12+
import config.Feature
13+
import util.SrcPos
14+
15+
/**
16+
*/
17+
class CheckExperimental extends MiniPhase:
18+
import ast.tpd._
19+
20+
override def phaseName: String = "checkExperimental"
21+
22+
override def transformIdent(tree: Ident)(using Context): Tree =
23+
checkExperimental(tree)
24+
25+
override def transformSelect(tree: Select)(using Context): Tree =
26+
checkExperimental(tree)
27+
28+
override def transformTypeTree(tree: TypeTree)(using Context): Tree =
29+
checkExperimentalTypes(tree)
30+
31+
override def transformOther(tree: Tree)(using Context): Tree =
32+
tree match
33+
case _: TypeTree => checkExperimentalTypes(tree) // TODO: why was transformTypeTree not called?
34+
case _ => tree
35+
36+
private def checkExperimental(tree: Tree)(using Context): tree.type =
37+
if tree.symbol.isExperimental && !tree.symbol.isConstructor then
38+
Feature.checkExperimentalFeature(tree.symbol.show, tree)
39+
tree
40+
41+
private def checkExperimentalTypes(tree: Tree)(using Context): tree.type =
42+
val checker = new TypeTraverser:
43+
def traverse(tp: Type): Unit =
44+
if tp.typeSymbol.isExperimental then
45+
Feature.checkExperimentalFeature(tp.typeSymbol.show, tree)
46+
else
47+
traverseChildren(tp)
48+
if !tree.span.isSynthetic then // avoid double errors
49+
checker.traverse(tree.tpe)
50+
tree

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,17 @@ 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+
if self.isClass then
265+
self.hasAnnotation(defn.ExperimentalAnnot)
266+
|| self.info.parents.exists(_.typeSymbol.isExperimental) // TODO infer @experimental
267+
|| (self.maybeOwner.exists && self.owner.isExperimental) // TODO infer @experimental
268+
else
269+
self.hasAnnotation(defn.ExperimentalAnnot)
270+
|| self.allOverriddenSymbols.exists(_.hasAnnotation(defn.ExperimentalAnnot)) // TODO infer @experimental
271+
|| (self.maybeOwner.exists && self.owner.isExperimental) // TODO infer @experimental
272+
262273
/** The declared self type of this class, as seen from `site`, stripping
263274
* all refinements for opaque types.
264275
*/

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
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
package scala.annotation
2+
3+
/** An annotation that can be used to mark a definition as experimental. */
4+
class experimental extends StaticAnnotation

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: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import scala.annotation.experimental
2+
3+
@experimental
4+
val x = ()
5+
6+
@experimental
7+
def f() = ()
8+
9+
@experimental
10+
class A:
11+
def f() = 1
12+
13+
class B extends A: // error
14+
override def f() = 2
15+
16+
@experimental
17+
type X
18+
19+
@experimental
20+
object X:
21+
def fx() = 1
22+
23+
def test(
24+
p1: A, // error
25+
p2: X, // error
26+
p3: List[A], // error
27+
): Unit =
28+
f() // error
29+
x // error
30+
new A // error
31+
new B // error
32+
X.fx() // error
33+
import X.fx
34+
fx() // error
35+
val i1 = identity[X] // error
36+
val i2 = identity[A] // error
37+
val a: A = ??? // error
38+
val b: B = ??? // error
39+
a.f() // error
40+
b.f() // error
41+
()
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
4+
inline def g() = ()
5+
6+
def test: Unit =
7+
g() // errors
8+
()

0 commit comments

Comments
 (0)