Skip to content

Commit

Permalink
Add @experimental annotation
Browse files Browse the repository at this point in the history
The `@exerimental` annotation marks definitions as _experimental_ feature.
These can be used in the same situattions where `languange.experimental` can be used.
  • Loading branch information
nicolasstucki committed Apr 15, 2021
1 parent 183ec08 commit 4aaca69
Show file tree
Hide file tree
Showing 12 changed files with 135 additions and 11 deletions.
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/Compiler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ class Compiler {
protected def transformPhases: List[List[Phase]] =
List(new FirstTransform, // Some transformations to put trees into a canonical form
new CheckReentrant, // Internal use only: Check that compiled program has no data races involving global vars
new CheckExperimental, // Check that no @experimental APIs are used
new ElimPackagePrefixes, // Eliminate references to package prefixes in Select nodes
new CookComments, // Cook the comments: expand variables, doc, etc.
new CheckStatic, // Check restrictions that apply to @static members
Expand Down
17 changes: 9 additions & 8 deletions compiler/src/dotty/tools/dotc/config/Feature.scala
Original file line number Diff line number Diff line change
Expand Up @@ -100,19 +100,20 @@ object Feature:
private val assumeExperimentalIn = Set("dotty.tools.vulpix.ParallelTesting")

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

/** Check that experimental compiler options are only set for snapshot or nightly compiler versions. */
def checkExperimentalSettings(using Context): Unit =
for setting <- ctx.settings.language.value
if setting.startsWith("experimental.") && setting != "experimental.macros"
do checkExperimentalFeature(s" $setting")
do checkExperimentalFeature(s"feature $setting")

def isExperimentalEnabled(using Context): Boolean =
def hasSpecialPermission =
new Exception().getStackTrace.exists(elem =>
assumeExperimentalIn.exists(elem.getClassName().startsWith(_)))
(Properties.experimental || hasSpecialPermission) && !ctx.settings.YnoExperimental.value

end Feature
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -910,6 +910,7 @@ class Definitions {
@tu lazy val ConstructorOnlyAnnot: ClassSymbol = requiredClass("scala.annotation.constructorOnly")
@tu lazy val CompileTimeOnlyAnnot: ClassSymbol = requiredClass("scala.annotation.compileTimeOnly")
@tu lazy val SwitchAnnot: ClassSymbol = requiredClass("scala.annotation.switch")
@tu lazy val ExperimentalAnnot: ClassSymbol = requiredClass("scala.annotation.experimental")
@tu lazy val ThrowsAnnot: ClassSymbol = requiredClass("scala.throws")
@tu lazy val TransientAnnot: ClassSymbol = requiredClass("scala.transient")
@tu lazy val UncheckedAnnot: ClassSymbol = requiredClass("scala.unchecked")
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/parsing/Parsers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3083,7 +3083,7 @@ object Parsers {
if prefix == nme.experimental
&& selectors.exists(sel => Feature.experimental(sel.name) != Feature.scala2macros)
then
Feature.checkExperimentalFeature("s", imp.srcPos)
Feature.checkExperimentalFeature("features", imp.srcPos)
for
case ImportSelector(id @ Ident(imported), EmptyTree, _) <- selectors
if allSourceVersionNames.contains(imported)
Expand Down
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/plugins/Plugins.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package plugins

import core._
import Contexts._
import config.{ PathResolver, Properties }
import config.{ PathResolver, Feature }
import dotty.tools.io._
import Phases._
import config.Printers.plugins.{ println => debug }
Expand Down Expand Up @@ -125,7 +125,7 @@ trait Plugins {
val updatedPlan = Plugins.schedule(plan, pluginPhases)

// add research plugins
if (Properties.experimental)
if (Feature.isExperimentalEnabled)
plugins.collect { case p: ResearchPlugin => p }.foldRight(updatedPlan) {
(plug, plan) => plug.init(options(plug), plan)
}
Expand Down
50 changes: 50 additions & 0 deletions compiler/src/dotty/tools/dotc/transform/CheckExperimental.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package dotty.tools.dotc
package transform

import core._
import dotty.tools.dotc.transform.MegaPhase._
import Flags._
import Contexts._
import Types._
import Symbols._
import SymUtils._
import Decorators._
import config.Feature
import util.SrcPos

/**
*/
class CheckExperimental extends MiniPhase:
import ast.tpd._

override def phaseName: String = "checkExperimental"

override def transformIdent(tree: Ident)(using Context): Tree =
checkExperimental(tree)

override def transformSelect(tree: Select)(using Context): Tree =
checkExperimental(tree)

override def transformTypeTree(tree: TypeTree)(using Context): Tree =
checkExperimentalTypes(tree)

override def transformOther(tree: Tree)(using Context): Tree =
tree match
case _: TypeTree => checkExperimentalTypes(tree) // TODO: why was transformTypeTree not called?
case _ => tree

private def checkExperimental(tree: Tree)(using Context): tree.type =
if tree.symbol.isExperimental && !tree.symbol.isConstructor then
Feature.checkExperimentalFeature(tree.symbol.show, tree)
tree

private def checkExperimentalTypes(tree: Tree)(using Context): tree.type =
val checker = new TypeTraverser:
def traverse(tp: Type): Unit =
if tp.typeSymbol.isExperimental then
Feature.checkExperimentalFeature(tp.typeSymbol.show, tree)
else
traverseChildren(tp)
if !tree.span.isSynthetic then // avoid double errors
checker.traverse(tree.tpe)
tree
11 changes: 11 additions & 0 deletions compiler/src/dotty/tools/dotc/transform/SymUtils.scala
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,17 @@ object SymUtils:
&& self.owner.linkedClass.is(Case)
&& self.owner.linkedClass.isDeclaredInfix

/** Is symbol declared experimental? */
def isExperimental(using Context): Boolean =
if self.isClass then
self.hasAnnotation(defn.ExperimentalAnnot)
|| self.info.parents.exists(_.typeSymbol.isExperimental) // TODO infer @experimental
|| (self.maybeOwner.exists && self.owner.isExperimental) // TODO infer @experimental
else
self.hasAnnotation(defn.ExperimentalAnnot)
|| self.allOverriddenSymbols.exists(_.hasAnnotation(defn.ExperimentalAnnot)) // TODO infer @experimental
|| (self.maybeOwner.exists && self.owner.isExperimental) // TODO infer @experimental

/** The declared self type of this class, as seen from `site`, stripping
* all refinements for opaque types.
*/
Expand Down
3 changes: 3 additions & 0 deletions compiler/src/dotty/tools/dotc/typer/Inliner.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import Annotations.Annotation
import SymDenotations.SymDenotation
import Inferencing.isFullyDefined
import config.Printers.inlining
import config.Feature
import ErrorReporting.errorTree
import dotty.tools.dotc.util.{SimpleIdentityMap, SimpleIdentitySet, EqHashMap, SourceFile, SourcePosition, SrcPos}
import dotty.tools.dotc.parsing.Parsers.Parser
Expand Down Expand Up @@ -92,6 +93,8 @@ object Inliner {
if (tree.symbol == defn.CompiletimeTesting_typeChecks) return Intrinsics.typeChecks(tree)
if (tree.symbol == defn.CompiletimeTesting_typeCheckErrors) return Intrinsics.typeCheckErrors(tree)

if tree.symbol.isExperimental then
Feature.checkExperimentalFeature(tree.symbol.show, tree)

/** Set the position of all trees logically contained in the expansion of
* inlined call `call` to the position of `call`. This transform is necessary
Expand Down
4 changes: 4 additions & 0 deletions library/src/scala/annotation/experimental.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package scala.annotation

/** An annotation that can be used to mark a definition as experimental. */
class experimental extends StaticAnnotation
4 changes: 4 additions & 0 deletions library/src/scala/util/FromDigits.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@ package scala.util
import scala.math.{BigInt}
import quoted._
import annotation.internal.sharable
import annotation.experimental


/** A type class for types that admit numeric literals.
*/
@experimental
trait FromDigits[T] {

/** Convert `digits` string to value of type `T`
Expand All @@ -20,6 +23,7 @@ trait FromDigits[T] {
def fromDigits(digits: String): T
}

@experimental
object FromDigits {

/** A subclass of `FromDigits` that also allows to convert whole number literals
Expand Down
41 changes: 41 additions & 0 deletions tests/neg-custom-args/no-experimental/experimentalAnnotation.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import scala.annotation.experimental

@experimental
val x = ()

@experimental
def f() = ()

@experimental
class A:
def f() = 1

class B extends A: // error
override def f() = 2

@experimental
type X

@experimental
object X:
def fx() = 1

def test(
p1: A, // error
p2: X, // error
p3: List[A], // error
): Unit =
f() // error
x // error
new A // error
new B // error
X.fx() // error
import X.fx
fx() // error
val i1 = identity[X] // error
val i2 = identity[A] // error
val a: A = ??? // error
val b: B = ??? // error
a.f() // error
b.f() // error
()
8 changes: 8 additions & 0 deletions tests/neg-custom-args/no-experimental/experimentalnline.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import scala.annotation.experimental

@experimental
inline def g() = ()

def test: Unit =
g() // errors
()

0 comments on commit 4aaca69

Please sign in to comment.