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..efaaa24a5ac3 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.copy(compilerVersion = "3.0.2") + 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.copy(compilerVersion = "3.0.2") + 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/compiler/src/dotty/tools/dotc/config/ScalaRelease.scala b/compiler/src/dotty/tools/dotc/config/ScalaRelease.scala new file mode 100644 index 000000000000..acca9ce9298c --- /dev/null +++ b/compiler/src/dotty/tools/dotc/config/ScalaRelease.scala @@ -0,0 +1,19 @@ +package dotty.tools.dotc.config + +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 45cc3406a736..4dccad86e98c 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 = { @@ -307,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. 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/compiler/src/dotty/tools/dotc/core/Contexts.scala b/compiler/src/dotty/tools/dotc/core/Contexts.scala index 3e3f8a800ebf..e9fbd6065261 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,6 +38,9 @@ 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, ScalaBuild } +import dotty.tools.dotc.core.tasty.TastyVersion object Contexts { @@ -480,7 +483,22 @@ 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 scalaRelease: ScalaRelease = + 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 < 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) + else + TastyVersion.compilerVersion /** Is the debug option set? */ def debug: Boolean = base.settings.Ydebug.value 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/core/classfile/ClassfileParser.scala b/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala index c0140c5b8641..9920fc060142 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 } 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 @@ -884,7 +885,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 +951,37 @@ 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" + + 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 = fileTastyVersion.isCompatibleWith(TastyVersion.compilerVersion) + if !isTastyReadable then + reportWrongTasty("its TASTy format cannot be read by the compiler", TastyVersion.compilerVersion) + else + 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_")).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) + 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..7396adf76991 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/DottyUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/DottyUnpickler.scala @@ -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 3aeb7e6f35c9..74b8b357e00c 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,7 +32,7 @@ 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) @@ -40,6 +42,8 @@ class TastyPickler(val rootCls: ClassSymbol) { 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 @@ -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(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/core/tasty/TastyVersion.scala b/compiler/src/dotty/tools/dotc/core/tasty/TastyVersion.scala new file mode 100644 index 000000000000..9c417563a1e3 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/core/tasty/TastyVersion.scala @@ -0,0 +1,23 @@ +package dotty.tools.dotc.core.tasty + +import dotty.tools.tasty.TastyFormat + +case class TastyVersion(major: Int, minor: Int, experimental: Int) { + 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 { + 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) + } + +} \ No newline at end of file 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 dcc4ac09d5ec..c7ba668b7e79 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._ @@ -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,29 @@ 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) + releaseName <- annot.argumentConstantString(0) + do + ScalaRelease.parse(releaseName) match + case Some(release) if release > ctx.scalaRelease => + report.error( + 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.error(i"$sym has an unparsable release name: '${releaseName}'", annot.tree.srcPos) + 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. */ @@ -1256,6 +1280,8 @@ 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) { tree.rhs match { @@ -1278,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 @@ -1344,9 +1371,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 @@ -1354,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/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/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 4ba1229feecb..28ed07a82ebb 100644 --- a/compiler/test/dotty/tools/vulpix/ParallelTesting.scala +++ b/compiler/test/dotty/tools/vulpix/ParallelTesting.scala @@ -2,8 +2,9 @@ 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 import java.nio.file.{Files, NoSuchFileException, Path, Paths} import java.nio.charset.StandardCharsets @@ -12,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 @@ -26,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 @@ -127,10 +128,10 @@ trait ParallelTesting extends RunnerOrchestration { self => } sb.toString + "\n\n" } - case self: SeparateCompilationSource => { + 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 => + 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, release: String) + + lazy val compilationGroups: List[(Group, Array[JFile])] = + val Release = """r([\d\.]+)""".r + val Compiler = """c([\d\.]+)""".r + val Ordinal = """(\d+)""".r + def groupFor(file: JFile): Group = + 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.release) } + .map { (g, f) => (g, f.sorted) } + + def sourceFiles = compilationGroups.map(_._2).flatten.toArray } private trait CompilationLogic { this: Test => @@ -216,7 +214,13 @@ 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) => + 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 + compileWithOtherCompiler(group.compiler, files, flags1, outDir) + } }) final def countErrorsAndWarnings(reporters: Seq[TestReporter]): (Int, Int) = @@ -500,6 +504,49 @@ 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 + + def substituteClasspath(old: String): String = + old.split(JFile.pathSeparator).map { o => + if JFile(o) == JFile(Properties.dottyLibrary) then s"$compilerDir/lib/scala3-library_3-$compiler.jar" + else o + }.mkString(JFile.pathSeparator) + + val flags1 = flags.copy(defaultClassPath = substituteClasspath(flags.defaultClassPath)) + .withClasspath(targetDir.getPath) + .and("-d", targetDir.getPath) + + 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 errorsText = Source.fromInputStream(process.getErrorStream).mkString + if process.waitFor() != 0 then + val diagnostics = parseErrors(errorsText, compiler) + diagnostics.foreach { diag => + val context = (new ContextBase).initialCtx + reporter.report(diag)(using context) + } + + reporter + protected def compileFromTasty(flags0: TestFlags, suppressErrors: Boolean, targetDir: JFile): TestReporter = { val tastyOutput = new JFile(targetDir.getPath + "_from-tasty") tastyOutput.mkdir() @@ -1227,14 +1274,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) @@ -1371,4 +1418,23 @@ object ParallelTesting { def isTastyFile(f: JFile): Boolean = f.getName.endsWith(".tasty") + + def getCompiler(version: String): JFile = + val dir = cache.resolve(s"scala3-${version}").toFile + synchronized { + if dir.exists then + dir + else + import scala.sys.process._ + 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) #>> archivePath.toFile #&& s"tar -xf $archivePath -C $cache").!! + archivePath.toFile.delete() + dir + } + + private lazy val cache = + val dir = Properties.testCache.resolve("compilers") + dir.toFile.mkdirs() + dir } 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/docs/docs/reference/language-versions/binary-compatibility.md b/docs/docs/reference/language-versions/binary-compatibility.md new file mode 100644 index 000000000000..a25a4203fd6b --- /dev/null +++ b/docs/docs/reference/language-versions/binary-compatibility.md @@ -0,0 +1,36 @@ +--- +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. On the contrary, Scala 3 has a stable encoding into JVM bytecode. + +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. + +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: +* 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 +) +``` + +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. 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/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/annotation/since.scala b/library/src/scala/annotation/since.scala new file mode 100644 index 000000000000..3c335dc020ba --- /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 */ +private[scala] class since(scalaRelease: String) extends scala.annotation.StaticAnnotation diff --git a/library/src/scala/quoted/Quotes.scala b/library/src/scala/quoted/Quotes.scala index f5decee041f4..88eecad65485 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 @@ -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 */ @@ -3704,6 +3717,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 */ @@ -3711,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]] 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/sbt-community-build/src/dotty/communitybuild/sbtplugin/CommunityBuildPlugin.scala b/sbt-community-build/src/dotty/communitybuild/sbtplugin/CommunityBuildPlugin.scala index 0b580e1cb77a..d6cebbfe3cac 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,30 @@ 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 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 } ) } 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..8dfeeeff3d29 --- /dev/null +++ b/tests/neg/forwardCompat-illegalReferences/Test_r3.0.scala @@ -0,0 +1,31 @@ +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 + + // 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-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-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/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/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/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]] 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)