From 69cc0ee479655fe720da438b34ad6892dae42392 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Tue, 19 Sep 2023 10:52:37 +0200 Subject: [PATCH] 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`. --- .../src/dotty/tools/dotc/config/Feature.scala | 9 +++++++-- .../tools/dotc/config/ScalaSettings.scala | 1 + .../src/dotty/tools/dotc/plugins/Plugins.scala | 3 ++- .../dotty/tools/dotc/transform/PostTyper.scala | 11 +++++++++-- .../changed-features/compiler-plugins.md | 2 +- docs/_docs/reference/experimental/overview.md | 6 ++++++ .../other-new-features/experimental-defs.md | 6 ++++++ .../expeimental-flag-with-lang-feature-1.scala | 5 +++++ .../expeimental-flag-with-lang-feature-2.scala | 7 +++++++ tests/neg/expeimental-flag.scala | 18 ++++++++++++++++++ .../expeimental-flag-with-lang-feature.scala | 10 ++++++++++ tests/pos/expeimental-flag.scala | 18 ++++++++++++++++++ 12 files changed, 90 insertions(+), 6 deletions(-) create mode 100644 tests/neg/expeimental-flag-with-lang-feature-1.scala create mode 100644 tests/neg/expeimental-flag-with-lang-feature-2.scala create mode 100644 tests/neg/expeimental-flag.scala create mode 100644 tests/pos/expeimental-flag-with-lang-feature.scala create mode 100644 tests/pos/expeimental-flag.scala diff --git a/compiler/src/dotty/tools/dotc/config/Feature.scala b/compiler/src/dotty/tools/dotc/config/Feature.scala index 5bcc139326f9..4486aaab7fc9 100644 --- a/compiler/src/dotty/tools/dotc/config/Feature.scala +++ b/compiler/src/dotty/tools/dotc/config/Feature.scala @@ -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) @@ -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..` if it is one * of the global imports `pureFunctions` or `captureChecking`. In this case diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index 3dbfbfc6bab9..0e684a6a3e5b 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -119,6 +119,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")) diff --git a/compiler/src/dotty/tools/dotc/plugins/Plugins.scala b/compiler/src/dotty/tools/dotc/plugins/Plugins.scala index c44fe4cf59b4..d3936e4280a9 100644 --- a/compiler/src/dotty/tools/dotc/plugins/Plugins.scala +++ b/compiler/src/dotty/tools/dotc/plugins/Plugins.scala @@ -10,6 +10,7 @@ import config.{ PathResolver, Feature } import dotty.tools.io._ import Phases._ import config.Printers.plugins.{ println => debug } +import config.Properties /** Support for run-time loading of compiler plugins. * @@ -126,7 +127,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) } diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index 964486632979..5420b120037c 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -379,6 +379,7 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase => ) } case tree: ValDef => + annotateExperimental(tree.symbol) registerIfHasMacroAnnotations(tree) checkErasedDef(tree) val tree1 = cpy.ValDef(tree)(rhs = normalizeErasedRhs(tree.rhs, tree.symbol)) @@ -386,6 +387,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) annotateContextResults(tree) @@ -537,9 +539,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 diff --git a/docs/_docs/reference/changed-features/compiler-plugins.md b/docs/_docs/reference/changed-features/compiler-plugins.md index 82d38bd44d96..6be8a62c7ac4 100644 --- a/docs/_docs/reference/changed-features/compiler-plugins.md +++ b/docs/_docs/reference/changed-features/compiler-plugins.md @@ -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 diff --git a/docs/_docs/reference/experimental/overview.md b/docs/_docs/reference/experimental/overview.md index 254f103896e4..f70cf32b9c24 100644 --- a/docs/_docs/reference/experimental/overview.md +++ b/docs/_docs/reference/experimental/overview.md @@ -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 diff --git a/docs/_docs/reference/other-new-features/experimental-defs.md b/docs/_docs/reference/other-new-features/experimental-defs.md index 88815ad1e136..b71b20ecc036 100644 --- a/docs/_docs/reference/other-new-features/experimental-defs.md +++ b/docs/_docs/reference/other-new-features/experimental-defs.md @@ -309,3 +309,9 @@ class MyExperimentalTests { ``` + +## `-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. diff --git a/tests/neg/expeimental-flag-with-lang-feature-1.scala b/tests/neg/expeimental-flag-with-lang-feature-1.scala new file mode 100644 index 000000000000..a5ece729fa3d --- /dev/null +++ b/tests/neg/expeimental-flag-with-lang-feature-1.scala @@ -0,0 +1,5 @@ +//> using options -Yno-experimental + +import scala.language.experimental.erasedDefinitions + +erased def erasedFun(erased x: Int): Int = x // error // error diff --git a/tests/neg/expeimental-flag-with-lang-feature-2.scala b/tests/neg/expeimental-flag-with-lang-feature-2.scala new file mode 100644 index 000000000000..3e0b9359711a --- /dev/null +++ b/tests/neg/expeimental-flag-with-lang-feature-2.scala @@ -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] diff --git a/tests/neg/expeimental-flag.scala b/tests/neg/expeimental-flag.scala new file mode 100644 index 000000000000..8b2e729ea8da --- /dev/null +++ b/tests/neg/expeimental-flag.scala @@ -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 diff --git a/tests/pos/expeimental-flag-with-lang-feature.scala b/tests/pos/expeimental-flag-with-lang-feature.scala new file mode 100644 index 000000000000..9cfb716b1015 --- /dev/null +++ b/tests/pos/expeimental-flag-with-lang-feature.scala @@ -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] diff --git a/tests/pos/expeimental-flag.scala b/tests/pos/expeimental-flag.scala new file mode 100644 index 000000000000..9d3daf12fddc --- /dev/null +++ b/tests/pos/expeimental-flag.scala @@ -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