Publish Scala 2 and Scala 3 macros together
The next release of MUnit makes use of a new compiler feature that allows you to publish Scala 2 and Scala 3 macros together in a single artifact. The blog post Forward Compatibility for the Scala 3 Transition by Jamie Thompson explains this feature in more detail. The Scala 3 Migration Guide contains a hands-on tutorial on how to use this feature. In this post, I want to share a small example to motivate why I think this feature will be critical for a smooth Scala 3 transition.
This blog post was written as part of a collaboration with the Scala Center.
While it's standard practice to cross-build a Scala library between 2.12 and 2.13, you should think twice before cross-building for Scala 2.13 and Scala 3. There is a chance you can skip 2.13 and publish only for Scala 3 instead. The reason you may want to skip 2.13 is to prevent unexpected runtime crashes.
To demonstrate how runtime crashes can happen, consider the following dependency graph for a Scala 3 application.
The application has two direct dependencies (a_library_3.0
, b_library_2.13
)
and one transitive dependency on MUnit. The problem is that the transitive MUnit
dependency appears twice on the classpath: once for Scala 3 (munit_3.0
) and
once for Scala 2.13 (munit_2.13
). If munit_3.0
and munit_2.13
have binary
incompatibilities then the application may compile successfully but crash at
runtime with a MethodNotFoundException
or ClassNotFoundException
.
The next release of MUnit avoids this problem by including Scala 2 macros in the
munit_3.0
artifact. With this change, b_library_2.13
can depend on
munit_3.0
and the dependency graph becomes like this instead.
This change is possible thanks to the new -Ytasty-reader
flag in the Scala
2.13.4 compiler that enables the Scala 2 compiler to read Scala 3 libraries.
This feature is new and has some limitations that's good to be aware of:
- Scala 2.13.4 can only understand libraries that are published with the old Scala 3.0.0-M1 version (latest is 3.0.0-M3 at the time of this writing). The upcoming Scala 2.13.5 release will be able to understand newer Scala 3.x releases.
- Some common features in Scala 2 macros don't work in Scala 3. Most notably,
you can't use quasiquotes. In the case of MUnit, we had to replace
typeOf[Location]
withc.mirror.staticClass(classOf[Location].getName)
becausetypeOf
is itself implemented as a Scala 2 macro. - Your Scala 3 library needs a compile-time dependecy on
scala-reflect:2.13.x
,
Given these limitations, MUnit will continue to publish for 2.13. Nevertheless, it's a phenomenal achievement that it's at all possible to publish Scala 2 and Scala 3 macros together. For certain libraries, I'm optimistic this feature will be a critical component to smoothen the Scala 3 transition.