Skip to content

Commit 9136582

Browse files
Add -experimental compiler flags (#18571)
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
2 parents 0dfe593 + 69cc0ee commit 9136582

12 files changed

+90
-6
lines changed

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

+7-2
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,12 @@ object Feature:
134134

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

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

161166
def isExperimentalEnabled(using Context): Boolean =
162-
Properties.experimental && !ctx.settings.YnoExperimental.value
167+
(Properties.experimental || ctx.settings.experimental.value) && !ctx.settings.YnoExperimental.value
163168

164169
/** Handle language import `import language.<prefix>.<imported>` if it is one
165170
* of the global imports `pureFunctions` or `captureChecking`. In this case

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

+1
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ trait CommonScalaSettings:
122122
val explainTypes: Setting[Boolean] = BooleanSetting("-explain-types", "Explain type errors in more detail (deprecated, use -explain instead).", aliases = List("--explain-types", "-explaintypes"))
123123
val unchecked: Setting[Boolean] = BooleanSetting("-unchecked", "Enable additional warnings where generated code depends on assumptions.", initialValue = true, aliases = List("--unchecked"))
124124
val language: Setting[List[String]] = MultiStringSetting("-language", "feature", "Enable one or more language features.", aliases = List("--language"))
125+
val experimental: Setting[Boolean] = BooleanSetting("-experimental", "Annotate all top-level definitions with @experimental. This enables the use of experimental features anywhere in the project.")
125126

126127
/* Coverage settings */
127128
val coverageOutputDir = PathSetting("-coverage-out", "Destination for coverage classfiles and instrumentation data.", "", aliases = List("--coverage-out"))

compiler/src/dotty/tools/dotc/plugins/Plugins.scala

+2-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import config.{ PathResolver, Feature }
1010
import dotty.tools.io.*
1111
import Phases.*
1212
import config.Printers.plugins.{ println => debug }
13+
import config.Properties
1314

1415
import scala.compiletime.uninitialized
1516

@@ -128,7 +129,7 @@ trait Plugins {
128129
val updatedPlan = Plugins.schedule(plan, pluginPhases)
129130

130131
// add research plugins
131-
if (Feature.isExperimentalEnabled)
132+
if Properties.experimental && !ctx.settings.YnoExperimental.value then
132133
plugins.collect { case p: ResearchPlugin => p }.foldRight(updatedPlan) {
133134
(plug, plan) => plug.init(options(plug), plan)
134135
}

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

+9-2
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,7 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase =>
379379
)
380380
}
381381
case tree: ValDef =>
382+
annotateExperimental(tree.symbol)
382383
registerIfHasMacroAnnotations(tree)
383384
checkErasedDef(tree)
384385
Checking.checkPolyFunctionType(tree.tpt)
@@ -387,6 +388,7 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase =>
387388
checkStableSelection(tree.rhs)
388389
processValOrDefDef(super.transform(tree1))
389390
case tree: DefDef =>
391+
annotateExperimental(tree.symbol)
390392
registerIfHasMacroAnnotations(tree)
391393
checkErasedDef(tree)
392394
Checking.checkPolyFunctionType(tree.tpt)
@@ -542,9 +544,14 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase =>
542544
report.error("`erased` definition cannot be implemented with en expression of type Null", tree.srcPos)
543545

544546
private def annotateExperimental(sym: Symbol)(using Context): Unit =
545-
if sym.is(Module) && sym.companionClass.hasAnnotation(defn.ExperimentalAnnot) then
547+
def isTopLevelDefinitionInSource(sym: Symbol) =
548+
!sym.is(Package) && !sym.name.isPackageObjectName &&
549+
(sym.owner.is(Package) || (sym.owner.isPackageObject && !sym.isConstructor))
550+
if !sym.hasAnnotation(defn.ExperimentalAnnot)
551+
&& (ctx.settings.experimental.value && isTopLevelDefinitionInSource(sym))
552+
|| (sym.is(Module) && sym.companionClass.hasAnnotation(defn.ExperimentalAnnot))
553+
then
546554
sym.addAnnotation(Annotation(defn.ExperimentalAnnot, sym.span))
547-
sym.companionModule.addAnnotation(Annotation(defn.ExperimentalAnnot, sym.span))
548555

