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

Introduce series concept in SourceVersions, add 3.2 to -source (same as 3.0, 3.1) #14629

Closed
wants to merge 3 commits into from
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
11 changes: 4 additions & 7 deletions compiler/src/dotty/tools/dotc/config/Feature.scala
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ object Feature:
def scala2ExperimentalMacroEnabled(using Context) = enabled(scala2macros)

def sourceVersionSetting(using Context): SourceVersion =
SourceVersion.valueOf(ctx.settings.source.value)
SourceVersion.lookupSourceVersion.fromSetting(ctx.settings.source.value)

def sourceVersion(using Context): SourceVersion =
ctx.compilationUnit.sourceVersion match
Expand All @@ -85,14 +85,11 @@ object Feature:

def migrateTo3(using Context): Boolean = sourceVersion == `3.0-migration`

/** If current source migrates to `version`, issue given warning message
/** If current source migrates to a version in the series denoted by `version`, issue given warning message
* and return `true`, otherwise return `false`.
*/
def warnOnMigration(msg: Message, pos: SrcPos,
version: SourceVersion)(using Context): Boolean =
if sourceVersion.isMigrating && sourceVersion.stable == version
|| (version == `3.0` || version == `3.1` || version == `3.2`) && migrateTo3
then
def warnOnMigration(msg: Message, pos: SrcPos, version: SourceVersion)(using Context): Boolean =
if sourceVersion.isMigrating && sourceVersion.series == version.series then
report.migrationWarning(msg, pos)
true
else
Expand Down
11 changes: 10 additions & 1 deletion compiler/src/dotty/tools/dotc/config/ScalaSettings.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import scala.language.unsafeNulls

import dotty.tools.dotc.config.PathResolver.Defaults
import dotty.tools.dotc.config.Settings.{Setting, SettingGroup}
import dotty.tools.dotc.config.SourceVersion
import dotty.tools.dotc.core.Contexts._
import dotty.tools.dotc.rewrites.Rewrites
import dotty.tools.io.{AbstractFile, Directory, JDK9Reflectors, PlainDirectory}
Expand Down Expand Up @@ -51,7 +52,15 @@ trait AllScalaSettings extends CommonScalaSettings, PluginSettings, VerboseSetti
/* Path related settings */
val semanticdbTarget: Setting[String] = PathSetting("-semanticdb-target", "Specify an alternative output directory for SemanticDB files.", "")

val source: Setting[String] = ChoiceSetting("-source", "source version", "source version", List("3.0", "3.1", "future", "3.0-migration", "future-migration"), "3.0", aliases = List("--source"))
private val SourceVersionOptions =
SourceVersion
.lookupSourceVersion.fromSetting
.toList
// we want `3.1-migration` to be before `3.1`, but both map to same SourceVersion
.sortBy((key, version) => (version.ordinal, !key.endsWith("-migration")))
.map((key, _) => key)

val source: Setting[String] = ChoiceSetting("-source", "source version", "source version", SourceVersionOptions, SourceVersion.defaultSourceVersion.toString, aliases = List("--source"))
val uniqid: Setting[Boolean] = BooleanSetting("-uniqid", "Uniquely tag all identifiers in debugging output.", aliases = List("--unique-id"))
val rewrite: Setting[Option[Rewrites]] = OptionSetting[Rewrites]("-rewrite", "When used in conjunction with a `...-migration` source version, rewrites sources to migrate to new version.", aliases = List("--rewrite"))
val fromTasty: Setting[Boolean] = BooleanSetting("-from-tasty", "Compile classes from tasty files. The arguments are .tasty or .jar files.", aliases = List("--from-tasty"))
Expand Down
54 changes: 49 additions & 5 deletions compiler/src/dotty/tools/dotc/config/SourceVersion.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,61 @@ package config
import core.Decorators.*
import util.Property

import scala.annotation.threadUnsafe
import dotty.tools.dotc.core.Names.Name

