Scala 2.13で-Xsource:3を指定すると変更される挙動一覧

Scalaコンパイラには、ある程度最近のversionから -Xsource:バージョン という、少し将来のversionの挙動に近づけるオプションがあります。

Scala 2.13.3時点で -Xsource:3 を指定できるわけですが、その場合の挙動の変更一覧を、scala/scalaをgit grepしてコンパイラのコードを読んで調べたので書いておきます。

ちなみに、当初2.14が出る予定だったので、Scala 2.13の最初の方は -Xsource:2.14 という指定方法だったのですが、2.14無くなったので、dottyに向けて -Xsource:3 となりました。

続きを読む

Throwableでtry catchしてしまっているのをNonFatalに書き換えるscalafixのrule

この程度の単純なものならば、正規表現の置換だけで可能な気がしますが、書いたので置いておきます。

NonFatalのimport加えるあたりは、そういう低レベルなやつでやると、適切な位置に追加するのは多少面倒かもしれないですね。

細かいところが多少雑なので、稀に変な書き換えをしてしまうかも。

gist.github.com

ScalazをDotty対応した知見

少し前に、こっそりと、かなりひさしぶりな(互換壊す)メジャーリリース?となる、7.3.0 finalをリリースしたけど全然リリースノート書いたり宣伝もしていないScalazですが、 もう最近自分しか開発していない感があり、猫のほうが明らかに流行っています。

が、それはそれとして、なにがあろうと、ゆったりとScalazの開発はやれる限り続けようと思うので、"そろそろいけるかな?"と思い、ScalazでのDotty対応をやってみたら(ごく一部を除いて)できたので、その知見をまとめました。

kind-projectorがとにかく辛かったけど、マクロは使っていないので、そういう意味ではまぁなんとかなりました・・・(?)

このblog書いてる時点のDotty 0.24.0-RC1時点の情報です。 Dotty 0.24.0-RC1でビルドしたscalazは7.4.0-M1としてpublish済です。 7.3や7.2にbackportしてのDotty対応は、クロスビルドしつつの互換考えると結構厳しいし、Dottyの安定度もわからない(7.2や7.3では、まだあまりDottyの仕様変更に振り回されたくない)ので、現状では、やるかどうかわかりません。

まだDottyのコンパイラは割と簡単にクラッシュしたりするので、Dottyがproduction readyになるのは結構時間かかりそうだなぁ、と思いました。

(nightlyビルド使ってもクラッシュする)

(tweetもしたけど、あるいはおそらく無限にthreadが終わらなくなる)

続きを読む

JDK11でGraalを有効にするとScalaのコンパイルが13%くらい速くなった

Graalについて全然詳しくないので、Graal自体の説明はしません、というかできませんが、JDK10以降で

-XX:+UnlockExperimentalVMOptions -XX:+UseJVMCICompiler

というオプションを付与すると有効にできるらしいですね。

というわけで、以下のような条件で計測した結果、結果からいうと13%くらい速く(コンパイル時間が短く)なりました。

続きを読む

Scalaのcompiler pluginをCrossVersion.fullにするかCrossVersion.binaryにするか

昨日のwartremoverのやつの続き、というか、Scalaのcompiler pluginに関する別の話。

いきなり雑に説明に入りますが

  • Scalaの普通のlibraryは、scala-libraryのjarのみに依存する
  • scala-libraryのjarは、たとえば基本的に2.13.xなら、xの部分が変わっても、99%以上バイナリ互換を壊さない
  • マクロやリフレクションを使う場合はscala-reflectのjarに依存することになり、それはscala-libraryよりは互換壊す
  • compiler pluginなどのcompilerに依存するものは、scala-compilerのjarに依存することになるわけだが、それはおそらくscala-libraryやscala-reflectよりもわりと互換が壊れる
  • sbt側にはCrossVersionという概念がある
  • デフォルトではCrossVersion.binaryであり、通常のライブラリは "groupId" % "artifactId" % "version" とあったときに、artifactIdの末尾に "foo_2.13" などと、Scalaのversion(で一番末尾含まない)が付与されている
  • これらの仕組みによって、たとえば普通Scala 2.13.0使ってpublishしたライブラリは、Scala 2.13.1がリリースされても、publishし直す必要がない
  • しかし、scala-compilerなどに依存したものは、CrossVersion.fullにしてartifactIdを foo_2.13.1 などとしてpublishする方法もある。つまりscalaのversion毎に互換が壊れる可能性がある場合は、こちらにしておいたほうが安全である
  • 実際に2.12.9 => 2.12.10、2.13.0 => 2.13.1あたりで互換が壊れて、大抵のcompiler plugiinがCrossVersion.binaryでpublishされていたので問題になった
  • そのタイミングでwartremover本体もCorssVersion.fullでpubilshするように変えた
  • fullでpublishすると、原理的に互換が壊れない(compiler pluginならcompile時に変なエラーで死なない)、というメリットがある一方、Scalaの新しいversionがリリースされるたびに毎回リリースする必要が出てきて面倒である
  • 頑張って使う側でsbtの設定をすれば、CrossVersion.fullでも、新しいversionがリリースされてないときに、互換さえあれば微妙に古いものをあえてつかうようにするのは原理的には可能であるはず
  • 原理的には可能ではあるが、理解して設定するのはわりと難しい(?) https://github.com/wartremover/wartremover-contrib/issues/49#issuecomment-609609716
  • また、wartremoverとwartremover-contribの関係上「wartremover側はscala-compilerが互換壊したら明らかに毎回リリースする必要があるが、wartremover-contribは基本的にwartremoverのclassを参照して、scala-compiler内部の互換が壊れやすい部分あまり参照しないから、contribはCrossVersion.fullではなくCrossVersion.binaryでいいのでは?」みたいなところもある

され、これらを色々考慮した結果どうしたか?というと

などをやってみた状態なのですが、これも何が正解なのかのベストプラクティスが存在していなくて難しいですね・・・。上記で説明したような「fullとbinary両方でpublish」は、自分が知るかぎり、それをやっているライブラリは存在していない気がしますが、なにかもっといい感じの仕組みがあれば教えてください