Community
Participate
Working Groups
Build Identifier: I20090611-1540 Eclipse sometimes generates class files with incorrect generic type information. I have the following example class: import java.lang.reflect.Method; public class Tester { public static interface Converter<T> { T convert(String input); } public static abstract class EnumConverter<T extends Enum<T>> implements Converter<Enum<T>> { public final T convert(String input) { return null; } } public static class SomeEnumConverter extends EnumConverter<Thread.State> { } public static void main(String[] args) throws Exception { Method m = SomeEnumConverter.class.getMethod("convert", String.class); System.out.println(m.getGenericReturnType()); } } Compiling and running this class with eclipse outputs "class java.lang.Object". Compiling and running this class with javac outputs "T". If I use "implements Converter<T>", both output "T". It happens both with and without a package declaration. Reproducible: Always Steps to Reproduce: Compile the above class with Eclipse and run it and you'll get: class java.lang.Object With javac: T
I cannot reproduce the problem using 3.5.0 (build I20090611-1540), 3.5.1 or 3.6M4... Could let us know what is the full command line you use to compile your snippet?
(In reply to comment #1) > I cannot reproduce the problem using 3.5.0 (build I20090611-1540), 3.5.1 or > 3.6M4... Could let us know what is the full command line you use to compile > your snippet? Forget this comment, I can reproduce it with all Eclipse compiler version since 3.5.0.
The following slightly modified program sheds some light on what is going on there: ------------------------8<----------------------- import java.lang.reflect.Method; public class Tester { public static interface Converter<T> { T convert(String input); } public static abstract class EnumConverter<T extends Enum<T>> implements Converter<Enum<T>> { @Override public final T convert(String input) { return null; } } public static class SomeEnumConverter extends EnumConverter<Thread.State> { } public static void main(String[] args) throws Exception { Method m = SomeEnumConverter.class.getMethod("convert", String.class); System.out.println(m.getGenericReturnType()); Method m2 = EnumConverter.class.getMethod("convert", String.class); System.out.println(m2.getGenericReturnType()); System.out.println(m); System.out.println(m2); if (!m.equals(m2)) { System.out.println("Compiler synthesized a new method for subclass"); } else { System.out.println("subclass inherited the method from base class"); } } } ----------------------------------------------------- With eclipse compiler, we get class java.lang.Object T public java.lang.Object Tester$SomeEnumConverter.convert(java.lang.String) public final java.lang.Enum Tester$EnumConverter.convert(java.lang.String) Compiler synthesized a new method for subclass while with javac we get: T T m = public final java.lang.Enum Tester$EnumConverter.convert(java.lang.String) m2 = public final java.lang.Enum Tester$EnumConverter.convert(java.lang.String) subclass inherited the method from base class Thus it is clear that the bridge method generated by eclipse is altering the picture. This bridge method is not generated by javac as can be observed from the output of class files generated by the two compilers. Under investigation to figure out which behavior is faulty.
Eclipse generated class files: ------------------------------ javap Tester$Converter ----------------------- Compiled from "Tester.java" public interface Tester$Converter{ public abstract java.lang.Object convert(java.lang.String); } javap Tester$EnumConverter -------------------------- Compiled from "Tester.java" public abstract class Tester$EnumConverter extends java.lang.Object implements T ester$Converter{ public Tester$EnumConverter(); public final java.lang.Enum convert(java.lang.String); public java.lang.Object convert(java.lang.String); } javap Tester$SomeEnumConverter ------------------------------- Compiled from "Tester.java" public class Tester$SomeEnumConverter extends Tester$EnumConverter{ public Tester$SomeEnumConverter(); public java.lang.Object convert(java.lang.String); } ----------------------------------------------------------------------------- javac compiled classes: ------------------------ javap Tester$Converter ----------------------- Compiled from "Tester.java" public interface Tester$Converter{ public abstract java.lang.Object convert(java.lang.String); } javap Tester$EnumConverter -------------------------- Compiled from "Tester.java" public abstract class Tester$EnumConverter extends java.lang.Object implements T ester$Converter{ public Tester$EnumConverter(); public final java.lang.Enum convert(java.lang.String); public java.lang.Object convert(java.lang.String); } javap Tester$SomeEnumConverter ------------------------------ Compiled from "Tester.java" public class Tester$SomeEnumConverter extends Tester$EnumConverter{ public Tester$SomeEnumConverter(); } As we can see, eclipse generates the bridge method to handle covariant method return types once in EnumConverter class and once again in SomeEnumConverter class, while javac is a bit smarter here and recognizes that the bridge itself is inherited. Other than the subject matter of this defect, there should be no other situation where this should matter.
Case (1) interface Foo<T> { void doSomething(T o); } abstract class AbstractFoo<T> implements Foo<T> { public void doSomething(String o) {} } class ConcreteFoo extends AbstractFoo<String> { } In the context of ConcreteFoo, its inherited method AbstractFoo#doSomething(String) overrides the interface method doSomething(T) with an erasure of doSomething(Object), but since they differ in erasure a bridge method is needed in ConcreteFoo. However in the context of AbstractFoo, its method doSomething(String o) does not override the interface method doSomething(T) and so there is no need for any bridge there. Case (2) public static interface Converter<T> { T convert(String input); } public static abstract class EnumConverter<T extends Enum<T>> implements Converter<Enum<T>> { public final T convert(String input) { return null; } } public static class SomeEnumConverter extends EnumConverter<Thread.State> { } In the context of SomeEnumConverter its inherited method EnumConverter#convert with an erasure of java.lang.Enum<E extends Enum<E>> convert(String) overrides the Converter interface's convert method with an erasure of java.lang.Object convert(String). Since these differ in return types but in a permissible manner, a bridge is required. However, we can clearly determine that the same bridge would have been generated in the context of EnumConverter itself since its method convert with an erasure of java.lang.Enum<E extends Enum<E>> convert(String) overrides the Converter interface's convert method with an erasure of java.lang.Object convert(String). But these already differ in return type and the equivalent bridge MUST have been generated in the context of the superclass (EnumConverter).
Created attachment 159756 [details] Patch under consideration This patch generates a bridge method for a class only if the super class will not had an equivalent bridge generated. New tests added: org.eclipse.jdt.core.tests.compiler.regression.MethodVerifyTest.test205() org.eclipse.jdt.core.tests.compiler.regression.MethodVerifyTest.test206() org.eclipse.jdt.core.tests.compiler.regression.MethodVerifyTest.test207() Note that the last test prints "class java.lang.Object" with this patch, matching javac 6,7. javac5 results in "T" which is incorrect.
All tests pass. Jay, appreciate a quick review -- Thanks.
Patch looks good to me.
Released in HEAD for 3.6M6
verified for 3.6M6 using build I20100305-1011.
Verified.