Skip to content

Commit

Permalink
Add -experimental compiler flags (#18571)
Browse files Browse the repository at this point in the history
When enabled, all top-level definitions are annotated as
`@experimental`.
This implies that all experimental language features and definitions can
be used in this project.

Note that this does not change the strong guarantees on stability of
non-experimental code. The experimental features can only be used in a
experimental scope (transitively).

This flags does not affect the use of `ResearchPlugin`.

Follow up of
https://contributors.scala-lang.org/t/behaviour-of-experimental-in-scala-3/6309/27?u=nicolasstucki
  • Loading branch information
nicolasstucki authored Nov 14, 2023
2 parents 0dfe593 + 69cc0ee commit 9136582
Show file tree
Hide file tree
Showing 12 changed files with 90 additions and 6 deletions.
9 changes: 7 additions & 2 deletions compiler/src/dotty/tools/dotc/config/Feature.scala
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,12 @@ object Feature:

def checkExperimentalFeature(which: String, srcPos: SrcPos, note: => String = "")(using Context) =
if !isExperimentalEnabled then
report.error(em"Experimental $which may only be used with a nightly or snapshot version of the compiler$note", srcPos)
report.error(
em"""Experimental $which may only be used under experimental mode:
| 1. In a definition marked as @experimental
| 2. Compiling with the -experimental compiler flag
| 3. With a nightly or snapshot version of the compiler$note
""", srcPos)

private def ccException(sym: Symbol)(using Context): Boolean =
ccEnabled && defn.ccExperimental.contains(sym)
Expand All @@ -159,7 +164,7 @@ object Feature:
do checkExperimentalFeature(s"feature $setting", NoSourcePosition)

def isExperimentalEnabled(using Context): Boolean =
Properties.experimental && !ctx.settings.YnoExperimental.value
(Properties.experimental || ctx.settings.experimental.value) && !ctx.settings.YnoExperimental.value

/** Handle language import `import language.<prefix>.<imported>` if it is one
* of the global imports `pureFunctions` or `captureChecking`. In this case
Expand Down
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/config/ScalaSettings.scala
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ trait CommonScalaSettings:
val explainTypes: Setting[Boolean] = BooleanSetting("-explain-types", "Explain type errors in more detail (deprecated, use -explain instead).", aliases = List("--explain-types", "-explaintypes"))
val unchecked: Setting[Boolean] = BooleanSetting("-unchecked", "Enable additional warnings where generated code depends on assumptions.", initialValue = true, aliases = List("--unchecked"))
val language: Setting[List[String]] = MultiStringSetting("-language", "feature", "Enable one or more language features.", aliases = List("--language"))
val experimental: Setting[Boolean] = BooleanSetting("-experimental", "Annotate all top-level definitions with @experimental. This enables the use of experimental features anywhere in the project.")

/* Coverage settings */
val coverageOutputDir = PathSetting("-coverage-out", "Destination for coverage classfiles and instrumentation data.", "", aliases = List("--coverage-out"))
Expand Down
3 changes: 2 additions & 1 deletion compiler/src/dotty/tools/dotc/plugins/Plugins.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import config.{ PathResolver, Feature }
import dotty.tools.io.*
import Phases.*
import config.Printers.plugins.{ println => debug }
import config.Properties

import scala.compiletime.uninitialized

Expand Down Expand Up @@ -128,7 +129,7 @@ trait Plugins {
val updatedPlan = Plugins.schedule(plan, pluginPhases)

// add research plugins
if (Feature.isExperimentalEnabled)
if Properties.experimental && !ctx.settings.YnoExperimental.value then
plugins.collect { case p: ResearchPlugin => p }.foldRight(updatedPlan) {
(plug, plan) => plug.init(options(plug), plan)
}
Expand Down
11 changes: 9 additions & 2 deletions compiler/src/dotty/tools/dotc/transform/PostTyper.scala
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,7 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase =>
)
}
case tree: ValDef =>
annotateExperimental(tree.symbol)
registerIfHasMacroAnnotations(tree)
checkErasedDef(tree)
Checking.checkPolyFunctionType(tree.tpt)
Expand All @@ -387,6 +388,7 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase =>
checkStableSelection(tree.rhs)
processValOrDefDef(super.transform(tree1))
case tree: DefDef =>
annotateExperimental(tree.symbol)
registerIfHasMacroAnnotations(tree)
checkErasedDef(tree)
Checking.checkPolyFunctionType(tree.tpt)
Expand Down Expand Up @@ -542,9 +544,14 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase =>
report.error("`erased` definition cannot be implemented with en expression of type Null", tree.srcPos)