/** Enum expressing series of source versions,
* where each series begins by a migration version, followed by a series of stable versions.
* e.g. `3.0-migration`, `3.0`, `3.1` make one series, `3.0`, as they describe the same semantics.
* `future-migration`, then begins the another series of stable versions, `future`, because in this version we
* enable more features.
*
* @note This enum does not need to correspond to the scala.language imports. E.g. if a user imports
* `scala.language.3.1-migration`, the SourceVersion will be set to `3.1` (see `lookupSourceVersion`).
*
*/
enum SourceVersion:
case `3.0-migration`, `3.0`, `3.1`, `3.2`, `future-migration`, `future`
case `3.0-migration`, `3.0`, `3.1` // Note: do not add `3.1-migration` here, 3.1 is the same language as 3.0.
// case `3.2-migration` // !!! UNCOMMENT `3.2-migration` BEFORE RELEASING 3.2.0 if we enable features from `future`
case `3.2`
case `future-migration`, `future`

val isMigrating: Boolean = toString.endsWith("-migration")

def stable: SourceVersion =
if isMigrating then SourceVersion.values(ordinal + 1) else this
private inline def nextVersion: SourceVersion = SourceVersion.fromOrdinal(ordinal + 1)
private inline def previousVersion: SourceVersion = SourceVersion.fromOrdinal(ordinal - 1)

@threadUnsafe lazy val series: SourceVersion =
if isMigrating then nextVersion else previousVersion.series

def isAtLeast(v: SourceVersion) = stable.ordinal >= v.ordinal
def isAtLeast(v: SourceVersion) = this.series.ordinal >= v.series.ordinal

object SourceVersion extends Property.Key[SourceVersion]:
def defaultSourceVersion = `3.2`

object lookupSourceVersion:

/** A map from with keys matching the `scala.language` imports,
* and values being the corresponding `SourceVersion`, or the next
* stable version if the key ends with `-migration` and no matching SourceVersion exists.
*/
val fromSetting: Map[String, SourceVersion] =
val (migratingVersions, stableVersions) = values.partition(_.isMigrating)
val entries = stableVersions.flatMap(stable =>
val migratingKey = s"${stable}-migration"
val migratingEntry =
migratingVersions.find(_.toString == migratingKey) match
case Some(migrating) => migratingKey -> migrating
case _ => migratingKey -> stable
val stableEntry = stable.toString -> stable
stableEntry :: migratingEntry :: Nil
)
Map.from(entries)

/** An immutable array with keys matching the `scala.language` imports
* corresponding to source versions
*/
val fromImport: IArray[Name] =
IArray.from(fromSetting.iterator.map((key, _) => key.toTermName))

