Skip to content

Commit 69cc0ee

Browse files
committed
Add -experimental compiler flags
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`.
1 parent cc40aca commit 69cc0ee

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
@@ -119,6 +119,7 @@ trait CommonScalaSettings:
119119
val explainTypes: Setting[Boolean] = BooleanSetting("-explain-types", "Explain type errors in more detail (deprecated, use -explain instead).", aliases = List("--explain-types", "-explaintypes"))
120120
val unchecked: Setting[Boolean] = BooleanSetting("-unchecked", "Enable additional warnings where generated code depends on assumptions.", initialValue = true, aliases = List("--unchecked"))
121121
val language: Setting[List[String]] = MultiStringSetting("-language", "feature", "Enable one or more language features.", aliases = List("--language"))
122+
val experimental: Setting[Boolean] = BooleanSetting("-experimental", "Annotate all top-level definitions with @experimental. This enables the use of experimental features anywhere in the project.")
122123

123124
/* Coverage settings */
124125
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
/** Support for run-time loading of compiler plugins.
1516
*
@@ -126,7 +127,7 @@ trait Plugins {
126127
val updatedPlan = Plugins.schedule(plan, pluginPhases)
127128

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

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

+9-2
Original file line numberDiff line numberDiff line change
@@ -379,13 +379,15 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase =>
379379
)
380380
}
381381
case tree: ValDef =>
382+
annotateExperimental(tree.symbol)
382383
registerIfHasMacroAnnotations(tree)
383384
checkErasedDef(tree)
384385
val tree1 = cpy.ValDef(tree)(rhs = normalizeErasedRhs(tree.rhs, tree.symbol))
385386
if tree1.removeAttachment(desugar.UntupledParam).isDefined then
386387
checkStableSelection(tree.rhs)
387388
processValOrDefDef(super.transform(tree1))
388389
case tree: DefDef =>
390+
annotateExperimental(tree.symbol)
389391
registerIfHasMacroAnnotations(tree)
390392
checkErasedDef(tree)
391393
annotateContextResults(tree)
@@ -537,9 +539,14 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase =>
537539
report.error("`erased` definition cannot be implemented with en expression of type Null", tree.srcPos)
538540

539541
private def annotateExperimental(sym: Symbol)(using Context): Unit =
540-
if sym.is(Module) && sym.companionClass.hasAnnotation(defn.ExperimentalAnnot) then
542+
def isTopLevelDefinitionInSource(sym: Symbol) =
543+
!sym.is(Package) && !sym.name.isPackageObjectName &&
544+
(sym.owner.is(Package) || (sym.owner.isPackageObject && !sym.isConstructor))
545+
if !sym.hasAnnotation(defn.ExperimentalAnnot)
546+
&& (ctx.settings.experimental.value && isTopLevelDefinitionInSource(sym))
547+
|| (sym.is(Module) && sym.companionClass.hasAnnotation(defn.ExperimentalAnnot))
548+
then
541549
sym.addAnnotation(Annotation(defn.ExperimentalAnnot, sym.span))
542-
sym.companionModule.addAnnotation(Annotation(defn.ExperimentalAnnot, sym.span))
543550

544551
private def scala2LibPatch(tree: TypeDef)(using Context) =
545552
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)