Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Default methods are not correctly backported #56

Closed
pietrodev opened this issue May 19, 2015 · 8 comments
Closed

Default methods are not correctly backported #56

pietrodev opened this issue May 19, 2015 · 8 comments

Comments

@pietrodev
Copy link

As stated in the guide:

Default methods are backported by copying the default methods to a companion class (interface name + "$") as static methods, replacing the default methods in the interface with abstract methods, and by adding the necessary method implementations to all classes which implement that interface.

If I have this interface:

public interface TestI {
  default void test() {}
}

and this class:

public class TestC implements TestI {}

as far as I understand the output of retrolambda will be:

  1. An interface with an abstract method test
  2. A new class with a static method (empty in the example)
  3. The class TestC with the implemented test method

So, If then I have a new class:

public class Test2 {
  static {
   new TestC().test();
  }
}

This should work. Am I right?

I'm asking this because it doesn't.

Test2.java:3: error: cannot find symbol
new TestC().test();
^
symbol: method test()
location: class TestC

If I decompile I found:

public abstract interface TestI {
  public abstract void test();
}
public class TestI$ {
  public static void test(TestI paramTestI) {}
}

and:

public class TestC  implements TestI {}

I don't think the last one is right. I'm doing something wrong or I didn't understand the section:

by adding the necessary method implementations to all classes which implement that interface.

If I use javap I obtain

public class TestC implements TestI {
  public TestC();
  public void test();
}

So the method is there but as the decompiler cannot find it neither javac does.

I tried both using compatibility with bytecode 50 and 51 using the latest version.

java -Dretrolambda.defaultMethods=true -Dretrolambda.bytecodeVersion=50 -Dretrolambda.inputDir=. -Dretrolambda.classpath=. -jar retrolambda.jar

You can reproduce it by using the same source code

Of course it would be nice if my TestC has all the implementation so that I can use it in other java7 project.

@pietrodev
Copy link
Author

If it can help, I can add the following:
Using IntelliJ as IDE, when I type "new TestC()." it autocomplete with test() method, it founds it. So if I type "new TestC().test()" it doesn't throw any error.
But when I then compile (maven or ant) it throws the same error as above.

@pietrodev
Copy link
Author

This will instead work:

public class Test2 {
  static {
    TestI t = new TestC();
    t.test();
  }
}

I also tried to run it inside main() and it works, while this doen't:

public class Test2 {
  static {
    TestC t = new TestC();
    t.test();
  }
}

(I just changed the type of t)
So the problem is the compilation phase.

@luontola
Copy link
Owner

luontola commented Jun 7, 2015

I'm unable to reproduce this. Here is the output from javap:

$ javap -p -v end-to-end-tests/target/classes/TestI.class
Classfile /C:/DEVEL/Retrolambda/retrolambda/end-to-end-tests/target/classes/TestI.class
  Last modified 7.6.2015; size 112 bytes
  MD5 checksum 41dda4160c62269e56d7aba1673b0e63
  Compiled from "TestI.java"
public interface TestI
  minor version: 0
  major version: 51
  flags: ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT
Constant pool:
  #1 = Utf8               TestI
  #2 = Class              #1              // TestI
  #3 = Utf8               java/lang/Object
  #4 = Class              #3              // java/lang/Object
  #5 = Utf8               TestI.java
  #6 = Utf8               test
  #7 = Utf8               ()V
  #8 = Utf8               SourceFile
{
  public abstract void test();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_ABSTRACT
}
SourceFile: "TestI.java"



$ javap -p -v end-to-end-tests/target/classes/TestI\$.class
Classfile /C:/DEVEL/Retrolambda/retrolambda/end-to-end-tests/target/classes/TestI$.class
  Last modified 7.6.2015; size 232 bytes
  MD5 checksum 3f3d7421bf979cb865cf6ae303ef6586
  Compiled from "TestI.java"
public class TestI$
  minor version: 0
  major version: 51
  flags: ACC_PUBLIC
