SIP-XXX: requireCompilerPlugin annotation.
Summary:
Problem: compiler-plugin can introduce binary incompatibility between code generated with and without plugin. To check such cases in the compile-time we can introduce method annotation “scala.annotation.requireCompilerPlugin(name, version),” which checks that the annotated method should be called only if the compiler plugin with the given name is enabled.
Motivation
Scala3 allows developers to write standalone compiler plugins, which are used for different purposes: generation of hosted code, simplified representation of computations, generation of boilerplate integration API, etc.
Usually, libraries are built with plugins distributed similarly, and any client can freely mix code built with and without compiler plugins. But often, compiler plugins change the binary call interface for some subset of processed code or enforce some constraints about the call interface. For example, scalus (GitHub - nau/scalus: Scalus - Scala implementation of Cardano Plutus) assumes that the caller of the compile
function should use a compiler plugin; dotty-cps-async (GitHub - rssh/dotty-cps-async: experimental CPS transformer for dotty ) changes the binary representation of the functions which use direct context encoding; scout (GitHub - xebia-functional/scourt: Scala Coroutines Compiler Plugin ) change the binary representation of the coroutines, etc.
Application developers can call in the library method, which some compiler plugins change the binary interface, and the Scala compiler will accept this method. The error will be in runtime during the linking or calling of changed methods.
Proposed Solution:
Add a static annotation “scala.annotation.requireCompilerPlugin” to the standard library, which should be put on methods or objects whose signature is changed after erasure by a compiler plugin.
final class requireCompilerPlugin(name: String, version: String) extends StaticAnnotation
Let’s define ‘method is required compiler plugin’’ if
-
the method is annotated by requireCompilerPlugin [or]
-
the template in which the method is defined is annotated by requireCompilerPlugin [or]
-
some of the types of parameters are annotated by requireCompilerPlugin [or]
-
method are annotated with annotations, which annotated by requireCompilerPlugin
Add to the compiler check that compiler plugins are loaded with versions, compatible in terms of semantic versioning or report an appropriate error for all method invocations required.
Alternatives:
- Allow changing the tasty representation of the compiled code between typing and pickling for compiler plugins. In the current compiler code, the changes of the symbol signatures after typing are possible only after erasure. (see comment in compiler/src/dotty/tools/dotc/Compiler.scala ).
- Pro:
- In the long-term, the ability to transform a typed tree into another typed tree and write a transformed type tree into tasty is worth having. This direction is superior to the requireCompilerPlugin annotation in most cases.
- Cons:
- The compiler has relatively big changes (change the search of the overloaded terms).
- This can require additional implementation effort from plugin authors.
- Some analog of requireComplierPlugin is still needed when the compiler plugin requires interaction with some custom infrastructure at compile-time.
- Disallow changing the signatures by plugins.
-
Pro:
- Simple
-
Cons:
- Probably, this will kill all untrivial compiler plugins.