Skip to content

Commit

Permalink
introduce infrastructure for 3.2 language features
Browse files Browse the repository at this point in the history
  • Loading branch information
bishabosha committed Mar 15, 2022
1 parent 5fcfeec commit 83bfea9
Show file tree
Hide file tree
Showing 10 changed files with 184 additions and 22 deletions.
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
53 changes: 48 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,60 @@ 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`, `3.2-migration` // !!! DELETE `3.2-migration` BEFORE RELEASING 3.2.0 if we do not enable features from `future`
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
98 changes: 98 additions & 0 deletions compiler/test/dotty/tools/dotc/config/SourceVersionTest.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
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"))
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)
assertEquals(SourceVersion.`3.2`, SourceVersion.`3.2`.series)
assertEquals(SourceVersion.`future`, SourceVersion.`future-migration`.series)
assertEquals(SourceVersion.`future`, SourceVersion.`future`.series)

@Test def `isAtLeast 3.0`: Unit =
// trues
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.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.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`))
assertFalse(SourceVersion.`3.0-migration`.isAtLeast(SourceVersion.`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`))
assertFalse(SourceVersion.`3.0`.isAtLeast(SourceVersion.`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`))
assertFalse(SourceVersion.`3.1`.isAtLeast(SourceVersion.`3.2-migration`))

@Test def `isAtLeast 3.2`: Unit =
// trues
assertTrue(SourceVersion.`3.2`.isAtLeast(SourceVersion.`3.2`))
assertTrue(SourceVersion.`3.2`.isAtLeast(SourceVersion.`3.2-migration`))
assertTrue(SourceVersion.`3.2`.isAtLeast(SourceVersion.`3.1`))
assertTrue(SourceVersion.`3.2`.isAtLeast(SourceVersion.`3.0`))
assertTrue(SourceVersion.`3.2`.isAtLeast(SourceVersion.`3.0-migration`))
assertTrue(SourceVersion.`3.2-migration`.isAtLeast(SourceVersion.`3.2`))
assertTrue(SourceVersion.`3.2-migration`.isAtLeast(SourceVersion.`3.2-migration`))
assertTrue(SourceVersion.`3.2-migration`.isAtLeast(SourceVersion.`3.1`))
assertTrue(SourceVersion.`3.2-migration`.isAtLeast(SourceVersion.`3.0`))
assertTrue(SourceVersion.`3.2-migration`.isAtLeast(SourceVersion.`3.0-migration`))
// falses
assertFalse(SourceVersion.`3.2-migration`.isAtLeast(SourceVersion.`future`))
assertFalse(SourceVersion.`3.2-migration`.isAtLeast(SourceVersion.`future-migration`))
assertFalse(SourceVersion.`3.2`.isAtLeast(SourceVersion.`future`))
assertFalse(SourceVersion.`3.2`.isAtLeast(SourceVersion.`future-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)
20 changes: 17 additions & 3 deletions library/src/scala/runtime/stdLibPatches/language.scala
Original file line number Diff line number Diff line change
Expand Up @@ -162,19 +162,33 @@ object language:
@compileTimeOnly("`3.1` can only be used at compile time in import statements")
object `3.1`

/* This can be added when we go to 3.2
/** Set source version to 3.2-migration.
*
* @see [[https://scalacenter.github.io/scala-3-migration-guide/docs/scala-3-migration-mode]]
* @see [[https://docs.scala-lang.org/scala3/guides/migration/compatibility-intro.html]]
*/
@compileTimeOnly("`3.2-migration` can only be used at compile time in import statements")
object `3.2-migration`

/** Set source version to 3.2
*
* @see [[https://scalacenter.github.io/scala-3-migration-guide/docs/scala-3-migration-mode]]
* @see [[https://docs.scala-lang.org/scala3/guides/migration/compatibility-intro.html]]
*/
@compileTimeOnly("`3.2` can only be used at compile time in import statements")
object `3.2`

/* This can be added when we go to 3.3
/** Set source version to 3.3-migration.
*
* @see [[https://docs.scala-lang.org/scala3/guides/migration/compatibility-intro.html]]
*/
@compileTimeOnly("`3.3-migration` can only be used at compile time in import statements")
object `3.3-migration`
/** Set source version to 3.3
*
* @see [[https://docs.scala-lang.org/scala3/guides/migration/compatibility-intro.html]]
*/
@compileTimeOnly("`3.3` can only be used at compile time in import statements")
object `3.3`
*/
end language
1 change: 1 addition & 0 deletions tests/pos/source-import-3-2-migration.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import language.`3.2-migration`
1 change: 1 addition & 0 deletions tests/pos/source-import-3-2.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import language.`3.2`

0 comments on commit 83bfea9

Please sign in to comment.