Constant pool:
   #1 = Utf8               TestI$
   #2 = Class              #1             // TestI$
   #3 = Utf8               java/lang/Object
   #4 = Class              #3             // java/lang/Object
   #5 = Utf8               TestI.java
   #6 = Utf8               test
   #7 = Utf8               (LTestI;)V
   #8 = Utf8               this
   #9 = Utf8               LTestI;
  #10 = Utf8               Code
  #11 = Utf8               LocalVariableTable
  #12 = Utf8               LineNumberTable
  #13 = Utf8               SourceFile
{
  public static void test(TestI);
    descriptor: (LTestI;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=0, locals=1, args_size=1
         0: return
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       1     0  this   LTestI;
      LineNumberTable:
        line 2: 0
}
SourceFile: "TestI.java"



$ javap -p -v end-to-end-tests/target/classes/TestC.class
Classfile /C:/DEVEL/Retrolambda/retrolambda/end-to-end-tests/target/classes/TestC.class
  Last modified 7.6.2015; size 326 bytes
  MD5 checksum 9c1281b74a7f95fa46102e959ea50a7b
  Compiled from "TestC.java"
public class TestC implements TestI
  minor version: 0
  major version: 51
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Utf8               TestC
   #2 = Class              #1             // TestC
   #3 = Utf8               java/lang/Object
   #4 = Class              #3             // java/lang/Object
   #5 = Utf8               TestI
   #6 = Class              #5             // TestI
   #7 = Utf8               TestC.java
   #8 = Utf8               <init>
   #9 = Utf8               ()V
  #10 = NameAndType        #8:#9          // "<init>":()V
  #11 = Methodref          #4.#10         // java/lang/Object."<init>":()V
  #12 = Utf8               this
  #13 = Utf8               LTestC;
  #14 = Utf8               test
  #15 = Utf8               TestI$
  #16 = Class              #15            // TestI$
  #17 = Utf8               (LTestI;)V
  #18 = NameAndType        #14:#17        // test:(LTestI;)V
  #19 = Methodref          #16.#18        // TestI$.test:(LTestI;)V
  #20 = Utf8               Code
  #21 = Utf8               LocalVariableTable
  #22 = Utf8               LineNumberTable
  #23 = Utf8               SourceFile
{
  public TestC();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #11                 // Method java/lang/Object."<init>":()V
         4: return
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   LTestC;
      LineNumberTable:
        line 1: 0

  public void test();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_SYNTHETIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokestatic  #19                 // Method TestI$.test:(LTestI;)V
         4: return
}
SourceFile: "TestC.java"



$ javap -p -v end-to-end-tests/target/classes/Test2.class
Classfile /C:/DEVEL/Retrolambda/retrolambda/end-to-end-tests/target/classes/Test2.class
  Last modified 7.6.2015; size 337 bytes
  MD5 checksum e04c6d30bd057a3141d0b4f79144218f
  Compiled from "Test2.java"
public class Test2
  minor version: 0
  major version: 51
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Utf8               Test2
   #2 = Class              #1             // Test2
   #3 = Utf8               java/lang/Object
   #4 = Class              #3             // java/lang/Object
   #5 = Utf8               Test2.java
   #6 = Utf8               <init>
   #7 = Utf8               ()V
   #8 = NameAndType        #6:#7          // "<init>":()V
   #9 = Methodref          #4.#8          // java/lang/Object."<init>":()V
  #10 = Utf8               this
  #11 = Utf8               LTest2;
  #12 = Utf8               <clinit>
  #13 = Utf8               TestC
  #14 = Class              #13            // TestC
  #15 = Methodref          #14.#8         // TestC."<init>":()V
  #16 = Utf8               test
  #17 = NameAndType        #16:#7         // test:()V
  #18 = Methodref          #14.#17        // TestC.test:()V
  #19 = Utf8               Code
  #20 = Utf8               LocalVariableTable
  #21 = Utf8               LineNumberTable
  #22 = Utf8               SourceFile
{
  public Test2();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #9                  // Method java/lang/Object."<init>":()V
         4: return
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   LTest2;
      LineNumberTable:
        line 1: 0

  static {};
    descriptor: ()V
    flags: ACC_STATIC
    Code:
      stack=2, locals=0, args_size=0
         0: new           #14                 // class TestC
         3: dup
         4: invokespecial #15                 // Method TestC."<init>":()V
         7: invokevirtual #18                 // Method TestC.test:()V
        10: return
      LineNumberTable:
        line 3: 0
        line 4: 10
}
SourceFile: "Test2.java"

@luontola
Copy link
Owner

luontola commented Jun 7, 2015

Based on your report of Test2.java:3: error: cannot find symbol, it seems to me that you are calling the Java compiler after running Retrolambda. The way that Retrolambda backports default methods is incompatible with incremental compiling. You must always make a clean build (e.g. mvn clean verify) or you will end up with weird compile errors.

I presume that this is the case here, so I'm closing this issue.

@luontola luontola closed this as completed Jun 7, 2015
@pietrodev
Copy link
Author

Hi, thanks for your time. I'm going to try to explain better what I want to achieve and what I understood for what the retrolambda incremental compiling limitation is.

First, I'd like to do the following, which I supposed I could do:

  1. I have a library project written in Java 8 with interfaces with default methods
  2. I compile this project with retrolambda and obtain a Java 7 compatible jar
  3. I want to use this library in another Java 7 project

All went well until I invoke, as the opened issue, a method on an object declared as itself when this method is declared as default in interface and it is not overridden. Maybe I was wrong but I supposed that the backport task would put the missing implementations in all the classes without the method. But it does not work.
But, it does work whenever:

  1. I declare the object as the interface (it finds the method)
  2. The default method is overridden (it obviously finds the method)

So the method is there.

For incremental compiling limitation I understood that, if I want to create or modify a class that implements the backported interface this does not work because it cannot backport the default method again in the new class. This sounds right and I can live with that. You already backported missing methods so I understand you cannot backport them twice. What made me open the issue is that the compiler output changes depending on how I declare the object (interface or itself)

I do not know the implementation details but is it possible to have the backported classes work if I just use it without adding any functionality (e.g., new overriding classes)?
I'd like the backported concrete classes to have the implementation of the default methods so I can invoke them when using the project as library of another Java 7 project. Is it possible?

Finally, now I'm using a simple workaround that is to implement the default methods in the concrete classes (point 2 of exceptions). Doing so it works.

Am I wrong or does it make any sense to you?

Thanks

@luontola luontola reopened this Jun 8, 2015
@luontola
Copy link
Owner

luontola commented Jun 8, 2015

Ok, so it's some issue to do with using the methods from a different module. I'll look into it. There are a bunch of corner cases in the Java language there.

@luontola
Copy link
Owner

luontola commented Jun 8, 2015

It might have to do with Retrolambda marking the method in TestC as synthetic. I'll need to check the language spec to see whether the Java compiler disallows such calls...

@luontola
Copy link
Owner

luontola commented Jul 8, 2015

This is fixed in Retrolambda 2.0.4

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants