From a3981309a59306c30cf384b05005b8bab3526e89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Marks?= Date: Thu, 28 Oct 2021 13:02:24 +0200 Subject: [PATCH 01/22] Run test groups with different compiler versions --- .../dotty/tools/vulpix/ParallelTesting.scala | 93 +++++++++++++------ tests/pos/target_version/A_1_t3.0.scala | 2 + tests/pos/target_version/B_2_v3.0.scala | 2 + 3 files changed, 69 insertions(+), 28 deletions(-) create mode 100644 tests/pos/target_version/A_1_t3.0.scala create mode 100644 tests/pos/target_version/B_2_v3.0.scala diff --git a/compiler/test/dotty/tools/vulpix/ParallelTesting.scala b/compiler/test/dotty/tools/vulpix/ParallelTesting.scala index 4ba1229feecb..cb26492b8e3b 100644 --- a/compiler/test/dotty/tools/vulpix/ParallelTesting.scala +++ b/compiler/test/dotty/tools/vulpix/ParallelTesting.scala @@ -4,6 +4,7 @@ package vulpix import java.io.{File => JFile, IOException} import java.lang.System.{lineSeparator => EOL} +import java.net.URL import java.nio.file.StandardCopyOption.REPLACE_EXISTING import java.nio.file.{Files, NoSuchFileException, Path, Paths} import java.nio.charset.StandardCharsets @@ -127,10 +128,10 @@ trait ParallelTesting extends RunnerOrchestration { self => } sb.toString + "\n\n" } - case self: SeparateCompilationSource => { + case self: SeparateCompilationSource => { // TODO: this is incorrect when using other versions of compiler val command = sb.toString val fsb = new StringBuilder(command) - self.compilationGroups.foreach { files => + self.compilationGroups.foreach { (_, files) => files.map(_.getPath).foreach { path => fsb.append(delimiter) lineLen = 8 @@ -173,31 +174,28 @@ trait ParallelTesting extends RunnerOrchestration { self => flags: TestFlags, outDir: JFile ) extends TestSource { - - /** Get the files grouped by `_X` as a list of groups, files missing this - * suffix will be put into the same group. - * Files in each group are sorted alphabetically. - * - * Filters out all none source files - */ - def compilationGroups: List[Array[JFile]] = - dir - .listFiles - .groupBy { file => - val name = file.getName - Try { - val potentialNumber = name - .substring(0, name.lastIndexOf('.')) - .reverse.takeWhile(_ != '_').reverse - - potentialNumber.toInt.toString - } - .toOption - .getOrElse("") - } - .toList.sortBy(_._1).map(_._2.filter(isSourceFile).sorted) - - def sourceFiles: Array[JFile] = compilationGroups.flatten.toArray + case class Group(ordinal: Int, compiler: String, target: String) + + def compilationGroups: List[(Group, Array[JFile])] = + val Name = """[^_]*((?:_.*)*)\.\w+""".r + val Target = """t([\d\.]+)""".r + val Compiler = """v([\d\.]+)""".r + val Ordinal = """(\d+)""".r + def groupFor(file: JFile): Group = + val Name(annotPart) = file.getName + val annots = annotPart.split("_") + val ordinal = annots.collectFirst { case Ordinal(n) => n.toInt }.getOrElse(Int.MinValue) + val target = annots.collectFirst { case Target(t) => t }.getOrElse("") + val compiler = annots.collectFirst { case Compiler(c) => c}.getOrElse("") + Group(ordinal, compiler, target) + + dir.listFiles + .groupBy(groupFor) + .toList + .sortBy { (g, _) => (g.ordinal, g.compiler, g.target) } + .map { (g, f) => (g, f.sorted) } + + def sourceFiles = compilationGroups.map(_._2).flatten.toArray } private trait CompilationLogic { this: Test => @@ -216,7 +214,12 @@ trait ParallelTesting extends RunnerOrchestration { self => List(reporter) case testSource @ SeparateCompilationSource(_, dir, flags, outDir) => - testSource.compilationGroups.map(files => compile(files, flags, suppressErrors, outDir)) // TODO? only `compile` option? + testSource.compilationGroups.map { (group, files) => + if group.compiler.isEmpty then + compile(files, flags, suppressErrors, outDir) + else + compileWithOtherCompiler(group.compiler, files, flags, outDir) + } }) final def countErrorsAndWarnings(reporters: Seq[TestReporter]): (Int, Int) = @@ -500,6 +503,18 @@ trait ParallelTesting extends RunnerOrchestration { self => reporter } + protected def compileWithOtherCompiler(compiler: String, files: Array[JFile], flags: TestFlags, targetDir: JFile): TestReporter = + val reporter = TestReporter.reporter(realStdout, ERROR) // TODO: do some reporting + + val command = Array(getCompiler(compiler).toString + "/bin/scalac") ++ flags.and("-d", targetDir.getPath).all ++ files.map(_.getPath) + val process = Runtime.getRuntime.exec(command) + val output = Source.fromInputStream(process.getErrorStream).mkString + if process.waitFor() != 0 then + echo(s"\nCompilation using Scala $compiler failed: \n$output") + fail() + + reporter + protected def compileFromTasty(flags0: TestFlags, suppressErrors: Boolean, targetDir: JFile): TestReporter = { val tastyOutput = new JFile(targetDir.getPath + "_from-tasty") tastyOutput.mkdir() @@ -1371,4 +1386,26 @@ object ParallelTesting { def isTastyFile(f: JFile): Boolean = f.getName.endsWith(".tasty") + + def getCompiler(version: String): JFile = + val patch = trueVersions(version) + val dir = cache.resolve(s"scala3-${patch}").toFile + if dir.exists then + dir + else + import scala.sys.process._ + val zipPath = cache.resolve(s"scala3-$patch.zip") + (URL(s"https://github.com/lampepfl/dotty/releases/download/$patch/scala3-$patch.zip") #>> zipPath.toFile #&& s"unzip $zipPath -d $cache").!! + dir + + + private val trueVersions = Map( + "3.0" -> "3.0.2", + "3.1" -> "3.1.0" + ) + + private lazy val cache = + val dir = Files.createTempDirectory("dotty.tests") + // dir.toFile.deleteOnExit() + dir } diff --git a/tests/pos/target_version/A_1_t3.0.scala b/tests/pos/target_version/A_1_t3.0.scala new file mode 100644 index 000000000000..39402d87587f --- /dev/null +++ b/tests/pos/target_version/A_1_t3.0.scala @@ -0,0 +1,2 @@ +trait A: + def a: Int \ No newline at end of file diff --git a/tests/pos/target_version/B_2_v3.0.scala b/tests/pos/target_version/B_2_v3.0.scala new file mode 100644 index 000000000000..bb4ae81da4b4 --- /dev/null +++ b/tests/pos/target_version/B_2_v3.0.scala @@ -0,0 +1,2 @@ +object B extends A: + def a = 7 \ No newline at end of file From 14719cf5fa3e9742e9955279d0176490431f9e9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Marks?= Date: Mon, 8 Nov 2021 18:09:02 +0100 Subject: [PATCH 02/22] Add -scala-release flag --- compiler/src/dotty/tools/dotc/config/ScalaSettings.scala | 1 + compiler/test/dotty/tools/vulpix/ParallelTesting.scala | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index 45cc3406a736..615b17277259 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -101,6 +101,7 @@ trait CommonScalaSettings: val silentWarnings: Setting[Boolean] = BooleanSetting("-nowarn", "Silence all warnings.", aliases = List("--no-warnings")) val release: Setting[String] = ChoiceSetting("-release", "release", "Compile code with classes specific to the given version of the Java platform available on the classpath and emit bytecode for this version.", ScalaSettings.supportedReleaseVersions, "", aliases = List("--release")) + val scalaRelease: Setting[String] = ChoiceSetting("-scala-release", "release", "Emit TASTy files that can be consumed by specified version of the compiler.", List("3.0", "3.1"), "3.1", aliases = List("--scala-release")) val deprecation: Setting[Boolean] = BooleanSetting("-deprecation", "Emit warning and location for usages of deprecated APIs.", aliases = List("--deprecation")) val feature: Setting[Boolean] = BooleanSetting("-feature", "Emit warning and location for usages of features that should be imported explicitly.", aliases = List("--feature")) val explain: Setting[Boolean] = BooleanSetting("-explain", "Explain errors in more detail.", aliases = List("--explain")) diff --git a/compiler/test/dotty/tools/vulpix/ParallelTesting.scala b/compiler/test/dotty/tools/vulpix/ParallelTesting.scala index cb26492b8e3b..65365e36f588 100644 --- a/compiler/test/dotty/tools/vulpix/ParallelTesting.scala +++ b/compiler/test/dotty/tools/vulpix/ParallelTesting.scala @@ -215,10 +215,11 @@ trait ParallelTesting extends RunnerOrchestration { self => case testSource @ SeparateCompilationSource(_, dir, flags, outDir) => testSource.compilationGroups.map { (group, files) => + val flags1 = if group.target.isEmpty then flags else flags.and("-scala-release", group.target) if group.compiler.isEmpty then - compile(files, flags, suppressErrors, outDir) + compile(files, flags1, suppressErrors, outDir) else - compileWithOtherCompiler(group.compiler, files, flags, outDir) + compileWithOtherCompiler(group.compiler, files, flags1, outDir) } }) From 99e0cc28fee5529d8910563dd245c7798b57ffaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Marks?= Date: Mon, 8 Nov 2021 20:06:51 +0100 Subject: [PATCH 03/22] Output specified tasty version --- compiler/src/dotty/tools/dotc/core/Contexts.scala | 10 +++++++++- .../dotty/tools/dotc/core/tasty/TastyPickler.scala | 12 ++++++++---- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Contexts.scala b/compiler/src/dotty/tools/dotc/core/Contexts.scala index 3e3f8a800ebf..cdb32272b9cf 100644 --- a/compiler/src/dotty/tools/dotc/core/Contexts.scala +++ b/compiler/src/dotty/tools/dotc/core/Contexts.scala @@ -38,6 +38,7 @@ import xsbti.AnalysisCallback import plugins._ import java.util.concurrent.atomic.AtomicInteger import java.nio.file.InvalidPathException +import dotty.tools.tasty.TastyFormat object Contexts { @@ -480,7 +481,14 @@ object Contexts { /** A new context that summarizes an import statement */ def importContext(imp: Import[?], sym: Symbol): FreshContext = - fresh.setImportInfo(ImportInfo(sym, imp.selectors, imp.expr)) + fresh.setImportInfo(ImportInfo(sym, imp.selectors, imp.expr)) + + def tastyVersion: (Int, Int, Int) = + base.settings.scalaRelease.value match + case "" => + import TastyFormat.* + (MajorVersion, MinorVersion, ExperimentalVersion) + case s"$maj.$min" => (maj.toInt + 25, min.toInt, 0) /** Is the debug option set? */ def debug: Boolean = base.settings.Ydebug.value diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TastyPickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TastyPickler.scala index 3aeb7e6f35c9..147eaef4eb7f 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TastyPickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TastyPickler.scala @@ -7,6 +7,8 @@ import dotty.tools.tasty.{TastyBuffer, TastyFormat, TastyHash} import TastyFormat._ import TastyBuffer._ +import Contexts._ + import collection.mutable import core.Symbols.{Symbol, ClassSymbol} import ast.tpd @@ -30,10 +32,12 @@ class TastyPickler(val rootCls: ClassSymbol) { def newSection(name: String, buf: TastyBuffer): Unit = sections += ((nameBuffer.nameIndex(name.toTermName), buf)) - def assembleParts(): Array[Byte] = { + def assembleParts()(using Context): Array[Byte] = { def lengthWithLength(buf: TastyBuffer) = buf.length + natSize(buf.length) + val (majorVersion, minorVersion, experimentalVersion) = ctx.tastyVersion + nameBuffer.assemble() sections.foreach(_._2.assemble()) @@ -48,9 +52,9 @@ class TastyPickler(val rootCls: ClassSymbol) { val headerBuffer = { val buf = new TastyBuffer(header.length + TastyPickler.versionStringBytes.length + 32) for (ch <- header) buf.writeByte(ch.toByte) - buf.writeNat(MajorVersion) - buf.writeNat(MinorVersion) - buf.writeNat(ExperimentalVersion) + buf.writeNat(majorVersion) + buf.writeNat(minorVersion) + buf.writeNat(experimentalVersion) buf.writeNat(TastyPickler.versionStringBytes.length) buf.writeBytes(TastyPickler.versionStringBytes, TastyPickler.versionStringBytes.length) buf.writeUncompressedLong(uuidLow) From 1c17ea85b2900512e16e2c56c49fb2dc263ba1fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Marks?= Date: Tue, 9 Nov 2021 11:24:20 +0100 Subject: [PATCH 04/22] Handle classpath substitution in classpath --- .../dotty/tools/vulpix/ParallelTesting.scala | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/compiler/test/dotty/tools/vulpix/ParallelTesting.scala b/compiler/test/dotty/tools/vulpix/ParallelTesting.scala index 65365e36f588..0a8f330c2309 100644 --- a/compiler/test/dotty/tools/vulpix/ParallelTesting.scala +++ b/compiler/test/dotty/tools/vulpix/ParallelTesting.scala @@ -505,9 +505,21 @@ trait ParallelTesting extends RunnerOrchestration { self => } protected def compileWithOtherCompiler(compiler: String, files: Array[JFile], flags: TestFlags, targetDir: JFile): TestReporter = + val compilerDir = getCompiler(compiler).toString + + def substituteClasspath(old: String): String = + old.split(JFile.pathSeparator).map { o => + if JFile(o) == JFile(Properties.dottyLibrary) then s"$compilerDir/lib/scala3-library_3-${trueVersions(compiler)}.jar" + else o + }.mkString(JFile.pathSeparator) + + val flags1 = flags.copy(defaultClassPath = substituteClasspath(flags.defaultClassPath)) + .withClasspath(targetDir.getPath) + .and("-d", targetDir.getPath) + val reporter = TestReporter.reporter(realStdout, ERROR) // TODO: do some reporting - val command = Array(getCompiler(compiler).toString + "/bin/scalac") ++ flags.and("-d", targetDir.getPath).all ++ files.map(_.getPath) + val command = Array(compilerDir + "/bin/scalac") ++ flags1.all ++ files.map(_.getPath) val process = Runtime.getRuntime.exec(command) val output = Source.fromInputStream(process.getErrorStream).mkString if process.waitFor() != 0 then @@ -1400,7 +1412,7 @@ object ParallelTesting { dir - private val trueVersions = Map( + val trueVersions = Map( "3.0" -> "3.0.2", "3.1" -> "3.1.0" ) From 1455c4ba41b84a296ee64aa0bde5bb52a55673af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Marks?= Date: Wed, 10 Nov 2021 13:29:25 +0100 Subject: [PATCH 05/22] Fix pickler tests --- .../test/dotty/tools/vulpix/ParallelTesting.scala | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/compiler/test/dotty/tools/vulpix/ParallelTesting.scala b/compiler/test/dotty/tools/vulpix/ParallelTesting.scala index 0a8f330c2309..4c25cab3ca19 100644 --- a/compiler/test/dotty/tools/vulpix/ParallelTesting.scala +++ b/compiler/test/dotty/tools/vulpix/ParallelTesting.scala @@ -176,13 +176,12 @@ trait ParallelTesting extends RunnerOrchestration { self => ) extends TestSource { case class Group(ordinal: Int, compiler: String, target: String) - def compilationGroups: List[(Group, Array[JFile])] = - val Name = """[^_]*((?:_.*)*)\.\w+""".r + lazy val compilationGroups: List[(Group, Array[JFile])] = val Target = """t([\d\.]+)""".r val Compiler = """v([\d\.]+)""".r val Ordinal = """(\d+)""".r def groupFor(file: JFile): Group = - val Name(annotPart) = file.getName + val annotPart = file.getName.dropWhile(_ != '_').stripSuffix(".scala").stripSuffix(".java") val annots = annotPart.split("_") val ordinal = annots.collectFirst { case Ordinal(n) => n.toInt }.getOrElse(Int.MinValue) val target = annots.collectFirst { case Target(t) => t }.getOrElse("") @@ -190,6 +189,7 @@ trait ParallelTesting extends RunnerOrchestration { self => Group(ordinal, compiler, target) dir.listFiles + .filter(isSourceFile) .groupBy(groupFor) .toList .sortBy { (g, _) => (g.ordinal, g.compiler, g.target) } @@ -1255,14 +1255,14 @@ trait ParallelTesting extends RunnerOrchestration { self => val (dirs, files) = compilationTargets(sourceDir, fileFilter) val isPicklerTest = flags.options.contains("-Ytest-pickler") - def ignoreDir(dir: JFile): Boolean = { + def picklerDirFilter(source: SeparateCompilationSource): Boolean = { // Pickler tests stop after pickler not producing class/tasty files. The second part of the compilation // will not be able to compile due to the missing artifacts from the first part. - isPicklerTest && dir.listFiles().exists(file => file.getName.endsWith("_2.scala") || file.getName.endsWith("_2.java")) + !isPicklerTest || source.compilationGroups.length == 1 } val targets = files.map(f => JointCompilationSource(testGroup.name, Array(f), flags, createOutputDirsForFile(f, sourceDir, outDir))) ++ - dirs.collect { case dir if !ignoreDir(dir) => SeparateCompilationSource(testGroup.name, dir, flags, createOutputDirsForDir(dir, sourceDir, outDir)) } + dirs.map { dir => SeparateCompilationSource(testGroup.name, dir, flags, createOutputDirsForDir(dir, sourceDir, outDir)) }.filter(picklerDirFilter) // Create a CompilationTest and let the user decide whether to execute a pos or a neg test new CompilationTest(targets) From 22e8f8fc14cd0db4e4e6483bc89f14bda01878e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Marks?= Date: Mon, 15 Nov 2021 17:50:10 +0100 Subject: [PATCH 06/22] Change `-scala-release` flag into proper VersionSetting --- compiler/src/dotty/tools/dotc/config/ScalaSettings.scala | 2 +- compiler/src/dotty/tools/dotc/core/Contexts.scala | 6 ++++-- compiler/test/dotty/tools/vulpix/ParallelTesting.scala | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index 615b17277259..6abc9b2e2435 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -101,7 +101,7 @@ trait CommonScalaSettings: val silentWarnings: Setting[Boolean] = BooleanSetting("-nowarn", "Silence all warnings.", aliases = List("--no-warnings")) val release: Setting[String] = ChoiceSetting("-release", "release", "Compile code with classes specific to the given version of the Java platform available on the classpath and emit bytecode for this version.", ScalaSettings.supportedReleaseVersions, "", aliases = List("--release")) - val scalaRelease: Setting[String] = ChoiceSetting("-scala-release", "release", "Emit TASTy files that can be consumed by specified version of the compiler.", List("3.0", "3.1"), "3.1", aliases = List("--scala-release")) + val scalaRelease: Setting[ScalaVersion] = VersionSetting("-scala-release", "Emit TASTy files that can be consumed by specified version of the compiler.") val deprecation: Setting[Boolean] = BooleanSetting("-deprecation", "Emit warning and location for usages of deprecated APIs.", aliases = List("--deprecation")) val feature: Setting[Boolean] = BooleanSetting("-feature", "Emit warning and location for usages of features that should be imported explicitly.", aliases = List("--feature")) val explain: Setting[Boolean] = BooleanSetting("-explain", "Explain errors in more detail.", aliases = List("--explain")) diff --git a/compiler/src/dotty/tools/dotc/core/Contexts.scala b/compiler/src/dotty/tools/dotc/core/Contexts.scala index cdb32272b9cf..ce827f6af59c 100644 --- a/compiler/src/dotty/tools/dotc/core/Contexts.scala +++ b/compiler/src/dotty/tools/dotc/core/Contexts.scala @@ -39,6 +39,7 @@ import plugins._ import java.util.concurrent.atomic.AtomicInteger import java.nio.file.InvalidPathException import dotty.tools.tasty.TastyFormat +import dotty.tools.dotc.config.{ NoScalaVersion, SpecificScalaVersion, AnyScalaVersion } object Contexts { @@ -485,10 +486,11 @@ object Contexts { def tastyVersion: (Int, Int, Int) = base.settings.scalaRelease.value match - case "" => + case NoScalaVersion => import TastyFormat.* (MajorVersion, MinorVersion, ExperimentalVersion) - case s"$maj.$min" => (maj.toInt + 25, min.toInt, 0) + case SpecificScalaVersion(maj, min, _, _) => (maj.toInt + 25, min.toInt, 0) + case AnyScalaVersion => (28, 0, 0) // 3.0 /** Is the debug option set? */ def debug: Boolean = base.settings.Ydebug.value diff --git a/compiler/test/dotty/tools/vulpix/ParallelTesting.scala b/compiler/test/dotty/tools/vulpix/ParallelTesting.scala index 4c25cab3ca19..537606afd584 100644 --- a/compiler/test/dotty/tools/vulpix/ParallelTesting.scala +++ b/compiler/test/dotty/tools/vulpix/ParallelTesting.scala @@ -215,7 +215,7 @@ trait ParallelTesting extends RunnerOrchestration { self => case testSource @ SeparateCompilationSource(_, dir, flags, outDir) => testSource.compilationGroups.map { (group, files) => - val flags1 = if group.target.isEmpty then flags else flags.and("-scala-release", group.target) + val flags1 = if group.target.isEmpty then flags else flags.and(s"-scala-release:${group.target}") if group.compiler.isEmpty then compile(files, flags1, suppressErrors, outDir) else From ab3efcf96c44d9da4d4045850a5bd7cdb9ec1cb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Marks?= Date: Tue, 16 Nov 2021 08:41:25 +0100 Subject: [PATCH 07/22] Add `since` annotation --- .../src/dotty/tools/dotc/core/Definitions.scala | 1 + .../src/dotty/tools/dotc/typer/RefChecks.scala | 16 ++++++++++++++++ library/src/scala/annotation/since.scala | 4 ++++ library/src/scala/quoted/Quotes.scala | 3 ++- tests/neg/since/since_t3.0.scala | 6 ++++++ 5 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 library/src/scala/annotation/since.scala create mode 100644 tests/neg/since/since_t3.0.scala diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 464c7900a54f..2e9c81332200 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -948,6 +948,7 @@ class Definitions { @tu lazy val FunctionalInterfaceAnnot: ClassSymbol = requiredClass("java.lang.FunctionalInterface") @tu lazy val TargetNameAnnot: ClassSymbol = requiredClass("scala.annotation.targetName") @tu lazy val VarargsAnnot: ClassSymbol = requiredClass("scala.annotation.varargs") + @tu lazy val SinceAnnot: ClassSymbol = requiredClass("scala.annotation.since") @tu lazy val JavaRepeatableAnnot: ClassSymbol = requiredClass("java.lang.annotation.Repeatable") diff --git a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala index dcc4ac09d5ec..d8cc59d2d430 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala @@ -911,6 +911,7 @@ object RefChecks { private def checkUndesiredProperties(sym: Symbol, pos: SrcPos)(using Context): Unit = checkDeprecated(sym, pos) checkExperimental(sym, pos) + checkSinceAnnot(sym, pos) val xMigrationValue = ctx.settings.Xmigration.value if xMigrationValue != NoScalaVersion then @@ -970,6 +971,21 @@ object RefChecks { for annot <- sym.annotations if annot.symbol.isExperimental do Feature.checkExperimentalDef(annot.symbol, annot.tree) + private def checkSinceAnnot(sym: Symbol, pos: SrcPos)(using Context): Unit = + for + annot <- sym.getAnnotation(defn.SinceAnnot) + version <- annot.argumentConstantString(0) + do + val releaseVersion = ctx.settings.scalaRelease.value + ScalaVersion.parse(version) match + case Success(symVersion) if symVersion > ctx.settings.scalaRelease.value => + report.error( + i"$sym was added in Scala $version, therefore it cannot be used in the code targeting Scala ${releaseVersion.unparse}", + pos) + case Failure(ex) => + report.warning(i"$sym has an unparsable version number: ${ex.getMessage}", pos) + case _ => + /** If @migration is present (indicating that the symbol has changed semantics between versions), * emit a warning. */ diff --git a/library/src/scala/annotation/since.scala b/library/src/scala/annotation/since.scala new file mode 100644 index 000000000000..082639dcfdd8 --- /dev/null +++ b/library/src/scala/annotation/since.scala @@ -0,0 +1,4 @@ +package scala.annotation + +/** An annotation that is used to mark symbols added to the stdlib after 3.0 release */ +class since(scalaRelease: String) extends scala.annotation.StaticAnnotation \ No newline at end of file diff --git a/library/src/scala/quoted/Quotes.scala b/library/src/scala/quoted/Quotes.scala index f5decee041f4..7f097c099e90 100644 --- a/library/src/scala/quoted/Quotes.scala +++ b/library/src/scala/quoted/Quotes.scala @@ -1,6 +1,6 @@ package scala.quoted -import scala.annotation.experimental +import scala.annotation.{ experimental, since } import scala.reflect.TypeTest /** Current Quotes in scope @@ -3704,6 +3704,7 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => def memberField(name: String): Symbol /** Get named non-private fields declared or inherited */ + @since("3.1") def fieldMember(name: String): Symbol /** Get all non-private fields declared or inherited */ diff --git a/tests/neg/since/since_t3.0.scala b/tests/neg/since/since_t3.0.scala new file mode 100644 index 000000000000..a902952b4f1d --- /dev/null +++ b/tests/neg/since/since_t3.0.scala @@ -0,0 +1,6 @@ +import scala.quoted.* + +def useQuotes(using Quotes) = + import quotes.reflect.* + def useFieldMember(s: Symbol) = s.fieldMember("abc") // error + useFieldMember \ No newline at end of file From 44ffb6845f281548c795c4f0c440963e8425ac8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Marks?= Date: Tue, 16 Nov 2021 13:47:41 +0100 Subject: [PATCH 08/22] Add since annotations to all symbols added in 3.1 --- library/src/scala/CanEqual.scala | 6 +++++- library/src/scala/Selectable.scala | 3 ++- library/src/scala/Tuple.scala | 4 +++- library/src/scala/annotation/experimental.scala | 1 + .../scala/annotation/internal/ErasedParam.scala | 1 + .../internal/ProvisionalSuperClass.scala | 1 + library/src/scala/quoted/Quotes.scala | 14 ++++++++++++++ library/src/scala/quoted/Type.scala | 3 ++- 8 files changed, 29 insertions(+), 4 deletions(-) diff --git a/library/src/scala/CanEqual.scala b/library/src/scala/CanEqual.scala index dfb4ec7d2bfc..f09df1455c2c 100644 --- a/library/src/scala/CanEqual.scala +++ b/library/src/scala/CanEqual.scala @@ -1,6 +1,6 @@ package scala -import annotation.implicitNotFound +import annotation.{ implicitNotFound, since } import scala.collection.{Seq, Set} /** A marker trait indicating that values of type `L` can be compared to values of type `R`. */ @@ -29,14 +29,18 @@ object CanEqual { // The next 6 definitions can go into the companion objects of their corresponding // classes. For now they are here in order not to have to touch the // source code of these classes + @since("3.1") given canEqualSeqs[T, U](using eq: CanEqual[T, U]): CanEqual[Seq[T], Seq[U]] = derived given canEqualSeq[T](using eq: CanEqual[T, T]): CanEqual[Seq[T], Seq[T]] = derived // for `case Nil` in pattern matching given canEqualSet[T, U](using eq: CanEqual[T, U]): CanEqual[Set[T], Set[U]] = derived + @since("3.1") given canEqualOptions[T, U](using eq: CanEqual[T, U]): CanEqual[Option[T], Option[U]] = derived + @since("3.1") given canEqualOption[T](using eq: CanEqual[T, T]): CanEqual[Option[T], Option[T]] = derived // for `case None` in pattern matching + @since("3.1") given canEqualEither[L1, R1, L2, R2]( using eqL: CanEqual[L1, L2], eqR: CanEqual[R1, R2] ): CanEqual[Either[L1, R1], Either[L2, R2]] = derived diff --git a/library/src/scala/Selectable.scala b/library/src/scala/Selectable.scala index 7a520ee6af64..536c760988ab 100644 --- a/library/src/scala/Selectable.scala +++ b/library/src/scala/Selectable.scala @@ -1,6 +1,6 @@ package scala -import scala.annotation.experimental +import scala.annotation.since /** A marker trait for objects that support structural selection via * `selectDynamic` and `applyDynamic` @@ -49,5 +49,6 @@ object Selectable: * the additional restriction that the signatures of the refinement and * the definition that implements the refinment must match. */ + @since("3.1") trait WithoutPreciseParameterTypes extends Selectable end Selectable diff --git a/library/src/scala/Tuple.scala b/library/src/scala/Tuple.scala index c509551ef12e..cc26061c203c 100644 --- a/library/src/scala/Tuple.scala +++ b/library/src/scala/Tuple.scala @@ -1,6 +1,6 @@ package scala -import annotation.{experimental, showAsInfix} +import annotation.{experimental, showAsInfix, since} import compiletime._ import compiletime.ops.int._ @@ -260,7 +260,9 @@ object Tuple { def fromProductTyped[P <: Product](p: P)(using m: scala.deriving.Mirror.ProductOf[P]): m.MirroredElemTypes = runtime.Tuples.fromProduct(p).asInstanceOf[m.MirroredElemTypes] + @since("3.1") given canEqualEmptyTuple: CanEqual[EmptyTuple, EmptyTuple] = CanEqual.derived + @since("3.1") given canEqualTuple[H1, T1 <: Tuple, H2, T2 <: Tuple]( using eqHead: CanEqual[H1, H2], eqTail: CanEqual[T1, T2] ): CanEqual[H1 *: T1, H2 *: T2] = CanEqual.derived diff --git a/library/src/scala/annotation/experimental.scala b/library/src/scala/annotation/experimental.scala index 3d7a023176e3..abd8012fbe54 100644 --- a/library/src/scala/annotation/experimental.scala +++ b/library/src/scala/annotation/experimental.scala @@ -5,4 +5,5 @@ package scala.annotation * @see [[https://dotty.epfl.ch/docs/reference/other-new-features/experimental-defs]] * @syntax markdown */ +@since("3.1") class experimental extends StaticAnnotation diff --git a/library/src/scala/annotation/internal/ErasedParam.scala b/library/src/scala/annotation/internal/ErasedParam.scala index a5ed192820c3..769b9a710d5f 100644 --- a/library/src/scala/annotation/internal/ErasedParam.scala +++ b/library/src/scala/annotation/internal/ErasedParam.scala @@ -2,4 +2,5 @@ package scala.annotation package internal /** An annotation produced by Namer to indicate an erased parameter */ +@since("3.1") final class ErasedParam() extends Annotation diff --git a/library/src/scala/annotation/internal/ProvisionalSuperClass.scala b/library/src/scala/annotation/internal/ProvisionalSuperClass.scala index 147b7b087c5a..a9748540de56 100644 --- a/library/src/scala/annotation/internal/ProvisionalSuperClass.scala +++ b/library/src/scala/annotation/internal/ProvisionalSuperClass.scala @@ -2,5 +2,6 @@ package scala.annotation package internal /** An annotation to record a provisional super class */ +@since("3.1") class ProvisionalSuperClass extends StaticAnnotation diff --git a/library/src/scala/quoted/Quotes.scala b/library/src/scala/quoted/Quotes.scala index 7f097c099e90..88eecad65485 100644 --- a/library/src/scala/quoted/Quotes.scala +++ b/library/src/scala/quoted/Quotes.scala @@ -68,6 +68,7 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => * Emits an error and aborts if the expression does not represent a value or possibly contains side effects. * Otherwise returns the value. */ + @since("3.1") def valueOrAbort(using FromExpr[T]): T end extension @@ -803,15 +804,18 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => end IdentMethods /** Pattern representing a `_` wildcard. */ + @since("3.1") type Wildcard <: Ident /** `TypeTest` that allows testing at runtime in a pattern match if a `Tree` is a `Wildcard` */ + @since("3.1") given WildcardTypeTest: TypeTest[Tree, Wildcard] /** Module object of `type Wildcard` */ val Wildcard: WildcardModule /** Methods of the module object `val Wildcard` */ + @since("3.1") trait WildcardModule { this: Wildcard.type => /** Create a tree representing a `_` wildcard. */ def apply(): Wildcard @@ -1614,15 +1618,19 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => end WhileMethods /** `TypeTest` that allows testing at runtime in a pattern match if a `Tree` is a `TypedOrTest` */ + @since("3.1") given TypedOrTestTypeTest: TypeTest[Tree, TypedOrTest] /** Tree representing a type ascription or type test pattern `x: T` in the source code. */ + @since("3.1") type TypedOrTest <: Tree /** Module object of `type TypedOrTest` */ + @since("3.1") val TypedOrTest: TypedOrTestModule /** Methods of the module object `val TypedOrTest` */ + @since("3.1") trait TypedOrTestModule { this: TypedOrTest.type => /** Create a type ascription `: ` */ @@ -1635,9 +1643,11 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => } /** Makes extension methods on `TypedOrTest` available without any imports */ + @since("3.1") given TypedOrTestMethods: TypedOrTestMethods /** Extension methods of `TypedOrTest` */ + @since("3.1") trait TypedOrTestMethods: extension (self: TypedOrTest) def tree: Tree @@ -2165,6 +2175,7 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => /** Methods of the module object `val Unapply` */ trait UnapplyModule { this: Unapply.type => /** Create an `Unapply` tree representing a pattern `()(using )` */ + @since("3.1") def apply(fun: Term, implicits: List[Term], patterns: List[Tree]): Unapply /** Copy an `Unapply` tree representing a pattern `()(using )` */ def copy(original: Tree)(fun: Term, implicits: List[Term], patterns: List[Tree]): Unapply @@ -2278,6 +2289,7 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => /** Is this a given parameter clause `(using X1, ..., Xn)` or `(using x1: X1, ..., xn: Xn)` */ def isGiven: Boolean /** Is this a erased parameter clause `(erased x1: X1, ..., xn: Xn)` */ + @since("3.1") def isErased: Boolean end TermParamClauseMethods @@ -2563,6 +2575,7 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => * * @return true if the dealiased type of `self` is `TupleN[T1, T2, ..., Tn]` */ + @since("3.1") def isTupleN: Boolean /** The type , reduced if possible */ @@ -3712,6 +3725,7 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => def memberFields: List[Symbol] /** Get all non-private fields declared or inherited */ + @since("3.1") def fieldMembers: List[Symbol] /** Get non-private named methods defined directly inside the class */ diff --git a/library/src/scala/quoted/Type.scala b/library/src/scala/quoted/Type.scala index b2516171b4ae..6f1f7aefede7 100644 --- a/library/src/scala/quoted/Type.scala +++ b/library/src/scala/quoted/Type.scala @@ -1,6 +1,6 @@ package scala.quoted -import scala.annotation.{compileTimeOnly, experimental} +import scala.annotation.{compileTimeOnly, experimental, since} /** Type (or type constructor) `T` needed contextually when using `T` in a quoted expression `'{... T ...}` */ abstract class Type[T <: AnyKind] private[scala]: @@ -68,6 +68,7 @@ object Type: * ``` * @syntax markdown */ + @since("3.1") def valueOfTuple[T <: Tuple](using Type[T])(using Quotes): Option[T] = valueOfTuple(quotes.reflect.TypeRepr.of[T]).asInstanceOf[Option[T]] From f75c5ee701df7b9737df6c3d102dccab3a141014 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Marks?= Date: Tue, 16 Nov 2021 15:11:53 +0100 Subject: [PATCH 09/22] Restrict scope of `since` annotation --- library/src/scala/annotation/since.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/src/scala/annotation/since.scala b/library/src/scala/annotation/since.scala index 082639dcfdd8..7fe57a0b4d96 100644 --- a/library/src/scala/annotation/since.scala +++ b/library/src/scala/annotation/since.scala @@ -1,4 +1,4 @@ package scala.annotation /** An annotation that is used to mark symbols added to the stdlib after 3.0 release */ -class since(scalaRelease: String) extends scala.annotation.StaticAnnotation \ No newline at end of file +private[scala] class since(scalaRelease: String) extends scala.annotation.StaticAnnotation \ No newline at end of file From f1bae32bc51918f3a8b4eb1778dda8886b57f6f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Marks?= Date: Wed, 17 Nov 2021 13:10:18 +0100 Subject: [PATCH 10/22] Check for types with `since` annotation --- compiler/src/dotty/tools/dotc/typer/RefChecks.scala | 11 +++++++++++ tests/neg/since/since_t3.0.scala | 7 ++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala index d8cc59d2d430..3b03e7d91256 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala @@ -986,6 +986,15 @@ object RefChecks { report.warning(i"$sym has an unparsable version number: ${ex.getMessage}", pos) case _ => + private def checkSinceAnnotInSignature(sym: Symbol, pos: SrcPos)(using Context) = + new TypeTraverser: + def traverse(tp: Type) = + if tp.typeSymbol.hasAnnotation(defn.SinceAnnot) then + checkSinceAnnot(tp.typeSymbol, pos) + else + traverseChildren(tp) + .traverse(sym.info) + /** If @migration is present (indicating that the symbol has changed semantics between versions), * emit a warning. */ @@ -1272,6 +1281,7 @@ class RefChecks extends MiniPhase { thisPhase => checkDeprecatedOvers(tree) checkExperimentalAnnots(tree.symbol) checkExperimentalSignature(tree.symbol, tree) + checkSinceAnnotInSignature(tree.symbol, tree) val sym = tree.symbol if (sym.exists && sym.owner.isTerm) { tree.rhs match { @@ -1294,6 +1304,7 @@ class RefChecks extends MiniPhase { thisPhase => checkDeprecatedOvers(tree) checkExperimentalAnnots(tree.symbol) checkExperimentalSignature(tree.symbol, tree) + checkSinceAnnotInSignature(tree.symbol, tree) checkImplicitNotFoundAnnotation.defDef(tree.symbol.denot) checkUnaryMethods(tree.symbol) tree diff --git a/tests/neg/since/since_t3.0.scala b/tests/neg/since/since_t3.0.scala index a902952b4f1d..79d709c1b4e7 100644 --- a/tests/neg/since/since_t3.0.scala +++ b/tests/neg/since/since_t3.0.scala @@ -2,5 +2,10 @@ import scala.quoted.* def useQuotes(using Quotes) = import quotes.reflect.* + def useFieldMember(s: Symbol) = s.fieldMember("abc") // error - useFieldMember \ No newline at end of file + def getWildcard: Wildcard = ??? // error + def acceptWildcard(w: Wildcard) = "" // error + def boundByWildcard[T <: Wildcard]: T = ??? // error + + (useFieldMember, getWildcard, acceptWildcard) // error \ No newline at end of file From 465ef4c68677cc42be714b53f73116059dd72bc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Marks?= Date: Mon, 22 Nov 2021 12:01:46 +0100 Subject: [PATCH 11/22] wip --- tests/neg/since/since_t3.0.scala | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/neg/since/since_t3.0.scala b/tests/neg/since/since_t3.0.scala index 79d709c1b4e7..1970034180bf 100644 --- a/tests/neg/since/since_t3.0.scala +++ b/tests/neg/since/since_t3.0.scala @@ -8,4 +8,9 @@ def useQuotes(using Quotes) = def acceptWildcard(w: Wildcard) = "" // error def boundByWildcard[T <: Wildcard]: T = ??? // error - (useFieldMember, getWildcard, acceptWildcard) // error \ No newline at end of file + (useFieldMember, getWildcard, acceptWildcard) // error + +def equality = + import language.strictEquality + val a = 1 :: Nil == Nil + val b = Some(5) == None // error \ No newline at end of file From 42160dc82bfcfdee5096f272f69cfb4dd396cf9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pa=C5=82ka?= Date: Mon, 20 Dec 2021 17:31:45 +0100 Subject: [PATCH 12/22] Finalize work on @since and -scala-release: * Add missing @since annotations to definitions * Add missing checks for @since * Add more tests --- .../tools/dotc/config/ScalaRelease.scala | 21 +++++++ .../tools/dotc/config/ScalaSettings.scala | 5 +- .../src/dotty/tools/dotc/core/Contexts.scala | 27 +++++---- .../dotc/core/classfile/ClassfileParser.scala | 20 ++++++- .../dotc/core/tasty/DottyUnpickler.scala | 4 +- .../tools/dotc/core/tasty/TastyPickler.scala | 10 ++-- .../tools/dotc/quoted/PickledQuotes.scala | 2 +- .../dotty/tools/dotc/transform/Pickler.scala | 2 +- .../dotty/tools/dotc/typer/RefChecks.scala | 17 +++--- .../dotc/core/tasty/CommentPicklingTest.scala | 2 +- .../dotty/tools/vulpix/ParallelTesting.scala | 56 +++++++++---------- library/src/scala/annotation/since.scala | 2 +- tasty/src/dotty/tools/tasty/TastyFormat.scala | 14 ++--- .../tools/tasty/TastyHeaderUnpickler.scala | 25 ++++----- .../src/dotty/tools/tasty/TastyVersion.scala | 16 ++++++ .../tools/tasty/TastyVersionFormatTest.scala | 28 +++++----- .../neg/forwardCompat-export/Test_r3.0.scala | 5 ++ .../Test_r3.0.scala | 27 +++++++++ .../Test_r3.0.scala | 1 + .../Bar_2_r3.0.scala | 1 + .../Foo_1_r3.1.scala | 1 + tests/neg/since/since_t3.0.scala | 16 ------ .../Test_r3.0.scala | 2 + tests/pos/target_version/A_1_t3.0.scala | 2 - tests/pos/target_version/B_2_v3.0.scala | 2 - .../Equality_1_r3.0.scala | 5 ++ .../Test_2_c3.0.2.scala | 3 + .../Imports_1_r3.0.scala | 14 +++++ .../Test_2_c3.0.2.scala | 7 +++ 29 files changed, 218 insertions(+), 119 deletions(-) create mode 100644 compiler/src/dotty/tools/dotc/config/ScalaRelease.scala create mode 100644 tasty/src/dotty/tools/tasty/TastyVersion.scala create mode 100644 tests/neg/forwardCompat-export/Test_r3.0.scala create mode 100644 tests/neg/forwardCompat-illegalReferences/Test_r3.0.scala create mode 100644 tests/neg/forwardCompat-leakingImplicit/Test_r3.0.scala create mode 100644 tests/neg/forwardCompat-rejectNewerTasty/Bar_2_r3.0.scala create mode 100644 tests/neg/forwardCompat-rejectNewerTasty/Foo_1_r3.1.scala delete mode 100644 tests/neg/since/since_t3.0.scala create mode 100644 tests/pos/forwardCompat-excludedExport/Test_r3.0.scala delete mode 100644 tests/pos/target_version/A_1_t3.0.scala delete mode 100644 tests/pos/target_version/B_2_v3.0.scala create mode 100644 tests/run/forwardCompat-strictEquals/Equality_1_r3.0.scala create mode 100644 tests/run/forwardCompat-strictEquals/Test_2_c3.0.2.scala create mode 100644 tests/run/forwardCompat-unusedImport/Imports_1_r3.0.scala create mode 100644 tests/run/forwardCompat-unusedImport/Test_2_c3.0.2.scala diff --git a/compiler/src/dotty/tools/dotc/config/ScalaRelease.scala b/compiler/src/dotty/tools/dotc/config/ScalaRelease.scala new file mode 100644 index 000000000000..9634768d309f --- /dev/null +++ b/compiler/src/dotty/tools/dotc/config/ScalaRelease.scala @@ -0,0 +1,21 @@ +package dotty.tools.dotc.config + +import dotty.tools.tasty.TastyVersion + +enum ScalaRelease(val majorVersion: Int, val minorVersion: Int) extends Ordered[ScalaRelease]: + case Release3_0 extends ScalaRelease(3, 0) + case Release3_1 extends ScalaRelease(3, 1) + + def show = s"$majorVersion.$minorVersion" + + def compare(that: ScalaRelease) = + val ord = summon[Ordering[(Int, Int)]] + ord.compare((majorVersion, minorVersion), (that.majorVersion, that.minorVersion)) + +object ScalaRelease: + def latest = Release3_1 + + def parse(name: String) = name match + case "3.0" => Some(Release3_0) + case "3.1" => Some(Release3_1) + case _ => None diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index 6abc9b2e2435..33407d7540e3 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -26,6 +26,9 @@ object ScalaSettings: (minTargetVersion to maxVersion).toList.map(_.toString) else List(minTargetVersion).map(_.toString) + def supportedScalaReleaseVersions: List[String] = + ScalaRelease.values.toList.map(_.show) + def defaultClasspath: String = sys.env.getOrElse("CLASSPATH", ".") def defaultPageWidth: Int = { @@ -101,7 +104,7 @@ trait CommonScalaSettings: val silentWarnings: Setting[Boolean] = BooleanSetting("-nowarn", "Silence all warnings.", aliases = List("--no-warnings")) val release: Setting[String] = ChoiceSetting("-release", "release", "Compile code with classes specific to the given version of the Java platform available on the classpath and emit bytecode for this version.", ScalaSettings.supportedReleaseVersions, "", aliases = List("--release")) - val scalaRelease: Setting[ScalaVersion] = VersionSetting("-scala-release", "Emit TASTy files that can be consumed by specified version of the compiler.") + val scalaRelease: Setting[String] = ChoiceSetting("-scala-release", "release", "Emit TASTy files that can be consumed by specified version of the compiler.", ScalaSettings.supportedScalaReleaseVersions, "", aliases = List("--scala-release")) val deprecation: Setting[Boolean] = BooleanSetting("-deprecation", "Emit warning and location for usages of deprecated APIs.", aliases = List("--deprecation")) val feature: Setting[Boolean] = BooleanSetting("-feature", "Emit warning and location for usages of features that should be imported explicitly.", aliases = List("--feature")) val explain: Setting[Boolean] = BooleanSetting("-explain", "Explain errors in more detail.", aliases = List("--explain")) diff --git a/compiler/src/dotty/tools/dotc/core/Contexts.scala b/compiler/src/dotty/tools/dotc/core/Contexts.scala index ce827f6af59c..1265063be7e6 100644 --- a/compiler/src/dotty/tools/dotc/core/Contexts.scala +++ b/compiler/src/dotty/tools/dotc/core/Contexts.scala @@ -24,7 +24,7 @@ import io.{AbstractFile, NoAbstractFile, PlainFile, Path} import scala.io.Codec import collection.mutable import printing._ -import config.{JavaPlatform, SJSPlatform, Platform, ScalaSettings} +import config.{JavaPlatform, SJSPlatform, Platform, ScalaSettings, ScalaRelease} import classfile.ReusableDataReader import StdNames.nme @@ -38,8 +38,8 @@ import xsbti.AnalysisCallback import plugins._ import java.util.concurrent.atomic.AtomicInteger import java.nio.file.InvalidPathException -import dotty.tools.tasty.TastyFormat -import dotty.tools.dotc.config.{ NoScalaVersion, SpecificScalaVersion, AnyScalaVersion } +import dotty.tools.tasty.{ TastyFormat, TastyVersion } +import dotty.tools.dotc.config.{ NoScalaVersion, SpecificScalaVersion, AnyScalaVersion, ScalaBuild } object Contexts { @@ -484,13 +484,20 @@ object Contexts { def importContext(imp: Import[?], sym: Symbol): FreshContext = fresh.setImportInfo(ImportInfo(sym, imp.selectors, imp.expr)) - def tastyVersion: (Int, Int, Int) = - base.settings.scalaRelease.value match - case NoScalaVersion => - import TastyFormat.* - (MajorVersion, MinorVersion, ExperimentalVersion) - case SpecificScalaVersion(maj, min, _, _) => (maj.toInt + 25, min.toInt, 0) - case AnyScalaVersion => (28, 0, 0) // 3.0 + def scalaRelease: ScalaRelease = + val releaseName = base.settings.scalaRelease.value + if releaseName.nonEmpty then ScalaRelease.parse(releaseName).get else ScalaRelease.latest + + def tastyVersion: TastyVersion = + import math.Ordered.orderingToOrdered + val latestRelease = ScalaRelease.latest + val specifiedRelease = scalaRelease + if ((specifiedRelease.majorVersion, specifiedRelease.minorVersion) < (latestRelease.majorVersion, latestRelease.majorVersion)) then + // This is needed to make -scala-release a no-op when set to the latest release for unstable versions of the compiler + // (which might have the tasty format version numbers set to higher values before they're decreased during a release) + TastyVersion.fromStableScalaRelease(specifiedRelease.majorVersion, specifiedRelease.minorVersion) + else + TastyVersion.compilerVersion /** Is the debug option set? */ def debug: Boolean = base.settings.Ydebug.value diff --git a/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala b/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala index c0140c5b8641..162d625538b6 100644 --- a/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala +++ b/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala @@ -3,7 +3,7 @@ package dotc package core package classfile -import dotty.tools.tasty.{ TastyReader, TastyHeaderUnpickler } +import dotty.tools.tasty.{ TastyFormat, TastyReader, TastyHeaderUnpickler, TastyVersion } import Contexts._, Symbols._, Types._, Names._, StdNames._, NameOps._, Scopes._, Decorators._ import SymDenotations._, unpickleScala2.Scala2Unpickler._, Constants._, Annotations._, util.Spans._ @@ -884,7 +884,7 @@ class ClassfileParser( } def unpickleTASTY(bytes: Array[Byte]): Some[Embedded] = { - val unpickler = new tasty.DottyUnpickler(bytes) + val unpickler = new tasty.DottyUnpickler(bytes, ctx.tastyVersion) unpickler.enter(roots = Set(classRoot, moduleRoot, moduleRoot.sourceModule))(using ctx.withSource(util.NoSource)) Some(unpickler) } @@ -950,9 +950,23 @@ class ClassfileParser( if (tastyBytes.nonEmpty) { val reader = new TastyReader(bytes, 0, 16) val expectedUUID = new UUID(reader.readUncompressedLong(), reader.readUncompressedLong()) - val tastyUUID = new TastyHeaderUnpickler(tastyBytes).readHeader() + val tastyHeader = new TastyHeaderUnpickler(tastyBytes).readFullHeader() + val fileTastyVersion = TastyVersion(tastyHeader.majorVersion, tastyHeader.minorVersion, tastyHeader.experimentalVersion) + val tastyUUID = tastyHeader.uuid if (expectedUUID != tastyUUID) report.warning(s"$classfile is out of sync with its TASTy file. Loaded TASTy file. Try cleaning the project to fix this issue", NoSourcePosition) + + val tastyFilePath = classfile.path.stripSuffix(".class") + ".tasty" + val isTastyCompatible = + TastyFormat.isVersionCompatible(fileVersion = fileTastyVersion, compilerVersion = ctx.tastyVersion) || + classRoot.symbol.showFullName.startsWith("scala.") // References to stdlib are considered safe because we check the values of @since annotations + + if !isTastyCompatible then + report.error(s"""The class ${classRoot.symbol.showFullName} cannot be loaded from file ${tastyFilePath} because its TASTy format version is too high + |highest allowed: ${ctx.tastyVersion.show} + |found: ${fileTastyVersion.show} + """.stripMargin) + return unpickleTASTY(tastyBytes) } } diff --git a/compiler/src/dotty/tools/dotc/core/tasty/DottyUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/DottyUnpickler.scala index ffcab8dc7a90..a9dac87413fe 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/DottyUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/DottyUnpickler.scala @@ -10,7 +10,7 @@ import classfile.ClassfileParser import Names.SimpleName import TreeUnpickler.UnpickleMode -import dotty.tools.tasty.TastyReader +import dotty.tools.tasty.{ TastyReader, TastyVersion } import dotty.tools.tasty.TastyFormat.{ASTsSection, PositionsSection, CommentsSection} object DottyUnpickler { @@ -39,7 +39,7 @@ object DottyUnpickler { * @param bytes the bytearray containing the Tasty file from which we unpickle * @param mode the tasty file contains package (TopLevel), an expression (Term) or a type (TypeTree) */ -class DottyUnpickler(bytes: Array[Byte], mode: UnpickleMode = UnpickleMode.TopLevel) extends ClassfileParser.Embedded with tpd.TreeProvider { +class DottyUnpickler(bytes: Array[Byte], maximalTastyVersion: TastyVersion, mode: UnpickleMode = UnpickleMode.TopLevel) extends ClassfileParser.Embedded with tpd.TreeProvider { import tpd._ import DottyUnpickler._ diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TastyPickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TastyPickler.scala index 147eaef4eb7f..74b8b357e00c 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TastyPickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TastyPickler.scala @@ -36,14 +36,14 @@ class TastyPickler(val rootCls: ClassSymbol) { def lengthWithLength(buf: TastyBuffer) = buf.length + natSize(buf.length) - val (majorVersion, minorVersion, experimentalVersion) = ctx.tastyVersion - nameBuffer.assemble() sections.foreach(_._2.assemble()) val nameBufferHash = TastyHash.pjwHash64(nameBuffer.bytes) val treeSectionHash +: otherSectionHashes = sections.map(x => TastyHash.pjwHash64(x._2.bytes)) + val tastyVersion = ctx.tastyVersion + // Hash of name table and tree val uuidLow: Long = nameBufferHash ^ treeSectionHash // Hash of positions, comments and any additional section @@ -52,9 +52,9 @@ class TastyPickler(val rootCls: ClassSymbol) { val headerBuffer = { val buf = new TastyBuffer(header.length + TastyPickler.versionStringBytes.length + 32) for (ch <- header) buf.writeByte(ch.toByte) - buf.writeNat(majorVersion) - buf.writeNat(minorVersion) - buf.writeNat(experimentalVersion) + buf.writeNat(tastyVersion.major) + buf.writeNat(tastyVersion.minor) + buf.writeNat(tastyVersion.experimental) buf.writeNat(TastyPickler.versionStringBytes.length) buf.writeBytes(TastyPickler.versionStringBytes, TastyPickler.versionStringBytes.length) buf.writeUncompressedLong(uuidLow) diff --git a/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala b/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala index 6347e6bc4dec..8c72177a76d0 100644 --- a/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala +++ b/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala @@ -193,7 +193,7 @@ object PickledQuotes { quotePickling.println(s"**** unpickling quote from TASTY\n${TastyPrinter.showContents(bytes, ctx.settings.color.value == "never")}") val mode = if (isType) UnpickleMode.TypeTree else UnpickleMode.Term - val unpickler = new DottyUnpickler(bytes, mode) + val unpickler = new DottyUnpickler(bytes, ctx.tastyVersion, mode) unpickler.enter(Set.empty) val tree = unpickler.tree diff --git a/compiler/src/dotty/tools/dotc/transform/Pickler.scala b/compiler/src/dotty/tools/dotc/transform/Pickler.scala index a7724b4a5a31..7752fc8f2a55 100644 --- a/compiler/src/dotty/tools/dotc/transform/Pickler.scala +++ b/compiler/src/dotty/tools/dotc/transform/Pickler.scala @@ -125,7 +125,7 @@ class Pickler extends Phase { ctx.initialize() val unpicklers = for ((cls, pickler) <- picklers) yield { - val unpickler = new DottyUnpickler(pickler.assembleParts()) + val unpickler = new DottyUnpickler(pickler.assembleParts(), ctx.tastyVersion) unpickler.enter(roots = Set.empty) cls -> unpickler } diff --git a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala index 3b03e7d91256..eb43580e8839 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala @@ -15,7 +15,7 @@ import ast._ import MegaPhase._ import config.Printers.{checks, noPrinter} import scala.util.{Try, Failure, Success} -import config.{ScalaVersion, NoScalaVersion} +import config.{ScalaVersion, NoScalaVersion, ScalaRelease} import Decorators._ import OverridingPairs.isOverridingPair import typer.ErrorReporting._ @@ -976,14 +976,14 @@ object RefChecks { annot <- sym.getAnnotation(defn.SinceAnnot) version <- annot.argumentConstantString(0) do - val releaseVersion = ctx.settings.scalaRelease.value - ScalaVersion.parse(version) match - case Success(symVersion) if symVersion > ctx.settings.scalaRelease.value => + val releaseVersion = ctx.scalaRelease + ScalaRelease.parse(version) match + case Some(symVersion) if symVersion > releaseVersion => report.error( - i"$sym was added in Scala $version, therefore it cannot be used in the code targeting Scala ${releaseVersion.unparse}", + i"$sym was added in Scala $version, therefore it cannot be used in the code targeting Scala ${releaseVersion.show}", pos) - case Failure(ex) => - report.warning(i"$sym has an unparsable version number: ${ex.getMessage}", pos) + case None => + report.warning(i"$sym has an unparsable release name: '${version}'", pos) case _ => private def checkSinceAnnotInSignature(sym: Symbol, pos: SrcPos)(using Context) = @@ -1320,6 +1320,7 @@ class RefChecks extends MiniPhase { thisPhase => checkImplicitNotFoundAnnotation.template(cls.classDenot) checkExperimentalInheritance(cls) checkExperimentalAnnots(cls) + checkSinceAnnot(cls, cls.srcPos) tree } catch { @@ -1371,9 +1372,11 @@ class RefChecks extends MiniPhase { thisPhase => case TypeRef(_, sym: Symbol) => checkDeprecated(sym, tree.srcPos) checkExperimental(sym, tree.srcPos) + checkSinceAnnot(sym, tree.srcPos) case TermRef(_, sym: Symbol) => checkDeprecated(sym, tree.srcPos) checkExperimental(sym, tree.srcPos) + checkSinceAnnot(sym, tree.srcPos) case _ => } tree diff --git a/compiler/test/dotty/tools/dotc/core/tasty/CommentPicklingTest.scala b/compiler/test/dotty/tools/dotc/core/tasty/CommentPicklingTest.scala index ef5d5c5fd657..064eedd6dceb 100644 --- a/compiler/test/dotty/tools/dotc/core/tasty/CommentPicklingTest.scala +++ b/compiler/test/dotty/tools/dotc/core/tasty/CommentPicklingTest.scala @@ -114,7 +114,7 @@ class CommentPicklingTest { implicit val ctx: Context = setup(args, initCtx).map(_._2).getOrElse(initCtx) ctx.initialize() val trees = files.flatMap { f => - val unpickler = new DottyUnpickler(f.toByteArray()) + val unpickler = new DottyUnpickler(f.toByteArray(), ctx.tastyVersion) unpickler.enter(roots = Set.empty) unpickler.rootTrees(using ctx) } diff --git a/compiler/test/dotty/tools/vulpix/ParallelTesting.scala b/compiler/test/dotty/tools/vulpix/ParallelTesting.scala index 537606afd584..0fcab853ece9 100644 --- a/compiler/test/dotty/tools/vulpix/ParallelTesting.scala +++ b/compiler/test/dotty/tools/vulpix/ParallelTesting.scala @@ -128,7 +128,7 @@ trait ParallelTesting extends RunnerOrchestration { self => } sb.toString + "\n\n" } - case self: SeparateCompilationSource => { // TODO: this is incorrect when using other versions of compiler + case self: SeparateCompilationSource => { // TODO: this won't work when using other versions of compiler val command = sb.toString val fsb = new StringBuilder(command) self.compilationGroups.foreach { (_, files) => @@ -174,25 +174,25 @@ trait ParallelTesting extends RunnerOrchestration { self => flags: TestFlags, outDir: JFile ) extends TestSource { - case class Group(ordinal: Int, compiler: String, target: String) + case class Group(ordinal: Int, compiler: String, release: String) lazy val compilationGroups: List[(Group, Array[JFile])] = - val Target = """t([\d\.]+)""".r - val Compiler = """v([\d\.]+)""".r + val Release = """r([\d\.]+)""".r + val Compiler = """c([\d\.]+)""".r val Ordinal = """(\d+)""".r def groupFor(file: JFile): Group = - val annotPart = file.getName.dropWhile(_ != '_').stripSuffix(".scala").stripSuffix(".java") - val annots = annotPart.split("_") - val ordinal = annots.collectFirst { case Ordinal(n) => n.toInt }.getOrElse(Int.MinValue) - val target = annots.collectFirst { case Target(t) => t }.getOrElse("") - val compiler = annots.collectFirst { case Compiler(c) => c}.getOrElse("") - Group(ordinal, compiler, target) + val groupSuffix = file.getName.dropWhile(_ != '_').stripSuffix(".scala").stripSuffix(".java") + val groupSuffixParts = groupSuffix.split("_") + val ordinal = groupSuffixParts.collectFirst { case Ordinal(n) => n.toInt }.getOrElse(Int.MinValue) + val release = groupSuffixParts.collectFirst { case Release(r) => r }.getOrElse("") + val compiler = groupSuffixParts.collectFirst { case Compiler(c) => c }.getOrElse("") + Group(ordinal, compiler, release) dir.listFiles .filter(isSourceFile) .groupBy(groupFor) .toList - .sortBy { (g, _) => (g.ordinal, g.compiler, g.target) } + .sortBy { (g, _) => (g.ordinal, g.compiler, g.release) } .map { (g, f) => (g, f.sorted) } def sourceFiles = compilationGroups.map(_._2).flatten.toArray @@ -215,7 +215,7 @@ trait ParallelTesting extends RunnerOrchestration { self => case testSource @ SeparateCompilationSource(_, dir, flags, outDir) => testSource.compilationGroups.map { (group, files) => - val flags1 = if group.target.isEmpty then flags else flags.and(s"-scala-release:${group.target}") + val flags1 = if group.release.isEmpty then flags else flags.and(s"-scala-release:${group.release}") if group.compiler.isEmpty then compile(files, flags1, suppressErrors, outDir) else @@ -509,7 +509,7 @@ trait ParallelTesting extends RunnerOrchestration { self => def substituteClasspath(old: String): String = old.split(JFile.pathSeparator).map { o => - if JFile(o) == JFile(Properties.dottyLibrary) then s"$compilerDir/lib/scala3-library_3-${trueVersions(compiler)}.jar" + if JFile(o) == JFile(Properties.dottyLibrary) then s"$compilerDir/lib/scala3-library_3-$compiler.jar" else o }.mkString(JFile.pathSeparator) @@ -517,7 +517,7 @@ trait ParallelTesting extends RunnerOrchestration { self => .withClasspath(targetDir.getPath) .and("-d", targetDir.getPath) - val reporter = TestReporter.reporter(realStdout, ERROR) // TODO: do some reporting + val reporter = TestReporter.reporter(realStdout, ERROR) val command = Array(compilerDir + "/bin/scalac") ++ flags1.all ++ files.map(_.getPath) val process = Runtime.getRuntime.exec(command) @@ -1401,24 +1401,20 @@ object ParallelTesting { f.getName.endsWith(".tasty") def getCompiler(version: String): JFile = - val patch = trueVersions(version) - val dir = cache.resolve(s"scala3-${patch}").toFile - if dir.exists then - dir - else - import scala.sys.process._ - val zipPath = cache.resolve(s"scala3-$patch.zip") - (URL(s"https://github.com/lampepfl/dotty/releases/download/$patch/scala3-$patch.zip") #>> zipPath.toFile #&& s"unzip $zipPath -d $cache").!! - dir - - - val trueVersions = Map( - "3.0" -> "3.0.2", - "3.1" -> "3.1.0" - ) + val dir = cache.resolve(s"scala3-${version}").toFile + synchronized { + if dir.exists then + dir + else + import scala.sys.process._ + val zipPath = cache.resolve(s"scala3-$version.zip") + val compilerDownloadUrl = s"https://github.com/lampepfl/dotty/releases/download/$version/scala3-$version.zip" + (URL(compilerDownloadUrl) #>> zipPath.toFile #&& s"unzip $zipPath -d $cache").!! + dir + } private lazy val cache = val dir = Files.createTempDirectory("dotty.tests") - // dir.toFile.deleteOnExit() + dir.toFile.deleteOnExit() dir } diff --git a/library/src/scala/annotation/since.scala b/library/src/scala/annotation/since.scala index 7fe57a0b4d96..3c335dc020ba 100644 --- a/library/src/scala/annotation/since.scala +++ b/library/src/scala/annotation/since.scala @@ -1,4 +1,4 @@ package scala.annotation /** An annotation that is used to mark symbols added to the stdlib after 3.0 release */ -private[scala] class since(scalaRelease: String) extends scala.annotation.StaticAnnotation \ No newline at end of file +private[scala] class since(scalaRelease: String) extends scala.annotation.StaticAnnotation diff --git a/tasty/src/dotty/tools/tasty/TastyFormat.scala b/tasty/src/dotty/tools/tasty/TastyFormat.scala index f4f72a6e7e19..046cda0efb53 100644 --- a/tasty/src/dotty/tools/tasty/TastyFormat.scala +++ b/tasty/src/dotty/tools/tasty/TastyFormat.scala @@ -343,16 +343,12 @@ object TastyFormat { * @syntax markdown */ def isVersionCompatible( - fileMajor: Int, - fileMinor: Int, - fileExperimental: Int, - compilerMajor: Int, - compilerMinor: Int, - compilerExperimental: Int + fileVersion: TastyVersion, + compilerVersion: TastyVersion ): Boolean = ( - fileMajor == compilerMajor && - ( fileMinor == compilerMinor && fileExperimental == compilerExperimental // full equality - || fileMinor < compilerMinor && fileExperimental == 0 // stable backwards compatibility + fileVersion.major == compilerVersion.major && + ( fileVersion.minor == compilerVersion.minor && fileVersion.experimental == compilerVersion.experimental // full equality + || fileVersion.minor < compilerVersion.minor && fileVersion.experimental == 0 // stable backwards compatibility ) ) diff --git a/tasty/src/dotty/tools/tasty/TastyHeaderUnpickler.scala b/tasty/src/dotty/tools/tasty/TastyHeaderUnpickler.scala index c0ed5dbd58fa..295250fdbad2 100644 --- a/tasty/src/dotty/tools/tasty/TastyHeaderUnpickler.scala +++ b/tasty/src/dotty/tools/tasty/TastyHeaderUnpickler.scala @@ -45,7 +45,8 @@ class TastyHeaderUnpickler(reader: TastyReader) { val fileMajor = readNat() if (fileMajor <= 27) { // old behavior before `tasty-core` 3.0.0-M4 val fileMinor = readNat() - val signature = signatureString(fileMajor, fileMinor, 0) + val fileTastyVersion = TastyVersion(fileMajor, fileMinor, 0) + val signature = signatureString(fileTastyVersion, TastyVersion.compilerVersion) throw new UnpickleException(signature + backIncompatAddendum + toolingAddendum) } else { @@ -59,17 +60,15 @@ class TastyHeaderUnpickler(reader: TastyReader) { new String(bytes, start.index, length) } + val fileTastyVersion = TastyVersion(fileMajor, fileMinor, fileExperimental) + val validVersion = TastyFormat.isVersionCompatible( - fileMajor = fileMajor, - fileMinor = fileMinor, - fileExperimental = fileExperimental, - compilerMajor = MajorVersion, - compilerMinor = MinorVersion, - compilerExperimental = ExperimentalVersion + fileVersion = fileTastyVersion, + compilerVersion = TastyVersion.compilerVersion ) check(validVersion, { - val signature = signatureString(fileMajor, fileMinor, fileExperimental) + val signature = signatureString(fileTastyVersion, TastyVersion.compilerVersion) val producedByAddendum = s"\nThe TASTy file was produced by $toolingVersion.$toolingAddendum" val msg = ( if (fileExperimental != 0) unstableAddendum @@ -100,16 +99,16 @@ object TastyHeaderUnpickler { "" ) - private def signatureString(fileMajor: Int, fileMinor: Int, fileExperimental: Int) = { + private def signatureString(found: TastyVersion, expected: TastyVersion) = { def showMinorVersion(min: Int, exp: Int) = { val expStr = if (exp == 0) "" else s" [unstable release: $exp]" s"$min$expStr" } - val minorVersion = showMinorVersion(MinorVersion, ExperimentalVersion) - val fileMinorVersion = showMinorVersion(fileMinor, fileExperimental) + val expectedMinorVersion = showMinorVersion(expected.minor, expected.experimental) + val foundMinorVersion = showMinorVersion(found.minor, found.experimental) s"""TASTy signature has wrong version. - | expected: {majorVersion: $MajorVersion, minorVersion: $minorVersion} - | found : {majorVersion: $fileMajor, minorVersion: $fileMinorVersion} + | expected: {majorVersion: ${expected.major}, minorVersion: $expectedMinorVersion} + | found : {majorVersion: ${found.major}, minorVersion: $foundMinorVersion} | |""".stripMargin } diff --git a/tasty/src/dotty/tools/tasty/TastyVersion.scala b/tasty/src/dotty/tools/tasty/TastyVersion.scala new file mode 100644 index 000000000000..9d7bd5c7ced9 --- /dev/null +++ b/tasty/src/dotty/tools/tasty/TastyVersion.scala @@ -0,0 +1,16 @@ +package dotty.tools.tasty + +case class TastyVersion(major: Int, minor: Int, experimental: Int) { + def show = s"$major.$minor-$experimental" +} + +object TastyVersion { + def compilerVersion = TastyVersion(TastyFormat.MajorVersion, TastyFormat.MinorVersion, TastyFormat.ExperimentalVersion) + + def fromStableScalaRelease(majorVersion: Int, minorVersion: Int) = { + val tastyMajor = majorVersion + 25 + val tastyMinor = minorVersion + TastyVersion(tastyMajor, tastyMinor, 0) + } + +} diff --git a/tasty/test/dotty/tools/tasty/TastyVersionFormatTest.scala b/tasty/test/dotty/tools/tasty/TastyVersionFormatTest.scala index 3e29c9baaf81..cdf27d366627 100644 --- a/tasty/test/dotty/tools/tasty/TastyVersionFormatTest.scala +++ b/tasty/test/dotty/tools/tasty/TastyVersionFormatTest.scala @@ -3,6 +3,8 @@ package dotty.tools.tasty import org.junit.Assert._ import org.junit.{Test, Ignore} +import dotty.tools.tasty.TastyVersion + import TastyFormat._ import TastyBuffer._ @@ -11,10 +13,10 @@ class TastyVersionFormatTest { import TastyVersionFormatTest._ /** aliases `TastyVersion.apply` */ - def compiler(major: Int, minor: Int, experimental: Experimental) = TastyVersion(major, minor, experimental) + def compiler(major: Int, minor: Int, experimental: Experimental) = tastyVersion(major, minor, experimental) /** aliases `TastyVersion.apply` */ - def file(major: Int, minor: Int, experimental: Experimental) = TastyVersion(major, minor, experimental) + def file(major: Int, minor: Int, experimental: Experimental) = tastyVersion(major, minor, experimental) @Test def accept_ExperimentalReadEQExperimental_EQMinor: Unit = { assert(file(28,1,Exp(1)) <:< compiler(28,1,Exp(1))) // same minor, same experimental @@ -69,22 +71,18 @@ object TastyVersionFormatTest { val Final: Experimental = 0 def Exp(i: Int): Experimental = i.ensuring(_ > 0) - case class TastyVersion(major: Int, minor: Int, experimental: Experimental) { file => + implicit class TastyVersionOps(fileVersion: TastyVersion) { + def <:<(compilerVersion: TastyVersion): Boolean = TastyFormat.isVersionCompatible(fileVersion, compilerVersion) + + /**if `file unrelated compiler` then tasty file must be rejected.*/ + def unrelatedTo(compilerVersion: TastyVersion): Boolean = !(fileVersion <:< compilerVersion) + } + + def tastyVersion(major: Int, minor: Int, experimental: Experimental) = { assert(major >= 0) assert(minor >= 0) assert(experimental >= 0) - - def <:<(compiler: TastyVersion): Boolean = TastyFormat.isVersionCompatible( - fileMajor = file.major, - fileMinor = file.minor, - fileExperimental = file.experimental, - compilerMajor = compiler.major, - compilerMinor = compiler.minor, - compilerExperimental = compiler.experimental - ) - - /**if `file unrelated compiler` then tasty file must be rejected.*/ - def unrelatedTo(compiler: TastyVersion): Boolean = !(file <:< compiler) + TastyVersion(major, minor, experimental) } } diff --git a/tests/neg/forwardCompat-export/Test_r3.0.scala b/tests/neg/forwardCompat-export/Test_r3.0.scala new file mode 100644 index 000000000000..92ba88d36d85 --- /dev/null +++ b/tests/neg/forwardCompat-export/Test_r3.0.scala @@ -0,0 +1,5 @@ +object A: + export CanEqual.canEqualOption // error + +object B: + export CanEqual.given // error diff --git a/tests/neg/forwardCompat-illegalReferences/Test_r3.0.scala b/tests/neg/forwardCompat-illegalReferences/Test_r3.0.scala new file mode 100644 index 000000000000..458040295b83 --- /dev/null +++ b/tests/neg/forwardCompat-illegalReferences/Test_r3.0.scala @@ -0,0 +1,27 @@ +import scala.quoted.* + +def useQuotes(using Quotes) = + import quotes.reflect.* + + def useFieldMember(s: Symbol) = s.fieldMember("abc") // error + def getWildcard: Wildcard = ??? // error + def acceptWildcard(w: Wildcard) = "" // error + def boundByWildcard[T <: Wildcard]: T = ??? // error + + val wildcard = getWildcard // error + + type MyWildcard = Wildcard // error + + type Foo[W <: Wildcard] = Any // error + + type Bar[T] = T match { case Wildcard => Any } // error + + type Baz[T] = T match { case String => Wildcard } // error + + trait Wrapped[T] + trait WrappedWildcard extends Wrapped[Wildcard] // error + trait WrappedLikeWildcard[W <: Wildcard] extends Wrapped[W] // error + + class Box(w: Wildcard) // error + + def castToWildcard(x: Any) = x.asInstanceOf[Wildcard] // error // error diff --git a/tests/neg/forwardCompat-leakingImplicit/Test_r3.0.scala b/tests/neg/forwardCompat-leakingImplicit/Test_r3.0.scala new file mode 100644 index 000000000000..c9f0835bc053 --- /dev/null +++ b/tests/neg/forwardCompat-leakingImplicit/Test_r3.0.scala @@ -0,0 +1 @@ +val canEq = summon[CanEqual[EmptyTuple, EmptyTuple]] // error diff --git a/tests/neg/forwardCompat-rejectNewerTasty/Bar_2_r3.0.scala b/tests/neg/forwardCompat-rejectNewerTasty/Bar_2_r3.0.scala new file mode 100644 index 000000000000..d78004d6dedf --- /dev/null +++ b/tests/neg/forwardCompat-rejectNewerTasty/Bar_2_r3.0.scala @@ -0,0 +1 @@ +def bar = foo // nopos-error diff --git a/tests/neg/forwardCompat-rejectNewerTasty/Foo_1_r3.1.scala b/tests/neg/forwardCompat-rejectNewerTasty/Foo_1_r3.1.scala new file mode 100644 index 000000000000..fe04f0623a1c --- /dev/null +++ b/tests/neg/forwardCompat-rejectNewerTasty/Foo_1_r3.1.scala @@ -0,0 +1 @@ +def foo = 1 diff --git a/tests/neg/since/since_t3.0.scala b/tests/neg/since/since_t3.0.scala deleted file mode 100644 index 1970034180bf..000000000000 --- a/tests/neg/since/since_t3.0.scala +++ /dev/null @@ -1,16 +0,0 @@ -import scala.quoted.* - -def useQuotes(using Quotes) = - import quotes.reflect.* - - def useFieldMember(s: Symbol) = s.fieldMember("abc") // error - def getWildcard: Wildcard = ??? // error - def acceptWildcard(w: Wildcard) = "" // error - def boundByWildcard[T <: Wildcard]: T = ??? // error - - (useFieldMember, getWildcard, acceptWildcard) // error - -def equality = - import language.strictEquality - val a = 1 :: Nil == Nil - val b = Some(5) == None // error \ No newline at end of file diff --git a/tests/pos/forwardCompat-excludedExport/Test_r3.0.scala b/tests/pos/forwardCompat-excludedExport/Test_r3.0.scala new file mode 100644 index 000000000000..c635935471d8 --- /dev/null +++ b/tests/pos/forwardCompat-excludedExport/Test_r3.0.scala @@ -0,0 +1,2 @@ +object A: + export Tuple.{canEqualEmptyTuple as _, canEqualTuple as _, given, *} diff --git a/tests/pos/target_version/A_1_t3.0.scala b/tests/pos/target_version/A_1_t3.0.scala deleted file mode 100644 index 39402d87587f..000000000000 --- a/tests/pos/target_version/A_1_t3.0.scala +++ /dev/null @@ -1,2 +0,0 @@ -trait A: - def a: Int \ No newline at end of file diff --git a/tests/pos/target_version/B_2_v3.0.scala b/tests/pos/target_version/B_2_v3.0.scala deleted file mode 100644 index bb4ae81da4b4..000000000000 --- a/tests/pos/target_version/B_2_v3.0.scala +++ /dev/null @@ -1,2 +0,0 @@ -object B extends A: - def a = 7 \ No newline at end of file diff --git a/tests/run/forwardCompat-strictEquals/Equality_1_r3.0.scala b/tests/run/forwardCompat-strictEquals/Equality_1_r3.0.scala new file mode 100644 index 000000000000..538b85075bf9 --- /dev/null +++ b/tests/run/forwardCompat-strictEquals/Equality_1_r3.0.scala @@ -0,0 +1,5 @@ +// Instances of CanEqual are erased during compilation so their absence at runtime should not cause a crash + +import scala.language.strictEquality + +def emptyTupleEquality = EmptyTuple == EmptyTuple diff --git a/tests/run/forwardCompat-strictEquals/Test_2_c3.0.2.scala b/tests/run/forwardCompat-strictEquals/Test_2_c3.0.2.scala new file mode 100644 index 000000000000..1d1d811d0a9b --- /dev/null +++ b/tests/run/forwardCompat-strictEquals/Test_2_c3.0.2.scala @@ -0,0 +1,3 @@ +object Test: + def main(args: Array[String]): Unit = + println(emptyTupleEquality) diff --git a/tests/run/forwardCompat-unusedImport/Imports_1_r3.0.scala b/tests/run/forwardCompat-unusedImport/Imports_1_r3.0.scala new file mode 100644 index 000000000000..7946a9c9e2cc --- /dev/null +++ b/tests/run/forwardCompat-unusedImport/Imports_1_r3.0.scala @@ -0,0 +1,14 @@ +object A: + import scala.quoted.Type.valueOfTuple + +object B: + import scala.quoted.Type.* + +object C: + import Tuple.canEqualTuple + +object D: + import Tuple.given + +object E: + import Selectable.WithoutPreciseParameterTypes diff --git a/tests/run/forwardCompat-unusedImport/Test_2_c3.0.2.scala b/tests/run/forwardCompat-unusedImport/Test_2_c3.0.2.scala new file mode 100644 index 000000000000..c670ace4e768 --- /dev/null +++ b/tests/run/forwardCompat-unusedImport/Test_2_c3.0.2.scala @@ -0,0 +1,7 @@ +object Test: + def main(args: Array[String]): Unit = + println(A) + println(B) + println(C) + println(D) + println(E) From 46b59c9aac115c505663b849c0e031dce28ef418 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pa=C5=82ka?= Date: Tue, 28 Dec 2021 10:32:47 +0100 Subject: [PATCH 13/22] Reworks for forward compat: * Make -scala-release an experimental setting * Add better checks of of TASTy version * Validate names of releases inside since annotations * Extend Vulpix to check actual counts and possitions of errors from other compilers --- .../tools/dotc/config/ScalaSettings.scala | 2 +- .../src/dotty/tools/dotc/core/Contexts.scala | 6 ++-- .../dotc/core/classfile/ClassfileParser.scala | 23 +++++++----- .../dotty/tools/dotc/typer/RefChecks.scala | 14 ++++---- .../dotty/tools/vulpix/ParallelTesting.scala | 35 ++++++++++++++----- .../src/dotty/tools/tasty/TastyVersion.scala | 2 +- .../Test_r3.0.scala | 4 +++ tests/neg/forwardCompat-invalidSince.scala | 21 +++++++++++ .../Lib_1_r3.0.scala | 8 +++++ .../Test_2_c3.0.2.scala | 5 +++ .../Lib1_1_r3.0.scala | 8 +++++ .../Lib2_2_r3.0.scala | 3 ++ .../Test_3_c3.0.2.scala | 3 ++ .../Lib_1_r3.0.scala | 16 +++++++++ .../Test_2_c3.0.2.scala | 7 ++++ 15 files changed, 129 insertions(+), 28 deletions(-) create mode 100644 tests/neg/forwardCompat-invalidSince.scala create mode 100644 tests/neg/forwardCompat-nestedSumMirror/Lib_1_r3.0.scala create mode 100644 tests/neg/forwardCompat-nestedSumMirror/Test_2_c3.0.2.scala create mode 100644 tests/run/forwardCompat-nestedSumMirror/Lib1_1_r3.0.scala create mode 100644 tests/run/forwardCompat-nestedSumMirror/Lib2_2_r3.0.scala create mode 100644 tests/run/forwardCompat-nestedSumMirror/Test_3_c3.0.2.scala create mode 100644 tests/run/forwardCompat-refinedGivens/Lib_1_r3.0.scala create mode 100644 tests/run/forwardCompat-refinedGivens/Test_2_c3.0.2.scala diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index 33407d7540e3..4914b2714622 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -104,7 +104,6 @@ trait CommonScalaSettings: val silentWarnings: Setting[Boolean] = BooleanSetting("-nowarn", "Silence all warnings.", aliases = List("--no-warnings")) val release: Setting[String] = ChoiceSetting("-release", "release", "Compile code with classes specific to the given version of the Java platform available on the classpath and emit bytecode for this version.", ScalaSettings.supportedReleaseVersions, "", aliases = List("--release")) - val scalaRelease: Setting[String] = ChoiceSetting("-scala-release", "release", "Emit TASTy files that can be consumed by specified version of the compiler.", ScalaSettings.supportedScalaReleaseVersions, "", aliases = List("--scala-release")) val deprecation: Setting[Boolean] = BooleanSetting("-deprecation", "Emit warning and location for usages of deprecated APIs.", aliases = List("--deprecation")) val feature: Setting[Boolean] = BooleanSetting("-feature", "Emit warning and location for usages of features that should be imported explicitly.", aliases = List("--feature")) val explain: Setting[Boolean] = BooleanSetting("-explain", "Explain errors in more detail.", aliases = List("--explain")) @@ -311,6 +310,7 @@ private sealed trait YSettings: val YexplicitNulls: Setting[Boolean] = BooleanSetting("-Yexplicit-nulls", "Make reference types non-nullable. Nullable types can be expressed with unions: e.g. String|Null.") val YcheckInit: Setting[Boolean] = BooleanSetting("-Ysafe-init", "Ensure safe initialization of objects") val YrequireTargetName: Setting[Boolean] = BooleanSetting("-Yrequire-targetName", "Warn if an operator is defined without a @targetName annotation") + val YscalaRelease: Setting[String] = ChoiceSetting("-Yscala-release", "release", "Emit TASTy files that can be consumed by specified version of the compiler.", ScalaSettings.supportedScalaReleaseVersions, "", aliases = List("--Yscala-release")) /** Area-specific debug output */ val YexplainLowlevel: Setting[Boolean] = BooleanSetting("-Yexplain-lowlevel", "When explaining type errors, show types at a lower level.") diff --git a/compiler/src/dotty/tools/dotc/core/Contexts.scala b/compiler/src/dotty/tools/dotc/core/Contexts.scala index 1265063be7e6..e3497b9fa3ab 100644 --- a/compiler/src/dotty/tools/dotc/core/Contexts.scala +++ b/compiler/src/dotty/tools/dotc/core/Contexts.scala @@ -485,15 +485,15 @@ object Contexts { fresh.setImportInfo(ImportInfo(sym, imp.selectors, imp.expr)) def scalaRelease: ScalaRelease = - val releaseName = base.settings.scalaRelease.value + val releaseName = base.settings.YscalaRelease.value if releaseName.nonEmpty then ScalaRelease.parse(releaseName).get else ScalaRelease.latest def tastyVersion: TastyVersion = import math.Ordered.orderingToOrdered val latestRelease = ScalaRelease.latest val specifiedRelease = scalaRelease - if ((specifiedRelease.majorVersion, specifiedRelease.minorVersion) < (latestRelease.majorVersion, latestRelease.majorVersion)) then - // This is needed to make -scala-release a no-op when set to the latest release for unstable versions of the compiler + if ((specifiedRelease.majorVersion, specifiedRelease.minorVersion) < (latestRelease.majorVersion, latestRelease.minorVersion)) then + // This is needed to make -Yscala-release a no-op when set to the latest release for unstable versions of the compiler // (which might have the tasty format version numbers set to higher values before they're decreased during a release) TastyVersion.fromStableScalaRelease(specifiedRelease.majorVersion, specifiedRelease.minorVersion) else diff --git a/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala b/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala index 162d625538b6..37690323854a 100644 --- a/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala +++ b/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala @@ -957,16 +957,23 @@ class ClassfileParser( report.warning(s"$classfile is out of sync with its TASTy file. Loaded TASTy file. Try cleaning the project to fix this issue", NoSourcePosition) val tastyFilePath = classfile.path.stripSuffix(".class") + ".tasty" - val isTastyCompatible = - TastyFormat.isVersionCompatible(fileVersion = fileTastyVersion, compilerVersion = ctx.tastyVersion) || - classRoot.symbol.showFullName.startsWith("scala.") // References to stdlib are considered safe because we check the values of @since annotations - - if !isTastyCompatible then - report.error(s"""The class ${classRoot.symbol.showFullName} cannot be loaded from file ${tastyFilePath} because its TASTy format version is too high - |highest allowed: ${ctx.tastyVersion.show} - |found: ${fileTastyVersion.show} + + def reportWrongTasty(reason: String, highestAllowed: TastyVersion) = + report.error(s"""The class ${classRoot.symbol.showFullName} cannot be loaded from file ${tastyFilePath} because $reason: + |highest allowed: ${highestAllowed.show} + |found: ${fileTastyVersion.show} """.stripMargin) + val isTastyReadable = TastyFormat.isVersionCompatible(fileVersion = fileTastyVersion, compilerVersion = TastyVersion.compilerVersion) + if !isTastyReadable then + reportWrongTasty("its TASTy format cannot be read by the compiler", TastyVersion.compilerVersion) + else + val isTastyCompatible = + TastyFormat.isVersionCompatible(fileVersion = fileTastyVersion, compilerVersion = ctx.tastyVersion) || + classRoot.symbol.showFullName.startsWith("scala.") // References to stdlib are considered safe because we check the values of @since annotations + if !isTastyCompatible then + reportWrongTasty(s"its TASTy format is not compatible with the one of the targeted Scala release (${ctx.scalaRelease.show})", ctx.tastyVersion) + return unpickleTASTY(tastyBytes) } } diff --git a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala index eb43580e8839..c7ba668b7e79 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala @@ -974,16 +974,15 @@ object RefChecks { private def checkSinceAnnot(sym: Symbol, pos: SrcPos)(using Context): Unit = for annot <- sym.getAnnotation(defn.SinceAnnot) - version <- annot.argumentConstantString(0) + releaseName <- annot.argumentConstantString(0) do - val releaseVersion = ctx.scalaRelease - ScalaRelease.parse(version) match - case Some(symVersion) if symVersion > releaseVersion => + ScalaRelease.parse(releaseName) match + case Some(release) if release > ctx.scalaRelease => report.error( - i"$sym was added in Scala $version, therefore it cannot be used in the code targeting Scala ${releaseVersion.show}", + i"$sym was added in Scala release ${releaseName.show}, therefore it cannot be used in the code targeting Scala ${ctx.scalaRelease.show}", pos) case None => - report.warning(i"$sym has an unparsable release name: '${version}'", pos) + report.error(i"$sym has an unparsable release name: '${releaseName}'", annot.tree.srcPos) case _ => private def checkSinceAnnotInSignature(sym: Symbol, pos: SrcPos)(using Context) = @@ -1281,6 +1280,7 @@ class RefChecks extends MiniPhase { thisPhase => checkDeprecatedOvers(tree) checkExperimentalAnnots(tree.symbol) checkExperimentalSignature(tree.symbol, tree) + checkSinceAnnot(tree.symbol, tree.srcPos) checkSinceAnnotInSignature(tree.symbol, tree) val sym = tree.symbol if (sym.exists && sym.owner.isTerm) { @@ -1320,7 +1320,6 @@ class RefChecks extends MiniPhase { thisPhase => checkImplicitNotFoundAnnotation.template(cls.classDenot) checkExperimentalInheritance(cls) checkExperimentalAnnots(cls) - checkSinceAnnot(cls, cls.srcPos) tree } catch { @@ -1384,6 +1383,7 @@ class RefChecks extends MiniPhase { thisPhase => override def transformTypeDef(tree: TypeDef)(using Context): TypeDef = { checkExperimentalAnnots(tree.symbol) + checkSinceAnnot(tree.symbol, tree.srcPos) tree } } diff --git a/compiler/test/dotty/tools/vulpix/ParallelTesting.scala b/compiler/test/dotty/tools/vulpix/ParallelTesting.scala index 0fcab853ece9..5bf3b34173da 100644 --- a/compiler/test/dotty/tools/vulpix/ParallelTesting.scala +++ b/compiler/test/dotty/tools/vulpix/ParallelTesting.scala @@ -2,7 +2,7 @@ package dotty package tools package vulpix -import java.io.{File => JFile, IOException} +import java.io.{File => JFile, IOException, PrintStream, ByteArrayOutputStream} import java.lang.System.{lineSeparator => EOL} import java.net.URL import java.nio.file.StandardCopyOption.REPLACE_EXISTING @@ -13,7 +13,7 @@ import java.util.{HashMap, Timer, TimerTask} import java.util.concurrent.{TimeUnit, TimeoutException, Executors => JExecutors} import scala.collection.mutable -import scala.io.Source +import scala.io.{Codec, Source} import scala.util.{Random, Try, Failure => TryFailure, Success => TrySuccess, Using} import scala.util.control.NonFatal import scala.util.matching.Regex @@ -27,7 +27,7 @@ import dotc.interfaces.Diagnostic.ERROR import dotc.reporting.{Reporter, TestReporter} import dotc.reporting.Diagnostic import dotc.config.Config -import dotc.util.DiffUtil +import dotc.util.{DiffUtil, SourceFile, SourcePosition, Spans} import io.AbstractFile import dotty.tools.vulpix.TestConfiguration.defaultOptions @@ -215,7 +215,7 @@ trait ParallelTesting extends RunnerOrchestration { self => case testSource @ SeparateCompilationSource(_, dir, flags, outDir) => testSource.compilationGroups.map { (group, files) => - val flags1 = if group.release.isEmpty then flags else flags.and(s"-scala-release:${group.release}") + val flags1 = if group.release.isEmpty then flags else flags.and("-Yscala-release", group.release) if group.compiler.isEmpty then compile(files, flags1, suppressErrors, outDir) else @@ -504,6 +504,21 @@ trait ParallelTesting extends RunnerOrchestration { self => reporter } + private def parseErrors(errorsText: String, compilerVersion: String) = + val errorPattern = """.*Error: (.*\.scala):(\d+):(\d+).*""".r + errorsText.linesIterator.toSeq.collect { + case errorPattern(filePath, line, column) => + val lineNum = line.toInt + val columnNum = column.toInt + val abstractFile = AbstractFile.getFile(filePath) + val sourceFile = SourceFile(abstractFile, Codec.UTF8) + val offset = sourceFile.lineToOffset(lineNum - 1) + columnNum - 1 + val span = Spans.Span(offset) + val sourcePos = SourcePosition(sourceFile, span) + + Diagnostic.Error(s"Compilation of $filePath with Scala $compilerVersion failed at line: $line, column: $column. Full error output:\n\n$errorsText\n", sourcePos) + } + protected def compileWithOtherCompiler(compiler: String, files: Array[JFile], flags: TestFlags, targetDir: JFile): TestReporter = val compilerDir = getCompiler(compiler).toString @@ -517,14 +532,18 @@ trait ParallelTesting extends RunnerOrchestration { self => .withClasspath(targetDir.getPath) .and("-d", targetDir.getPath) - val reporter = TestReporter.reporter(realStdout, ERROR) + val dummyStream = new PrintStream(new ByteArrayOutputStream()) + val reporter = TestReporter.reporter(dummyStream, ERROR) val command = Array(compilerDir + "/bin/scalac") ++ flags1.all ++ files.map(_.getPath) val process = Runtime.getRuntime.exec(command) - val output = Source.fromInputStream(process.getErrorStream).mkString + val errorsText = Source.fromInputStream(process.getErrorStream).mkString if process.waitFor() != 0 then - echo(s"\nCompilation using Scala $compiler failed: \n$output") - fail() + val diagnostics = parseErrors(errorsText, compiler) + diagnostics.foreach { diag => + val context = (new ContextBase).initialCtx + reporter.report(diag)(using context) + } reporter diff --git a/tasty/src/dotty/tools/tasty/TastyVersion.scala b/tasty/src/dotty/tools/tasty/TastyVersion.scala index 9d7bd5c7ced9..f97433485a12 100644 --- a/tasty/src/dotty/tools/tasty/TastyVersion.scala +++ b/tasty/src/dotty/tools/tasty/TastyVersion.scala @@ -1,7 +1,7 @@ package dotty.tools.tasty case class TastyVersion(major: Int, minor: Int, experimental: Int) { - def show = s"$major.$minor-$experimental" + def show = "" + major + "." + minor + "-" + experimental } object TastyVersion { diff --git a/tests/neg/forwardCompat-illegalReferences/Test_r3.0.scala b/tests/neg/forwardCompat-illegalReferences/Test_r3.0.scala index 458040295b83..8dfeeeff3d29 100644 --- a/tests/neg/forwardCompat-illegalReferences/Test_r3.0.scala +++ b/tests/neg/forwardCompat-illegalReferences/Test_r3.0.scala @@ -24,4 +24,8 @@ def useQuotes(using Quotes) = class Box(w: Wildcard) // error + // The inferred result type also gets reported even though it's not written explicitly def castToWildcard(x: Any) = x.asInstanceOf[Wildcard] // error // error + + // 2 errors reported because at the stage of compilation when this is checked (already after some code transformations) the illegal type is referred to more than once + val selectable: Any = new Selectable.WithoutPreciseParameterTypes {} // error // error diff --git a/tests/neg/forwardCompat-invalidSince.scala b/tests/neg/forwardCompat-invalidSince.scala new file mode 100644 index 000000000000..dfd128e84061 --- /dev/null +++ b/tests/neg/forwardCompat-invalidSince.scala @@ -0,0 +1,21 @@ +package scala.test + +import annotation.since + +@since("") // error +val x = 1 + +@since("1.2.3.4") // error +val y = "abc" + +@since("xyz") // error +class Foo + +@since("-3") // error +trait Bar + +@since("3.0.2") // error +type Baz = Int + +@since("3.0 ") // error +given String = "" \ No newline at end of file diff --git a/tests/neg/forwardCompat-nestedSumMirror/Lib_1_r3.0.scala b/tests/neg/forwardCompat-nestedSumMirror/Lib_1_r3.0.scala new file mode 100644 index 000000000000..aaac31229228 --- /dev/null +++ b/tests/neg/forwardCompat-nestedSumMirror/Lib_1_r3.0.scala @@ -0,0 +1,8 @@ +// Adapted from i11050 + +sealed trait TreeValue + +sealed trait SubLevel extends TreeValue + +case class Leaf1(value: String) extends TreeValue +case class Leaf2(value: Int) extends SubLevel diff --git a/tests/neg/forwardCompat-nestedSumMirror/Test_2_c3.0.2.scala b/tests/neg/forwardCompat-nestedSumMirror/Test_2_c3.0.2.scala new file mode 100644 index 000000000000..9fa2e4003002 --- /dev/null +++ b/tests/neg/forwardCompat-nestedSumMirror/Test_2_c3.0.2.scala @@ -0,0 +1,5 @@ +import scala.deriving._ + +object Test: + def main(args: Array[String]): Unit = + println(summon[Mirror.Of[TreeValue]]) // error diff --git a/tests/run/forwardCompat-nestedSumMirror/Lib1_1_r3.0.scala b/tests/run/forwardCompat-nestedSumMirror/Lib1_1_r3.0.scala new file mode 100644 index 000000000000..aaac31229228 --- /dev/null +++ b/tests/run/forwardCompat-nestedSumMirror/Lib1_1_r3.0.scala @@ -0,0 +1,8 @@ +// Adapted from i11050 + +sealed trait TreeValue + +sealed trait SubLevel extends TreeValue + +case class Leaf1(value: String) extends TreeValue +case class Leaf2(value: Int) extends SubLevel diff --git a/tests/run/forwardCompat-nestedSumMirror/Lib2_2_r3.0.scala b/tests/run/forwardCompat-nestedSumMirror/Lib2_2_r3.0.scala new file mode 100644 index 000000000000..833fe13bb71e --- /dev/null +++ b/tests/run/forwardCompat-nestedSumMirror/Lib2_2_r3.0.scala @@ -0,0 +1,3 @@ +import scala.deriving._ + +val treeValueMirror = summon[Mirror.Of[TreeValue]] \ No newline at end of file diff --git a/tests/run/forwardCompat-nestedSumMirror/Test_3_c3.0.2.scala b/tests/run/forwardCompat-nestedSumMirror/Test_3_c3.0.2.scala new file mode 100644 index 000000000000..13438d173ffb --- /dev/null +++ b/tests/run/forwardCompat-nestedSumMirror/Test_3_c3.0.2.scala @@ -0,0 +1,3 @@ +object Test: + def main(args: Array[String]): Unit = + println(treeValueMirror) diff --git a/tests/run/forwardCompat-refinedGivens/Lib_1_r3.0.scala b/tests/run/forwardCompat-refinedGivens/Lib_1_r3.0.scala new file mode 100644 index 000000000000..6da2077b0086 --- /dev/null +++ b/tests/run/forwardCompat-refinedGivens/Lib_1_r3.0.scala @@ -0,0 +1,16 @@ +// Adapted from i12949 + +object Catch22: + trait TC[V] + object TC: + export Hodor.TC.given + +object Hodor: + object TC: + import Catch22.TC + given fromString[V <: String]: TC[V] = new TC[V] {} + transparent inline given fromDouble[V <: Double]: TC[V] = + new TC[V]: + type Out = Double + given fromInt[V <: Int]: TC[V] with + type Out = Int \ No newline at end of file diff --git a/tests/run/forwardCompat-refinedGivens/Test_2_c3.0.2.scala b/tests/run/forwardCompat-refinedGivens/Test_2_c3.0.2.scala new file mode 100644 index 000000000000..35f2b594881d --- /dev/null +++ b/tests/run/forwardCompat-refinedGivens/Test_2_c3.0.2.scala @@ -0,0 +1,7 @@ +// Adapted from i12949 + +object Test: + def main(args: Array[String]): Unit = + summon[Catch22.TC["hi"]] + summon[Catch22.TC[7.7]] + summon[Catch22.TC[1]] From 72cb4848fa6844babb93116955a5292c1982a804 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pa=C5=82ka?= Date: Wed, 29 Dec 2021 11:35:16 +0100 Subject: [PATCH 14/22] Use a more portable way to download an old compiler for tests --- compiler/test/dotty/tools/vulpix/ParallelTesting.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/compiler/test/dotty/tools/vulpix/ParallelTesting.scala b/compiler/test/dotty/tools/vulpix/ParallelTesting.scala index 5bf3b34173da..02fc8d785a61 100644 --- a/compiler/test/dotty/tools/vulpix/ParallelTesting.scala +++ b/compiler/test/dotty/tools/vulpix/ParallelTesting.scala @@ -1426,9 +1426,9 @@ object ParallelTesting { dir else import scala.sys.process._ - val zipPath = cache.resolve(s"scala3-$version.zip") - val compilerDownloadUrl = s"https://github.com/lampepfl/dotty/releases/download/$version/scala3-$version.zip" - (URL(compilerDownloadUrl) #>> zipPath.toFile #&& s"unzip $zipPath -d $cache").!! + val zipPath = cache.resolve(s"scala3-$version.tar.gz") + val compilerDownloadUrl = s"https://github.com/lampepfl/dotty/releases/download/$version/scala3-$version.tar.gz" + (URL(compilerDownloadUrl) #>> zipPath.toFile #&& s"tar -xf $zipPath -C $cache").!! dir } From 52f0177df766641b81d319f76bf88d6d1eaeea31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pa=C5=82ka?= Date: Wed, 29 Dec 2021 13:34:30 +0100 Subject: [PATCH 15/22] scala-release: Adjust MiMa filters; Revert changes to tasty module --- .../tools/dotc/config/ScalaRelease.scala | 2 -- .../src/dotty/tools/dotc/core/Contexts.scala | 3 +- .../dotc/core/classfile/ClassfileParser.scala | 7 +++-- .../dotc/core/tasty/DottyUnpickler.scala | 2 +- .../tools/dotc/core}/tasty/TastyVersion.scala | 13 +++++++-- project/MiMaFilters.scala | 3 ++ tasty/src/dotty/tools/tasty/TastyFormat.scala | 14 ++++++---- .../tools/tasty/TastyHeaderUnpickler.scala | 25 +++++++++-------- .../tools/tasty/TastyVersionFormatTest.scala | 28 ++++++++++--------- 9 files changed, 57 insertions(+), 40 deletions(-) rename {tasty/src/dotty/tools => compiler/src/dotty/tools/dotc/core}/tasty/TastyVersion.scala (57%) diff --git a/compiler/src/dotty/tools/dotc/config/ScalaRelease.scala b/compiler/src/dotty/tools/dotc/config/ScalaRelease.scala index 9634768d309f..acca9ce9298c 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaRelease.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaRelease.scala @@ -1,7 +1,5 @@ package dotty.tools.dotc.config -import dotty.tools.tasty.TastyVersion - enum ScalaRelease(val majorVersion: Int, val minorVersion: Int) extends Ordered[ScalaRelease]: case Release3_0 extends ScalaRelease(3, 0) case Release3_1 extends ScalaRelease(3, 1) diff --git a/compiler/src/dotty/tools/dotc/core/Contexts.scala b/compiler/src/dotty/tools/dotc/core/Contexts.scala index e3497b9fa3ab..0af08146bca0 100644 --- a/compiler/src/dotty/tools/dotc/core/Contexts.scala +++ b/compiler/src/dotty/tools/dotc/core/Contexts.scala @@ -38,8 +38,9 @@ import xsbti.AnalysisCallback import plugins._ import java.util.concurrent.atomic.AtomicInteger import java.nio.file.InvalidPathException -import dotty.tools.tasty.{ TastyFormat, TastyVersion } +import dotty.tools.tasty.TastyFormat import dotty.tools.dotc.config.{ NoScalaVersion, SpecificScalaVersion, AnyScalaVersion, ScalaBuild } +import dotty.tools.dotc.core.tasty.TastyVersion object Contexts { diff --git a/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala b/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala index 37690323854a..9e388b88b72a 100644 --- a/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala +++ b/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala @@ -3,7 +3,7 @@ package dotc package core package classfile -import dotty.tools.tasty.{ TastyFormat, TastyReader, TastyHeaderUnpickler, TastyVersion } +import dotty.tools.tasty.{ TastyFormat, TastyReader, TastyHeaderUnpickler } import Contexts._, Symbols._, Types._, Names._, StdNames._, NameOps._, Scopes._, Decorators._ import SymDenotations._, unpickleScala2.Scala2Unpickler._, Constants._, Annotations._, util.Spans._ @@ -20,6 +20,7 @@ import java.util.UUID import scala.collection.immutable import scala.collection.mutable.{ ListBuffer, ArrayBuffer } import scala.annotation.switch +import tasty.TastyVersion import typer.Checking.checkNonCyclic import io.{AbstractFile, PlainFile, ZipArchive} import scala.util.control.NonFatal @@ -964,12 +965,12 @@ class ClassfileParser( |found: ${fileTastyVersion.show} """.stripMargin) - val isTastyReadable = TastyFormat.isVersionCompatible(fileVersion = fileTastyVersion, compilerVersion = TastyVersion.compilerVersion) + val isTastyReadable = fileTastyVersion.isCompatibleWith(TastyVersion.compilerVersion) if !isTastyReadable then reportWrongTasty("its TASTy format cannot be read by the compiler", TastyVersion.compilerVersion) else val isTastyCompatible = - TastyFormat.isVersionCompatible(fileVersion = fileTastyVersion, compilerVersion = ctx.tastyVersion) || + fileTastyVersion.isCompatibleWith(ctx.tastyVersion) || classRoot.symbol.showFullName.startsWith("scala.") // References to stdlib are considered safe because we check the values of @since annotations if !isTastyCompatible then reportWrongTasty(s"its TASTy format is not compatible with the one of the targeted Scala release (${ctx.scalaRelease.show})", ctx.tastyVersion) diff --git a/compiler/src/dotty/tools/dotc/core/tasty/DottyUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/DottyUnpickler.scala index a9dac87413fe..7396adf76991 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/DottyUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/DottyUnpickler.scala @@ -10,7 +10,7 @@ import classfile.ClassfileParser import Names.SimpleName import TreeUnpickler.UnpickleMode -import dotty.tools.tasty.{ TastyReader, TastyVersion } +import dotty.tools.tasty.TastyReader import dotty.tools.tasty.TastyFormat.{ASTsSection, PositionsSection, CommentsSection} object DottyUnpickler { diff --git a/tasty/src/dotty/tools/tasty/TastyVersion.scala b/compiler/src/dotty/tools/dotc/core/tasty/TastyVersion.scala similarity index 57% rename from tasty/src/dotty/tools/tasty/TastyVersion.scala rename to compiler/src/dotty/tools/dotc/core/tasty/TastyVersion.scala index f97433485a12..9c417563a1e3 100644 --- a/tasty/src/dotty/tools/tasty/TastyVersion.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TastyVersion.scala @@ -1,7 +1,14 @@ -package dotty.tools.tasty +package dotty.tools.dotc.core.tasty + +import dotty.tools.tasty.TastyFormat case class TastyVersion(major: Int, minor: Int, experimental: Int) { - def show = "" + major + "." + minor + "-" + experimental + def show = s"$major.$minor-$experimental" + + def isCompatibleWith(that: TastyVersion): Boolean = TastyFormat.isVersionCompatible( + this.major, this.minor, this.experimental, + that.major, that.minor, that.experimental + ) } object TastyVersion { @@ -13,4 +20,4 @@ object TastyVersion { TastyVersion(tastyMajor, tastyMinor, 0) } -} +} \ No newline at end of file diff --git a/project/MiMaFilters.scala b/project/MiMaFilters.scala index 097a82ff2ef0..ff1e7ae906e2 100644 --- a/project/MiMaFilters.scala +++ b/project/MiMaFilters.scala @@ -27,5 +27,8 @@ object MiMaFilters { ProblemFilters.exclude[MissingFieldProblem]("scala.runtime.stdLibPatches.language.3.1"), ProblemFilters.exclude[MissingClassProblem]("scala.runtime.stdLibPatches.language$3$u002E1$"), ProblemFilters.exclude[MissingClassProblem]("scala.runtime.stdLibPatches.language$3$u002E1$minusmigration$"), + + // Private to the compiler - needed for forward binary compatibility + ProblemFilters.exclude[MissingClassProblem]("scala.annotation.since") ) } diff --git a/tasty/src/dotty/tools/tasty/TastyFormat.scala b/tasty/src/dotty/tools/tasty/TastyFormat.scala index 046cda0efb53..f4f72a6e7e19 100644 --- a/tasty/src/dotty/tools/tasty/TastyFormat.scala +++ b/tasty/src/dotty/tools/tasty/TastyFormat.scala @@ -343,12 +343,16 @@ object TastyFormat { * @syntax markdown */ def isVersionCompatible( - fileVersion: TastyVersion, - compilerVersion: TastyVersion + fileMajor: Int, + fileMinor: Int, + fileExperimental: Int, + compilerMajor: Int, + compilerMinor: Int, + compilerExperimental: Int ): Boolean = ( - fileVersion.major == compilerVersion.major && - ( fileVersion.minor == compilerVersion.minor && fileVersion.experimental == compilerVersion.experimental // full equality - || fileVersion.minor < compilerVersion.minor && fileVersion.experimental == 0 // stable backwards compatibility + fileMajor == compilerMajor && + ( fileMinor == compilerMinor && fileExperimental == compilerExperimental // full equality + || fileMinor < compilerMinor && fileExperimental == 0 // stable backwards compatibility ) ) diff --git a/tasty/src/dotty/tools/tasty/TastyHeaderUnpickler.scala b/tasty/src/dotty/tools/tasty/TastyHeaderUnpickler.scala index 295250fdbad2..c0ed5dbd58fa 100644 --- a/tasty/src/dotty/tools/tasty/TastyHeaderUnpickler.scala +++ b/tasty/src/dotty/tools/tasty/TastyHeaderUnpickler.scala @@ -45,8 +45,7 @@ class TastyHeaderUnpickler(reader: TastyReader) { val fileMajor = readNat() if (fileMajor <= 27) { // old behavior before `tasty-core` 3.0.0-M4 val fileMinor = readNat() - val fileTastyVersion = TastyVersion(fileMajor, fileMinor, 0) - val signature = signatureString(fileTastyVersion, TastyVersion.compilerVersion) + val signature = signatureString(fileMajor, fileMinor, 0) throw new UnpickleException(signature + backIncompatAddendum + toolingAddendum) } else { @@ -60,15 +59,17 @@ class TastyHeaderUnpickler(reader: TastyReader) { new String(bytes, start.index, length) } - val fileTastyVersion = TastyVersion(fileMajor, fileMinor, fileExperimental) - val validVersion = TastyFormat.isVersionCompatible( - fileVersion = fileTastyVersion, - compilerVersion = TastyVersion.compilerVersion + fileMajor = fileMajor, + fileMinor = fileMinor, + fileExperimental = fileExperimental, + compilerMajor = MajorVersion, + compilerMinor = MinorVersion, + compilerExperimental = ExperimentalVersion ) check(validVersion, { - val signature = signatureString(fileTastyVersion, TastyVersion.compilerVersion) + val signature = signatureString(fileMajor, fileMinor, fileExperimental) val producedByAddendum = s"\nThe TASTy file was produced by $toolingVersion.$toolingAddendum" val msg = ( if (fileExperimental != 0) unstableAddendum @@ -99,16 +100,16 @@ object TastyHeaderUnpickler { "" ) - private def signatureString(found: TastyVersion, expected: TastyVersion) = { + private def signatureString(fileMajor: Int, fileMinor: Int, fileExperimental: Int) = { def showMinorVersion(min: Int, exp: Int) = { val expStr = if (exp == 0) "" else s" [unstable release: $exp]" s"$min$expStr" } - val expectedMinorVersion = showMinorVersion(expected.minor, expected.experimental) - val foundMinorVersion = showMinorVersion(found.minor, found.experimental) + val minorVersion = showMinorVersion(MinorVersion, ExperimentalVersion) + val fileMinorVersion = showMinorVersion(fileMinor, fileExperimental) s"""TASTy signature has wrong version. - | expected: {majorVersion: ${expected.major}, minorVersion: $expectedMinorVersion} - | found : {majorVersion: ${found.major}, minorVersion: $foundMinorVersion} + | expected: {majorVersion: $MajorVersion, minorVersion: $minorVersion} + | found : {majorVersion: $fileMajor, minorVersion: $fileMinorVersion} | |""".stripMargin } diff --git a/tasty/test/dotty/tools/tasty/TastyVersionFormatTest.scala b/tasty/test/dotty/tools/tasty/TastyVersionFormatTest.scala index cdf27d366627..3e29c9baaf81 100644 --- a/tasty/test/dotty/tools/tasty/TastyVersionFormatTest.scala +++ b/tasty/test/dotty/tools/tasty/TastyVersionFormatTest.scala @@ -3,8 +3,6 @@ package dotty.tools.tasty import org.junit.Assert._ import org.junit.{Test, Ignore} -import dotty.tools.tasty.TastyVersion - import TastyFormat._ import TastyBuffer._ @@ -13,10 +11,10 @@ class TastyVersionFormatTest { import TastyVersionFormatTest._ /** aliases `TastyVersion.apply` */ - def compiler(major: Int, minor: Int, experimental: Experimental) = tastyVersion(major, minor, experimental) + def compiler(major: Int, minor: Int, experimental: Experimental) = TastyVersion(major, minor, experimental) /** aliases `TastyVersion.apply` */ - def file(major: Int, minor: Int, experimental: Experimental) = tastyVersion(major, minor, experimental) + def file(major: Int, minor: Int, experimental: Experimental) = TastyVersion(major, minor, experimental) @Test def accept_ExperimentalReadEQExperimental_EQMinor: Unit = { assert(file(28,1,Exp(1)) <:< compiler(28,1,Exp(1))) // same minor, same experimental @@ -71,18 +69,22 @@ object TastyVersionFormatTest { val Final: Experimental = 0 def Exp(i: Int): Experimental = i.ensuring(_ > 0) - implicit class TastyVersionOps(fileVersion: TastyVersion) { - def <:<(compilerVersion: TastyVersion): Boolean = TastyFormat.isVersionCompatible(fileVersion, compilerVersion) - - /**if `file unrelated compiler` then tasty file must be rejected.*/ - def unrelatedTo(compilerVersion: TastyVersion): Boolean = !(fileVersion <:< compilerVersion) - } - - def tastyVersion(major: Int, minor: Int, experimental: Experimental) = { + case class TastyVersion(major: Int, minor: Int, experimental: Experimental) { file => assert(major >= 0) assert(minor >= 0) assert(experimental >= 0) - TastyVersion(major, minor, experimental) + + def <:<(compiler: TastyVersion): Boolean = TastyFormat.isVersionCompatible( + fileMajor = file.major, + fileMinor = file.minor, + fileExperimental = file.experimental, + compilerMajor = compiler.major, + compilerMinor = compiler.minor, + compilerExperimental = compiler.experimental + ) + + /**if `file unrelated compiler` then tasty file must be rejected.*/ + def unrelatedTo(compiler: TastyVersion): Boolean = !(file <:< compiler) } } From e39b618f9a9a1c4f306b1abfbfdb740758f5b37c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pa=C5=82ka?= Date: Wed, 12 Jan 2022 13:24:19 +0100 Subject: [PATCH 16/22] -Yscala-release support: extend community build with basic forward-compat tests (compiling selected projects with "-Yscala-release 3.0") --- .github/workflows/ci.yaml | 39 +++- .gitmodules | 31 +++ community-build/README.md | 2 +- .../cats-effect-3-forward-compat | 1 + .../community-projects/cats-forward-compat | 1 + .../cats-mtl-forward-compat | 1 + .../community-projects/coop-forward-compat | 1 + .../discipline-forward-compat | 1 + .../discipline-munit-forward-compat | 1 + .../discipline-specs2-forward-compat | 1 + .../community-projects/munit-forward-compat | 1 + .../scalacheck-forward-compat | 1 + .../simulacrum-scalafix-forward-compat | 1 + .../communitybuild/CommunityBuildRunner.scala | 5 +- .../src/scala/dotty/communitybuild/Main.scala | 2 +- .../scala/dotty/communitybuild/projects.scala | 220 +++++++++++------- .../communitybuild/CommunityBuildTest.scala | 14 ++ .../sbtplugin/CommunityBuildPlugin.scala | 30 ++- 18 files changed, 265 insertions(+), 88 deletions(-) create mode 160000 community-build/community-projects/cats-effect-3-forward-compat create mode 160000 community-build/community-projects/cats-forward-compat create mode 160000 community-build/community-projects/cats-mtl-forward-compat create mode 160000 community-build/community-projects/coop-forward-compat create mode 160000 community-build/community-projects/discipline-forward-compat create mode 160000 community-build/community-projects/discipline-munit-forward-compat create mode 160000 community-build/community-projects/discipline-specs2-forward-compat create mode 160000 community-build/community-projects/munit-forward-compat create mode 160000 community-build/community-projects/scalacheck-forward-compat create mode 160000 community-build/community-projects/simulacrum-scalafix-forward-compat diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index aa7a7a773a99..ad26d9881243 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -317,6 +317,43 @@ jobs: git submodule update --init --recursive --jobs 7 ./project/scripts/sbt "community-build/testOnly dotty.communitybuild.CommunityBuildTestC" + community_build_forward_compat: + runs-on: [self-hosted, Linux] + container: + image: lampepfl/dotty:2021-03-22 + options: --cpu-shares 4096 + volumes: + - ${{ github.workspace }}/../../cache/sbt:/root/.sbt + - ${{ github.workspace }}/../../cache/ivy:/root/.ivy2/cache + - ${{ github.workspace }}/../../cache/general:/root/.cache + if: "github.event_name == 'schedule' && github.repository == 'lampepfl/dotty' + || ( + github.event_name == 'workflow_dispatch' + && github.repository == 'lampepfl/dotty' + )" + + steps: + - name: Reset existing repo + run: git -c "http.https://github.com/.extraheader=" fetch --recurse-submodules=no "https://github.com/lampepfl/dotty" && git reset --hard FETCH_HEAD || true + + - name: Checkout cleanup script + uses: actions/checkout@v2 + + - name: Cleanup + run: .github/workflows/cleanup.sh + + - name: Git Checkout + uses: actions/checkout@v2 + + - name: Add SBT proxy repositories + run: cp -vf .github/workflows/repositories /root/.sbt/ ; true + + - name: Test + run: | + git submodule sync + git submodule update --init --recursive --jobs 7 + ./project/scripts/sbt "community-build/testOnly dotty.communitybuild.CommunityBuildTestForwardCompat" + test_sbt: runs-on: [self-hosted, Linux] container: @@ -415,7 +452,7 @@ jobs: - ${{ github.workspace }}/../../cache/sbt:/root/.sbt - ${{ github.workspace }}/../../cache/ivy:/root/.ivy2/cache - ${{ github.workspace }}/../../cache/general:/root/.cache - needs: [test_non_bootstrapped, test, community_build_a, community_build_b, community_build_c, test_sbt, test_java8] + needs: [test_non_bootstrapped, test, community_build_a, community_build_b, community_build_c, community_build_forward_compat, test_sbt, test_java8] if: "(github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && github.repository == 'lampepfl/dotty'" env: NIGHTLYBUILD: yes diff --git a/.gitmodules b/.gitmodules index e35eb824949a..f93c2c9a9857 100644 --- a/.gitmodules +++ b/.gitmodules @@ -219,3 +219,34 @@ [submodule "community-build/community-projects/spire"] path = community-build/community-projects/spire url = https://github.com/dotty-staging/spire.git +[submodule "community-build/community-projects/munit-forward-compat"] + path = community-build/community-projects/munit-forward-compat + url = https://github.com/dotty-staging/munit.git +[submodule "community-build/community-projects/discipline-forward-compat"] + path = community-build/community-projects/discipline-forward-compat + url = https://github.com/dotty-staging/discipline.git +[submodule "community-build/community-projects/discipline-munit-forward-compat"] + path = community-build/community-projects/discipline-munit-forward-compat + url = https://github.com/dotty-staging/discipline-munit.git +[submodule "community-build/community-projects/discipline-specs2-forward-compat"] + path = community-build/community-projects/discipline-specs2-forward-compat + url = https://github.com/dotty-staging/discipline-specs2.git +[submodule "community-build/community-projects/simulacrum-scalafix-forward-compat"] + path = community-build/community-projects/simulacrum-scalafix-forward-compat + url = https://github.com/dotty-staging/simulacrum-scalafix.git +[submodule "community-build/community-projects/cats-forward-compat"] + path = community-build/community-projects/cats-forward-compat + url = https://github.com/dotty-staging/cats.git +[submodule "community-build/community-projects/cats-mtl-forward-compat"] + path = community-build/community-projects/cats-mtl-forward-compat + url = https://github.com/dotty-staging/cats-mtl.git +[submodule "community-build/community-projects/coop-forward-compat"] + path = community-build/community-projects/coop-forward-compat + url = https://github.com/dotty-staging/coop.git +[submodule "community-build/community-projects/cats-effect-3-forward-compat"] + path = community-build/community-projects/cats-effect-3-forward-compat + url = https://github.com/dotty-staging/cats-effect.git + branch = series/3.x +[submodule "community-build/community-projects/scalacheck-forward-compat"] + path = community-build/community-projects/scalacheck-forward-compat + url = https://github.com/dotty-staging/scalacheck diff --git a/community-build/README.md b/community-build/README.md index 297aad93ec46..26396d372936 100644 --- a/community-build/README.md +++ b/community-build/README.md @@ -17,6 +17,6 @@ To add your project to the community build you can follow these steps: 2. Open a PR against this repo that: - Adds your project as a new git submodule - - `git submodule add https://github.com/lampepfl/XYZ.git community-build/community-projects/XYZ` + - `git submodule add https://github.com/dotty-staging/XYZ.git community-build/community-projects/XYZ` - Add the project to [projects.scala](https://github.com/lampepfl/dotty/blob/master/community-build/src/scala/dotty/communitybuild/projects.scala) - Adds a test in [CommunityBuildTest.scala](https://github.com/lampepfl/dotty/blob/master/community-build/test/scala/dotty/communitybuild/CommunityBuildTest.scala) diff --git a/community-build/community-projects/cats-effect-3-forward-compat b/community-build/community-projects/cats-effect-3-forward-compat new file mode 160000 index 000000000000..af11317d40ce --- /dev/null +++ b/community-build/community-projects/cats-effect-3-forward-compat @@ -0,0 +1 @@ +Subproject commit af11317d40cee8979c4e40b60431bc0f3b9e03f0 diff --git a/community-build/community-projects/cats-forward-compat b/community-build/community-projects/cats-forward-compat new file mode 160000 index 000000000000..878472d7bff4 --- /dev/null +++ b/community-build/community-projects/cats-forward-compat @@ -0,0 +1 @@ +Subproject commit 878472d7bff4c3bfec8a265782c1e0d6a3147541 diff --git a/community-build/community-projects/cats-mtl-forward-compat b/community-build/community-projects/cats-mtl-forward-compat new file mode 160000 index 000000000000..7679d606336a --- /dev/null +++ b/community-build/community-projects/cats-mtl-forward-compat @@ -0,0 +1 @@ +Subproject commit 7679d606336a4da0e6dfca43c0a481db273cc10c diff --git a/community-build/community-projects/coop-forward-compat b/community-build/community-projects/coop-forward-compat new file mode 160000 index 000000000000..4babee9613a4 --- /dev/null +++ b/community-build/community-projects/coop-forward-compat @@ -0,0 +1 @@ +Subproject commit 4babee9613a4bc0713d195676dd169c4f636a31a diff --git a/community-build/community-projects/discipline-forward-compat b/community-build/community-projects/discipline-forward-compat new file mode 160000 index 000000000000..b0865da0c5e0 --- /dev/null +++ b/community-build/community-projects/discipline-forward-compat @@ -0,0 +1 @@ +Subproject commit b0865da0c5e016ad1d45060f52885219256d3205 diff --git a/community-build/community-projects/discipline-munit-forward-compat b/community-build/community-projects/discipline-munit-forward-compat new file mode 160000 index 000000000000..014f8ca26fef --- /dev/null +++ b/community-build/community-projects/discipline-munit-forward-compat @@ -0,0 +1 @@ +Subproject commit 014f8ca26fefab7c32a2779b9d3382df14ccf860 diff --git a/community-build/community-projects/discipline-specs2-forward-compat b/community-build/community-projects/discipline-specs2-forward-compat new file mode 160000 index 000000000000..3603b0874940 --- /dev/null +++ b/community-build/community-projects/discipline-specs2-forward-compat @@ -0,0 +1 @@ +Subproject commit 3603b08749404f83946aab48203f8bd9f9410b49 diff --git a/community-build/community-projects/munit-forward-compat b/community-build/community-projects/munit-forward-compat new file mode 160000 index 000000000000..662953cdb57f --- /dev/null +++ b/community-build/community-projects/munit-forward-compat @@ -0,0 +1 @@ +Subproject commit 662953cdb57fec0d8e1baa7fcd1ab178a0bba8c6 diff --git a/community-build/community-projects/scalacheck-forward-compat b/community-build/community-projects/scalacheck-forward-compat new file mode 160000 index 000000000000..976db31cd549 --- /dev/null +++ b/community-build/community-projects/scalacheck-forward-compat @@ -0,0 +1 @@ +Subproject commit 976db31cd549328167a90ecc6f5f31efa83cd845 diff --git a/community-build/community-projects/simulacrum-scalafix-forward-compat b/community-build/community-projects/simulacrum-scalafix-forward-compat new file mode 160000 index 000000000000..2515271c46ad --- /dev/null +++ b/community-build/community-projects/simulacrum-scalafix-forward-compat @@ -0,0 +1 @@ +Subproject commit 2515271c46ad46512a43d20e1e8ae0793433cf0b diff --git a/community-build/src/scala/dotty/communitybuild/CommunityBuildRunner.scala b/community-build/src/scala/dotty/communitybuild/CommunityBuildRunner.scala index 5f0dffd15705..3d52a3ff0631 100644 --- a/community-build/src/scala/dotty/communitybuild/CommunityBuildRunner.scala +++ b/community-build/src/scala/dotty/communitybuild/CommunityBuildRunner.scala @@ -17,10 +17,10 @@ object CommunityBuildRunner: * for more infrastructural details. */ extension (self: CommunityProject) def run()(using suite: CommunityBuildRunner): Unit = - if self.requiresExperimental && !compilerSupportExperimental then + if self.requiresExperimental && !self.compilerSupportExperimental then log(s"Skipping ${self.project} - it needs experimental features unsupported in this build.") return - self.dependencies.foreach(_.publish()) + self.dependencies().foreach(_.publish()) self.testOnlyDependencies().foreach(_.publish()) suite.runProject(self) @@ -45,6 +45,7 @@ trait CommunityBuildRunner: val project = projectDef.project val command = projectDef.binaryName val arguments = projectDef.buildCommands + val compilerVersion = projectDef.compilerVersion @annotation.tailrec def execTimes(task: () => Int, timesToRerun: Int): Boolean = diff --git a/community-build/src/scala/dotty/communitybuild/Main.scala b/community-build/src/scala/dotty/communitybuild/Main.scala index 852cee46af22..7c3a39261eb0 100644 --- a/community-build/src/scala/dotty/communitybuild/Main.scala +++ b/community-build/src/scala/dotty/communitybuild/Main.scala @@ -57,7 +57,7 @@ object Main: val (toRun, ignored) = allProjects.partition( p => p.docCommand != null - && (!p.requiresExperimental || compilerSupportExperimental) + && (!p.requiresExperimental || p.compilerSupportExperimental) ) val paths = toRun.map { project => diff --git a/community-build/src/scala/dotty/communitybuild/projects.scala b/community-build/src/scala/dotty/communitybuild/projects.scala index 8dfbd9fbea7e..424922a572a5 100644 --- a/community-build/src/scala/dotty/communitybuild/projects.scala +++ b/community-build/src/scala/dotty/communitybuild/projects.scala @@ -6,13 +6,10 @@ import java.nio.charset.StandardCharsets.UTF_8 lazy val communitybuildDir: Path = Paths.get(sys.props("user.dir")) -lazy val compilerVersion: String = +lazy val testedCompilerVersion: String = val file = communitybuildDir.resolve("scala3-bootstrapped.version") new String(Files.readAllBytes(file), UTF_8) -lazy val compilerSupportExperimental: Boolean = - compilerVersion.contains("SNAPSHOT") || compilerVersion.contains("NIGHTLY") - lazy val sbtPluginFilePath: String = // Workaround for https://github.com/sbt/sbt/issues/4395 new File(sys.props("user.home") + "/.sbt/1.0/plugins").mkdirs() @@ -39,17 +36,21 @@ sealed trait CommunityProject: val testCommand: String val publishCommand: String val docCommand: String - val dependencies: List[CommunityProject] + val dependencies: () => List[CommunityProject] val testOnlyDependencies: () => List[CommunityProject] val binaryName: String val runCommandsArgs: List[String] = Nil val requiresExperimental: Boolean val environment: Map[String, String] = Map.empty + val compilerVersion: String final val projectDir = communitybuildDir.resolve("community-projects").resolve(project) + final val compilerSupportExperimental: Boolean = + compilerVersion.contains("SNAPSHOT") || compilerVersion.contains("NIGHTLY") + final def publishDependencies(): Unit = - dependencies.foreach(_.publish()) + dependencies().foreach(_.publish()) /** Publish this project to the local Maven repository */ final def publish(): Unit = @@ -87,10 +88,11 @@ end CommunityProject final case class MillCommunityProject( project: String, baseCommand: String, - dependencies: List[CommunityProject] = Nil, + dependencies: () => List[CommunityProject] = () => Nil, testOnlyDependencies: () => List[CommunityProject] = () => Nil, ignoreDocs: Boolean = false, requiresExperimental: Boolean = false, + compilerVersion: String = testedCompilerVersion ) extends CommunityProject: override val binaryName: String = "./mill" override val testCommand = s"$baseCommand.test" @@ -105,12 +107,14 @@ final case class SbtCommunityProject( project: String, sbtTestCommand: String, extraSbtArgs: List[String] = Nil, - dependencies: List[CommunityProject] = Nil, + dependencies: () => List[CommunityProject] = () => Nil, testOnlyDependencies: () => List[CommunityProject] = () => Nil, sbtPublishCommand: String = null, sbtDocCommand: String = null, scalacOptions: List[String] = SbtCommunityProject.scalacOptions, requiresExperimental: Boolean = false, + compilerVersion: String = testedCompilerVersion, + isForwardCompatProject: Boolean = false ) extends CommunityProject: override val binaryName: String = "sbt" @@ -119,6 +123,7 @@ final case class SbtCommunityProject( private val baseCommand = "clean; set Global/logLevel := Level.Error; set Global/updateOptions ~= (_.withLatestSnapshots(false)); " + ++ (if isForwardCompatProject then "set Global / isForwardCompatProject := true; " else "") ++ (if scalacOptions.isEmpty then "" else s"""set Global/scalacOptions ++= $scalacOptionsString;""") ++ s"++$compilerVersion!; " @@ -146,6 +151,19 @@ final case class SbtCommunityProject( s"--addPluginSbtFile=$sbtPluginFilePath" ) + def forwardCompat: SbtCommunityProject = + this.copy( + project = project + "-forward-compat", + dependencies = () => dependencies().map(forwardCompatMapping), + testOnlyDependencies = () => testOnlyDependencies().map(forwardCompatMapping), + isForwardCompatProject = true + ) + + def withScalaRelease(release: String): SbtCommunityProject = + this.copy( + scalacOptions = scalacOptions ++ Seq("-Yscala-release", release) + ) + object SbtCommunityProject: def scalacOptions = List( "-Xcheck-macros", @@ -166,89 +184,89 @@ object projects: lazy val utest = MillCommunityProject( project = "utest", - baseCommand = s"utest.jvm[$compilerVersion]", + baseCommand = s"utest.jvm[$testedCompilerVersion]", ignoreDocs = true ) lazy val sourcecode = MillCommunityProject( project = "sourcecode", - baseCommand = s"sourcecode.jvm[$compilerVersion]", + baseCommand = s"sourcecode.jvm[$testedCompilerVersion]", ignoreDocs = true ) lazy val oslib = MillCommunityProject( project = "os-lib", - baseCommand = s"os.jvm[$compilerVersion]", - dependencies = List(utest, sourcecode) + baseCommand = s"os.jvm[$testedCompilerVersion]", + dependencies = () => List(utest, sourcecode) ) lazy val oslibWatch = MillCommunityProject( project = "os-lib", - baseCommand = s"os.watch[$compilerVersion]", - dependencies = List(utest, sourcecode), + baseCommand = s"os.watch[$testedCompilerVersion]", + dependencies = () => List(utest, sourcecode), ignoreDocs = true ) lazy val ujson = MillCommunityProject( project = "upickle", - baseCommand = s"ujson.jvm[$compilerVersion]", - dependencies = List(geny) + baseCommand = s"ujson.jvm[$testedCompilerVersion]", + dependencies = () => List(geny) ) lazy val upickle = MillCommunityProject( project = "upickle", - baseCommand = s"upickle.jvm[$compilerVersion]", - dependencies = List(geny, utest) + baseCommand = s"upickle.jvm[$testedCompilerVersion]", + dependencies = () => List(geny, utest) ) lazy val upickleCore = MillCommunityProject( project = "upickle", - baseCommand = s"core.jvm[$compilerVersion]", - dependencies = List(geny, utest) + baseCommand = s"core.jvm[$testedCompilerVersion]", + dependencies = () => List(geny, utest) ) lazy val upickleImplicits = MillCommunityProject( project = "upickle", - baseCommand = s"implicits.jvm[$compilerVersion]", - dependencies = List(upickleCore, ujson) + baseCommand = s"implicits.jvm[$testedCompilerVersion]", + dependencies = () => List(upickleCore, ujson) ) lazy val upack = MillCommunityProject( project = "upickle", - baseCommand = s"upack.jvm[$compilerVersion]", - dependencies = List(ujson, upickleCore) + baseCommand = s"upack.jvm[$testedCompilerVersion]", + dependencies = () => List(ujson, upickleCore) ) lazy val geny = MillCommunityProject( project = "geny", - baseCommand = s"geny.jvm[$compilerVersion]", - dependencies = List(utest) + baseCommand = s"geny.jvm[$testedCompilerVersion]", + dependencies = () => List(utest) ) lazy val fansi = MillCommunityProject( project = "fansi", - baseCommand = s"fansi.jvm[$compilerVersion]", - dependencies = List(utest, sourcecode), + baseCommand = s"fansi.jvm[$testedCompilerVersion]", + dependencies = () => List(utest, sourcecode), ignoreDocs = true ) lazy val pprint = MillCommunityProject( project = "PPrint", - baseCommand = s"pprint.jvm[$compilerVersion]", - dependencies = List(fansi), + baseCommand = s"pprint.jvm[$testedCompilerVersion]", + dependencies = () => List(fansi), ignoreDocs = true ) lazy val requests = MillCommunityProject( project = "requests-scala", - baseCommand = s"requests[$compilerVersion]", - dependencies = List(geny, utest, ujson, upickleCore) + baseCommand = s"requests[$testedCompilerVersion]", + dependencies = () => List(geny, utest, ujson, upickleCore) ) lazy val cask = MillCommunityProject( project = "cask", - baseCommand = s"cask[$compilerVersion]", - dependencies = List(utest, geny, sourcecode, pprint, upickle, upickleImplicits, upack, requests) + baseCommand = s"cask[$testedCompilerVersion]", + dependencies = () => List(utest, geny, sourcecode, pprint, upickle, upickleImplicits, upack, requests) ) lazy val scas = MillCommunityProject( @@ -276,6 +294,8 @@ object projects: sbtDocCommand = forceDoc("jvm") ) + lazy val scalacheckForwardCompat = scalacheck.forwardCompat.withScalaRelease("3.0") + lazy val scalatest: SbtCommunityProject = SbtCommunityProject( project = "scalatest", sbtTestCommand = @@ -297,7 +317,7 @@ object projects: // org.scalatest.Outcome // Problem parsing scalatest.dotty/target/scala-3.0.0-M2/src_managed/main/org/scalatest/concurrent/ConductorFixture.scala:[602..624..3843], documentation may not be generated. // dotty.tools.dotc.core.MissingType: - dependencies = List(scalaXml), + dependencies = () => List(scalaXml), testOnlyDependencies = () => List(scalatestplusJunit, scalatestplusTestNG) ) @@ -306,21 +326,21 @@ object projects: sbtTestCommand = "scalatestPlusScalaCheckJVM/test", sbtPublishCommand = "scalatestPlusScalaCheckJVM/publishLocal", sbtDocCommand = "scalatestPlusScalaCheckJVM/doc", - dependencies = List(scalatest, scalacheck) + dependencies = () => List(scalatest, scalacheck) ) lazy val scalatestplusJunit = SbtCommunityProject( project = "scalatestplus-junit", sbtTestCommand = "scalatestplus-junit/test", sbtPublishCommand = "scalatestplus-junit/publishLocal", - dependencies = List(scalatest) + dependencies = () => List(scalatest) ) lazy val scalatestplusTestNG = SbtCommunityProject( project = "scalatestplus-testng", sbtTestCommand = "test", sbtPublishCommand = "publishLocal", - dependencies = List(scalatest) + dependencies = () => List(scalatest) ) lazy val scalaXml = SbtCommunityProject( @@ -356,7 +376,7 @@ object projects: project = "minitest", sbtTestCommand = "test", sbtDocCommand = aggregateDoc("lawsJVM")("minitestJVM"), - dependencies = List(scalacheck) + dependencies = () => List(scalacheck) ) lazy val fastparse = SbtCommunityProject( @@ -414,14 +434,14 @@ object projects: project = "sconfig", sbtTestCommand = "sconfigJVM/test", sbtDocCommand = "sconfigJVM/doc", - dependencies = List(scalaCollectionCompat) + dependencies = () => List(scalaCollectionCompat) ) lazy val zio = SbtCommunityProject( project = "zio", sbtTestCommand = "testJVMDotty", sbtDocCommand = forceDoc("coreJVM"), - dependencies = List(izumiReflect) + dependencies = () => List(izumiReflect) ) lazy val munit = SbtCommunityProject( @@ -429,15 +449,17 @@ object projects: sbtTestCommand = "testsJVM/test;testsJS/test;", sbtPublishCommand = "munitJVM/publishLocal; munitJS/publishLocal; munitScalacheckJVM/publishLocal; munitScalacheckJS/publishLocal; junit/publishLocal", sbtDocCommand = "junit/doc; munitJVM/doc", - dependencies = List(scalacheck) + dependencies = () => List(scalacheck) ) + lazy val munitForwardCompat = munit.forwardCompat.withScalaRelease("3.0") + lazy val scodecBits = SbtCommunityProject( project = "scodec-bits", sbtTestCommand = "coreJVM/test;coreJS/test", sbtPublishCommand = "coreJVM/publishLocal;coreJS/publishLocal", sbtDocCommand = "coreJVM/doc", - dependencies = List(munit), + dependencies = () => List(munit), ) lazy val scodec = SbtCommunityProject( @@ -445,7 +467,7 @@ object projects: sbtTestCommand = "unitTests/test", // Adds package sbtDocCommand = "coreJVM/doc", - dependencies = List(munit, scodecBits), + dependencies = () => List(munit, scodecBits), ) lazy val scalaParserCombinators = SbtCommunityProject( @@ -478,7 +500,7 @@ object projects: // [error] class scalaz.iteratee.Iteratee cannot be unpickled because no class file was found sbtDocCommand = forceDoc("effectJVM"), - dependencies = List(scalacheck) + dependencies = () => List(scalacheck) ) lazy val endpoints4s = SbtCommunityProject( @@ -492,14 +514,16 @@ object projects: sbtTestCommand = "test", sbtPublishCommand = "publishLocal", sbtDocCommand = ";coreJVM/doc ;lawsJVM/doc ;kernelJVM/doc", - dependencies = List(cats, coop, disciplineSpecs2, scalacheck) + dependencies = () => List(cats, coop, disciplineSpecs2, scalacheck) ) + lazy val catsEffect3ForwardCompat = catsEffect3.forwardCompat.withScalaRelease("3.0") + lazy val scalaParallelCollections = SbtCommunityProject( project = "scala-parallel-collections", sbtTestCommand = "test", sbtDocCommand = forceDoc("core"), - dependencies = List(scalacheck) + dependencies = () => List(scalacheck) ) lazy val scalaCollectionCompat = SbtCommunityProject( @@ -511,8 +535,8 @@ object projects: lazy val scalaJava8Compat = SbtCommunityProject( project = "scala-java8-compat", // the fnGen subproject must be built with 2.12.x - sbtTestCommand = s"++2.12.14; ++$compilerVersion; set fnGen/dependencyOverrides := Nil; test", - sbtPublishCommand = s"++2.12.14; ++$compilerVersion; set fnGen/dependencyOverrides := Nil; publishLocal", + sbtTestCommand = s"++2.12.14; ++$testedCompilerVersion; set fnGen/dependencyOverrides := Nil; test", + sbtPublishCommand = s"++2.12.14; ++$testedCompilerVersion; set fnGen/dependencyOverrides := Nil; publishLocal", scalacOptions = Nil // avoid passing Scala 3 options to Scala 2.12 in fnGen subproject ) @@ -527,67 +551,81 @@ object projects: project = "discipline", sbtTestCommand = "coreJVM/test;coreJS/test", sbtPublishCommand = "set every credentials := Nil;coreJVM/publishLocal;coreJS/publishLocal", - dependencies = List(scalacheck) + dependencies = () => List(scalacheck) ) + lazy val disciplineForwardCompat = discipline.forwardCompat.withScalaRelease("3.0") + lazy val disciplineMunit = SbtCommunityProject( project = "discipline-munit", sbtTestCommand = "coreJVM/test;coreJS/test", sbtPublishCommand = "coreJVM/publishLocal;coreJS/publishLocal", - dependencies = List(discipline, munit) + dependencies = () => List(discipline, munit) ) + lazy val disciplineMunitForwardCompat = disciplineMunit.forwardCompat.withScalaRelease("3.0") + lazy val disciplineSpecs2 = SbtCommunityProject( project = "discipline-specs2", sbtTestCommand = "test", sbtPublishCommand = "coreJVM/publishLocal;coreJS/publishLocal", - dependencies = List(discipline), + dependencies = () => List(discipline), scalacOptions = SbtCommunityProject.scalacOptions.filter(_ != "-Ysafe-init") ) + lazy val disciplineSpecs2ForwardCompat = disciplineSpecs2.forwardCompat.withScalaRelease("3.0") + lazy val simulacrumScalafixAnnotations = SbtCommunityProject( project = "simulacrum-scalafix", sbtTestCommand = "annotation/test:compile;annotationJS/test:compile", sbtPublishCommand = "annotation/publishLocal;annotationJS/publishLocal", ) + lazy val simulacrumScalafixAnnotationsForwardCompat = simulacrumScalafixAnnotations.forwardCompat.withScalaRelease("3.0") + lazy val cats = SbtCommunityProject( project = "cats", sbtTestCommand = "set Global/scalaJSStage := FastOptStage;buildJVM;validateAllJS", sbtPublishCommand = "catsJVM/publishLocal;catsJS/publishLocal", - dependencies = List(discipline, disciplineMunit, scalacheck, simulacrumScalafixAnnotations), + dependencies = () => List(discipline, disciplineMunit, scalacheck, simulacrumScalafixAnnotations), scalacOptions = SbtCommunityProject.scalacOptions.filter(_ != "-Ysafe-init") // disable -Ysafe-init, due to -Xfatal-warning ) + lazy val catsForwardCompat = cats.forwardCompat.withScalaRelease("3.0") + lazy val catsMtl = SbtCommunityProject( project = "cats-mtl", sbtTestCommand = "testsJVM/test;testsJS/test", sbtPublishCommand = "coreJVM/publishLocal;coreJS/publishLocal;lawsJVM/publishLocal;lawsJS/publishLocal", - dependencies = List(cats, disciplineMunit) + dependencies = () => List(cats, disciplineMunit) ) + lazy val catsMtlForwardCompat = catsMtl.forwardCompat.withScalaRelease("3.0") + lazy val coop = SbtCommunityProject( project = "coop", sbtTestCommand = "test", sbtPublishCommand = "coreJVM/publishLocal;coreJS/publishLocal", - dependencies = List(cats, catsMtl) + dependencies = () => List(cats, catsMtl) ) + lazy val coopForwardCompat = coop.forwardCompat.withScalaRelease("3.0") + // 'Sciss/Lucre' with its dependencies: lazy val scissEqual = SbtCommunityProject( project = "Equal", sbtTestCommand = "rootJVM/test", sbtPublishCommand = "rootJVM/publishLocal", - dependencies = List(scalatest), + dependencies = () => List(scalatest), ) lazy val scissFingerTree = SbtCommunityProject( project = "FingerTree", sbtTestCommand = "rootJVM/test", sbtPublishCommand = "rootJVM/publishLocal", - dependencies = List(scalatest), + dependencies = () => List(scalatest), ) lazy val scissLog = SbtCommunityProject( @@ -600,42 +638,42 @@ object projects: project = "Model", sbtTestCommand = "rootJVM/test", sbtPublishCommand = "rootJVM/publishLocal", - dependencies = List(scalatest), + dependencies = () => List(scalatest), ) lazy val scissNumbers = SbtCommunityProject( project = "Numbers", sbtTestCommand = "rootJVM/test", sbtPublishCommand = "rootJVM/publishLocal", - dependencies = List(scalatest), + dependencies = () => List(scalatest), ) lazy val scissSerial = SbtCommunityProject( project = "Serial", sbtTestCommand = "rootJVM/test", sbtPublishCommand = "rootJVM/publishLocal", - dependencies = List(scalatest), + dependencies = () => List(scalatest), ) lazy val scissAsyncFile = SbtCommunityProject( project = "AsyncFile", sbtTestCommand = "rootJVM/test", sbtPublishCommand = "rootJVM/publishLocal", - dependencies = List(scissLog, scalatest), + dependencies = () => List(scissLog, scalatest), ) lazy val scissSpan = SbtCommunityProject( project = "Span", sbtTestCommand = "rootJVM/test", sbtPublishCommand = "rootJVM/publishLocal", - dependencies = List(scissSerial, scalatest), + dependencies = () => List(scissSerial, scalatest), ) lazy val scalaSTM = SbtCommunityProject( project = "scala-stm", sbtTestCommand = "rootJVM/test", sbtPublishCommand = "rootJVM/publishLocal", - dependencies = List(scalatestplusJunit), + dependencies = () => List(scalatestplusJunit), ) lazy val scissLucre = SbtCommunityProject( @@ -643,14 +681,14 @@ object projects: sbtTestCommand = "adjunctJVM/test;baseJVM/test;confluentJVM/test;coreJVM/test;dataJVM/test;expr0JVM/test;expr1JVM/test;exprJVM/test;geomJVM/test;lucre-bdb/test;testsJVM/test", extraSbtArgs = List("-Dde.sciss.lucre.ShortTests=true"), sbtPublishCommand = "adjunctJVM/publishLocal;baseJVM/publishLocal;confluentJVM/publishLocal;coreJVM/publishLocal;dataJVM/publishLocal;expr0JVM/publishLocal;expr1JVM/publishLocal;exprJVM/publishLocal;geomJVM/publishLocal;lucre-bdb/publishLocal", - dependencies = List(scalaSTM, scissAsyncFile, scissEqual, scissFingerTree, scissLog, scissModel, scissNumbers, scissSerial, scissSpan, scalatest), + dependencies = () => List(scalaSTM, scissAsyncFile, scissEqual, scissFingerTree, scissLog, scissModel, scissNumbers, scissSerial, scissSpan, scalatest), ) lazy val izumiReflect = SbtCommunityProject( project = "izumi-reflect", sbtTestCommand = "test", sbtPublishCommand = "publishLocal", - dependencies = List(scalatest) + dependencies = () => List(scalatest) ) lazy val perspective = SbtCommunityProject( @@ -658,27 +696,27 @@ object projects: // No library with easy typeclasses to verify data against exist for Dotty, so no tests yet // Until then I guess this mainly serves to check that it still compiles at all sbtTestCommand = "dottyPerspectiveExamples/compile", - dependencies = List(cats) + dependencies = () => List(cats) ) lazy val akka = SbtCommunityProject( project = "akka", - extraSbtArgs = List(s"-Dakka.build.scalaVersion=$compilerVersion"), + extraSbtArgs = List(s"-Dakka.build.scalaVersion=$testedCompilerVersion"), sbtTestCommand = "set every targetSystemJdk := true; akka-actor-tests/Test/compile", - dependencies = List(scalatest, scalatestplusJunit, scalatestplusScalacheck) + dependencies = () => List(scalatest, scalatestplusJunit, scalatestplusScalacheck) ) lazy val monocle = SbtCommunityProject( project = "Monocle", sbtTestCommand = "coreJVM/test; macrosJVM/test; testJVM/test", - dependencies = List(cats, munit, discipline, disciplineMunit) + dependencies = () => List(cats, munit, discipline, disciplineMunit) ) lazy val protoquill = SbtCommunityProject( project = "protoquill", sbtTestCommand = "test", sbtPublishCommand = "publishLocal", - dependencies = List(), // TODO add scalatest and pprint (see protoquill/build.sbt) + dependencies = () => List(), // TODO add scalatest and pprint (see protoquill/build.sbt) scalacOptions = List("-language:implicitConversions"), // disabled -Ysafe-init, due to bug in macro ) @@ -686,67 +724,80 @@ object projects: project = "onnx-scala", sbtTestCommand = "test", sbtPublishCommand = "publishLocal", - dependencies = List(scalatest) + dependencies = () => List(scalatest) ) lazy val playJson = SbtCommunityProject( project = "play-json", sbtTestCommand = "test", sbtPublishCommand = "publishLocal", - dependencies = List(scalatest, scalatestplusScalacheck), + dependencies = () => List(scalatest, scalatestplusScalacheck), ) lazy val munitCatsEffect = SbtCommunityProject( project = "munit-cats-effect", sbtTestCommand = "ce3JVM/test; ce3JS/test", sbtPublishCommand = "ce3JVM/publishLocal; ce3JS/publishLocal", - dependencies = List(munit, catsEffect3) + dependencies = () => List(munit, catsEffect3) ) lazy val scalacheckEffect = SbtCommunityProject( project = "scalacheck-effect", sbtTestCommand = "test", sbtPublishCommand = "publishLocal", - dependencies = List(cats, catsEffect3, munit, scalacheck) + dependencies = () => List(cats, catsEffect3, munit, scalacheck) ) lazy val fs2 = SbtCommunityProject( project = "fs2", sbtTestCommand = "coreJVM/test; coreJS/test", // io/test requires JDK9+ sbtPublishCommand = "coreJVM/publishLocal; coreJS/publishLocal", - dependencies = List(cats, catsEffect3, munitCatsEffect, scalacheckEffect, scodecBits) + dependencies = () => List(cats, catsEffect3, munitCatsEffect, scalacheckEffect, scodecBits) ) lazy val libretto = SbtCommunityProject( project = "libretto", sbtTestCommand = "core/test; examples/compile", sbtPublishCommand = "core/publishLocal; examples/publishLocal", - dependencies = List(scalatest) + dependencies = () => List(scalatest) ) lazy val jacksonModuleScala = SbtCommunityProject( project = "jackson-module-scala", sbtTestCommand = "test", sbtPublishCommand = "publishLocal", - dependencies = List(scalaJava8Compat, scalatest) + dependencies = () => List(scalaJava8Compat, scalatest) ) lazy val specs2 = SbtCommunityProject( project = "specs2", sbtTestCommand = "core/testOnly -- exclude ci", sbtPublishCommand = "core/publishLocal", - dependencies = List() + dependencies = () => List() ) lazy val spire = SbtCommunityProject( project = "spire", sbtTestCommand = "test", sbtPublishCommand = "publishLocal", - dependencies = List(cats, disciplineMunit) + dependencies = () => List(cats, disciplineMunit) ) end projects +lazy val forwardCompatMapping = Map[CommunityProject, CommunityProject]( + projects.scalacheck -> projects.scalacheckForwardCompat, + projects.munit -> projects.munitForwardCompat, + projects.discipline -> projects.disciplineForwardCompat, + projects.disciplineMunit -> projects.disciplineMunitForwardCompat, + projects.disciplineSpecs2 -> projects.disciplineSpecs2ForwardCompat, + projects.simulacrumScalafixAnnotations -> projects.simulacrumScalafixAnnotationsForwardCompat, + projects.cats -> projects.catsForwardCompat, + projects.catsMtl -> projects.catsMtlForwardCompat, + projects.coop -> projects.coopForwardCompat, + projects.catsEffect3 -> projects.catsEffect3ForwardCompat, +) + def allProjects = List( projects.utest, projects.sourcecode, @@ -766,6 +817,7 @@ def allProjects = List( projects.intent, projects.algebra, projects.scalacheck, + projects.scalacheckForwardCompat, projects.scalatest, projects.scalatestplusScalacheck, projects.scalatestplusJunit, @@ -782,6 +834,7 @@ def allProjects = List( projects.sconfig, projects.zio, projects.munit, + projects.munitForwardCompat, projects.scodecBits, projects.scodec, projects.scalaParserCombinators, @@ -789,16 +842,23 @@ def allProjects = List( projects.scalaz, projects.endpoints4s, projects.catsEffect3, + projects.catsEffect3ForwardCompat, projects.scalaParallelCollections, projects.scalaCollectionCompat, projects.scalaJava8Compat, projects.verify, projects.discipline, + projects.disciplineForwardCompat, projects.disciplineMunit, + projects.disciplineMunitForwardCompat, projects.disciplineSpecs2, + projects.disciplineSpecs2ForwardCompat, projects.simulacrumScalafixAnnotations, + projects.simulacrumScalafixAnnotationsForwardCompat, projects.cats, + projects.catsForwardCompat, projects.catsMtl, + projects.catsMtlForwardCompat, projects.coop, projects.scissEqual, projects.scissFingerTree, @@ -824,6 +884,8 @@ def allProjects = List( projects.libretto, projects.jacksonModuleScala, projects.specs2, + projects.coop, + projects.coopForwardCompat ) lazy val projectMap = allProjects.groupBy(_.project) diff --git a/community-build/test/scala/dotty/communitybuild/CommunityBuildTest.scala b/community-build/test/scala/dotty/communitybuild/CommunityBuildTest.scala index 4aad0e26d265..f3f0bf90188e 100644 --- a/community-build/test/scala/dotty/communitybuild/CommunityBuildTest.scala +++ b/community-build/test/scala/dotty/communitybuild/CommunityBuildTest.scala @@ -100,3 +100,17 @@ class CommunityBuildTestC: @Test def verify = projects.verify.run() @Test def xmlInterpolator = projects.xmlInterpolator.run() end CommunityBuildTestC + +@Category(Array(classOf[TestCategory])) +class CommunityBuildTestForwardCompat: + @Test def catsEffect3ForwardCompat = projects.catsEffect3ForwardCompat.run() + @Test def catsForwardCompat = projects.catsForwardCompat.run() + @Test def catsMtlForwardCompat = projects.catsMtlForwardCompat.run() + @Test def coopForwardCompat = projects.coopForwardCompat.run() + @Test def disciplineForwardCompat = projects.disciplineForwardCompat.run() + @Test def disciplineMunitForwardCompat = projects.disciplineMunitForwardCompat.run() + @Test def disciplineSpecs2ForwardCompat = projects.disciplineSpecs2ForwardCompat.run() + @Test def munitForwardCompat = projects.munitForwardCompat.run() + @Test def scalacheckForwardCompat = projects.scalacheckForwardCompat.run() + @Test def simulacrumScalafixAnnotationsForwardCompat = projects.simulacrumScalafixAnnotationsForwardCompat.run() +end CommunityBuildTestForwardCompat diff --git a/sbt-community-build/src/dotty/communitybuild/sbtplugin/CommunityBuildPlugin.scala b/sbt-community-build/src/dotty/communitybuild/sbtplugin/CommunityBuildPlugin.scala index 0b580e1cb77a..536d8a36d453 100644 --- a/sbt-community-build/src/dotty/communitybuild/sbtplugin/CommunityBuildPlugin.scala +++ b/sbt-community-build/src/dotty/communitybuild/sbtplugin/CommunityBuildPlugin.scala @@ -15,6 +15,16 @@ object CommunityBuildPlugin extends AutoPlugin { override def requires = plugins.JvmPlugin override def trigger = allRequirements + object autoImport { + val isForwardCompatProject = settingKey[Boolean]("Is it a project used for testing forward binary compatibility?") + } + + import autoImport._ + + override val globalSettings: Seq[Setting[_]] = Seq( + isForwardCompatProject := false + ) + override val projectSettings: Seq[Setting[_]] = Seq( publishLocal := Def.taskDyn { val pubLocalResult = publishLocal.value @@ -23,14 +33,26 @@ object CommunityBuildPlugin extends AutoPlugin { CommunityBuildDependencies.publish(projectID.value) pubLocalResult } - }.value + }.value, + projectID := { + val id = projectID.value + if (isForwardCompatProject.value) { + val revision = if (id.revision.endsWith("-SNAPSHOT")) + id.revision.replace("-SNAPSHOT", "-forward-compat-SNAPSHOT") + else + id.revision + "-forward-compat" + id.withRevision(revision) + } else + id + } ) override val buildSettings: Seq[Setting[_]] = Seq( dependencyOverrides ++= { - if (scalaVersion.value.startsWith("3.")) - CommunityBuildDependencies.allOverrides(sLog.value) - else Nil + if (scalaVersion.value.startsWith("3.")) { + val predicate: ModuleID => Boolean = if (isForwardCompatProject.value) (_.revision.contains("-forward-compat")) else (!_.revision.contains("-forward-compat")) + CommunityBuildDependencies.allOverrides(sLog.value).filter(predicate) + } else Nil } ) } From e1f00c876770795df78e65b4a38716d7615d98c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pa=C5=82ka?= Date: Thu, 13 Jan 2022 18:07:24 +0100 Subject: [PATCH 17/22] -Yscala-release support: test cats-effect compiled with scala 3.0.2 while the dependencies are compiled with -Yscala-release 3.0; improve documentation --- .../src/scala/dotty/communitybuild/projects.scala | 2 +- .../src/dotty/tools/dotc/config/ScalaSettings.scala | 2 +- docs/docs/contributing/testing.md | 11 +++++++++++ .../sbtplugin/CommunityBuildPlugin.scala | 8 ++++++-- 4 files changed, 19 insertions(+), 4 deletions(-) diff --git a/community-build/src/scala/dotty/communitybuild/projects.scala b/community-build/src/scala/dotty/communitybuild/projects.scala index 424922a572a5..f45d6b6109b8 100644 --- a/community-build/src/scala/dotty/communitybuild/projects.scala +++ b/community-build/src/scala/dotty/communitybuild/projects.scala @@ -517,7 +517,7 @@ object projects: dependencies = () => List(cats, coop, disciplineSpecs2, scalacheck) ) - lazy val catsEffect3ForwardCompat = catsEffect3.forwardCompat.withScalaRelease("3.0") + lazy val catsEffect3ForwardCompat = catsEffect3.forwardCompat.copy(compilerVersion = "3.0.2") lazy val scalaParallelCollections = SbtCommunityProject( project = "scala-parallel-collections", diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index 4914b2714622..4dccad86e98c 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -310,7 +310,7 @@ private sealed trait YSettings: val YexplicitNulls: Setting[Boolean] = BooleanSetting("-Yexplicit-nulls", "Make reference types non-nullable. Nullable types can be expressed with unions: e.g. String|Null.") val YcheckInit: Setting[Boolean] = BooleanSetting("-Ysafe-init", "Ensure safe initialization of objects") val YrequireTargetName: Setting[Boolean] = BooleanSetting("-Yrequire-targetName", "Warn if an operator is defined without a @targetName annotation") - val YscalaRelease: Setting[String] = ChoiceSetting("-Yscala-release", "release", "Emit TASTy files that can be consumed by specified version of the compiler.", ScalaSettings.supportedScalaReleaseVersions, "", aliases = List("--Yscala-release")) + val YscalaRelease: Setting[String] = ChoiceSetting("-Yscala-release", "release", "Emit TASTy files that can be consumed by specified version of the compiler. The compilation will fail if for any reason valid TASTy cannot be produced (e.g. the code contains references to some parts of the standard library API that are missing in the older stdlib or uses language features unexpressible in the older version of TASTy format)", ScalaSettings.supportedScalaReleaseVersions, "", aliases = List("--Yscala-release")) /** Area-specific debug output */ val YexplainLowlevel: Setting[Boolean] = BooleanSetting("-Yexplain-lowlevel", "When explaining type errors, show types at a lower level.") diff --git a/docs/docs/contributing/testing.md b/docs/docs/contributing/testing.md index fa333634f07b..41e8b0b9b85e 100644 --- a/docs/docs/contributing/testing.md +++ b/docs/docs/contributing/testing.md @@ -127,6 +127,17 @@ $ sbt > testCompilation --help ``` +### Joint and separate sources compilation + +When the sources of a test consist of multiple source files places in a single directory they are passed to the compiler in a single run and the compiler decides in which order to compile them. In some cases, however, to reproduce a specific test scenario it might be necessary to compile the source files in several steps in a specified order. To achieve that one can add a `_${step_index}` suffix to a file name (before the `.scala` or `.java` extension) indicating the order of compilation. E.g. if the test directory contains files named `Foo_1.scala`, `Bar_2.scala` and `Baz_2.scala` then `Foo_1.scala` will be compiled first and after that `Bar_2.scala` together with `Baz_2.scala`. + +There are also other suffixes indicating how some particular files are compiled: +* `_c${compilerVersion}` - compile a file with a specific version of the compiler instead of the one developed on the current branch + (e.g. `Foo_c3.0.2.scala`) +* `_r${release}` - compile a file with a given value of `-Yscala-release` flag (e.g. `Foo_r3.0.scala`) + +Different suffixes can be mixed together (their order is not important although consistency is advised), e.g. `Foo_1_r3.0`, `Bar_2_c3.0.2`. + ### Bootstrapped-only tests To run `testCompilation` on a bootstrapped Dotty compiler, use diff --git a/sbt-community-build/src/dotty/communitybuild/sbtplugin/CommunityBuildPlugin.scala b/sbt-community-build/src/dotty/communitybuild/sbtplugin/CommunityBuildPlugin.scala index 536d8a36d453..ab5fc803f361 100644 --- a/sbt-community-build/src/dotty/communitybuild/sbtplugin/CommunityBuildPlugin.scala +++ b/sbt-community-build/src/dotty/communitybuild/sbtplugin/CommunityBuildPlugin.scala @@ -50,8 +50,12 @@ object CommunityBuildPlugin extends AutoPlugin { override val buildSettings: Seq[Setting[_]] = Seq( dependencyOverrides ++= { if (scalaVersion.value.startsWith("3.")) { - val predicate: ModuleID => Boolean = if (isForwardCompatProject.value) (_.revision.contains("-forward-compat")) else (!_.revision.contains("-forward-compat")) - CommunityBuildDependencies.allOverrides(sLog.value).filter(predicate) + val forwardCompatFilter: ModuleID => Boolean = if (isForwardCompatProject.value) (_.revision.contains("-forward-compat")) else (!_.revision.contains("-forward-compat")) + val stdlibOverrides = Seq( + scalaOrganization.value %% "scala3-library" % scalaVersion.value, + scalaOrganization.value %% "scala3-library_sjs1" % scalaVersion.value, + ) + CommunityBuildDependencies.allOverrides(sLog.value).filter(forwardCompatFilter) ++ stdlibOverrides } else Nil } ) From f2aede6f93eae948be984313d9d73071df240d78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pa=C5=82ka?= Date: Fri, 14 Jan 2022 12:25:48 +0100 Subject: [PATCH 18/22] -Yscala-release support: Improve caching artifacts downloaded for tests --- compiler/test/dotty/Properties.scala | 5 +++++ compiler/test/dotty/tools/vulpix/ParallelTesting.scala | 9 +++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/compiler/test/dotty/Properties.scala b/compiler/test/dotty/Properties.scala index 042773505dc5..fd0d8c7c1886 100644 --- a/compiler/test/dotty/Properties.scala +++ b/compiler/test/dotty/Properties.scala @@ -15,6 +15,11 @@ object Properties { val isRunByCI: Boolean = sys.env.isDefinedAt("DOTTY_CI_RUN") || sys.env.isDefinedAt("DRONE") // TODO remove this when we drop Drone + val testCache: Path = + sys.env.get("DOTTY_TEST_CACHE").map(Paths.get(_)).getOrElse { + Paths.get(sys.props("user.home"), ".cache", "dotty", "test") + } + /** Tests should run interactive? */ val testsInteractive: Boolean = propIsNullOrTrue("dotty.tests.interactive") diff --git a/compiler/test/dotty/tools/vulpix/ParallelTesting.scala b/compiler/test/dotty/tools/vulpix/ParallelTesting.scala index 02fc8d785a61..28ed07a82ebb 100644 --- a/compiler/test/dotty/tools/vulpix/ParallelTesting.scala +++ b/compiler/test/dotty/tools/vulpix/ParallelTesting.scala @@ -1426,14 +1426,15 @@ object ParallelTesting { dir else import scala.sys.process._ - val zipPath = cache.resolve(s"scala3-$version.tar.gz") + val archivePath = cache.resolve(s"scala3-$version.tar.gz") val compilerDownloadUrl = s"https://github.com/lampepfl/dotty/releases/download/$version/scala3-$version.tar.gz" - (URL(compilerDownloadUrl) #>> zipPath.toFile #&& s"tar -xf $zipPath -C $cache").!! + (URL(compilerDownloadUrl) #>> archivePath.toFile #&& s"tar -xf $archivePath -C $cache").!! + archivePath.toFile.delete() dir } private lazy val cache = - val dir = Files.createTempDirectory("dotty.tests") - dir.toFile.deleteOnExit() + val dir = Properties.testCache.resolve("compilers") + dir.toFile.mkdirs() dir } From 3f88de487858a761fdbf87ac0991085b8a340789 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pa=C5=82ka?= Date: Fri, 14 Jan 2022 12:34:22 +0100 Subject: [PATCH 19/22] -Yscala-release support: temporarily enable forward compat community build tests to run always --- .github/workflows/ci.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index ad26d9881243..b5477b526ecc 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -327,6 +327,12 @@ jobs: - ${{ github.workspace }}/../../cache/ivy:/root/.ivy2/cache - ${{ github.workspace }}/../../cache/general:/root/.cache if: "github.event_name == 'schedule' && github.repository == 'lampepfl/dotty' + || github.event_name == 'push' + || ( + github.event_name == 'pull_request' + && !contains(github.event.pull_request.body, '[skip ci]') + && !contains(github.event.pull_request.body, '[skip community_build]') + ) || ( github.event_name == 'workflow_dispatch' && github.repository == 'lampepfl/dotty' From a86db0565917df96e02be90783f083809ae0837a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pa=C5=82ka?= Date: Mon, 17 Jan 2022 15:17:46 +0100 Subject: [PATCH 20/22] -Yscala-release support: checking if classes come from stdlib based on jar name; better documentation --- .../src/dotty/tools/dotc/core/Contexts.scala | 2 +- .../dotc/core/classfile/ClassfileParser.scala | 10 +++++--- .../language-versions/binary-compatibility.md | 25 +++++++++++++++++++ .../language-versions/language-versions.md | 6 +++++ .../source-compatibility.md} | 6 +++-- docs/sidebar.yml | 6 ++++- .../sbtplugin/CommunityBuildPlugin.scala | 2 +- 7 files changed, 49 insertions(+), 8 deletions(-) create mode 100644 docs/docs/reference/language-versions/binary-compatibility.md create mode 100644 docs/docs/reference/language-versions/language-versions.md rename docs/docs/reference/{language-versions.md => language-versions/source-compatibility.md} (74%) diff --git a/compiler/src/dotty/tools/dotc/core/Contexts.scala b/compiler/src/dotty/tools/dotc/core/Contexts.scala index 0af08146bca0..e9fbd6065261 100644 --- a/compiler/src/dotty/tools/dotc/core/Contexts.scala +++ b/compiler/src/dotty/tools/dotc/core/Contexts.scala @@ -493,7 +493,7 @@ object Contexts { import math.Ordered.orderingToOrdered val latestRelease = ScalaRelease.latest val specifiedRelease = scalaRelease - if ((specifiedRelease.majorVersion, specifiedRelease.minorVersion) < (latestRelease.majorVersion, latestRelease.minorVersion)) then + if specifiedRelease < latestRelease then // This is needed to make -Yscala-release a no-op when set to the latest release for unstable versions of the compiler // (which might have the tasty format version numbers set to higher values before they're decreased during a release) TastyVersion.fromStableScalaRelease(specifiedRelease.majorVersion, specifiedRelease.minorVersion) diff --git a/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala b/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala index 9e388b88b72a..264a46f92eff 100644 --- a/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala +++ b/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala @@ -969,9 +969,13 @@ class ClassfileParser( if !isTastyReadable then reportWrongTasty("its TASTy format cannot be read by the compiler", TastyVersion.compilerVersion) else - val isTastyCompatible = - fileTastyVersion.isCompatibleWith(ctx.tastyVersion) || - classRoot.symbol.showFullName.startsWith("scala.") // References to stdlib are considered safe because we check the values of @since annotations + def isStdlibClass(cls: ClassDenotation): Boolean = + ctx.platform.classPath.findClassFile(cls.fullName.mangledString) match { + case Some(entry: ZipArchive#Entry) => + entry.underlyingSource.map(_.name.startsWith("scala3-library_3-")).getOrElse(false) + case _ => false + } + val isTastyCompatible = fileTastyVersion.isCompatibleWith(ctx.tastyVersion) || isStdlibClass(classRoot) if !isTastyCompatible then reportWrongTasty(s"its TASTy format is not compatible with the one of the targeted Scala release (${ctx.scalaRelease.show})", ctx.tastyVersion) diff --git a/docs/docs/reference/language-versions/binary-compatibility.md b/docs/docs/reference/language-versions/binary-compatibility.md new file mode 100644 index 000000000000..5697a3e7a388 --- /dev/null +++ b/docs/docs/reference/language-versions/binary-compatibility.md @@ -0,0 +1,25 @@ +--- +layout: doc-page +title: "Binary Compatibility" +--- + +Thanks to TASTy files, which are produced during compilation of Scala 3 sources, unlike Scala 2, Scala 3 is backward binary compatible between different minor versions (i.e. binary artifacts produced with Scala 3.x can be consumed by Scala 3.y programs as long as x <= y). +There are however already some ongoing attempts to make Scala 3 forward binary compatible which means that, with some restrictions, it might be possible to compile Scala code with a newer version of the compiler and then use the produced binaries as dependencies for a project using an older compiler. + +Scala 3.1.2-RC1 adds an experimental `-Yscala-release ` compiler flag which makes the compiler produce TASTy files that should be possible to use by all Scala 3 compilers in version `` or newer (this flag was inspired by how `-release` works for specifying the target version of JDK). More specifically this flag enforces emitting TASTy files in an older format ensuring that: +* the code contains no references to parts of the standard library which were added to the API after `` and would crash at runtime when a program is executed with the older version of the standard library on the classpath +* no dependency found on the classpath during compilation (except for the standard library itself) contains TASTy files produced by a compiler newer than `` (otherwise they could potentially leak such disallowed references to the standard library) +If any of the checks above is not fulfilled or for any other reason older TASTy cannot be emitted (e.g. the code uses some new language features which cannot be expressed the the older format) the entire compilation fails (with errors reported for each of such issues). + +As this feature is experimental it does not have any special support in build tools yet (at least not in sbt 1.6.1 or lower). +E.g. when a project gets compiled with Scala compiler `3.x1.y1` and `-Yscala-release 3.x2` option and then published using sbt +then the standard library in version `3.x1.y1` gets added to the project's dependencies instead of `3.x2.y2`. +When the dependencies are added to the classpath during compilation with Scala `3.x2.y2` the compiler will crash while trying to read TASTy files in the newer format. +A currently known workaround is to modify the build definition of the dependent project by explicitly overriding the version of Scala standard library in dependencies, e.g. + +```scala +dependencyOverrides ++= Seq( + scalaOrganization.value %% "scala3-library" % scalaVersion.value, + scalaOrganization.value %% "scala3-library_sjs1" % scalaVersion.value // for Scala.js projects +) +``` \ No newline at end of file diff --git a/docs/docs/reference/language-versions/language-versions.md b/docs/docs/reference/language-versions/language-versions.md new file mode 100644 index 000000000000..e98aba32fd34 --- /dev/null +++ b/docs/docs/reference/language-versions/language-versions.md @@ -0,0 +1,6 @@ +--- +layout: doc-page +title: "Language Versions" +--- + +Additional information on interoperability and migration between Scala 2 and 3 can be found [here](https://docs.scala-lang.org/scala3/guides/migration/compatibility-intro.html). diff --git a/docs/docs/reference/language-versions.md b/docs/docs/reference/language-versions/source-compatibility.md similarity index 74% rename from docs/docs/reference/language-versions.md rename to docs/docs/reference/language-versions/source-compatibility.md index ef31ccc28707..029a3674ba73 100644 --- a/docs/docs/reference/language-versions.md +++ b/docs/docs/reference/language-versions/source-compatibility.md @@ -1,10 +1,12 @@ --- layout: doc-page -title: "Language Versions" +title: "Source Compatibility" movedTo: https://docs.scala-lang.org/scala3/reference/language-versions.html --- -The default Scala language version currently supported by the Dotty compiler is [`3.0`](https://scala-lang.org/api/3.x/scala/runtime/stdLibPatches/language$$3/0$.html). There are also other language versions that can be specified instead: +Scala 3 does NOT guarantee source compatibility between different minor language versions (e.g. some syntax valid in 3.x might get deprecated and then phased out in 3.y for y > x). There are also some syntax structures that were valid in Scala 2 but are not anymore in Scala 3. However the compiler provides a possibility to specify the desired version of syntax used in a particular file or globally for a run of the compiler to make migration between versions easier. + +The default Scala language syntax version currently supported by the Dotty compiler is [`3.0`](https://scala-lang.org/api/3.x/scala/runtime/stdLibPatches/language$$3/0$.html). There are also other language versions that can be specified instead: - [`3.0-migration`](https://scala-lang.org/api/3.x/scala/runtime/stdLibPatches/language$$3/0-migration$.html): Same as `3.0` but with a Scala 2 compatibility mode that helps moving Scala 2.13 sources over to Scala 3. In particular, it diff --git a/docs/sidebar.yml b/docs/sidebar.yml index 9ff759a98afe..6f8af7cd53eb 100644 --- a/docs/sidebar.yml +++ b/docs/sidebar.yml @@ -119,7 +119,11 @@ sidebar: - page: docs/reference/experimental/named-typeargs-spec.md - page: docs/reference/experimental/numeric-literals.md - page: docs/reference/syntax.md - - page: docs/reference/language-versions.md + - title: Language Versions + index: docs/reference/language-versions/language-versions.md + subsection: + - page: docs/reference/language-versions/source-compatibility.md + - page: docs/reference/language-versions/binary-compatibility.md - title: Contributing subsection: - page: docs/contributing/contribute-knowledge.md diff --git a/sbt-community-build/src/dotty/communitybuild/sbtplugin/CommunityBuildPlugin.scala b/sbt-community-build/src/dotty/communitybuild/sbtplugin/CommunityBuildPlugin.scala index ab5fc803f361..d6cebbfe3cac 100644 --- a/sbt-community-build/src/dotty/communitybuild/sbtplugin/CommunityBuildPlugin.scala +++ b/sbt-community-build/src/dotty/communitybuild/sbtplugin/CommunityBuildPlugin.scala @@ -53,7 +53,7 @@ object CommunityBuildPlugin extends AutoPlugin { val forwardCompatFilter: ModuleID => Boolean = if (isForwardCompatProject.value) (_.revision.contains("-forward-compat")) else (!_.revision.contains("-forward-compat")) val stdlibOverrides = Seq( scalaOrganization.value %% "scala3-library" % scalaVersion.value, - scalaOrganization.value %% "scala3-library_sjs1" % scalaVersion.value, + scalaOrganization.value %% "scala3-library_sjs1" % scalaVersion.value ) CommunityBuildDependencies.allOverrides(sLog.value).filter(forwardCompatFilter) ++ stdlibOverrides } else Nil From 0c8ac84f16db13cbaf2d4509fac0e37c9f4340de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pa=C5=82ka?= Date: Tue, 18 Jan 2022 14:22:41 +0100 Subject: [PATCH 21/22] -Yscala-release support: checking if classes come from stdlib based on jar name; better documentation - v2 --- .../scala/dotty/communitybuild/projects.scala | 2 +- .../dotc/core/classfile/ClassfileParser.scala | 2 +- .../language-versions/binary-compatibility.md | 18 +++++++++++++----- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/community-build/src/scala/dotty/communitybuild/projects.scala b/community-build/src/scala/dotty/communitybuild/projects.scala index f45d6b6109b8..efaaa24a5ac3 100644 --- a/community-build/src/scala/dotty/communitybuild/projects.scala +++ b/community-build/src/scala/dotty/communitybuild/projects.scala @@ -601,7 +601,7 @@ object projects: dependencies = () => List(cats, disciplineMunit) ) - lazy val catsMtlForwardCompat = catsMtl.forwardCompat.withScalaRelease("3.0") + lazy val catsMtlForwardCompat = catsMtl.forwardCompat.copy(compilerVersion = "3.0.2") lazy val coop = SbtCommunityProject( project = "coop", diff --git a/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala b/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala index 264a46f92eff..281ada4b9ae3 100644 --- a/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala +++ b/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala @@ -972,7 +972,7 @@ class ClassfileParser( def isStdlibClass(cls: ClassDenotation): Boolean = ctx.platform.classPath.findClassFile(cls.fullName.mangledString) match { case Some(entry: ZipArchive#Entry) => - entry.underlyingSource.map(_.name.startsWith("scala3-library_3-")).getOrElse(false) + entry.underlyingSource.map(_.name.startsWith("scala3-library_")).getOrElse(false) case _ => false } val isTastyCompatible = fileTastyVersion.isCompatibleWith(ctx.tastyVersion) || isStdlibClass(classRoot) diff --git a/docs/docs/reference/language-versions/binary-compatibility.md b/docs/docs/reference/language-versions/binary-compatibility.md index 5697a3e7a388..1f04aaf4bdf2 100644 --- a/docs/docs/reference/language-versions/binary-compatibility.md +++ b/docs/docs/reference/language-versions/binary-compatibility.md @@ -3,12 +3,18 @@ layout: doc-page title: "Binary Compatibility" --- -Thanks to TASTy files, which are produced during compilation of Scala 3 sources, unlike Scala 2, Scala 3 is backward binary compatible between different minor versions (i.e. binary artifacts produced with Scala 3.x can be consumed by Scala 3.y programs as long as x <= y). -There are however already some ongoing attempts to make Scala 3 forward binary compatible which means that, with some restrictions, it might be possible to compile Scala code with a newer version of the compiler and then use the produced binaries as dependencies for a project using an older compiler. +In Scala 2 different minor versions of the compiler were free to change the way how they encode different language features in JVM bytecode so each bump of the compiler's minor version resulted in breaking binary compatibility and if a project had any Scala dependencies they all needed to be (cross-)compiled to the same minor Scala version that was used in that project itself. +While in Scala 3 the JVM encoding might still change between minor versions, an additional intermediate format of code representation called TASTy (from `Typed Abstract Syntax Tree`) was introduced. -Scala 3.1.2-RC1 adds an experimental `-Yscala-release ` compiler flag which makes the compiler produce TASTy files that should be possible to use by all Scala 3 compilers in version `` or newer (this flag was inspired by how `-release` works for specifying the target version of JDK). More specifically this flag enforces emitting TASTy files in an older format ensuring that: +TASTy files are produced from Scala 3 sources during compilation, together with classfiles, and they're normally also included in published artifacts. A Scala compiler in version `3.x1.y1` is able to read TASTy files produced by another compiler in version `3.x2.y2` if `x1 >= x2` (assuming two stable versions of the compiler are considered - `SNAPSHOT` or `NIGHTLY` compiler versions can read TASTy in an older stable format but their TASTY versions are not compatible between each other even if the compilers have the same minor version; also compilers in stable versions cannot read TASTy generated by an unstable version). While having TASTy files in an understandable format on its classpath a Scala 3 compiler can generate bytecode for a project's dependencies on the fly. + +Being able to bump the compiler version in a project without having to wait for all of its dependencies to do the same is already a big leap forward when compared to Scala 2. However, me might still try to do better, especially from the perspective of authors of libraries. +If you maintain a library and you would like it to be usable as a dependency for all Scala 3 projects, you would have to always emit TASTy in a version that would be readble by everyone, which would normally mean getting stuck at 3.0.x forever. + +To solve this problem a new experimental compiler flag `-Yscala-release ` (available since 3.1.2-RC1) has been added. Setting this flag makes the compiler produce TASTy files that should be possible to use by all Scala 3 compilers in version `` or newer (this flag was inspired by how `-release` works for specifying the target version of JDK). More specifically this enforces emitting TASTy files in an older format ensuring that: * the code contains no references to parts of the standard library which were added to the API after `` and would crash at runtime when a program is executed with the older version of the standard library on the classpath -* no dependency found on the classpath during compilation (except for the standard library itself) contains TASTy files produced by a compiler newer than `` (otherwise they could potentially leak such disallowed references to the standard library) +* no dependency found on the classpath during compilation (except for the standard library itself) contains TASTy files produced by a compiler newer than `` (otherwise they could potentially leak such disallowed references to the standard library). + If any of the checks above is not fulfilled or for any other reason older TASTy cannot be emitted (e.g. the code uses some new language features which cannot be expressed the the older format) the entire compilation fails (with errors reported for each of such issues). As this feature is experimental it does not have any special support in build tools yet (at least not in sbt 1.6.1 or lower). @@ -22,4 +28,6 @@ dependencyOverrides ++= Seq( scalaOrganization.value %% "scala3-library" % scalaVersion.value, scalaOrganization.value %% "scala3-library_sjs1" % scalaVersion.value // for Scala.js projects ) -``` \ No newline at end of file +``` + +The behaviour of `-Yscala-release` flag might still change in the future, especially it's not guaranteed that every new version of the compiler would be able to generate TASTy in all older formats going back to the one produced by `3.0.x` compiler. From 85cf9bef162d2e2179945c50949f179376ccc10c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pa=C5=82ka?= Date: Wed, 19 Jan 2022 10:45:21 +0100 Subject: [PATCH 22/22] -Yscala-release support: fix documentation; run extended CI only for nightly builds --- .github/workflows/ci.yaml | 6 ------ .../tools/dotc/core/classfile/ClassfileParser.scala | 3 +++ .../language-versions/binary-compatibility.md | 11 +++++++---- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index b5477b526ecc..ad26d9881243 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -327,12 +327,6 @@ jobs: - ${{ github.workspace }}/../../cache/ivy:/root/.ivy2/cache - ${{ github.workspace }}/../../cache/general:/root/.cache if: "github.event_name == 'schedule' && github.repository == 'lampepfl/dotty' - || github.event_name == 'push' - || ( - github.event_name == 'pull_request' - && !contains(github.event.pull_request.body, '[skip ci]') - && !contains(github.event.pull_request.body, '[skip community_build]') - ) || ( github.event_name == 'workflow_dispatch' && github.repository == 'lampepfl/dotty' diff --git a/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala b/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala index 281ada4b9ae3..9920fc060142 100644 --- a/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala +++ b/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala @@ -975,6 +975,9 @@ class ClassfileParser( entry.underlyingSource.map(_.name.startsWith("scala3-library_")).getOrElse(false) case _ => false } + // While emitting older TASTy the the newer standard library used by the compiler will still be on the class path so trying to read its TASTy files should not cause a crash. + // This is OK however because references to elements of stdlib API are validated according to the values of their `@since` annotations. + // This should guarantee that the code won't crash at runtime when used with the stdlib provided by an older compiler. val isTastyCompatible = fileTastyVersion.isCompatibleWith(ctx.tastyVersion) || isStdlibClass(classRoot) if !isTastyCompatible then reportWrongTasty(s"its TASTy format is not compatible with the one of the targeted Scala release (${ctx.scalaRelease.show})", ctx.tastyVersion) diff --git a/docs/docs/reference/language-versions/binary-compatibility.md b/docs/docs/reference/language-versions/binary-compatibility.md index 1f04aaf4bdf2..a25a4203fd6b 100644 --- a/docs/docs/reference/language-versions/binary-compatibility.md +++ b/docs/docs/reference/language-versions/binary-compatibility.md @@ -3,12 +3,15 @@ layout: doc-page title: "Binary Compatibility" --- -In Scala 2 different minor versions of the compiler were free to change the way how they encode different language features in JVM bytecode so each bump of the compiler's minor version resulted in breaking binary compatibility and if a project had any Scala dependencies they all needed to be (cross-)compiled to the same minor Scala version that was used in that project itself. -While in Scala 3 the JVM encoding might still change between minor versions, an additional intermediate format of code representation called TASTy (from `Typed Abstract Syntax Tree`) was introduced. +In Scala 2 different minor versions of the compiler were free to change the way how they encode different language features in JVM bytecode so each bump of the compiler's minor version resulted in breaking binary compatibility and if a project had any Scala dependencies they all needed to be (cross-)compiled to the same minor Scala version that was used in that project itself. On the contrary, Scala 3 has a stable encoding into JVM bytecode. -TASTy files are produced from Scala 3 sources during compilation, together with classfiles, and they're normally also included in published artifacts. A Scala compiler in version `3.x1.y1` is able to read TASTy files produced by another compiler in version `3.x2.y2` if `x1 >= x2` (assuming two stable versions of the compiler are considered - `SNAPSHOT` or `NIGHTLY` compiler versions can read TASTy in an older stable format but their TASTY versions are not compatible between each other even if the compilers have the same minor version; also compilers in stable versions cannot read TASTy generated by an unstable version). While having TASTy files in an understandable format on its classpath a Scala 3 compiler can generate bytecode for a project's dependencies on the fly. +In addition to classfiles the compilation process in Scala 3 also produces files with `.tasty` extension. The [TASTy](https://docs.scala-lang.org/scala3/guides/tasty-overview.html) format is an intermediate representation of Scala code containing full information about sources together with information provided by the typer. Some of this information is lost during generation of bytecode so Scala 3 compilers read TASTy files during compilation in addition to classfiles to know the exact types of values, methods, etc. in already compiled classes (although compilation from TASTy files only is also possible). TASTy files are also typically distributed together with classfiles in published artifacts. -Being able to bump the compiler version in a project without having to wait for all of its dependencies to do the same is already a big leap forward when compared to Scala 2. However, me might still try to do better, especially from the perspective of authors of libraries. +TASTy format is extensible but it preserves backward compatibility and the evolution happens between minor releases of the language. This means a Scala compiler in version `3.x1.y1` is able to read TASTy files produced by another compiler in version `3.x2.y2` if `x1 >= x2` (assuming two stable versions of the compiler are considered - `SNAPSHOT` or `NIGHTLY` compiler versions can read TASTy in an older stable format but their TASTY versions are not compatible between each other even if the compilers have the same minor version; also compilers in stable versions cannot read TASTy generated by an unstable version). + +TASTy version number has the format of `.-` and the numbering changes in parallel to language releases in such a way that a bump in language minor version corresponds to a bump in TASTy minor version (e.g. for Scala `3.0.0` the TASTy version is `28.0-0`). Experimental version set to 0 signifies a stable version while others are considered unstable/experimental. TASTy version is not strictly bound to the data format itself - any changes to the API of the standard library also require a change in TASTy minor version. + +Being able to bump the compiler version in a project without having to wait for all of its dependencies to do the same is already a big leap forward when compared to Scala 2. However, we might still try to do better, especially from the perspective of authors of libraries. If you maintain a library and you would like it to be usable as a dependency for all Scala 3 projects, you would have to always emit TASTy in a version that would be readble by everyone, which would normally mean getting stuck at 3.0.x forever. To solve this problem a new experimental compiler flag `-Yscala-release ` (available since 3.1.2-RC1) has been added. Setting this flag makes the compiler produce TASTy files that should be possible to use by all Scala 3 compilers in version `` or newer (this flag was inspired by how `-release` works for specifying the target version of JDK). More specifically this enforces emitting TASTy files in an older format ensuring that: