I am trying to update a large multi module repository to Scala 3 in stages.
According to the Scala 3 migration guide, there is good forward and backward runtime compatiblity, so I should be able to update modules arbitrarily
This assumes that three things are true,
- you provide the
-Ytasty-readerscalac option to Scala 2 projects that depend on scala 3, - any macro libraries have implemented Macro Mixing
- you depend on the Scala 3 versions of any Macro libraries (with
.cross(CrossVersion.for2_13Use3))
If macro mixing has not been implemented, this won't work as the Scala 2 compiler won't have the Macro definitions that it needs at compile time.
I tried offering to update the upstream library, but this proposal was rejected by the maintainer on account of complexity as it's a temporary situation. In general, macro mixing is not commonly implemented so this also affects other upstream libraries (scala-logging, play-json, circe, etc).
Since it is not always possible or feasible to ensure all upstream macro libraries support Macro mixing, how can I efficiently update my large interdependent multi module build to Scala 3 in stages?
From what I see there is several thing to unpack here:
For simplicity I'll assume that you have a project like this:
where arrow means "depends on".
The first assumption that you would have to operate on is that "there is only 1 version of particular library in the runtime". So if you need e.g. Circe, you will only use Circe compiled for 2.13 or Circe compiled against 3 - you are able to call (non-macro) Scala 2.13 methods from Scala 3 and vice-versa, so it is not an issue, but something you have to keep in mind.
The other assumption is that macro-mixing is not a thing. Let me explain why, from library maintainer's POV:
scala.reflect.macros.blackbox.Contextscala.quotesQuotesapply,unapply, mixins, abstract types can be cross-compiles (so you can technically write Scala 2.13 macro that will be compiled with Scala 3) Scala 2's quasiquotes and Scala 3 quotes only works on their dedicated compiler versionlibrary_2.13name in Maven, notlibrary_3which would force them to use thisfor213use3, so it might even become Some projects would be fine with it, some would not, it's a choice between friction in the library and the friction in downstream build definitionsruntimeTypes, so its companion is also inruntimeTypesbut suddenly it requires something that can only be defined inscalaMacroMixing, 2 modules later!So when the library maintainer weights the costs of having "macro mixing" (huge) against the benefits (it only benefits people who would migrate a piece of project at a time, like you, but I'm afraid that you are a minority) then it's easy to understand why it didn't caught up.
So how can you work under such constraints?
Gradual cross-compilation
You start to cross-compile projects from bottom up:
then
then
and then you drop 2.13
How do you cross-compile each module? There are 2 ways:
I'd say if people want to migrate to Scala 3 and they do it gradually... they simply do not use any Scala 3 specific features until they are done. Then they start using Scala 3 in subsequent PRs. Such a migration usually is easier r review because it only updates build.sbt and introduces the least amount of changes that would make the code compile. Ideally, no changes at all.
Using macros only in 1 module
Since your issue are macros there is another approach to try out.
While each macro can only be expanded in its dedicated Scala version the code emitted by Scala 2.13 can be read by both 2.13 and 3, and same is true about code emmittd by 3.
So let's say you want to use e.g. Jsoniter with its Scala 3 macros and Cats Kittens in Scala 2.13 (both of these are released for both 2.13 and 3, but I needed some examples).
You can then arrange your code like this:
The issue here is that:
module Bthat required 2.13 macro, you'd have to move that definition tomodule Aand expand that macro thereimportthe results of the expansionThis approach IMHO only makes sense if at least 1 of your macro dependencies is not published for Scala 3 at all.
If all your macro dependencies are available on Scala 3, I would
scalacOptionsfor3use213only for those non-macro libraries that weren't cross-published yetbuild.sbtchanges, and possibly several small changes (adding type ascription, suppressing some warning, etc) that should be pretty trivial to review, making it an easy changeI'm afraid that any other approach (like migrating only 1 module at once, but immediately using new features) would actually increase the amount of work rather than make it easier.