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 16, 2021
1 parent 1f503e7 commit c23db64
Show file tree
Hide file tree
Showing 20 changed files with 239 additions and 22 deletions.
24 changes: 14 additions & 10 deletions compiler/src/dotty/tools/dotc/config/Feature.scala
Original file line number Diff line number Diff line change
Expand Up @@ -99,20 +99,24 @@ 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
//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)
def checkExperimentalFeature(which: String, srcPos: SrcPos)(using Context) =
if !isExperimentalEnabled then
report.error(i"Experimental $which may only be used with nightly or snapshot version of compiler", srcPos)

def checkExperimentalDef(sym: Symbol, srcPos: SrcPos)(using Context) =
if !isExperimentalEnabled then
report.error(i"Experimental $sym 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", NoSourcePosition)

def isExperimentalEnabled(using Context): Boolean =
def hasSpecialPermission =
Thread.currentThread.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
23 changes: 15 additions & 8 deletions compiler/src/dotty/tools/dotc/transform/PostTyper.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import Symbols._, SymUtils._, NameOps._
import ContextFunctionResults.annotateContextResults
import config.Printers.typr
import reporting._
import util.Experimental


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

override def transform(tree: Tree)(using Context): Tree =
try tree match {
case tree: Ident if !tree.isType =>
if tree.symbol.is(Inline) && !Inliner.inInlineMethod then
ctx.compilationUnit.needsInlining = true
checkNoConstructorProxy(tree)
tree.tpe match {
case tpe: ThisType => This(tpe.cls).withSpan(tree.span)
case _ => tree
}
case tree: Ident =>
Experimental.checkExperimental(tree)
if tree.isType then super.transform(tree)
else
if tree.symbol.is(Inline) && !Inliner.inInlineMethod then
ctx.compilationUnit.needsInlining = true
checkNoConstructorProxy(tree)
tree.tpe match {
case tpe: ThisType => This(tpe.cls).withSpan(tree.span)
case _ => tree
}
case tree @ Select(qual, name) =>
Experimental.checkExperimental(tree)
if tree.symbol.is(Inline) then
ctx.compilationUnit.needsInlining = true
if (name.isTypeName) {
Expand Down Expand Up @@ -382,6 +388,7 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase
Checking.checkRealizable(ref.tpe, ref.srcPos)
super.transform(tree)
case tree: TypeTree =>
Experimental.checkExperimental(tree)
tree.withType(
tree.tpe match {
case AnnotatedType(tpe, annot) => AnnotatedType(tpe, transformAnnot(annot))
Expand Down
7 changes: 7 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,13 @@ object SymUtils:
&& self.owner.linkedClass.is(Case)
&& self.owner.linkedClass.isDeclaredInfix

/** Is symbol declared experimental? */
def isExperimental(using Context): Boolean =
(self eq defn.ExperimentalAnnot)
|| self.hasAnnotation(defn.ExperimentalAnnot)
|| self.allOverriddenSymbols.nonEmpty && self.allOverriddenSymbols.forall(_.hasAnnotation(defn.ExperimentalAnnot)) // TODO infer @experimental?
|| (self.maybeOwner.exists && self.owner.isClass && self.owner.hasAnnotation(defn.ExperimentalAnnot)) // TODO infer @experimental?

/** The declared self type of this class, as seen from `site`, stripping
* all refinements for opaque types.
*/
Expand Down
8 changes: 8 additions & 0 deletions compiler/src/dotty/tools/dotc/typer/Checking.scala
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,14 @@ object Checking {
}
}

/** Check that classes extending experimental classes have the @experimental annotation */
def checkExperimentalInheritance(cls: ClassSymbol, parents: List[Type], srcPos: SrcPos)(using Context): Unit =
if !cls.hasAnnotation(defn.ExperimentalAnnot) then
parents.find(_.typeSymbol.isExperimental) match
case Some(parent) =>
report.error(em"extension of experimental ${parent.typeSymbol} must have @experimental annotation", srcPos)
case _ =>

/** Check that symbol's definition is well-formed. */
def checkWellFormed(sym: Symbol)(using Context): Unit = {
def fail(msg: Message) = report.error(msg, sym.srcPos)
Expand Down
2 changes: 2 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,7 @@ object Inliner {
if (tree.symbol == defn.CompiletimeTesting_typeChecks) return Intrinsics.typeChecks(tree)
if (tree.symbol == defn.CompiletimeTesting_typeCheckErrors) return Intrinsics.typeCheckErrors(tree)

Feature.checkExperimentalDef(tree.symbol, 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
3 changes: 3 additions & 0 deletions compiler/src/dotty/tools/dotc/typer/RefChecks.scala
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ object RefChecks {
* 1.9. If M is erased, O is erased. If O is erased, M is erased or inline.
* 1.10. If O is inline (and deferred, otherwise O would be final), M must be inline
* 1.11. If O is a Scala-2 macro, M must be a Scala-2 macro.
* 1.12. If O is non-experimental, M must be non-experimental.
* 2. Check that only abstract classes have deferred members
* 3. Check that concrete classes do not have deferred definitions
* that are not implemented in a subclass.
Expand Down Expand Up @@ -475,6 +476,8 @@ object RefChecks {
overrideError(i"needs to be declared with @targetName(${"\""}${other.targetName}${"\""}) so that external names match")
else
overrideError("cannot have a @targetName annotation since external names would be different")
else if !other.isExperimental && member.hasAnnotation(defn.ExperimentalAnnot) then // (1.12)
overrideError("may not override non-experimental member")
else
checkOverrideDeprecated()
}
Expand Down
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2352,6 +2352,7 @@ class Typer extends Namer
}

checkNonCyclicInherited(cls.thisType, cls.info.parents, cls.info.decls, cdef.srcPos)
checkExperimentalInheritance(cls, cls.info.parents, cdef.srcPos)

// check value class constraints
checkDerivedValueClass(cls, body1)
Expand Down
32 changes: 32 additions & 0 deletions compiler/src/dotty/tools/dotc/util/Experimental.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package dotty.tools.dotc
package util

import dotty.tools.dotc.ast.tpd
import dotty.tools.dotc.ast.Trees._
import dotty.tools.dotc.config.Feature
import dotty.tools.dotc.core.Contexts._
import dotty.tools.dotc.core.Symbols._
import dotty.tools.dotc.core.Types._
import dotty.tools.dotc.core.Flags._
import dotty.tools.dotc.transform.SymUtils._

object Experimental:
import tpd._

def checkExperimental(tree: Tree)(using Context): Unit =
if tree.symbol.isExperimental
&& !tree.symbol.isConstructor // already reported on the class
&& !tree.symbol.is(ModuleClass) // already reported on the module
&& (tree.span.exists || tree.symbol != defn.ExperimentalAnnot) // already reported on inferred annotations
then
Feature.checkExperimentalDef(tree.symbol, tree)

def checkExperimentalTypes(tree: Tree)(using Context): Unit =
val checker = new TypeTraverser:
def traverse(tp: Type): Unit =
if tp.typeSymbol.isExperimental then
Feature.checkExperimentalDef(tp.typeSymbol, tree)
else
traverseChildren(tp)
if !tree.span.isSynthetic then // avoid double errors
checker.traverse(tree.tpe)
2 changes: 1 addition & 1 deletion compiler/test/dotty/tools/dotc/CompilationTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ class CompilationTests {
Properties.compilerInterface, Properties.scalaLibrary, Properties.scalaAsm,
Properties.dottyInterfaces, Properties.jlineTerminal, Properties.jlineReader,
).mkString(File.pathSeparator),
Array("-Ycheck-reentrant", "-language:postfixOps", "-Xsemanticdb", "-Yno-experimental")
Array("-Ycheck-reentrant", "-language:postfixOps", "-Xsemanticdb")
)

val libraryDirs = List(Paths.get("library/src"), Paths.get("library/src-bootstrapped"))
Expand Down
14 changes: 14 additions & 0 deletions library/src/scala/annotation/experimental.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package scala.annotation

/** An annotation that can be used to mark a definition as experimental.
*
* This class is experimental as well as if it was defined as
* ```scala
* @experimental
* class experimental extends StaticAnnotation
* ```
*
* @syntax markdown
*/
// @experimental
class experimental extends StaticAnnotation
1 change: 1 addition & 0 deletions library/src/scala/runtime/stdLibPatches/language.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ object language:
*
* @group experimental
*/
@scala.annotation.experimental
object experimental:

/* Experimental support for richer dependent types (disabled for now)
Expand Down
13 changes: 13 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,11 +23,13 @@ trait FromDigits[T] {
def fromDigits(digits: String): T
}

@experimental
object FromDigits {

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

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

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

/** The base type for exceptions that can be thrown from
* `fromDigits` conversions
*/
@experimental
abstract class FromDigitsException(msg: String) extends NumberFormatException(msg)

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

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

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

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

@experimental
given BigIntFromDigits: WithRadix[BigInt] with {
def fromDigits(digits: String, radix: Int): BigInt = BigInt(digits, radix)
}

@experimental
given BigDecimalFromDigits: Floating[BigDecimal] with {
def fromDigits(digits: String): BigDecimal = BigDecimal(digits)
}
Expand Down
Loading

0 comments on commit c23db64

Please sign in to comment.