Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable experimental on language imports #19760

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/Driver.scala
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ class Driver {
val ictx = rootCtx.fresh
val summary = command.distill(args, ictx.settings)(ictx.settingsState)(using ictx)
ictx.setSettings(summary.sstate)
Feature.checkExperimentalSettings(using ictx)
// Feature.checkExperimentalSettings(using ictx)
MacroClassLoader.init(ictx)
Positioned.init(using ictx)

Expand Down
67 changes: 47 additions & 20 deletions compiler/src/dotty/tools/dotc/config/Feature.scala
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ object Feature:
private val genericNumberLiterals = experimental("genericNumberLiterals")
val scala2macros = experimental("macros")

val mode = experimental("mode")
val dependent = experimental("dependent")
val erasedDefinitions = experimental("erasedDefinitions")
val symbolLiterals = deprecated("symbolLiterals")
Expand All @@ -32,6 +33,23 @@ object Feature:
val pureFunctions = experimental("pureFunctions")
val captureChecking = experimental("captureChecking")
val into = experimental("into")
val relaxedExtensionImports = experimental("relaxedExtensionImports")

// TODO compute this list
// TODO remove features that do not enable experimental mode
val experimentalAutoEnableFeatures: List[TermName] = List(
mode,
namedTypeArguments,
genericNumberLiterals,
erasedDefinitions,
fewerBraces,
saferExceptions,
clauseInterleaving,
pureFunctions,
captureChecking,
into,
relaxedExtensionImports,
)

val globalOnlyImports: Set[TermName] = Set(pureFunctions, captureChecking)

Expand Down Expand Up @@ -132,38 +150,47 @@ object Feature:
false

def checkExperimentalFeature(which: String, srcPos: SrcPos, note: => String = "")(using Context) =
if !isExperimentalEnabled then
if !isExperimentalGloballyEnabled && !isExperimentalByImportEnabled then
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
| 2. An experimental language import is in scope or within the current definition
| Example: import scala.language.experimental.mode
| 3. Compiling with the -experimental compiler flag$note
""", srcPos)

private def ccException(sym: Symbol)(using Context): Boolean =
ccEnabled && defn.ccExperimental.contains(sym)

def checkExperimentalDef(sym: Symbol, srcPos: SrcPos)(using Context) =
if !isExperimentalEnabled then
val experimentalSym =
if sym.hasAnnotation(defn.ExperimentalAnnot) then sym
else if sym.owner.hasAnnotation(defn.ExperimentalAnnot) then sym.owner
else NoSymbol
if !ccException(experimentalSym) then
val symMsg =
if experimentalSym.exists
then i"$experimentalSym is marked @experimental"
else i"$sym inherits @experimental"
report.error(em"$symMsg and therefore may only be used in an experimental scope.", srcPos)
val experimentalSym =
if sym.hasAnnotation(defn.ExperimentalAnnot) then sym
else if sym.owner.hasAnnotation(defn.ExperimentalAnnot) then sym.owner
else NoSymbol
if !ccException(experimentalSym) then
val symMsg =
if experimentalSym.exists
then i"$experimentalSym is marked @experimental"
else i"$sym inherits @experimental"
checkExperimentalFeature("definition", srcPos, s"\n\n$symMsg")

/** 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"feature $setting", NoSourcePosition)
// def checkExperimentalSettings(using Context): Unit =
// for setting <- ctx.settings.language.value
// if setting.startsWith("experimental.") && setting != "experimental.macros"
// do checkExperimentalFeature(s"feature $setting", NoSourcePosition)

/** Experimental mode enabled in this compilation unit
* - Compiled with `-experimental`
* - Compiled with `-language:experimental.xyz`
*/
def isExperimentalGloballyEnabled(using Context): Boolean =
ctx.settings.experimental.value ||
experimentalAutoEnableFeatures.exists(enabledBySetting)

def isExperimentalEnabled(using Context): Boolean =
(Properties.experimental && !ctx.settings.YnoExperimental.value) || ctx.settings.experimental.value
/** Experimental mode enabled by a `language.experimental` import in scope */
def isExperimentalByImportEnabled(using Context): Boolean =
experimentalAutoEnableFeatures.exists(enabledByImport)

/** Handle language import `import language.<prefix>.<imported>` if it is one
* of the global imports `pureFunctions` or `captureChecking`. In this case
Expand Down
13 changes: 13 additions & 0 deletions compiler/src/dotty/tools/dotc/core/SymDenotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1254,6 +1254,19 @@ object SymDenotations {
final def isTopLevelClass(using Context): Boolean =
!this.exists || this.isEffectiveRoot || this.is(PackageClass) || this.owner.is(PackageClass)

/** The top-level definition containing this denotation.
* Returns top-level class or members of top-level package objects.
* If this is not in a definition, returns NoSymbol.
*/
@tailrec final def topLevelDefinition(using Context): Symbol =
if !this.exists || this.is(Package) then NoSymbol
else if this.isTopLevelDefinition then this.symbol
else this.owner.topLevelDefinition

final def isTopLevelDefinition(using Context): Boolean =
!this.is(Package) && !this.name.isPackageObjectName &&
(this.owner.is(Package) || (this.owner.isPackageObject && !this.isConstructor))

/** The package class containing this denotation */
final def enclosingPackageClass(using Context): Symbol =
if (this.is(PackageClass)) symbol else owner.enclosingPackageClass
Expand Down
21 changes: 14 additions & 7 deletions compiler/src/dotty/tools/dotc/transform/PostTyper.scala
Original file line number Diff line number Diff line change
Expand Up @@ -545,15 +545,22 @@ 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 =
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
def companionOfExperimental =
sym.is(Module) && sym.companionClass.hasAnnotation(defn.ExperimentalAnnot)
def topLevelDefinitionWithExperimentalEnabledInUnit =
sym.isTopLevelDefinition && (Feature.isExperimentalGloballyEnabled || Feature.isExperimentalByImportEnabled)
if !sym.hasAnnotation(defn.ExperimentalAnnot) && (companionOfExperimental || topLevelDefinitionWithExperimentalEnabledInUnit) then
sym.addAnnotation(Annotation(defn.ExperimentalAnnot, sym.span))

// TODO do this on import statement
val topLevelDefinition = sym.topLevelDefinition
if topLevelDefinition.exists &&
!topLevelDefinition.hasAnnotation(defn.ExperimentalAnnot)
&& Feature.isExperimentalByImportEnabled
then
topLevelDefinition.addAnnotation(Annotation(defn.ExperimentalAnnot, sym.span))
if topLevelDefinition.is(Module) then topLevelDefinition.companionModule.addAnnotation(Annotation(defn.ExperimentalAnnot, sym.span))

private def scala2LibPatch(tree: TypeDef)(using Context) =
val sym = tree.symbol
if compilingScala2StdLib && sym.is(ModuleClass) then
Expand Down
1 change: 1 addition & 0 deletions compiler/test-resources/repl/erased
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
scala> import scala.language.experimental.erasedDefinitions
scala> def f(erased a: Int): Int = ???
def f(erased a: Int): Int
1 change: 1 addition & 0 deletions compiler/test-resources/repl/erased-implicit
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
scala> import scala.language.experimental.erasedDefinitions
scala> def f(using erased a: Int): Int = ???
def f(using erased a: Int): Int
2 changes: 2 additions & 0 deletions compiler/test-resources/type-printer/test-definitions
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,7 @@ scala> trait E
scala> implicit def x: Int = 1
def x: Int

scala> import scala.language.experimental.erasedDefinitions

scala> erased def y: Int = 1
def y: Int
1 change: 0 additions & 1 deletion compiler/test/dotty/tools/dotc/TastyBootstrapTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ class TastyBootstrapTests {
val lib =
compileList("lib", librarySources,
defaultOptions.and("-Ycheck-reentrant",
"-language:experimental.erasedDefinitions", // support declaration of scala.compiletime.erasedValue
// "-source", "future", // TODO: re-enable once library uses updated syntax for vararg splices, wildcard imports, and import renaming
))(libGroup)

Expand Down
2 changes: 1 addition & 1 deletion compiler/test/dotty/tools/repl/ReplTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,6 @@ extends ReplDriver(options, new PrintStream(out, true, StandardCharsets.UTF_8.na
}

object ReplTest:
val commonOptions = Array("-color:never", "-language:experimental.erasedDefinitions", "-pagewidth", "80")
val commonOptions = Array("-color:never", "-pagewidth", "80")
val defaultOptions = commonOptions ++ Array("-classpath", TestConfiguration.basicClasspath)
lazy val withStagingOptions = commonOptions ++ Array("-classpath", TestConfiguration.withStagingClasspath)
4 changes: 3 additions & 1 deletion docs/_docs/reference/other-new-features/experimental-defs.md
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,9 @@ Experimental definitions can only be referenced in an experimental scope. Experi

</details>

6. Any code compiled using a [_Nightly_](https://search.maven.org/artifact/org.scala-lang/scala3-compiler_3) or _Snapshot_ version of the compiler is considered to be in an experimental scope.
6. An experimental language feature is imported in the current scope

7. Any code compiled using a [_Nightly_](https://search.maven.org/artifact/org.scala-lang/scala3-compiler_3) or _Snapshot_ version of the compiler is considered to be in an experimental scope.
Can use the `-Yno-experimental` compiler flag to disable it and run as a proper release.

In any other situation, a reference to an experimental definition will cause a compilation error.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ Experimental definitions can only be referenced in an experimental scope. Experi

<details>
<summary>Example 1</summary>

```scala
import scala.annotation.experimental

Expand All @@ -242,7 +242,7 @@ Experimental definitions can only be referenced in an experimental scope. Experi
}
}
```

</details>

5. Annotations of an experimental definition are in experimental scopes. Examples:
Expand All @@ -265,7 +265,9 @@ Experimental definitions can only be referenced in an experimental scope. Experi

</details>

6. Any code compiled using a [_Nightly_](https://search.maven.org/artifact/org.scala-lang/scala3-compiler_3) or _Snapshot_ version of the compiler is considered to be in an experimental scope.
6. An experimental language feature is imported in the current scope

7. Any code compiled using a [_Nightly_](https://search.maven.org/artifact/org.scala-lang/scala3-compiler_3) or _Snapshot_ version of the compiler is considered to be in an experimental scope.
Can use the `-Yno-experimental` compiler flag to disable it and run as a proper release.

In any other situation, a reference to an experimental definition will cause a compilation error.
Expand Down
8 changes: 8 additions & 0 deletions library/src/scala/runtime/stdLibPatches/language.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,14 @@ object language:
*/
object experimental:

/** Enable use of experimental APIs by making the current scope an experimental one.
*
* @see [[https://dotty.epfl.ch/docs/reference/other-new-features/experimental-defs]]
*/
@compileTimeOnly("`mode` can only be used at compile time in import statements")
object mode


/* Experimental support for richer dependent types (disabled for now)
* One can still run the compiler with support for parsing singleton applications
* using command line option `-language:experimental.dependent`.
Expand Down
4 changes: 4 additions & 0 deletions project/Build.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1056,6 +1056,7 @@ object Build {
settings(
moduleName := "scala2-library-cc",
scalacOptions += "-Ycheck:all",
scalacOptions += "-language:experimental.mode",
)

lazy val scala2LibraryBootstrappedSettings = Seq(
Expand Down Expand Up @@ -1757,6 +1758,9 @@ object Build {
SourceLinksIntegrationTest / scalaSource := baseDirectory.value / "test-source-links",
SourceLinksIntegrationTest / test:= ((SourceLinksIntegrationTest / test) dependsOn generateScalaDocumentation.toTask("")).value,
).
settings(
scalacOptions += "-experimental" // workaround use of experimental .info in Scaladoc2AnchorCreator
).
settings(
Compile / resourceGenerators ++= Seq(
generateStaticAssetsTask.taskValue,
Expand Down
2 changes: 2 additions & 0 deletions project/MiMaFilters.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ object MiMaFilters {
val ForwardsBreakingChanges: Map[String, Seq[ProblemFilter]] = Map(
// Additions that require a new minor version of the library
Build.previousDottyVersion -> Seq(
ProblemFilters.exclude[MissingFieldProblem]("scala.runtime.stdLibPatches.language#experimental.mode"),
ProblemFilters.exclude[MissingClassProblem]("scala.runtime.stdLibPatches.language$experimental$mode$"),
),

// Additions since last LTS
Expand Down
2 changes: 2 additions & 0 deletions scaladoc-testcases/src/tests/extensionParams.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package tests.extensionParams

import scala.language.experimental.mode // FIXME remove

trait Animal

extension [A](thiz: A)
Expand Down
3 changes: 3 additions & 0 deletions scaladoc-testcases/src/tests/hugetype.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package tests.hugetype

import scala.language.experimental.mode // FIXME remove

import compiletime._
import compiletime.ops.int._
import scala.annotation.experimental
Expand All @@ -17,6 +19,7 @@ import scala.annotation.experimental
* - He took me to a bar full of actor types trying to get noticed.
*
*/
@experimental // FIXME remove
type Take[T <: Tuple, N <: Int] <: Tuple = N match {
case 0 => EmptyTuple
case S[n1] => T match {
Expand Down
1 change: 1 addition & 0 deletions tests/init-global/warn/i18628_3.scala
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import scala.language.experimental.mode
import scala.annotation.init.widen

object Test:
Expand Down
3 changes: 1 addition & 2 deletions tests/neg-macros/i18677-a/Macro_1.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
//> using -expermiental

import scala.language.experimental.mode
import annotation.MacroAnnotation
import quoted.*

Expand Down
2 changes: 1 addition & 1 deletion tests/neg-macros/i18677-a/Test_2.scala
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//> using -expermiental
import scala.language.experimental.mode

@extendFoo
class AFoo // error
3 changes: 1 addition & 2 deletions tests/neg-macros/i18677-b/Macro_1.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
//> using -expermiental

import scala.language.experimental.mode
import annotation.MacroAnnotation
import quoted.*

Expand Down
2 changes: 1 addition & 1 deletion tests/neg-macros/i18677-b/Test_2.scala
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//> using -expermiental
import scala.language.experimental.mode

@extendFoo
class AFoo // error
1 change: 1 addition & 0 deletions tests/neg-macros/newClassExtendsNoParents/Macro_1.scala
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import scala.language.experimental.mode
import scala.quoted.*

inline def makeClass(inline name: String): Any = ${ makeClassExpr('name) }
Expand Down
1 change: 1 addition & 0 deletions tests/neg-macros/newClassExtendsNoParents/Test_2.scala
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
import scala.language.experimental.mode
def test: Any = makeClass("foo") // error
1 change: 1 addition & 0 deletions tests/neg-macros/newClassExtendsOnlyTrait/Macro_1.scala
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import scala.language.experimental.mode
import scala.quoted.*

inline def makeClass(inline name: String): Foo = ${ makeClassExpr('name) }
Expand Down
2 changes: 2 additions & 0 deletions tests/neg-macros/newClassExtendsOnlyTrait/Test_2.scala
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
import scala.language.experimental.mode

def test: Foo = makeClass("foo") // error
11 changes: 0 additions & 11 deletions tests/neg/experimental-erased.scala

This file was deleted.

31 changes: 0 additions & 31 deletions tests/neg/experimental-nested-imports-2.scala

This file was deleted.

Loading
Loading