macro-compat: cross version Scala macro support
macro-compat is a small library which, in conjunction with the macro-paradise compiler plugin, allows you to compile macros with Scala 2.10.x which are written to the Scala 2.11/12/13 macro API. This means that your macros can be written just once, for the current API, and still be portable to earlier Scala releases.
Why you should use macro-compat
Scala macros are hard enough to write once ... writing them twice, once for each of the two API versions is even more of a chore.
Currently people adopt one of the following approaches if they want portable macros,
- They maintain separate git branches for Scala 2.11.x vs. 2.10.x variants.
- They use SBT's cross-version support for Scala sources.
- They write an abstraction layer over the macro API to hide the differences.
None of these is entirely satisfactory.
The branching model has worked fairly well for shapeless but has become more cumbersome with the arrival of Scala.JS ... a single branch build has become increasingly desirable.
Using SBT's cross version source support is effectively maintaining an "internal branch" within a single real branch of your project, but without any of the tools which support managing branches effectively. Whilst this might be adequate for very small amounts of macro code it gets increasingly awkward and error prone as the amount of macro code grows.
Writing an abstraction layer that hides the differences between the macro API versions (insofar as it does so by bringing Scala 2.10.x up to the 2.11.x API) is a part of the solution proposed here. However it isn't enough. Two of the biggest improvements of the Scala 2.11.x macro API over 2.10.x were the introduction of macro-bundles and the ability to type macro implementation method arguments and results as Tree
rather than Expr[T]
. Both of these allow the signatures of macro implementation methods to be written significantly more succintly and readably, and it would be a shame to have to give up on them just to remain compatible with Scala 2.10.x within a single branch.
macro-compat provides a backport of (parts of) the Scala 2.11.x macro API to 2.10.x and also provides an annotation macro which provides support for macro bundles in 2.10.x and Tree
as the type of macro implementation method arguments and results. The intention is that you write macro code as macro bundles, exactly as you would for Scala 2.11.x with the exception of a single @bundle
annotation on the macro bundle class,
import scala.language.experimental.macros
import scala.reflect.macros.whitebox
import macrocompat.bundle
object Test {
def foo: Int = macro TestMacro.fooImpl
def bar(i: Int): String = macro TestMacro.barImpl
def baz(is: Int*): Int = macro TestMacro.bazImpl
}
@bundle // macro-compat addition
class TestMacro(val c: whitebox.Context) {
import c.universe._
def fooImpl: Tree = q""" 23 """ // explicit return type : Tree required
def barImpl(i: Tree): Tree = q""" "bar" """
def bazImpl(is: Tree*): Tree = q""" 13 """
}
This code compiles on Scala 2.10.x, 2.11.x, 2.12.x and 2.13.x.
The @bundle
annotation is implemented as a macro annotation via the macro-paradise compiler plugin (the plugin is no longer necessary with 2.13.x). On Scala 2.11.x, 2.12.x and 2.13.x the annotation is simply eliminated during compilation, leaving no trace in the resulting binaries. On Scala 2.10.x the annotation macro transforms the macro bundle class to an object definition which is compatible with the 2.10.x macro API.
Current status
This is a young project, initially extracted out of the export-hook project and massaged into a more or less usable form in free moments snatched during ICFP 2015. Since then a number of generous contributors have made additions to the backport component and it is now seeing use in several other projects. As of version 1.1.1 backport coverage has been expanded sufficiently to cover all the macro API usage in shapeless. I would be delighted for more projects to pick it up and extend it to cover their needs as well.
If you would like to see or contribute particular extensions to the backport, please create issues here or hop on the gitter channel. Discussion is also welcome on the shapeless and cats gitter channels ... please let us know what you think.
Using macro-compat
Binary release artefacts are published to the Sonatype OSS Repository Hosting service and synced to Maven Central. Snapshots of the master branch are built using Travis CI and automatically published to the Sonatype OSS Snapshot repository. To include the Sonatype repositories in your SBT build you should add,
resolvers ++= Seq(
Resolver.sonatypeRepo("releases"),
Resolver.sonatypeRepo("snapshots")
)
Builds are available for Scala 2.10.x, 2.11.x, 2.12.x and 2.13.x for Scala JDK and Scala.js.
For Scala 2.12.x and earlier, add the following to your build,
libraryDependencies ++= Seq(
"org.typelevel" %% "macro-compat" % "1.1.1",
"org.scala-lang" % "scala-compiler" % scalaVersion.value % "provided",
compilerPlugin("org.scalamacros" % "paradise" % "2.1.0" cross CrossVersion.patch)
)
For Scala 2.13.0-RC2 and later, add the following,
libraryDependencies ++= Seq(
"org.typelevel" %% "macro-compat" % "1.1.1",
"org.scala-lang" % "scala-compiler" % scalaVersion.value % "provided"
)
Binary compatibility
As of version 1.0.7 macro-compat uses MiMa to verify binary compatibility within minor versions. Binary compatibility was broken in 1.0.3 and again in 1.0.6. In version 1.0.7 binary compatibility with 1.0.3-5 has been restored and 1.0.6 is now deprecated. The binary compatibility breaking changes were moved to 1.1.0 and hopefully the addition of MiMa to the build will make a recurrence of this sort of breakage much less likely in future.
Building macro-compat
macro-compat is built with SBT 0.13.16 or later.
Participation
The macro-compat project supports the Scala Code of Conduct and wants all of its channels (Gitter, github, etc.) to be welcoming environments for everyone.
Projects using macro-compat
Contributors
- Adelbert Chang [email protected] @adelbertchang
- Alexandre Archambault [email protected] @alxarchambault
- Alistair Johnson [email protected] @AlistairUSM
- Chris Hodapp [email protected] @clhodapp
- Dale Wijnand [email protected] @dwijnand
- Frank S. Thomas [email protected] @fst9000
- Michael Pilquist [email protected] @mpilquist
- Miles Sabin [email protected] @milessabin
- Naoki Aoyama [email protected] @AoiroAoino
- Philip Wills [email protected] @philwills
- Travis Brown [email protected] @travisbrown
- Your name here :-)