From f4eff2da772dd6085d138575848adeab037bbd0b Mon Sep 17 00:00:00 2001 From: bitloi Date: Mon, 16 Mar 2026 00:15:06 +0100 Subject: [PATCH] [2.x] fix: Eviction error for downgrade of Scala 3.x (#6694) **Problem** When scalaVersion is Scala 3.x and a dependency brings a newer scala3-library_3, sbt did not report an eviction error or warning. The compiler could be older than the standard library on the classpath, breaking compile-time alignment (e.g. scala/scala3#25406). **Solution** - In Compiler.scala, add an else if ScalaArtifacts.isScala3(sv) branch in scalaInstanceConfigFromUpdate that finds scala3-library_* on the Compile report and, if scalaVersion < that revision, fails (or warns when allowUnsafeScalaLibUpgrade := true) with the same pattern as the existing Scala 2.13 check (PR 7480). Message uses compile-time alignment wording and "See evicted to know why ... was upgraded from". - Set allowUnsafeScalaLibUpgrade := true on b3 in stdlib-unfreeze so existing b3/run and b3/checkScala still pass. - Add scripted tests: stdlib-unfreeze-scala3-eviction (expect compile to fail) and stdlib-unfreeze-scala3-warn (expect success with warning). Closes #6694 --- .../main/scala/sbt/internal/Compiler.scala | 33 +++++++++++++++++++ .../stdlib-unfreeze-scala3-eviction/build.sbt | 12 +++++++ .../high/H.scala | 1 + .../low/L.scala | 1 + .../stdlib-unfreeze-scala3-eviction/test | 2 ++ .../stdlib-unfreeze-scala3-warn/build.sbt | 11 +++++++ .../stdlib-unfreeze-scala3-warn/high/H.scala | 1 + .../stdlib-unfreeze-scala3-warn/low/L.scala | 1 + .../stdlib-unfreeze-scala3-warn/test | 3 ++ .../stdlib-unfreeze/build.sbt | 1 + 10 files changed, 66 insertions(+) create mode 100644 sbt-app/src/sbt-test/dependency-management/stdlib-unfreeze-scala3-eviction/build.sbt create mode 100644 sbt-app/src/sbt-test/dependency-management/stdlib-unfreeze-scala3-eviction/high/H.scala create mode 100644 sbt-app/src/sbt-test/dependency-management/stdlib-unfreeze-scala3-eviction/low/L.scala create mode 100644 sbt-app/src/sbt-test/dependency-management/stdlib-unfreeze-scala3-eviction/test create mode 100644 sbt-app/src/sbt-test/dependency-management/stdlib-unfreeze-scala3-warn/build.sbt create mode 100644 sbt-app/src/sbt-test/dependency-management/stdlib-unfreeze-scala3-warn/high/H.scala create mode 100644 sbt-app/src/sbt-test/dependency-management/stdlib-unfreeze-scala3-warn/low/L.scala create mode 100644 sbt-app/src/sbt-test/dependency-management/stdlib-unfreeze-scala3-warn/test diff --git a/main/src/main/scala/sbt/internal/Compiler.scala b/main/src/main/scala/sbt/internal/Compiler.scala index 93d1fc5c8..aefee7471 100644 --- a/main/src/main/scala/sbt/internal/Compiler.scala +++ b/main/src/main/scala/sbt/internal/Compiler.scala @@ -215,6 +215,39 @@ object Compiler: if err then sys.error(msg) else s.log.warn(msg) else () + else if ScalaArtifacts.isScala3(sv) then + val scala3LibOpt = for + compileReport <- fullReport.configuration(Configurations.Compile) + lib <- compileReport.modules.find( + _.module.name.startsWith(ScalaArtifacts.Scala3LibraryPrefix) + ) + yield lib + for lib <- scala3LibOpt do + val libVer = lib.module.revision + val libName = lib.module.name + val proj = + Def.displayBuildRelative(Keys.thisProjectRef.value.build, Keys.thisProjectRef.value) + if VersionNumber(sv).matchesSemVer(SemanticSelector(s"<$libVer")) then + val err = !Keys.allowUnsafeScalaLibUpgrade.value + val fix = + if err then + """Upgrade the `scalaVersion` to fix the build. If upgrading the Scala compiler version is + |not possible (for example due to a regression in the compiler or a missing dependency), + |this error can be demoted by setting `allowUnsafeScalaLibUpgrade := true`.""".stripMargin + else s"""Note that the dependency classpath and the runtime classpath of your project + |contain the newer $libName $libVer, even if the scalaVersion is $sv. + |Compilation (macro expansion) or using the Scala REPL in sbt may fail with a LinkageError.""".stripMargin + val msg = + s"""Expected `$proj scalaVersion` to be $libVer or later, but found $sv. + |To maintain the compile-time alignment, the Scala compiler cannot be older than $libName on the dependency classpath. + | + |$fix + | + |See `$proj evicted` to know why $libName was upgraded from $sv to $libVer. + |""".stripMargin + if err then sys.error(msg) + else s.log.warn(msg) + else () else () def file(id: String): Option[File] = for diff --git a/sbt-app/src/sbt-test/dependency-management/stdlib-unfreeze-scala3-eviction/build.sbt b/sbt-app/src/sbt-test/dependency-management/stdlib-unfreeze-scala3-eviction/build.sbt new file mode 100644 index 000000000..32f1dd59e --- /dev/null +++ b/sbt-app/src/sbt-test/dependency-management/stdlib-unfreeze-scala3-eviction/build.sbt @@ -0,0 +1,12 @@ +// Regression test for #6694: Scala 3 eviction error when scalaVersion < scala3-library_3 on classpath. +// low has scalaVersion 3.3.2 and depends on high (3.3.4), so resolved scala3-library_3 is 3.3.4. +// Without allowUnsafeScalaLibUpgrade, compile must fail with the eviction error. + +lazy val high = project.settings( + scalaVersion := "3.3.4", +) + +lazy val low = project.dependsOn(high).settings( + scalaVersion := "3.3.2", + // do NOT set allowUnsafeScalaLibUpgrade — we expect the build to fail +) diff --git a/sbt-app/src/sbt-test/dependency-management/stdlib-unfreeze-scala3-eviction/high/H.scala b/sbt-app/src/sbt-test/dependency-management/stdlib-unfreeze-scala3-eviction/high/H.scala new file mode 100644 index 000000000..ddf3d0078 --- /dev/null +++ b/sbt-app/src/sbt-test/dependency-management/stdlib-unfreeze-scala3-eviction/high/H.scala @@ -0,0 +1 @@ +object H { def id[A](a: A): A = a } diff --git a/sbt-app/src/sbt-test/dependency-management/stdlib-unfreeze-scala3-eviction/low/L.scala b/sbt-app/src/sbt-test/dependency-management/stdlib-unfreeze-scala3-eviction/low/L.scala new file mode 100644 index 000000000..271d731a9 --- /dev/null +++ b/sbt-app/src/sbt-test/dependency-management/stdlib-unfreeze-scala3-eviction/low/L.scala @@ -0,0 +1 @@ +object L { def main(args: Array[String]): Unit = println(H.id(1)) } diff --git a/sbt-app/src/sbt-test/dependency-management/stdlib-unfreeze-scala3-eviction/test b/sbt-app/src/sbt-test/dependency-management/stdlib-unfreeze-scala3-eviction/test new file mode 100644 index 000000000..6700c4044 --- /dev/null +++ b/sbt-app/src/sbt-test/dependency-management/stdlib-unfreeze-scala3-eviction/test @@ -0,0 +1,2 @@ +# Expect compile to fail: scalaVersion 3.3.2 < scala3-library_3 3.3.4 from high +-> low/compile diff --git a/sbt-app/src/sbt-test/dependency-management/stdlib-unfreeze-scala3-warn/build.sbt b/sbt-app/src/sbt-test/dependency-management/stdlib-unfreeze-scala3-warn/build.sbt new file mode 100644 index 000000000..29cb13495 --- /dev/null +++ b/sbt-app/src/sbt-test/dependency-management/stdlib-unfreeze-scala3-warn/build.sbt @@ -0,0 +1,11 @@ +// Same as stdlib-unfreeze-scala3-eviction but with allowUnsafeScalaLibUpgrade := true on low. +// low has scalaVersion 3.3.2 and depends on high (3.3.4); we demote the eviction to a warning so compile succeeds. + +lazy val high = project.settings( + scalaVersion := "3.3.4", +) + +lazy val low = project.dependsOn(high).settings( + allowUnsafeScalaLibUpgrade := true, + scalaVersion := "3.3.2", +) diff --git a/sbt-app/src/sbt-test/dependency-management/stdlib-unfreeze-scala3-warn/high/H.scala b/sbt-app/src/sbt-test/dependency-management/stdlib-unfreeze-scala3-warn/high/H.scala new file mode 100644 index 000000000..ddf3d0078 --- /dev/null +++ b/sbt-app/src/sbt-test/dependency-management/stdlib-unfreeze-scala3-warn/high/H.scala @@ -0,0 +1 @@ +object H { def id[A](a: A): A = a } diff --git a/sbt-app/src/sbt-test/dependency-management/stdlib-unfreeze-scala3-warn/low/L.scala b/sbt-app/src/sbt-test/dependency-management/stdlib-unfreeze-scala3-warn/low/L.scala new file mode 100644 index 000000000..271d731a9 --- /dev/null +++ b/sbt-app/src/sbt-test/dependency-management/stdlib-unfreeze-scala3-warn/low/L.scala @@ -0,0 +1 @@ +object L { def main(args: Array[String]): Unit = println(H.id(1)) } diff --git a/sbt-app/src/sbt-test/dependency-management/stdlib-unfreeze-scala3-warn/test b/sbt-app/src/sbt-test/dependency-management/stdlib-unfreeze-scala3-warn/test new file mode 100644 index 000000000..29571a432 --- /dev/null +++ b/sbt-app/src/sbt-test/dependency-management/stdlib-unfreeze-scala3-warn/test @@ -0,0 +1,3 @@ +# With allowUnsafeScalaLibUpgrade := true, compile and run succeed (eviction is a warning only) +> low/compile +> low/run diff --git a/sbt-app/src/sbt-test/dependency-management/stdlib-unfreeze/build.sbt b/sbt-app/src/sbt-test/dependency-management/stdlib-unfreeze/build.sbt index ab04ff427..bf9ab5c55 100644 --- a/sbt-app/src/sbt-test/dependency-management/stdlib-unfreeze/build.sbt +++ b/sbt-app/src/sbt-test/dependency-management/stdlib-unfreeze/build.sbt @@ -16,6 +16,7 @@ lazy val a3 = project.settings( ) lazy val b3 = project.dependsOn(a3).settings( + allowUnsafeScalaLibUpgrade := true, // b3 has 3.3.2, a3 brings 3.3.4; demote to warn so b3/run still passes scalaVersion := "3.3.2", // 2.13.12 library TaskKey[Unit]("checkScala") := { val i = scalaInstance.value