549556
private def scala2LibPatch(tree: TypeDef)(using Context) =
550557
val sym = tree.symbol

docs/_docs/reference/changed-features/compiler-plugins.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ For experimentation and research, Scala 3 introduces _research plugin_. Research
1818
are more powerful than Scala 2 analyzer plugins as they let plugin authors customize
1919
the whole compiler pipeline. One can easily replace the standard typer by a custom one or
2020
create a parser for a domain-specific language. However, research plugins are only
21-
enabled for nightly or snaphot releases of Scala 3.
21+
enabled with the `-experimental` compiler flag or in nightly/snapshot releases of Scala 3.
2222

2323
Common plugins that add new phases to the compiler pipeline are called
2424
_standard plugins_ in Scala 3. In terms of features, they are similar to

docs/_docs/reference/experimental/overview.md

+6
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@ They are enabled by importing the feature or using the `-language` compiler flag
2121
In general, experimental language features can be imported in an experimental scope (see [experimental definitions](../other-new-features/experimental-defs.md)).
2222
They can be imported at the top-level if all top-level definitions are `@experimental`.
2323

24+
### `-experimental` compiler flag
25+
26+
This flag enables the use of any experimental language feature in the project.
27+
It does this by adding an `@experimental` annotation to all top-level definitions.
28+
Hence, dependent projects also have to be experimental.
29+
2430
## Experimental language features supported by special compiler options
2531

2632
Some experimental language features that are still in research and development can be enabled with special compiler options. These include

docs/_docs/reference/other-new-features/experimental-defs.md

+6
Original file line numberDiff line numberDiff line change
@@ -309,3 +309,9 @@ class MyExperimentalTests {
309309
```
310310

311311
</details>
312+
313+
## `-experimental` compiler flag
314+
315+
This flag enables the use of any experimental language feature in the project.
316+
It does this by adding an `@experimental` annotation to all top-level definitions.
317+
Hence, dependent projects also have to be experimental.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
//> using options -Yno-experimental
2+
3+
import scala.language.experimental.erasedDefinitions
4+
5+
erased def erasedFun(erased x: Int): Int = x // error // error
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
//> using options -Yno-experimental
2+
3+
import scala.language.experimental.namedTypeArguments // error
4+
5+
def namedTypeArgumentsFun[T, U]: Int =
6+
namedTypeArgumentsFun[T = Int, U = Int]
7+
namedTypeArgumentsFun[U = Int, T = Int]

tests/neg/expeimental-flag.scala

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
//> using options -Yno-experimental
2+
3+
import scala.annotation.experimental
4+
5+
class Foo:
6+
def foo: Int = experimentalDef // error
7+
8+
class Bar:
9+
def bar: Int = experimentalDef // error
10+
object Bar:
11+
def bar: Int = experimentalDef // error
12+
13+
object Baz:
14+
def bar: Int = experimentalDef // error
15+
16+
def toplevelMethod: Int = experimentalDef // error
17+
18+
@experimental def experimentalDef: Int = 1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
//> using options -experimental -Yno-experimental
2+
3+
import scala.language.experimental.erasedDefinitions
4+
import scala.language.experimental.namedTypeArguments
5+
6+
erased def erasedFun(erased x: Int): Int = x
7+
8+
def namedTypeArgumentsFun[T, U]: Int =
9+
namedTypeArgumentsFun[T = Int, U = Int]
10+
namedTypeArgumentsFun[U = Int, T = Int]

tests/pos/expeimental-flag.scala

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
//> using options -experimental -Yno-experimental
2+
3+
import scala.annotation.experimental
4+
5+
class Foo:
6+
def foo: Int = experimentalDef
7+
8+
class Bar:
9+
def bar: Int = experimentalDef
10+
object Bar:
11+
def bar: Int = experimentalDef
12+
13+
object Baz:
14+
def bar: Int = experimentalDef
15+
16+
def toplevelMethod: Int = experimentalDef
17+
18+
@experimental def experimentalDef: Int = 1

0 commit comments

Comments
 (0)