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 c65b5197d1f0..7a7e0f89175b 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 0b323836d59a..e50aa3b9e9d3 100644 --- a/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala +++ b/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala @@ -198,7 +198,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..4a703461a292 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-${patchVersions(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,27 @@ object ParallelTesting { f.getName.endsWith(".tasty") def getCompiler(version: String): JFile = - val patch = trueVersions(version) + val patch = patchVersions(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 + synchronized { + if dir.exists then + dir + else + import scala.sys.process._ + val zipPath = cache.resolve(s"scala3-$patch.zip") + val compilerDownloadUrl = s"https://github.com/lampepfl/dotty/releases/download/$patch/scala3-$patch.zip" + (URL(compilerDownloadUrl) #>> zipPath.toFile #&& s"unzip $zipPath -d $cache").!! + dir + } - val trueVersions = Map( + val patchVersions = 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.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 22922ed6d5f3..9f4960086c6b 100644 --- a/tasty/src/dotty/tools/tasty/TastyFormat.scala +++ b/tasty/src/dotty/tools/tasty/TastyFormat.scala @@ -338,16 +338,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/futureRelease-export/Test_r3.0.scala b/tests/neg/futureRelease-export/Test_r3.0.scala new file mode 100644 index 000000000000..92ba88d36d85 --- /dev/null +++ b/tests/neg/futureRelease-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/futureRelease-illegalReferences/Test_r3.0.scala b/tests/neg/futureRelease-illegalReferences/Test_r3.0.scala new file mode 100644 index 000000000000..458040295b83 --- /dev/null +++ b/tests/neg/futureRelease-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/futureRelease-leakingImplicit/Test_r3.0.scala b/tests/neg/futureRelease-leakingImplicit/Test_r3.0.scala new file mode 100644 index 000000000000..c9f0835bc053 --- /dev/null +++ b/tests/neg/futureRelease-leakingImplicit/Test_r3.0.scala @@ -0,0 +1 @@ +val canEq = summon[CanEqual[EmptyTuple, EmptyTuple]] // error diff --git a/tests/neg/futureRelease-rejectNewerTasty/Bar_2_r3.0.scala b/tests/neg/futureRelease-rejectNewerTasty/Bar_2_r3.0.scala new file mode 100644 index 000000000000..d78004d6dedf --- /dev/null +++ b/tests/neg/futureRelease-rejectNewerTasty/Bar_2_r3.0.scala @@ -0,0 +1 @@ +def bar = foo // nopos-error diff --git a/tests/neg/futureRelease-rejectNewerTasty/Foo_1_r3.1.scala b/tests/neg/futureRelease-rejectNewerTasty/Foo_1_r3.1.scala new file mode 100644 index 000000000000..fe04f0623a1c --- /dev/null +++ b/tests/neg/futureRelease-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/futureRelease-excludedExport/Test_r3.0.scala b/tests/pos/futureRelease-excludedExport/Test_r3.0.scala new file mode 100644 index 000000000000..c635935471d8 --- /dev/null +++ b/tests/pos/futureRelease-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/futureRelease-strictEquals/Equality_1_r3.0.scala b/tests/run/futureRelease-strictEquals/Equality_1_r3.0.scala new file mode 100644 index 000000000000..538b85075bf9 --- /dev/null +++ b/tests/run/futureRelease-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/futureRelease-strictEquals/Test_2_c3.0.scala b/tests/run/futureRelease-strictEquals/Test_2_c3.0.scala new file mode 100644 index 000000000000..1d1d811d0a9b --- /dev/null +++ b/tests/run/futureRelease-strictEquals/Test_2_c3.0.scala @@ -0,0 +1,3 @@ +object Test: + def main(args: Array[String]): Unit = + println(emptyTupleEquality) diff --git a/tests/run/futureRelease-unusedImport/Imports_1_r3.0.scala b/tests/run/futureRelease-unusedImport/Imports_1_r3.0.scala new file mode 100644 index 000000000000..7946a9c9e2cc --- /dev/null +++ b/tests/run/futureRelease-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/futureRelease-unusedImport/Test_2_c3.0.scala b/tests/run/futureRelease-unusedImport/Test_2_c3.0.scala new file mode 100644 index 000000000000..c670ace4e768 --- /dev/null +++ b/tests/run/futureRelease-unusedImport/Test_2_c3.0.scala @@ -0,0 +1,7 @@ +object Test: + def main(args: Array[String]): Unit = + println(A) + println(B) + println(C) + println(D) + println(E)