Scala 3 遷移指南

移植 sbt 專案(使用 sbt-scala3-migrate)

語言

sbt-scala3-migrate 是 sbt 外掛程式,可在將 sbt 專案遷移至 Scala 3 時提供協助。它包含四個 sbt 指令

  • migrateDependencies 可協助您更新 libraryDependencies 清單
  • migrateScalacOptions 可協助您更新 scalacOptions 清單
  • migrateSyntax 可修正 Scala 2.13 和 Scala 3 之間的許多語法不相容性
  • migrateTypes 會嘗試編譯您的程式碼至 Scala 3,方法是在需要時推論類型並解析隱含式。

以下會詳細說明每個指令。

需求

  • Scala 2.13,建議使用 2.13.13
  • sbt 1.5 或更高版本
  • 免責聲明:此工具無法遷移包含巨集的函式庫。

建議

在遷移之前,請在您的 scalac 選項中加入 -Xsource:3 以在 Scala 2 編譯器中啟用 Scala 3 遷移警告。請參閱 使用 -Xsource:3 的 Scala 2 頁面,以取得更多詳細資料。

在本教學課程中,我們將遷移 scalacenter/scala3-migration-example 中的專案。若要深入了解遷移,並自行訓練,您可以複製這個儲存庫,並遵循教學課程步驟。

1. 安裝

在您的 sbt 專案的 project/plugins.sbt 檔案中加入 sbt-scala3-migrate

// project/plugins.sbt
addSbtPlugin("ch.epfl.scala" % "sbt-scala3-migrate" % "0.6.1")

最新發布的版本為 scala3-migrate Scala 版本支援

2. 選擇模組

如果您的專案包含多個模組,第一步是選擇要優先遷移的模組。

由於 Scala 2.13 和 Scala 3 之間具有互通性,因此您可以從任何模組開始。然而,從依賴項最少的模組開始可能會比較簡單。

sbt-scala3-migrate 一次只處理一個模組。請確定您選擇的模組不是聚合。

3. 遷移依賴項

本教學課程中的所有指令都必須在 sbt shell 中執行。

用法: migrateDependencies <project>

在本教學課程中,我們將考慮下列建置組態

//build.sbt
lazy val main = project
  .in(file("."))
  .settings(
    scalaVersion := "2.13.11",
    libraryDependencies ++= Seq(
      "org.typelevel" %% "cats-core" % "2.4.0",
      "io.github.java-diff-utils" % "java-diff-utils" % "4.12",
      "org.scalameta" %% "parsers" % "4.8.9",
      "org.scalameta" %% "munit" % "0.7.23" % Test,
      "com.softwaremill.scalamacrodebug" %% "macros" % "0.4.1" % Test
    ),
    addCompilerPlugin(("org.typelevel" %% "kind-projector" % "0.13.2").cross(CrossVersion.full)),
    addCompilerPlugin("com.olegpy" %% "better-monadic-for" % "0.3.1")
  )

執行 migrateDependencies main 會產生輸出


sbt:main> migrateDependencies main
[info] 
[info] Starting migration of libraries and compiler plugins of project main
[info] 
[info] Valid dependencies:
[info] "io.github.java-diff-utils" % "java-diff-utils" % "4.12"

[warn] 
[warn] Versions to update:
[warn] "org.typelevel" %% "cats-core" % "2.6.1" (Other versions: 2.7.0, ..., 2.10.0)
[warn] "org.scalameta" %% "munit" % "0.7.25" % Test (Other versions: 0.7.26, ..., 1.0.0-M8)
[warn] 
[warn] For Scala 3 use 2.13:
[warn] ("org.scalameta" %% "parsers" % "4.8.9").cross(CrossVersion.for3Use2_13)
[warn] 
[warn] Integrated compiler plugins:
[warn] addCompilerPlugin(("org.typelevel" %% "kind-projector" % "0.13.2").cross(CrossVersion.full))
[warn] replaced by scalacOptions += "-Ykind-projector"
[error] 
[error] Incompatible Libraries:
[error] "com.softwaremill.scalamacrodebug" %% "macros" % "0.4.1" % Test (Macro Library)
[error] addCompilerPlugin("com.olegpy" %% "better-monadic-for" % "0.3.1") (Compiler Plugin)
[info] 
[success] Total time: 0 s, completed Aug 28, 2023 9:18:04 AM

