Summary: | [1.5][compiler] Compiler returns java.lang.Object instead of generic type T when javac returns T | ||||||
---|---|---|---|---|---|---|---|
Product: | [Eclipse Project] JDT | Reporter: | Rob Clevenger <rcleveng> | ||||
Component: | Core | Assignee: | Srikanth Sankaran <srikanth_sankaran> | ||||
Status: | VERIFIED FIXED | QA Contact: | |||||
Severity: | normal | ||||||
Priority: | P3 | CC: | amj87.iitr, jarthana, kellyc, Olivier_Thomann | ||||
Version: | 3.6 | Flags: | jarthana:
review+
|
||||
Target Milestone: | 3.6 M6 | ||||||
Hardware: | PC | ||||||
OS: | Linux | ||||||
Whiteboard: | |||||||
Attachments: |
|
Description
Rob Clevenger
2009-12-21 18:33:25 EST
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. |