Skip to content

Commit 83bfea9

Browse files
committed
introduce infrastructure for 3.2 language features
1 parent 5fcfeec commit 83bfea9

File tree

10 files changed

+184
-22
lines changed

10 files changed

+184
-22
lines changed

compiler/src/dotty/tools/dotc/config/Feature.scala

+4-7
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ object Feature:
7676
def scala2ExperimentalMacroEnabled(using Context) = enabled(scala2macros)
7777

7878
def sourceVersionSetting(using Context): SourceVersion =
79-
SourceVersion.valueOf(ctx.settings.source.value)
79+
SourceVersion.lookupSourceVersion.fromSetting(ctx.settings.source.value)
8080

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

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

88-
/** If current source migrates to `version`, issue given warning message
88+
/** If current source migrates to a version in the series denoted by `version`, issue given warning message
8989
* and return `true`, otherwise return `false`.
9090
*/
91-
def warnOnMigration(msg: Message, pos: SrcPos,
92-
version: SourceVersion)(using Context): Boolean =
93-
if sourceVersion.isMigrating && sourceVersion.stable == version
94-
|| (version == `3.0` || version == `3.1` || version == `3.2`) && migrateTo3
95-
then
91+
def warnOnMigration(msg: Message, pos: SrcPos, version: SourceVersion)(using Context): Boolean =
92+
if sourceVersion.isMigrating && sourceVersion.series == version.series then
9693
report.migrationWarning(msg, pos)
9794
true
9895
else

compiler/src/dotty/tools/dotc/config/ScalaSettings.scala

+10-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import scala.language.unsafeNulls
55

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

54-
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"))
55+
private val SourceVersionOptions =
56+
SourceVersion
57+
.lookupSourceVersion.fromSetting
58+
.toList
59+
// we want `3.1-migration` to be before `3.1`, but both map to same SourceVersion
60+
.sortBy((key, version) => (version.ordinal, !key.endsWith("-migration")))
61+
.map((key, _) => key)
62+
63+
val source: Setting[String] = ChoiceSetting("-source", "source version", "source version", SourceVersionOptions, SourceVersion.defaultSourceVersion.toString, aliases = List("--source"))
5564
val uniqid: Setting[Boolean] = BooleanSetting("-uniqid", "Uniquely tag all identifiers in debugging output.", aliases = List("--unique-id"))
5665
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"))
5766
val fromTasty: Setting[Boolean] = BooleanSetting("-from-tasty", "Compile classes from tasty files. The arguments are .tasty or .jar files.", aliases = List("--from-tasty"))

compiler/src/dotty/tools/dotc/config/SourceVersion.scala

+48-5
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,60 @@ package config
55
import core.Decorators.*
66
import util.Property
77

8+
import scala.annotation.threadUnsafe
9+
import dotty.tools.dotc.core.Names.Name
10+
11+
/** Enum expressing series of source versions,
12+
* where each series begins by a migration version, followed by a series of stable versions.
13+
* e.g. `3.0-migration`, `3.0`, `3.1` make one series, `3.0`, as they describe the same semantics.
14+
* `future-migration`, then begins the another series of stable versions, `future`, because in this version we
15+
* enable more features.
16+
*
17+
* @note This enum does not need to correspond to the scala.language imports. E.g. if a user imports
18+
* `scala.language.3.1-migration`, the SourceVersion will be set to `3.1` (see `lookupSourceVersion`).
19+
*
20+
*/
821
enum SourceVersion:
9-
case `3.0-migration`, `3.0`, `3.1`, `3.2`, `future-migration`, `future`
22+
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.
23+
case `3.2`, `3.2-migration` // !!! DELETE `3.2-migration` BEFORE RELEASING 3.2.0 if we do not enable features from `future`
24+
case `future-migration`, `future`
1025

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

13-
def stable: SourceVersion =
14-
if isMigrating then SourceVersion.values(ordinal + 1) else this
28+
private inline def nextVersion: SourceVersion = SourceVersion.fromOrdinal(ordinal + 1)
29+
private inline def previousVersion: SourceVersion = SourceVersion.fromOrdinal(ordinal - 1)
30+
31+
@threadUnsafe lazy val series: SourceVersion =
32+
if isMigrating then nextVersion else previousVersion.series
1533

16-
def isAtLeast(v: SourceVersion) = stable.ordinal >= v.ordinal
34+
def isAtLeast(v: SourceVersion) = this.series.ordinal >= v.series.ordinal
1735