讓我們仔細檢視此輸出訊息的每個部分。

有效的依賴項

有效的依賴項與 Scala 3 相容,原因可能是它們是標準 Java 函式庫,或是它們已跨版本發布到 Scala 3。


[info] Valid dependencies:
[info] "io.github.java-diff-utils" % "java-diff-utils" % "4.12"

您可以保持它們的原樣。

需要更新的版本

這些函式庫已在後續版本中跨版本發布到 Scala 3。您需要更新它們的版本。


[warn] Versions to update:
[warn] "org.typelevel" %% "cats-core" % "2.6.1" (Other versions: 2.7.0, ..., 2.10.0)
[warn] "org.scalameta" %% "munit" % "0.7.25" % Test (Other versions: 0.7.26, ..., 1.0.0-M8)

在給定的範例中,我們需要將 cats-core 的版本提升至 2.6.1,並將 munit 的版本提升至 0.7.25。

輸出訊息的 其他版本 部分指出 Scala 3 中有哪些其他版本可用。如果您願意,您可以提升至其中一個最新版本,但請小心選擇來源相容的版本。根據 語意版本化架構,修補程式或次要版本提升是安全的,但主要版本提升則不然。

對於 Scala 3,請使用 2.13

這些函式庫尚未跨版本發布到 Scala 3,但它們是跨版本相容的。您可以使用它們的 2.13 版本編譯至 Scala 3。

在函式庫中加入 .cross(CrossVersion.for3Use2_13),指示 sbt 使用 _2.13 字尾,而非 _3


[warn] For Scala 3 use 2.13:
[warn] ("org.scalameta" %% "parsers" % "4.8.9").cross(CrossVersion.for3Use2_13)

關於 CrossVersion.for3Use2_13 的免責聲明

  • 它可能會導致傳遞依賴項的 _2.13_3 字尾發生衝突。在這種情況下,sbt 將無法解析依賴項,並會顯示明確的錯誤訊息。
  • 發布依賴於 Scala 2.13 函式庫的 Scala 3 函式庫通常是不安全的。否則,函式庫的使用者可能會在同一個依賴項上遇到衝突的 _2.13_3 字尾。

整合式編譯器外掛程式

一些編譯器外掛程式已整合到 Scala 3 編譯器本身。在 Scala 3 中,您不需要將它們解析為依賴項,但可以使用編譯器標記來啟用它們。


[warn] Integrated compiler plugins:
[warn] addCompilerPlugin(("org.typelevel" %% "kind-projector" % "0.13.2").cross(CrossVersion.full))
[warn] replaced by scalacOptions += "-Ykind-projector"

例如,您可以將 -Ykind-projector 加入 scalacOptions 清單中,來啟用 kind-projector。

在遷移過程中,重要的是要維持與 Scala 2.13 的相容性。後續的 migrateSyntaxmigrateTypes 指令會使用 Scala 2.13 編譯自動重寫程式碼的某些部分。

您可以使用以下方式以跨相容的方式設定 kind-projector

// add kind-projector as a dependency on Scala 2
libraryDependencies ++= {
  if (scalaVersion.value.startsWith("3.")) Seq.empty
  else Seq(
    compilerPlugin(("org.typelevel" %% "kind-projector" % "0.13.2").cross(CrossVersion.full))
  )
},
// activate kind-projector in Scala 3
scalacOptions ++= {
  if (scalaVersion.value.startsWith("3.")) Seq("-Ykind-projector")
  else Seq.empty
}

不相容的函式庫

有些巨集函式庫或編譯器外掛程式與 Scala 3 不相容。


[error] Incompatible Libraries:
[error] "com.softwaremill.scalamacrodebug" %% "macros" % "0.4.1" % Test (Macro Library)
[error] addCompilerPlugin("com.olegpy" %% "better-monadic-for" % "0.3.1") (Compiler Plugin)

要解決這些不相容性,您可以

  • 向維護人員確認他們是否計畫將它們移植到 Scala 3,並可能協助他們進行移植。
  • 從您的建置中移除這些依賴項,並適當地調整程式碼。

更新後的建置

更新建置後,它應該如下所示

