Skip to content

Commit

Permalink
Make preview private[scala], enable all preview features globally u…
Browse files Browse the repository at this point in the history
…sing flag only
  • Loading branch information
WojciechMazur committed Jan 28, 2025
1 parent 30a4f60 commit 0ab56bd
Show file tree
Hide file tree
Showing 20 changed files with 54 additions and 104 deletions.
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/ast/TreeInfo.scala
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,7 @@ trait TreeInfo[T <: Untyped] { self: Trees.Instance[T] =>
case TypeDefs(_) => true
case _ => false

private val languageSubCategories = Set(nme.experimental, nme.preview, nme.deprecated)
private val languageSubCategories = Set(nme.experimental, nme.deprecated)

/** If `path` looks like a language import, `Some(name)` where name
* is `experimental` if that sub-module is imported, and the empty
Expand Down
29 changes: 8 additions & 21 deletions compiler/src/dotty/tools/dotc/config/Feature.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,6 @@ object Feature:

def experimental(str: PreName): TermName =
QualifiedName(nme.experimental, str.toTermName)

def preview(str: PreName): TermName =
QualifiedName(nme.preview, str.toTermName)

private def deprecated(str: PreName): TermName =
QualifiedName(nme.deprecated, str.toTermName)
Expand Down Expand Up @@ -48,10 +45,6 @@ object Feature:
defn.languageExperimentalFeatures
.map(sym => experimental(sym.name))
.filterNot(_ == captureChecking) // TODO is this correct?

def previewAutoEnableFeatures(using Context): List[TermName] =
defn.languagePreviewFeatures
.map(sym => preview(sym.name))

val values = List(
(nme.help, "Display all available features"),
Expand Down Expand Up @@ -232,7 +225,7 @@ object Feature:

def isExperimentalEnabledByImport(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
* make the compilation unit's and current run's fields accordingly.
Expand All @@ -250,15 +243,14 @@ object Feature:
true
else
false

def isPreviewEnabled(using Context): Boolean =
ctx.settings.preview.value ||
previewAutoEnableFeatures.exists(enabled)


def isPreviewEnabled(using Context): Boolean =
ctx.settings.preview.value

def checkPreviewFeature(which: String, srcPos: SrcPos, note: => String = "")(using Context) =
if !isPreviewEnabled then
report.error(previewUseSite(which) + note, srcPos)

def checkPreviewDef(sym: Symbol, srcPos: SrcPos)(using Context) = if !isPreviewEnabled then
val previewSym =
if sym.hasAnnotation(defn.PreviewAnnot) then sym
Expand All @@ -273,12 +265,7 @@ object Feature:
then i"$previewSym is marked @preview$msg"
else i"$sym inherits @preview$msg"
report.error(markedPreview + "\n\n" + previewUseSite("definition"), srcPos)

private def previewUseSite(which: String): String =
s"""Preview $which may only be used under preview mode:
| 1. in a definition marked as @preview, or
| 2. a preview feature is imported at the package level, or
| 3. compiling with the -preview compiler flag.
|""".stripMargin
s"Preview $which may only be used when compiling with the `-preview` compiler flag"
end Feature

2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/config/ScalaSettings.scala
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ trait CommonScalaSettings:
val language: Setting[List[ChoiceWithHelp[String]]] = MultiChoiceHelpSetting(RootSetting, "language", "feature", "Enable one or more language features.", choices = ScalaSettingsProperties.supportedLanguageFeatures, legacyChoices = ScalaSettingsProperties.legacyLanguageFeatures, default = Nil, aliases = List("--language"))
val experimental: Setting[Boolean] = BooleanSetting(RootSetting, "experimental", "Annotate all top-level definitions with @experimental. This enables the use of experimental features anywhere in the project.")
val preview: Setting[Boolean] = BooleanSetting(RootSetting, "preview", "Enable the use of preview features anywhere in the project.")

/* Coverage settings */
val coverageOutputDir = PathSetting(RootSetting, "coverage-out", "Destination for coverage classfiles and instrumentation data.", "", aliases = List("--coverage-out"))
val coverageExcludeClasslikes: Setting[List[String]] = MultiStringSetting(RootSetting, "coverage-exclude-classlikes", "packages, classes and modules", "List of regexes for packages, classes and modules to exclude from coverage.", aliases = List("--coverage-exclude-classlikes"))
Expand Down
7 changes: 1 addition & 6 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -808,7 +808,6 @@ class Definitions {
@tu lazy val LanguageModule: Symbol = requiredModule("scala.language")
@tu lazy val LanguageModuleClass: Symbol = LanguageModule.moduleClass.asClass
@tu lazy val LanguageExperimentalModule: Symbol = requiredModule("scala.language.experimental")
@tu lazy val LanguagePreviewModule: Symbol = requiredModule("scala.language.preview")
@tu lazy val LanguageDeprecatedModule: Symbol = requiredModule("scala.language.deprecated")
@tu lazy val NonLocalReturnControlClass: ClassSymbol = requiredClass("scala.runtime.NonLocalReturnControl")
@tu lazy val SelectableClass: ClassSymbol = requiredClass("scala.Selectable")
Expand Down Expand Up @@ -1054,7 +1053,7 @@ class Definitions {
@tu lazy val CompileTimeOnlyAnnot: ClassSymbol = requiredClass("scala.annotation.compileTimeOnly")
@tu lazy val SwitchAnnot: ClassSymbol = requiredClass("scala.annotation.switch")
@tu lazy val ExperimentalAnnot: ClassSymbol = requiredClass("scala.annotation.experimental")
@tu lazy val PreviewAnnot: ClassSymbol = requiredClass("scala.annotation.preview")
@tu lazy val PreviewAnnot: ClassSymbol = requiredClass("scala.annotation.internal.preview")
@tu lazy val ThrowsAnnot: ClassSymbol = requiredClass("scala.throws")
@tu lazy val TransientAnnot: ClassSymbol = requiredClass("scala.transient")
@tu lazy val UncheckedAnnot: ClassSymbol = requiredClass("scala.unchecked")
Expand Down Expand Up @@ -2081,10 +2080,6 @@ class Definitions {
@tu lazy val languageExperimentalFeatures: List[TermSymbol] =
LanguageExperimentalModule.moduleClass.info.decls.toList.filter(_.isAllOf(Lazy | Module)).map(_.asTerm)

/** Preview language features defined in `scala.runtime.stdLibPatches.language.preview` */
@tu lazy val languagePreviewFeatures: List[TermSymbol] =
LanguagePreviewModule.moduleClass.info.decls.toList.filter(_.isAllOf(Lazy | Module)).map(_.asTerm)

// ----- primitive value class machinery ------------------------------------------

class PerRun[T](generate: Context ?=> T) {
Expand Down
1 change: 0 additions & 1 deletion compiler/src/dotty/tools/dotc/core/StdNames.scala
Original file line number Diff line number Diff line change
Expand Up @@ -579,7 +579,6 @@ object StdNames {
val parts: N = "parts"
val postfixOps: N = "postfixOps"
val prefix : N = "prefix"
val preview: N = "preview"
val processEscapes: N = "processEscapes"
val productArity: N = "productArity"
val productElement: N = "productElement"
Expand Down
10 changes: 5 additions & 5 deletions compiler/src/dotty/tools/dotc/core/SymUtils.scala
Original file line number Diff line number Diff line change
Expand Up @@ -370,13 +370,13 @@ class SymUtils:
def isInExperimentalScope(using Context): Boolean = isInFeatureScope(defn.ExperimentalAnnot, _.isExperimental, _.isInExperimentalScope)

/** Is symbol declared or inherits @preview? */
def isPreview(using Context): Boolean = isFeatureAnnotated(defn.PreviewAnnot)
def isPreview(using Context): Boolean = isFeatureAnnotated(defn.PreviewAnnot)
def isInPreviewScope(using Context): Boolean = isInFeatureScope(defn.PreviewAnnot, _.isPreview, _.isInPreviewScope)
private inline def isFeatureAnnotated(checkAnnotaton: ClassSymbol)(using Context): Boolean =

private inline def isFeatureAnnotated(checkAnnotaton: ClassSymbol)(using Context): Boolean =
self.hasAnnotation(checkAnnotaton)
|| (self.maybeOwner.isClass && self.owner.hasAnnotation(checkAnnotaton))

private inline def isInFeatureScope(checkAnnotation: ClassSymbol, checkSymbol: Symbol => Boolean, checkOwner: Symbol => Boolean)(using Context): Boolean =
def isDefaultArgumentOfCheckedMethod =
self.name.is(DefaultGetterName)
Expand All @@ -390,7 +390,7 @@ class SymUtils:
self.hasAnnotation(checkAnnotation)
|| isDefaultArgumentOfCheckedMethod
|| (!self.is(Package) && checkOwner(self.owner))

/** The declared self type of this class, as seen from `site`, stripping
* all refinements for opaque types.
*/
Expand Down
2 changes: 0 additions & 2 deletions compiler/src/dotty/tools/dotc/typer/Checking.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1079,7 +1079,6 @@ trait Checking {
case Some(prefix) =>
val required =
if prefix == nme.experimental then defn.LanguageExperimentalModule
else if prefix == nme.preview then defn.LanguagePreviewModule
else if prefix == nme.deprecated then defn.LanguageDeprecatedModule
else defn.LanguageModule
if path.symbol != required then
Expand All @@ -1088,7 +1087,6 @@ trait Checking {
val foundClasses = path.tpe.classSymbols
if foundClasses.contains(defn.LanguageModule.moduleClass)
|| foundClasses.contains(defn.LanguageExperimentalModule.moduleClass)
|| foundClasses.contains(defn.LanguagePreviewModule.moduleClass)
then
report.error(em"no aliases can be used to refer to a language import", path.srcPos)

Expand Down
8 changes: 4 additions & 4 deletions compiler/src/dotty/tools/dotc/typer/RefChecks.scala
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,7 @@ object RefChecks {
* that passes its value on to O.
* 1.13. If O is non-experimental, M must be non-experimental.
* 1.14. If O has @publicInBinary, M must have @publicInBinary.
* 1.15. If O is non-preview, M must be non-preview
* 1.15. If O is non-preview, M must be non-preview
* 2. Check that only abstract classes have deferred members
* 3. Check that concrete classes do not have deferred definitions
* that are not implemented in a subclass.
Expand Down Expand Up @@ -644,10 +644,10 @@ object RefChecks {
MigrationVersion.OverrideValParameter)
else if !other.isExperimental && member.hasAnnotation(defn.ExperimentalAnnot) then // (1.13)
overrideError("may not override non-experimental member")
else if !member.hasAnnotation(defn.PublicInBinaryAnnot) && other.hasAnnotation(defn.PublicInBinaryAnnot) then // (1.14)
else if !member.hasAnnotation(defn.PublicInBinaryAnnot) && other.hasAnnotation(defn.PublicInBinaryAnnot) then // (1.14)
overrideError("also needs to be declared with @publicInBinary")
else if !other.isPreview && member.hasAnnotation(defn.PreviewAnnot) then // (1.15)
overrideError("may not override non-preview member")
else if !other.isPreview && member.hasAnnotation(defn.PreviewAnnot) then // (1.15)
overrideError("may not override non-preview member")
else if other.hasAnnotation(defn.DeprecatedOverridingAnnot) then
overrideDeprecation("", member, other, "removed or renamed")
end checkOverride
Expand Down
30 changes: 13 additions & 17 deletions docs/_docs/reference/other-new-features/preview-defs.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,23 @@ title: "Preview Definitions"
nightlyOf: https://docs.scala-lang.org/scala3/reference/other-new-features/preview-defs.html
---

The [`@preview`](https://scala-lang.org/api/3.x/scala/annotation/preview.html) annotation allows the definition of an API that is not guaranteed backward binary, but might become stable in next minor version of the compiler.
New Scala language features or standard library APIs are initially introduced as experimental, but once they become fully implemented and acceppted by the [SIP](https://docs.scala-lang.org/sips/) these can become a preview features.
Preview language features and APIs are guaranteed to be standarized in some next Scala minor release, but allow compiler team to introduce small, possibly binary incompatible, changes based on the community feedback.
These can be used by early adopters who can accept possibility of binary compatibility breakage. As an example these can be used for project internal tools and applications, but are discouraged to be used by libraries.

New Scala language features or standard library APIs initially introduced as experimental can become a preview features when they have become fully implemented and acceppted by the [SIP](https://docs.scala-lang.org/sips/) before they're accepted as standard features.
Such definitions can be used by early adopters that can accept possibility of binary compatibility breakage, for example these can be used for project internal tools and applications, but are discouraged to be used by libraries.

The [`@preview`](https://scala-lang.org/api/3.x/scala/annotation/preview.html) definitions follows similar rules as the [`@experimental`](https://scala-lang.org/api/3.x/scala/annotation/experimental.html) - to enable access to preview feature or API in given compilation unit Scala compiler requires either:

- explicit `-preview` flag passed to the compiler,
- top level import for explicit `scala.language.preview.<feature>`,
- annotating defintion that referes to preview feature with `@preview`
Users can enable access to preview features and definitions by compiling with `-preview` flag. The flag would enable all preview features and definitions. There is no way for enabling only a subset of preview features.

The biggest difference of preview features when compared with experimental features is their non-viral behaviour.
Any defintion that was compiles in the preview scope (using `-preview` flag or `scala.language.preview` top-level import) is not annotated as `@preview` defintion itself. It behaviour allows to use preview features transitively in other compilation units without enabled preview mode.
Any defintion compiled in the preview mode (using `-preview` flag) is not marked as preview defintion itself.
This behaviour allows to use preview features transitively in other compilation units without explicitlly enabled preview mode, as long as it does not directly reference APIs or features marked as preview.

The [`@preview`](https://scala-lang.org/api/3.x/scala/annotation/internal/preview.html) annotations are used to mark Scala 3 standard library APIs currently available under enabled preview mode.
The definitions follows similar rules as the [`@experimental`](https://scala-lang.org/api/3.x/scala/annotation/experimental.html) when it comes to accessing, subtyping, overriding or overloading definitions marked with this annotation - all of these can only be performed in compilation unit that enables preview mode.

```scala
//> using options -preview
import scala.annotation.preview
package scala.stdlib
import scala.annotation.internal.preview

@preview def previewFeature: Unit = ()

Expand All @@ -29,10 +29,6 @@ def usePreviewFeature = previewFeature
```

```scala
def usePreviewFeatureTransitively = usePreviewFeature
def usePreviewFeatureDirectly = previewFeature // error - refering to preview definition outside preview scope
def useWrappedPreviewFeature = wrappedPreviewFeature // error - refering to preview definition outside preview scope

@scala.annotation.preview
def wrappedPreviewFeature = previewFeature
def usePreviewFeatureTransitively = scala.stdlib.usePreviewFeature
def usePreviewFeatureDirectly = scala.stdlib.previewFeature // error - refering to preview definition outside preview scope
```
1 change: 1 addition & 0 deletions docs/sidebar.yml
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ subsection:
- page: reference/other-new-features/safe-initialization.md
- page: reference/other-new-features/type-test.md
- page: reference/other-new-features/experimental-defs.md
- page: reference/other-new-features/preview-defs.md
- page: reference/other-new-features/binary-literals.md
- title: Other Changed Features
directory: changed-features
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package scala.annotation
package internal


/** An annotation that can be used to mark a definition as preview.
*
* @see [[https://dotty.epfl.ch/docs/reference/other-new-features/preview-defs]]
* @syntax markdown
*/
final class preview(message: String) extends StaticAnnotation:
private[scala] final class preview(message: String) extends StaticAnnotation:
def this() = this("")
15 changes: 0 additions & 15 deletions library/src/scala/runtime/stdLibPatches/language.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,6 @@ import scala.annotation.compileTimeOnly
/** Scala 3 additions and replacements to the `scala.language` object.
*/
object language:

/** The preview object contains previously experimental features that are fully implemented
* but are awaiting to be stablized as a standard features.
*
* Preview features '''may undergo binary compatibility changes''' in future releases,
* but their API is unlikely to change. These can be used by early adopters that do don't care
* about the binary breakage, i.e. applications, but not libraries.
*
* Programmers are encouraged to try out preview features and
* [[https://github.com/scala/scala3/issues report any bugs or API inconsistencies]]
* they encounter so they can be improved in future releases.
*
* @group preview
*/
object preview

/** The experimental object contains features that have been recently added but have not
* been thoroughly tested in production yet.
Expand Down
4 changes: 1 addition & 3 deletions project/MiMaFilters.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,7 @@ object MiMaFilters {
ProblemFilters.exclude[MissingFieldProblem]("scala.runtime.stdLibPatches.language#experimental.quotedPatternsWithPolymorphicFunctions"),
ProblemFilters.exclude[MissingClassProblem]("scala.runtime.stdLibPatches.language$experimental$quotedPatternsWithPolymorphicFunctions$"),
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.quoted.runtime.Patterns.higherOrderHoleWithTypes"),
ProblemFilters.exclude[MissingClassProblem]("scala.annotation.preview"),
ProblemFilters.exclude[MissingFieldProblem]("scala.runtime.stdLibPatches.language.preview"),
ProblemFilters.exclude[MissingClassProblem]("scala.runtime.stdLibPatches.language$preview$"),
ProblemFilters.exclude[MissingClassProblem]("scala.annotation.internal.preview"),
),

// Additions since last LTS
Expand Down
15 changes: 3 additions & 12 deletions tests/neg/preview-message.check
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,16 @@
| ^^
| method f1 is marked @preview
|
| Preview definition may only be used under preview mode:
| 1. in a definition marked as @preview, or
| 2. a preview feature is imported at the package level, or
| 3. compiling with the -preview compiler flag.
| Preview definition may only be used when compiling with the `-preview` compiler flag
-- Error: tests/neg/preview-message.scala:16:2 -------------------------------------------------------------------------
16 | f2() // error
| ^^
| method f2 is marked @preview
|
| Preview definition may only be used under preview mode:
| 1. in a definition marked as @preview, or
| 2. a preview feature is imported at the package level, or
| 3. compiling with the -preview compiler flag.
| Preview definition may only be used when compiling with the `-preview` compiler flag
-- Error: tests/neg/preview-message.scala:17:2 -------------------------------------------------------------------------
17 | f3() // error
| ^^
| method f3 is marked @preview: not yet stable
|
| Preview definition may only be used under preview mode:
| 1. in a definition marked as @preview, or
| 2. a preview feature is imported at the package level, or
| 3. compiling with the -preview compiler flag.
| Preview definition may only be used when compiling with the `-preview` compiler flag
4 changes: 2 additions & 2 deletions tests/neg/preview-message.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package scala // @preview in private[scala]


import scala.annotation.preview
import scala.annotation.internal.preview

@preview
def f1() = ???
Expand Down
3 changes: 2 additions & 1 deletion tests/neg/preview-non-viral/defs_1.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//> using options -preview
import scala.annotation.preview
package scala // @preview in private[scala]
import scala.annotation.internal.preview

@preview def previewFeature = 42

Expand Down
7 changes: 2 additions & 5 deletions tests/neg/preview-non-viral/usage_2.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,2 @@
def usePreviewFeatureTransitively = usePreviewFeature
def usePreviewFeatureDirectly = previewFeature // error

@scala.annotation.preview
def wrappedPreviewFeature = previewFeature
def usePreviewFeatureTransitively = scala.usePreviewFeature
def usePreviewFeatureDirectly = scala.previewFeature // error
4 changes: 2 additions & 2 deletions tests/neg/previewOverloads.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package scala // @preview in private[scala]


import scala.annotation.preview
import scala.annotation.internal.preview

trait A:
def f: Int
Expand Down
4 changes: 2 additions & 2 deletions tests/neg/previewOverride.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package scala // @preview in private[scala]


import scala.annotation.preview
import scala.annotation.internal.preview

@preview
class A:
Expand Down
6 changes: 3 additions & 3 deletions tests/pos/preview-flag.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
//> using options -preview
package scala // @preview in private[scala]
import scala.annotation.internal.preview

import scala.annotation.preview
@preview def previewDef: Int = 42

class Foo:
def foo: Int = previewDef
Expand All @@ -14,5 +16,3 @@ object Baz:
def bar: Int = previewDef

def toplevelMethod: Int = previewDef

@preview def previewDef: Int = 1

0 comments on commit 0ab56bd

Please sign in to comment.