tweag/inline-java

Support inner classes in inline-java

facundominguez opened this issue · 3 comments

Try the following snippet

diff --git a/dep/inline-java/tests/Language/Java/InlineSpec.hs b/dep/inline-java/tests/Language/Java/InlineSpec.hs
index 6c50a70..2f7f1a6 100644
--- a/dep/inline-java/tests/Language/Java/InlineSpec.hs
+++ b/dep/inline-java/tests/Language/Java/InlineSpec.hs
@@ -51,3 +51,8 @@ spec = do
         let foo = 1 :: Int32
         ([java| { class Foo { int f() { return $foo; } }; return 1; } |]
           >>= reify) `shouldReturn` (1 :: Int32)
+
+      it "Supports using antiquotation variables of inner classes" $ do
+        foo <- [java| p.Outer.Inner.A |] :: IO (J ('Class "p.Outer$Inner"))
+        _ <- [java| $foo |] :: IO JObject
+        return ()

with

package p;

public class Outer {
  public static enum Inner { A, B, C }
}

the result is

[1 of 3] Compiling Language.Java.InlineSpec ( tests/Language/Java/InlineSpec.hs, .stack-work/dist/x86_64-linux-nix/Cabal-1000.24.2.0/build/spec/spec-tmp/Language/Java/InlineSpec.o )
/run/user/1000/inlinejava21090/Inline__main_Language_Java_InlineSpec.java:60: error: cannot find symbol
  public static java.lang.Object function_6989586621679076301 (final p.Outer$Inner $foo)
                                                                      ^
  symbol:   class Outer$Inner
  location: package p
1 error      
callProcess: javac "/run/user/1000/inlinejava21090/Inline__main_Language_Java_InlineSpec.java" (exit 1): failed

The problem seems to be that the jni package wants the name of the inner class to be p.Outer$Inner (this is the same name that javap prints) and inline-java wants p.Outer.Inner.

Once we understand what syntax these names are expected to have, we can see what inline-java can do to translate them into java source names (e.g. p.Outer.Inner), and what jni can do to translate them to JNI internal names (e.g. p/Outer$Inner).

In principle, replacing '$' with '.' wouldn't work because class names can contain '$'. But it could be an acceptable stop-gap.

Examining the output of javap -v, it looks like some metadata is included in the bytecode to specify whether a class is an inner class or it is top level. The occurrences of $ in the name may or may not come from separating the outer and inner class names.

Thus translating names between internal and source representations does seem to require additional metadata.

There is an interesting advice about dealing with inner classes here. It refers to serialization, but it also serves as a warning that we are dealing with a non-standard encoding of inner classes and names.

Some options:

  1. Don't support inner classes or enums in inline-java.
  2. Offer a new kind of antiquotation that overrides the type of the antiquoted variable.
  3. Like (2), but substitute '$' with '.' when the normal antiquotation is used.

Note that (2) can be achieved without any changes to inline-java, if verbosely:

      it "Supports using antiquotation variables of inner classes" $ do
        foo <- [java| p.Outer.Inner.A |] :: IO (J ('Class "p.Outer$Inner"))
        let foo2 = upcast foo :: J ('Class "java.lang.Object")
        _ <- [java| (p.Outer.Inner) $foo2 |] :: IO JObject
        return ()

with a new antiquotation it could be for instance:

      it "Supports using antiquotation variables of inner classes" $ do
        foo <- [java| p.Outer.Inner.A |] :: IO (J ('Class "p.Outer$Inner"))
        _ <- [java| $(p.Outer.Inner foo) |] :: IO JObject
        return ()
mboes commented

"Verbose" option (2) sounds reasonable to me. Maybe just documentation work to be done then?

Fixed in #90.