//build.sbt
lazy val main = project
  .in(file("."))
  .settings(
    scalaVersion := "2.13.11",
    libraryDependencies ++= Seq(
      "org.typelevel" %% "cats-core" % "2.6.1",
      "io.github.java-diff-utils" % "java-diff-utils" % "4.12",
      ("org.scalameta" %% "parsers" % "4.8.9").cross(CrossVersion.for3Use2_13),
      "org.scalameta" %% "munit" % "0.7.25" % Test
    ),
    libraryDependencies ++= {
      if (scalaVersion.value.startsWith("3.")) Seq.empty
      else Seq(
        compilerPlugin(("org.typelevel" %% "kind-projector" % "0.13.2").cross(CrossVersion.full))
      )
    },
    scalacOptions ++= {
      if (scalaVersion.value.startsWith("3.")) Seq("-Ykind-projector")
      else Seq.empty
    }
  )

重新載入 sbt,確認專案編譯(為 Scala 2.13),確認測試成功執行,並提交您的變更。現在您可以遷移編譯器選項。

4. 遷移編譯器選項

用法: migrateScalacOptions <project>

Scala 3 編譯器不包含與 Scala 2 編譯器完全相同的選項組。您可以查看 編譯器選項表格,以取得所有編譯器選項的完整比較。

migrateScalacOptions 將協助您更新建置中的 scalacOptions 清單。

在本教學課程中,我們將考慮下列建置組態

lazy val main = project
  .in(file("."))
  .settings(
    scalaVersion := "2.13.11",
    scalacOptions ++= Seq(
      "-encoding",
      "UTF-8",
      "-target:jvm-1.8",
      "-Xsource:3",
      "-Wunused:imports,privates,locals",
      "-explaintypes"
    )
  )

執行 migrateScalacOptions main 輸出


sbt:main> migrateScalacOptions main
[info] 
[info] Starting migration of scalacOptions in main
[info] 
[info] Valid scalacOptions:
[info] -encoding UTF-8
[info] -Wunused:imports,privates,locals
[warn] 
[warn] Renamed scalacOptions:
[warn] -target:jvm-1.8 -> -Xunchecked-java-output-version:8
[warn] -explaintypes   -> -explain
[warn] 
[warn] Removed scalacOptions:
[warn] -Xsource:3
[warn] -Yrangepos
[success] Total time: 0 s, completed Aug 29, 2023 2:00:57 PM

有些 scalac 選項仍然有效,有些必須重新命名,有些必須移除。

有些選項會出現在 migrateScalacOptions 的輸出中,但不會出現在您的 build.sbt 中。它們是由 sbt 或某些 sbt 外掛程式新增的。請務必使用最新版本的 sbt 和 sbt 外掛程式。它們應該能夠自動將新增的編譯器選項調整為 Scala 版本。

再次強調,與 Scala 2.13 保持相容性非常重要,因為 migrateSyntaxmigrateTypes 指令會使用 Scala 2.13 編譯自動套用一些修補程式。

以下是我們如何更新 scalacOptions 清單的方法

lazy val main = project
  .in(file("."))
  .settings(
    scalaVersion := "2.13.11",
    scalacOptions ++= {
      if (scalaVersion.value.startsWith("3.")) scala3Options
      else scala2Options
    }
  )

lazy val sharedScalacOptions =
  Seq("-encoding", "UTF-8", "-Wunused:imports,privates,locals")

lazy val scala2Options = sharedScalacOptions ++
  Seq("-target:jvm-1.8", "-Xsource:3", "-explaintypes")

lazy val scala3Options = sharedScalacOptions ++
  Seq("-Xunchecked-java-output-version:8", "-explain")

重新載入 sbt,檢查專案是否編譯(為 Scala 2.13),檢查測試是否成功執行,並提交您的變更。現在您可以準備好移轉語法。

5. 移轉語法

用法: migrateSyntax <project>

此指令會執行多個 Scalafix 規則來修補一些已捨棄的語法。

已套用 Scalafix 規則的清單如下

如需有關 Scala 2.13 和 Scala 3 之間語法變更的詳細資訊,您可以參閱 不相容性表

migrateSyntax 未修正 不相容性表 中列出的某些不相容性。其中大部分並不常見,而且可以輕鬆手動修正。如果您想透過 Scalafix 重寫規則提供協助,我們非常樂意將其新增到 migrateSyntax 指令中。

執行 migrateSyntax main 輸出


