miniboxing-plugin
miniboxing-plugin copied to clipboard
Miniboxing client-side warning: Should compile with the miniboxing-plugin!
I am not sure where is the miniboxing compiler plugin and runtime required, for example, if I annotate a class with miniboxed in module A, I need to add both compiler and runtime for this module. However, if module B has a dependency to module A, does he also need to add miniboxing dependencies? If yes, are the compiler and the runtime required or only one of those.
ps: sorry for spamming you with issues
Yes, both the miniboxing runtime and plugin are required to compile any source code that uses or interacts with miniboxed bytecode. This is quite restrictive, and although there are solutions to it (see #115 for example), they are not yet implemented. In principle, it would be best if one could mark a class as @api
and the plugin would make sure no special features are added to that class, so its users can be compiled with the vanilla scala compiler.
But you raise a very good point. There should be a warning or an error message when using miniboxed code from generic environments. I do not know how to inject that in the compiler as long as I don't have the plugin in there, but maybe I can find something -- I just need to think hard enough about it.
Assigned to milestone 0.5
since it's quite a difficult problem.
ps: sorry for spamming you with issues
Thank you for doing so. How else would the miniboxing plugin become bug-free? :D
During a discussion today with @xeno-by I learned about the @copileTimeOnly
annotation: https://issues.scala-lang.org/browse/SI-6539, which could be used to implement this feature.
Here's a test case:
$ cat gh-bug-185-1.scala
package miniboxing.tests.compile.bug185
class C[@miniboxed T]
$ mb-scalac gh-bug-185-1.scala
$ cat gh-bug-185-2.scala
package miniboxing.tests.compile.bug185
object Test {
def main(args: Array[String]): Unit = {
val c = new C[Int] // unless the miniboxing plugin is attached, this is incorrect bytecode
}
}
$ scalac gh-bug-185-2.scala
$ scala miniboxing.tests.compile.bug185.Test
java.lang.InstantiationError: miniboxing.tests.compile.bug185.C
at miniboxing.tests.compile.bug185.Test$.main(gh-bug-185-2.scala:5)
at miniboxing.tests.compile.bug185.Test.main(gh-bug-185-2.scala)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at scala.reflect.internal.util.ScalaClassLoader$$anonfun$run$1.apply(ScalaClassLoader.scala:70)
at scala.reflect.internal.util.ScalaClassLoader$class.asContext(ScalaClassLoader.scala:31)
at scala.reflect.internal.util.ScalaClassLoader$URLClassLoader.asContext(ScalaClassLoader.scala:101)
at scala.reflect.internal.util.ScalaClassLoader$class.run(ScalaClassLoader.scala:70)
at scala.reflect.internal.util.ScalaClassLoader$URLClassLoader.run(ScalaClassLoader.scala:101)
at scala.tools.nsc.CommonRunner$class.run(ObjectRunner.scala:22)
at scala.tools.nsc.ObjectRunner$.run(ObjectRunner.scala:39)
at scala.tools.nsc.CommonRunner$class.runAndCatch(ObjectRunner.scala:29)
at scala.tools.nsc.ObjectRunner$.runAndCatch(ObjectRunner.scala:39)
at scala.tools.nsc.MainGenericRunner.runTarget$1(MainGenericRunner.scala:65)
at scala.tools.nsc.MainGenericRunner.run$1(MainGenericRunner.scala:87)
at scala.tools.nsc.MainGenericRunner.process(MainGenericRunner.scala:98)
at scala.tools.nsc.MainGenericRunner$.main(MainGenericRunner.scala:103)
at scala.tools.nsc.MainGenericRunner.main(MainGenericRunner.scala)
The idea is:
scala> import scala.reflect.internal.annotations.compileTimeOnly
import scala.reflect.internal.annotations.compileTimeOnly
scala> @compileTimeOnly("use the miniboxing plugin!") class C
defined class C
scala> new C
<console>:10: error: use the miniboxing plugin!
new C
^
So the solution is simple. Here are the phases in Scala:
$ mb-scalac -Xshow-phases
phase name id description
---------- -- -----------
... .. ...
pickler 11 ... <== this is where signatures are saved
refchecks 12 ... <== this is where compileTimeOnly is checked
... .. ...
So the steps that need to be done in the miniboxing plugin are:
- tag all classes with
@miniboxed
type params by@compileTimeOnly
before pickling their signatures (before thepickler
phase) - remove the
@compileTimeOnly
annotation between thepickler
andrefchecks
phases
Then, we get this:
- if the miniboxing plugin is not present, a miniboxed class gets loaded with the
@compileTimeOnly
annotation and is rejected by therefchecks
phase (and the "use the miniboxing plugin message gets output") - if the miniboxing plugin is present, a miniboxed class gets loaded with the
@compileTimeOnly
annotation, but that annotation is removed by the phase injected betweenpickler
andrefchecks
, so the compilation goes through correctly.
There are 2 ideas on further developing this:
- adding the
@compileTimeOnly
annotation to@miniboxed
,@api
and the reflection classes, so they can only be used with the miniboxing plugin activated - adding the miniboxing plugin version to the message and, when removing the signature, checking the version: if the major version changed, the miniboxing plugin should reject the class.