miniboxing-plugin icon indicating copy to clipboard operation
miniboxing-plugin copied to clipboard

Miniboxing client-side warning: Should compile with the miniboxing-plugin!

Open julien-truffaut opened this issue 10 years ago • 5 comments

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

julien-truffaut avatar Feb 25 '15 19:02 julien-truffaut

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.

VladUreche avatar Feb 25 '15 21:02 VladUreche

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

VladUreche avatar Feb 25 '15 21:02 VladUreche

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.

VladUreche avatar Mar 31 '15 13:03 VladUreche

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)

VladUreche avatar May 04 '15 13:05 VladUreche

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 the pickler phase)
  • remove the @compileTimeOnly annotation between the pickler and refchecks 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 the refchecks 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 between pickler and refchecks, 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.

VladUreche avatar May 04 '15 13:05 VladUreche