private def annotateExperimental(sym: Symbol)(using Context): Unit =
if sym.is(Module) && sym.companionClass.hasAnnotation(defn.ExperimentalAnnot) then
def isTopLevelDefinitionInSource(sym: Symbol) =
!sym.is(Package) && !sym.name.isPackageObjectName &&
(sym.owner.is(Package) || (sym.owner.isPackageObject && !sym.isConstructor))
if !sym.hasAnnotation(defn.ExperimentalAnnot)
&& (ctx.settings.experimental.value && isTopLevelDefinitionInSource(sym))
|| (sym.is(Module) && sym.companionClass.hasAnnotation(defn.ExperimentalAnnot))
then
sym.addAnnotation(Annotation(defn.ExperimentalAnnot, sym.span))
sym.companionModule.addAnnotation(Annotation(defn.ExperimentalAnnot, sym.span))

private def scala2LibPatch(tree: TypeDef)(using Context) =
val sym = tree.symbol
Expand Down
2 changes: 1 addition & 1 deletion docs/_docs/reference/changed-features/compiler-plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ For experimentation and research, Scala 3 introduces _research plugin_. Research
are more powerful than Scala 2 analyzer plugins as they let plugin authors customize
the whole compiler pipeline. One can easily replace the standard typer by a custom one or
create a parser for a domain-specific language. However, research plugins are only
enabled for nightly or snaphot releases of Scala 3.
enabled with the `-experimental` compiler flag or in nightly/snapshot releases of Scala 3.

Common plugins that add new phases to the compiler pipeline are called
_standard plugins_ in Scala 3. In terms of features, they are similar to
Expand Down
6 changes: 6 additions & 0 deletions docs/_docs/reference/experimental/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ They are enabled by importing the feature or using the `-language` compiler flag
In general, experimental language features can be imported in an experimental scope (see [experimental definitions](../other-new-features/experimental-defs.md)).
They can be imported at the top-level if all top-level definitions are `@experimental`.

### `-experimental` compiler flag

This flag enables the use of any experimental language feature in the project.
It does this by adding an `@experimental` annotation to all top-level definitions.
Hence, dependent projects also have to be experimental.

## Experimental language features supported by special compiler options

Some experimental language features that are still in research and development can be enabled with special compiler options. These include
Expand Down
6 changes: 6 additions & 0 deletions docs/_docs/reference/other-new-features/experimental-defs.md
Original file line number Diff line number Diff line change
Expand Up @@ -309,3 +309,9 @@ class MyExperimentalTests {
```

</details>

## `-experimental` compiler flag

This flag enables the use of any experimental language feature in the project.
It does this by adding an `@experimental` annotation to all top-level definitions.
Hence, dependent projects also have to be experimental.
5 changes: 5 additions & 0 deletions tests/neg/expeimental-flag-with-lang-feature-1.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
//> using options -Yno-experimental

import scala.language.experimental.erasedDefinitions

erased def erasedFun(erased x: Int): Int = x // error // error
7 changes: 7 additions & 0 deletions tests/neg/expeimental-flag-with-lang-feature-2.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
//> using options -Yno-experimental

import scala.language.experimental.namedTypeArguments // error

def namedTypeArgumentsFun[T, U]: Int =
namedTypeArgumentsFun[T = Int, U = Int]
namedTypeArgumentsFun[U = Int, T = Int]
18 changes: 18 additions & 0 deletions tests/neg/expeimental-flag.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//> using options -Yno-experimental

import scala.annotation.experimental

class Foo:
def foo: Int = experimentalDef // error

class Bar:
def bar: Int = experimentalDef // error
object Bar:
def bar: Int = experimentalDef // error

object Baz:
def bar: Int = experimentalDef // error

def toplevelMethod: Int = experimentalDef // error

@experimental def experimentalDef: Int = 1
10 changes: 10 additions & 0 deletions tests/pos/expeimental-flag-with-lang-feature.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
//> using options -experimental -Yno-experimental

import scala.language.experimental.erasedDefinitions
import scala.language.experimental.namedTypeArguments

erased def erasedFun(erased x: Int): Int = x

def namedTypeArgumentsFun[T, U]: Int =
namedTypeArgumentsFun[T = Int, U = Int]
namedTypeArgumentsFun[U = Int, T = Int]
18 changes: 18 additions & 0 deletions tests/pos/expeimental-flag.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//> using options -experimental -Yno-experimental

import scala.annotation.experimental

class Foo:
def foo: Int = experimentalDef

class Bar:
def bar: Int = experimentalDef
object Bar:
def bar: Int = experimentalDef

object Baz:
def bar: Int = experimentalDef

def toplevelMethod: Int = experimentalDef

@experimental def experimentalDef: Int = 1

0 comments on commit 9136582

Please sign in to comment.