sbt:main> migrateSyntax main
[info] Starting migration of syntax in main
[info] Run syntactic rules in 7 Scala sources successfully
[info] Applied 3 patches in src/main/scala/example/SyntaxRewrites.scala
[info] Run syntactic rules in 8 Scala sources successfully
[info] Applied 1 patch in src/test/scala/example/SyntaxRewritesTests.scala
[info] Migration of syntax in main succeeded.
[success] Total time: 2 s, completed Aug 31, 2023 11:23:51 AM

查看已套用的變更,檢查專案是否仍然編譯,檢查測試是否成功執行,並提交變更。下一步也是最後一步是移轉類型。

6. 移轉類型

用法: migrateTypes <project>

Scala 3 編譯器使用略有不同的類型推論演算法。它有時會無法推論出與 Scala 2 編譯器相同的類型,這可能會導致編譯錯誤。此最後步驟將新增必要的類型註記,以使程式碼編譯為 Scala 3。

執行 migrateTypes main 輸出


sbt:main> migrateTypes main
[info] compiling 8 Scala sources to /home/piquerez/github/scalacenter/scala3-migration-example/target/scala-2.13/classes ...
[warn] 1 deprecation; re-run with -deprecation for details
[warn] one warning found
[info] compiling 8 Scala sources to /home/piquerez/github/scalacenter/scala3-migration-example/target/scala-2.13/test-classes ...
[warn] 2 deprecations; re-run with -deprecation for details
[warn] one warning found
[success] Total time: 7 s, completed Aug 31, 2023 11:26:25 AM
[info] Defining scalaVersion
[info] The new value will be used by Compile / bspBuildTarget, Compile / dependencyTreeCrossProjectId and 68 others.
[info]  Run `last` for details.
[info] Reapplying settings...
[info] set current project to main (in build file:/home/piquerez/github/scalacenter/scala3-migration-example/)
[info] 
[info] Migrating types in main / Compile
[info] 
[info] Found 3 patches in 1 Scala source
[info] Starting migration of src/main/scala/example/TypeIncompat.scala
[info] 3 remaining candidates
[info] 1 remaining candidate
[info] Found 1 required patch in src/main/scala/example/TypeIncompat.scala
[info] Compiling to Scala 3 with -source:3.0-migration -rewrite
[info] compiling 1 Scala source to /home/piquerez/github/scalacenter/scala3-migration-example/target/scala-3.3.1/classes ...
[info] 
[info] Migrating types in main / Test
[info] 
[info] Found 4 patches in 1 Scala source
[info] Starting migration of src/test/scala/example/TypeIncompatTests.scala.scala
[info] 4 remaining candidates
[info] 3 remaining candidates
[info] 2 remaining candidates
[info] Found 1 required patch in src/test/scala/example/TypeIncompatTests.scala.scala
[info] Compiling to Scala 3 with -source:3.0-migration -rewrite
[info] 
[info] You can safely upgrade main to Scala 3:
[info] scalaVersion := "3.3.1"
[success] Total time: 18 s, completed Aug 31, 2023 11:26:45 AM
[info] Defining scalaVersion
[info] The new value will be used by Compile / bspBuildTarget, Compile / dependencyTreeCrossProjectId and 68 others.
[info]  Run `last` for details.
[info] Reapplying settings...
[info] set current project to main (in build file:/home/piquerez/github/scalacenter/scala3-migration-example/)
sbt:main>

migrateTypes main 找到 2 個必要的修補程式:一個在 src/test/scala/example/TypeIncompatTests.scala.scala,另一個在 src/main/scala/example/TypeIncompat.scala。它套用它們,然後使用 -source:3.0-migration -rewrite 編譯為 Scala 3 以完成遷移。

恭喜!您的專案現在可以編譯為 Scala 3。

接下來要做什麼?

如果您的專案只包含一個模組,您可以設定 scalaVersion := 3.3.1

如果您有多個模組,您可以從 3. 遷移相依關係 重新開始,使用另一個模組。

一旦您完成所有模組,您可以從 project/plugins.abt 中移除 sbt-scala3-migrate,以及所有 Scala 2.13 相關設定。

歡迎提供意見回饋和貢獻

每一個意見回饋都能幫助我們改善 sbt-scala3-migrate:錯字、更清晰的記錄訊息、更好的文件、錯誤報告、功能建議。請不要猶豫,在 GitHub 提出問題

此頁面的貢獻者