val allSourceVersionNames = values.toList.map(_.toString.toTermName)
end SourceVersion
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/parsing/Parsers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3140,14 +3140,14 @@ object Parsers {
in.languageImportContext = in.languageImportContext.importContext(imp, NoSymbol)
for
case ImportSelector(id @ Ident(imported), EmptyTree, _) <- selectors
if allSourceVersionNames.contains(imported)
if lookupSourceVersion.fromImport.contains(imported)
do
if !outermost then
syntaxError(i"source version import is only allowed at the toplevel", id.span)
else if ctx.compilationUnit.sourceVersion.isDefined then
syntaxError(i"duplicate source version import", id.span)
else
ctx.compilationUnit.sourceVersion = Some(SourceVersion.valueOf(imported.toString))
ctx.compilationUnit.sourceVersion = Some(lookupSourceVersion.fromSetting(imported.toString))
case None =>
imp

Expand Down
5 changes: 2 additions & 3 deletions compiler/src/dotty/tools/dotc/report.scala
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,9 @@ object report:
error(ex.toMessage, pos, sticky = true)
if ctx.settings.YdebugTypeError.value then ex.printStackTrace()

def errorOrMigrationWarning(msg: Message, pos: SrcPos = NoSourcePosition,
from: SourceVersion)(using Context): Unit =
def errorOrMigrationWarning(msg: Message, pos: SrcPos = NoSourcePosition, from: SourceVersion)(using Context): Unit =
if sourceVersion.isAtLeast(from) then
if sourceVersion.isMigrating && sourceVersion.ordinal <= from.ordinal then migrationWarning(msg, pos)
if sourceVersion.isMigrating && sourceVersion.series == from.series then migrationWarning(msg, pos)
else error(msg, pos)

def restrictionError(msg: Message, pos: SrcPos = NoSourcePosition)(using Context): Unit =
Expand Down
2 changes: 1 addition & 1 deletion compiler/test/dotty/tools/dotc/CompilationTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ class CompilationTests {
compileFilesInDir("tests/pos-special/spec-t5545", defaultOptions),
compileFilesInDir("tests/pos-special/strawman-collections", allowDeepSubtypes),
compileFilesInDir("tests/pos-special/isInstanceOf", allowDeepSubtypes.and("-Xfatal-warnings")),
compileFilesInDir("tests/new", defaultOptions.and("-source", "3.1")), // just to see whether 3.1 works
compileFilesInDir("tests/new", defaultOptions.and("-source", "3.2")), // just to see whether 3.2 works
compileFilesInDir("tests/pos-scala2", scala2CompatMode),
compileFilesInDir("tests/pos-custom-args/erased", defaultOptions.and("-language:experimental.erasedDefinitions")),
compileFilesInDir("tests/pos", defaultOptions.and("-Ysafe-init")),
Expand Down
111 changes: 111 additions & 0 deletions compiler/test/dotty/tools/dotc/config/SourceVersionTest.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package dotty.tools.dotc.config

import dotty.tools.dotc.core.Decorators.*

import org.junit.Test
import org.junit.Assert.*

import SourceVersionTest.*

class SourceVersionTest:

@Test def `importedVersion`: Unit =
assertEquals(SourceVersion.`3.0-migration`, importLanguageDot("3.0-migration"))
assertEquals(SourceVersion.`3.0`, importLanguageDot("3.0"))

// crucial that here `import scala.language.3.1-migration` sets the source version to `3.1`
assertEquals(SourceVersion.`3.1`, importLanguageDot("3.1-migration"))

assertEquals(SourceVersion.`3.1`, importLanguageDot("3.1"))
// assertEquals(SourceVersion.`3.2-migration`, importLanguageDot("3.2-migration")) // uncomment when we introduce `3.2-migration`
assertEquals(SourceVersion.`3.2`, importLanguageDot("3.2-migration")) // delete when we introduce `3.2-migration`
assertEquals(SourceVersion.`3.2`, importLanguageDot("3.2"))
assertEquals(SourceVersion.`future-migration`, importLanguageDot("future-migration"))
assertEquals(SourceVersion.`future`, importLanguageDot("future"))

@Test def `series`: Unit =
assertEquals(SourceVersion.`3.0`, SourceVersion.`3.0-migration`.series)
assertEquals(SourceVersion.`3.0`, SourceVersion.`3.0`.series)
assertEquals(SourceVersion.`3.0`, SourceVersion.`3.1`.series)
// assertEquals(SourceVersion.`3.2`, SourceVersion.`3.2-migration`.series) // uncomment when we introduce `3.2-migration`
// assertEquals(SourceVersion.`3.2`, SourceVersion.`3.2`.series) // uncomment when we introduce `3.2-migration`
assertEquals(SourceVersion.`3.0`, SourceVersion.`3.2`.series) // delete when we introduce `3.2-migration`
assertEquals(SourceVersion.`future`, SourceVersion.`future-migration`.series)
assertEquals(SourceVersion.`future`, SourceVersion.`future`.series)

@Test def `isAtLeast 3.0`: Unit =
// trues
assertTrue(SourceVersion.`3.2`.isAtLeast(SourceVersion.`3.2`)) // delete when we introduce `3.2-migration`
assertTrue(SourceVersion.`3.2`.isAtLeast(SourceVersion.`3.1`)) // delete when we introduce `3.2-migration`
assertTrue(SourceVersion.`3.2`.isAtLeast(SourceVersion.`3.0`)) // delete when we introduce `3.2-migration`
assertTrue(SourceVersion.`3.2`.isAtLeast(SourceVersion.`3.0-migration`)) // delete when we introduce `3.2-migration`
assertTrue(SourceVersion.`3.1`.isAtLeast(SourceVersion.`3.2`)) // delete when we introduce `3.2-migration`
assertTrue(SourceVersion.`3.1`.isAtLeast(SourceVersion.`3.1`))
assertTrue(SourceVersion.`3.1`.isAtLeast(SourceVersion.`3.0`))
assertTrue(SourceVersion.`3.1`.isAtLeast(SourceVersion.`3.0-migration`))
assertTrue(SourceVersion.`3.0`.isAtLeast(SourceVersion.`3.2`)) // delete when we introduce `3.2-migration`
assertTrue(SourceVersion.`3.0`.isAtLeast(SourceVersion.`3.1`))
assertTrue(SourceVersion.`3.0`.isAtLeast(SourceVersion.`3.0`))
assertTrue(SourceVersion.`3.0`.isAtLeast(SourceVersion.`3.0-migration`))
assertTrue(SourceVersion.`3.0-migration`.isAtLeast(SourceVersion.`3.2`)) // delete when we introduce `3.2-migration`
assertTrue(SourceVersion.`3.0-migration`.isAtLeast(SourceVersion.`3.1`))
assertTrue(SourceVersion.`3.0-migration`.isAtLeast(SourceVersion.`3.0`))
assertTrue(SourceVersion.`3.0-migration`.isAtLeast(SourceVersion.`3.0-migration`))


// falses
assertFalse(SourceVersion.`3.0-migration`.isAtLeast(SourceVersion.`future`))
assertFalse(SourceVersion.`3.0-migration`.isAtLeast(SourceVersion.`future-migration`))
// assertFalse(SourceVersion.`3.0-migration`.isAtLeast(SourceVersion.`3.2`)) // uncomment when we introduce `3.2-migration`
// assertFalse(SourceVersion.`3.0-migration`.isAtLeast(SourceVersion.`3.2-migration`)) // uncomment when we introduce `3.2-migration`
assertFalse(SourceVersion.`3.0`.isAtLeast(SourceVersion.`future`))
assertFalse(SourceVersion.`3.0`.isAtLeast(SourceVersion.`future-migration`))
// assertFalse(SourceVersion.`3.0`.isAtLeast(SourceVersion.`3.2`)) // uncomment when we introduce `3.2-migration`
// assertFalse(SourceVersion.`3.0`.isAtLeast(SourceVersion.`3.2-migration`)) // uncomment when we introduce `3.2-migration`
assertFalse(SourceVersion.`3.1`.isAtLeast(SourceVersion.`future`))
assertFalse(SourceVersion.`3.1`.isAtLeast(SourceVersion.`future-migration`))
// assertFalse(SourceVersion.`3.1`.isAtLeast(SourceVersion.`3.2`)) // uncomment when we introduce `3.2-migration`
// assertFalse(SourceVersion.`3.1`.isAtLeast(SourceVersion.`3.2-migration`)) // uncomment when we introduce `3.2-migration
assertFalse(SourceVersion.`3.2`.isAtLeast(SourceVersion.`future`)) // delete when we introduce `3.2-migration`
assertFalse(SourceVersion.`3.2`.isAtLeast(SourceVersion.`future-migration`)) // delete when we introduce `3.2-migration`

// @Test def `isAtLeast 3.2`: Unit = // uncomment when we introduce `3.2-migration`
// trues
// assertTrue(SourceVersion.`3.2`.isAtLeast(SourceVersion.`3.2`)) // uncomment when we introduce `3.2-migration`
// assertTrue(SourceVersion.`3.2`.isAtLeast(SourceVersion.`3.2-migration`)) // uncomment when we introduce `3.2-migration`
// assertTrue(SourceVersion.`3.2`.isAtLeast(SourceVersion.`3.1`)) // uncomment when we introduce `3.2-migration`
// assertTrue(SourceVersion.`3.2`.isAtLeast(SourceVersion.`3.0`)) // uncomment when we introduce `3.2-migration`
// assertTrue(SourceVersion.`3.2`.isAtLeast(SourceVersion.`3.0-migration`)) // uncomment when we introduce `3.2-migration`
// assertTrue(SourceVersion.`3.2-migration`.isAtLeast(SourceVersion.`3.2`)) // uncomment when we introduce `3.2-migration`
// assertTrue(SourceVersion.`3.2-migration`.isAtLeast(SourceVersion.`3.2-migration`)) // uncomment when we introduce `3.2-migration`
// assertTrue(SourceVersion.`3.2-migration`.isAtLeast(SourceVersion.`3.1`)) // uncomment when we introduce `3.2-migration`
// assertTrue(SourceVersion.`3.2-migration`.isAtLeast(SourceVersion.`3.0`)) // uncomment when we introduce `3.2-migration`
// assertTrue(SourceVersion.`3.2-migration`.isAtLeast(SourceVersion.`3.0-migration`)) // uncomment when we introduce `3.2-migration`
// falses
// assertFalse(SourceVersion.`3.2-migration`.isAtLeast(SourceVersion.`future`)) // uncomment when we introduce `3.2-migration`
// assertFalse(SourceVersion.`3.2-migration`.isAtLeast(SourceVersion.`future-migration`)) // uncomment when we introduce `3.2-migration`
// assertFalse(SourceVersion.`3.2`.isAtLeast(SourceVersion.`future`)) // uncomment when we introduce `3.2-migration`
// assertFalse(SourceVersion.`3.2`.isAtLeast(SourceVersion.`future-migration`)) // uncomment when we introduce `3.2-migration`

@Test def `isAtLeast future`: Unit =
// trues
assertTrue(SourceVersion.`future`.isAtLeast(SourceVersion.`future`))
assertTrue(SourceVersion.`future`.isAtLeast(SourceVersion.`future-migration`))
assertTrue(SourceVersion.`future`.isAtLeast(SourceVersion.`3.2`))
// assertTrue(SourceVersion.`future`.isAtLeast(SourceVersion.`3.2-migration`))
assertTrue(SourceVersion.`future`.isAtLeast(SourceVersion.`3.1`))
assertTrue(SourceVersion.`future`.isAtLeast(SourceVersion.`3.0`))
assertTrue(SourceVersion.`future`.isAtLeast(SourceVersion.`3.0-migration`))
assertTrue(SourceVersion.`future-migration`.isAtLeast(SourceVersion.`future`))
assertTrue(SourceVersion.`future-migration`.isAtLeast(SourceVersion.`future-migration`))
assertTrue(SourceVersion.`future-migration`.isAtLeast(SourceVersion.`3.2`))
// assertTrue(SourceVersion.`future-migration`.isAtLeast(SourceVersion.`3.2-migration`))
assertTrue(SourceVersion.`future-migration`.isAtLeast(SourceVersion.`3.1`))
assertTrue(SourceVersion.`future-migration`.isAtLeast(SourceVersion.`3.0`))
assertTrue(SourceVersion.`future-migration`.isAtLeast(SourceVersion.`3.0-migration`))
// no falses

object SourceVersionTest:

def importLanguageDot(feature: String): SourceVersion =
SourceVersion.lookupSourceVersion.fromSetting(feature)