[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
This commit is contained in:
bitloi 2026-03-16 00:15:06 +01:00
parent ed501b71bd
commit f4eff2da77
10 changed files with 66 additions and 0 deletions

View File

@ -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

View File

@ -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
)

View File

@ -0,0 +1 @@
object H { def id[A](a: A): A = a }

View File

@ -0,0 +1 @@
object L { def main(args: Array[String]): Unit = println(H.id(1)) }

View File

@ -0,0 +1,2 @@
# Expect compile to fail: scalaVersion 3.3.2 < scala3-library_3 3.3.4 from high
-> low/compile

View File

@ -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",
)

View File

@ -0,0 +1 @@
object H { def id[A](a: A): A = a }

View File

@ -0,0 +1 @@
object L { def main(args: Array[String]): Unit = println(H.id(1)) }

View File

@ -0,0 +1,3 @@
# With allowUnsafeScalaLibUpgrade := true, compile and run succeed (eviction is a warning only)
> low/compile
> low/run

View File

@ -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