1836
object SourceVersion extends Property.Key[SourceVersion]:
37+
def defaultSourceVersion = `3.2`
38+
39+
object lookupSourceVersion:
40+
41+
/** A map from with keys matching the `scala.language` imports,
42+
* and values being the corresponding `SourceVersion`, or the next
43+
* stable version if the key ends with `-migration` and no matching SourceVersion exists.
44+
*/
45+
val fromSetting: Map[String, SourceVersion] =
46+
val (migratingVersions, stableVersions) = values.partition(_.isMigrating)
47+
val entries = stableVersions.flatMap(stable =>
48+
val migratingKey = s"${stable}-migration"
49+
val migratingEntry =
50+
migratingVersions.find(_.toString == migratingKey) match
51+
case Some(migrating) => migratingKey -> migrating
52+
case _ => migratingKey -> stable
53+
val stableEntry = stable.toString -> stable
54+
stableEntry :: migratingEntry :: Nil
55+
)
56+
Map.from(entries)
57+
58+
/** An immutable array with keys matching the `scala.language` imports
59+
* corresponding to source versions
60+
*/
61+
val fromImport: IArray[Name] =
62+
IArray.from(fromSetting.iterator.map((key, _) => key.toTermName))
1963

20-
val allSourceVersionNames = values.toList.map(_.toString.toTermName)
2164
end SourceVersion

compiler/src/dotty/tools/dotc/parsing/Parsers.scala

+2-2
Original file line numberDiff line numberDiff line change
@@ -3140,14 +3140,14 @@ object Parsers {
31403140
in.languageImportContext = in.languageImportContext.importContext(imp, NoSymbol)
31413141
for
31423142
case ImportSelector(id @ Ident(imported), EmptyTree, _) <- selectors
3143-
if allSourceVersionNames.contains(imported)
3143+
if lookupSourceVersion.fromImport.contains(imported)
31443144
do
31453145
if !outermost then
31463146
syntaxError(i"source version import is only allowed at the toplevel", id.span)
31473147
else if ctx.compilationUnit.sourceVersion.isDefined then
31483148
syntaxError(i"duplicate source version import", id.span)
31493149
else
3150-
ctx.compilationUnit.sourceVersion = Some(SourceVersion.valueOf(imported.toString))
3150+
ctx.compilationUnit.sourceVersion = Some(lookupSourceVersion.fromSetting(imported.toString))
31513151
case None =>
31523152
imp
31533153

compiler/src/dotty/tools/dotc/report.scala

+2-3
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,9 @@ object report:
6767
error(ex.toMessage, pos, sticky = true)
6868
if ctx.settings.YdebugTypeError.value then ex.printStackTrace()
6969

70-
def errorOrMigrationWarning(msg: Message, pos: SrcPos = NoSourcePosition,
71-
from: SourceVersion)(using Context): Unit =
70+
def errorOrMigrationWarning(msg: Message, pos: SrcPos = NoSourcePosition, from: SourceVersion)(using Context): Unit =
7271
if sourceVersion.isAtLeast(from) then
73-
if sourceVersion.isMigrating && sourceVersion.ordinal <= from.ordinal then migrationWarning(msg, pos)
72+
if sourceVersion.isMigrating && sourceVersion.series == from.series then migrationWarning(msg, pos)
7473
else error(msg, pos)
7574

7675
def restrictionError(msg: Message, pos: SrcPos = NoSourcePosition)(using Context): Unit =

compiler/test/dotty/tools/dotc/CompilationTests.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ class CompilationTests {
3838
compileFilesInDir("tests/pos-special/spec-t5545", defaultOptions),
3939
compileFilesInDir("tests/pos-special/strawman-collections", allowDeepSubtypes),
4040
compileFilesInDir("tests/pos-special/isInstanceOf", allowDeepSubtypes.and("-Xfatal-warnings")),
41-
compileFilesInDir("tests/new", defaultOptions.and("-source", "3.1")), // just to see whether 3.1 works
41+
compileFilesInDir("tests/new", defaultOptions.and("-source", "3.2")), // just to see whether 3.2 works
4242
compileFilesInDir("tests/pos-scala2", scala2CompatMode),
4343
compileFilesInDir("tests/pos-custom-args/erased", defaultOptions.and("-language:experimental.erasedDefinitions")),
4444
compileFilesInDir("tests/pos", defaultOptions.and("-Ysafe-init")),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
package dotty.tools.dotc.config
2+
3+
import dotty.tools.dotc.core.Decorators.*
4+
5+
import org.junit.Test
6+
import org.junit.Assert.*
7+
8+
import SourceVersionTest.*
9+
10+
class SourceVersionTest:
11+
12+
@Test def `importedVersion`: Unit =
13+
assertEquals(SourceVersion.`3.0-migration`, importLanguageDot("3.0-migration"))
14+
assertEquals(SourceVersion.`3.0`, importLanguageDot("3.0"))
15+
16+
// crucial that here `import scala.language.3.1-migration` sets the source version to `3.1`
17+
assertEquals(SourceVersion.`3.1`, importLanguageDot("3.1-migration"))
18+
19+
assertEquals(SourceVersion.`3.1`, importLanguageDot("3.1"))
20+
assertEquals(SourceVersion.`3.2-migration`, importLanguageDot("3.2-migration"))
21+
assertEquals(SourceVersion.`3.2`, importLanguageDot("3.2"))
22+
assertEquals(SourceVersion.`future-migration`, importLanguageDot("future-migration"))
23+
assertEquals(SourceVersion.`future`, importLanguageDot("future"))
24+
25+
@Test def `series`: Unit =
26+
assertEquals(SourceVersion.`3.0`, SourceVersion.`3.0-migration`.series)
27+
assertEquals(SourceVersion.`3.0`, SourceVersion.`3.0`.series)
28+
assertEquals(SourceVersion.`3.0`, SourceVersion.`3.1`.series)
29+
assertEquals(SourceVersion.`3.2`, SourceVersion.`3.2-migration`.series)
30+
assertEquals(SourceVersion.`3.2`, SourceVersion.`3.2`.series)
31+
assertEquals(SourceVersion.`future`, SourceVersion.`future-migration`.series)
32+
assertEquals(SourceVersion.`future`, SourceVersion.`future`.series)
33+
34+
@Test def `isAtLeast 3.0`: Unit =
35+
// trues
36+
assertTrue(SourceVersion.`3.1`.isAtLeast(SourceVersion.`3.1`))
37+
assertTrue(SourceVersion.`3.1`.isAtLeast(SourceVersion.`3.0`))
38+
assertTrue(SourceVersion.`3.1`.isAtLeast(SourceVersion.`3.0-migration`))
39+
assertTrue(SourceVersion.`3.0`.isAtLeast(SourceVersion.`3.1`))
40+
assertTrue(SourceVersion.`3.0`.isAtLeast(SourceVersion.`3.0`))
41+
assertTrue(SourceVersion.`3.0`.isAtLeast(SourceVersion.`3.0-migration`))
42+
assertTrue(SourceVersion.`3.0-migration`.isAtLeast(SourceVersion.`3.1`))
43+
assertTrue(SourceVersion.`3.0-migration`.isAtLeast(SourceVersion.`3.0`))
44+
assertTrue(SourceVersion.`3.0-migration`.isAtLeast(SourceVersion.`3.0-migration`))
45+
// falses
46+
assertFalse(SourceVersion.`3.0-migration`.isAtLeast(SourceVersion.`future`))
47+
assertFalse(SourceVersion.`3.0-migration`.isAtLeast(SourceVersion.`future-migration`))
48+
assertFalse(SourceVersion.`3.0-migration`.isAtLeast(SourceVersion.`3.2`))
49+
assertFalse(SourceVersion.`3.0-migration`.isAtLeast(SourceVersion.`3.2-migration`))
50+
assertFalse(SourceVersion.`3.0`.isAtLeast(SourceVersion.`future`))
51+
assertFalse(SourceVersion.`3.0`.isAtLeast(SourceVersion.`future-migration`))
52+
assertFalse(SourceVersion.`3.0`.isAtLeast(SourceVersion.`3.2`))
53+
assertFalse(SourceVersion.`3.0`.isAtLeast(SourceVersion.`3.2-migration`))
54+
assertFalse(SourceVersion.`3.1`.isAtLeast(SourceVersion.`future`))
55+
assertFalse(SourceVersion.`3.1`.isAtLeast(SourceVersion.`future-migration`))
56+
assertFalse(SourceVersion.`3.1`.isAtLeast(SourceVersion.`3.2`))
57+
assertFalse(SourceVersion.`3.1`.isAtLeast(SourceVersion.`3.2-migration`))
58+
59+
@Test def `isAtLeast 3.2`: Unit =
60+
// trues
61+
assertTrue(SourceVersion.`3.2`.isAtLeast(SourceVersion.`3.2`))
62+
assertTrue(SourceVersion.`3.2`.isAtLeast(SourceVersion.`3.2-migration`))
63+
assertTrue(SourceVersion.`3.2`.isAtLeast(SourceVersion.`3.1`))
64+
assertTrue(SourceVersion.`3.2`.isAtLeast(SourceVersion.`3.0`))
65+
assertTrue(SourceVersion.`3.2`.isAtLeast(SourceVersion.`3.0-migration`))
66+
assertTrue(SourceVersion.`3.2-migration`.isAtLeast(SourceVersion.`3.2`))
67+
assertTrue(SourceVersion.`3.2-migration`.isAtLeast(SourceVersion.`3.2-migration`))
68+
assertTrue(SourceVersion.`3.2-migration`.isAtLeast(SourceVersion.`3.1`))
69+
assertTrue(SourceVersion.`3.2-migration`.isAtLeast(SourceVersion.`3.0`))
70+
assertTrue(SourceVersion.`3.2-migration`.isAtLeast(SourceVersion.`3.0-migration`))
71+
// falses
72+
assertFalse(SourceVersion.`3.2-migration`.isAtLeast(SourceVersion.`future`))
73+
assertFalse(SourceVersion.`3.2-migration`.isAtLeast(SourceVersion.`future-migration`))
74+
assertFalse(SourceVersion.`3.2`.isAtLeast(SourceVersion.`future`))
75+
assertFalse(SourceVersion.`3.2`.isAtLeast(SourceVersion.`future-migration`))
76+
77+
@Test def `isAtLeast future`: Unit =
78+
// trues
79+
assertTrue(SourceVersion.`future`.isAtLeast(SourceVersion.`future`))
80+
assertTrue(SourceVersion.`future`.isAtLeast(SourceVersion.`future-migration`))
81+
assertTrue(SourceVersion.`future`.isAtLeast(SourceVersion.`3.2`))
82+
assertTrue(SourceVersion.`future`.isAtLeast(SourceVersion.`3.2-migration`))
83+
assertTrue(SourceVersion.`future`.isAtLeast(SourceVersion.`3.1`))
84+
assertTrue(SourceVersion.`future`.isAtLeast(SourceVersion.`3.0`))
85+
assertTrue(SourceVersion.`future`.isAtLeast(SourceVersion.`3.0-migration`))
86+
assertTrue(SourceVersion.`future-migration`.isAtLeast(SourceVersion.`future`))
87+
assertTrue(SourceVersion.`future-migration`.isAtLeast(SourceVersion.`future-migration`))
88+
assertTrue(SourceVersion.`future-migration`.isAtLeast(SourceVersion.`3.2`))
89+
assertTrue(SourceVersion.`future-migration`.isAtLeast(SourceVersion.`3.2-migration`))
90+
assertTrue(SourceVersion.`future-migration`.isAtLeast(SourceVersion.`3.1`))
91+
assertTrue(SourceVersion.`future-migration`.isAtLeast(SourceVersion.`3.0`))
92+
assertTrue(SourceVersion.`future-migration`.isAtLeast(SourceVersion.`3.0-migration`))
93+
// no falses
94+
95+
object SourceVersionTest:
96+
97+
def importLanguageDot(feature: String): SourceVersion =
98+
SourceVersion.lookupSourceVersion.fromSetting(feature)

library/src/scala/runtime/stdLibPatches/language.scala

+17-3
Original file line numberDiff line numberDiff line change
@@ -162,19 +162,33 @@ object language:
162162
@compileTimeOnly("`3.1` can only be used at compile time in import statements")
163163
object `3.1`
164164

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

173172
/** Set source version to 3.2
174173
*
175-
* @see [[https://scalacenter.github.io/scala-3-migration-guide/docs/scala-3-migration-mode]]
174+
* @see [[https://docs.scala-lang.org/scala3/guides/migration/compatibility-intro.html]]
176175
*/
177176
@compileTimeOnly("`3.2` can only be used at compile time in import statements")
178177
object `3.2`
178+
179+
/* This can be added when we go to 3.3
180+
/** Set source version to 3.3-migration.
181+
*
182+
* @see [[https://docs.scala-lang.org/scala3/guides/migration/compatibility-intro.html]]
183+
*/
184+
@compileTimeOnly("`3.3-migration` can only be used at compile time in import statements")
185+
object `3.3-migration`
186+
187+
/** Set source version to 3.3
188+
*
189+
* @see [[https://docs.scala-lang.org/scala3/guides/migration/compatibility-intro.html]]
190+
*/
191+
@compileTimeOnly("`3.3` can only be used at compile time in import statements")
192+
object `3.3`
179193
*/
180194
end language
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
import language.`3.2-migration`

tests/pos/source-import-3-2.scala

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
import language.`3.2`

0 commit comments

Comments
 (0)