Bug 298362 - [1.5][compiler] Compiler returns java.lang.Object instead of generic type T when javac returns T
Summary: [1.5][compiler] Compiler returns java.lang.Object instead of generic type T w...
Status: VERIFIED FIXED
Alias: None
Product: JDT
Classification: Eclipse Project
Component: Core (show other bugs)
Version: 3.6   Edit
Hardware: PC Linux
: P3 normal (vote)
Target Milestone: 3.6 M6   Edit
Assignee: Srikanth Sankaran CLA
QA Contact:
URL:
Whiteboard:
Keywords:
Depends on:
Blocks:
 
Reported: 2009-12-21 18:33 EST by Rob Clevenger CLA
Modified: 2010-03-08 07:27 EST (History)
4 users (show)

See Also:
jarthana: review+


Attachments
Patch under consideration (5.40 KB, patch)
2010-02-22 04:47 EST, Srikanth Sankaran CLA
no flags Details | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Rob Clevenger CLA 2009-12-21 18:33:25 EST
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
Comment 1 Frederic Fusier CLA 2009-12-22 06:23:46 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?
Comment 2 Frederic Fusier CLA 2009-12-22 06:32:43 EST
(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.
Comment 3 Srikanth Sankaran CLA 2010-01-05 05:21:56 EST
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.
Comment 4 Srikanth Sankaran CLA 2010-02-18 06:33:12 EST
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.
Comment 5 Srikanth Sankaran CLA 2010-02-22 04:22:34 EST
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).
Comment 6 Srikanth Sankaran CLA 2010-02-22 04:47:48 EST
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.
Comment 7 Srikanth Sankaran CLA 2010-02-22 07:52:53 EST
All tests pass. Jay, appreciate a quick review -- Thanks.
Comment 8 Jay Arthanareeswaran CLA 2010-02-23 00:25:58 EST
Patch looks good to me.
Comment 9 Srikanth Sankaran CLA 2010-02-23 01:01:05 EST
Released in HEAD for 3.6M6
Comment 10 Ayushman Jain CLA 2010-03-08 06:32:59 EST
verified for 3.6M6 using build I20100305-1011.
Comment 11 Jay Arthanareeswaran CLA 2010-03-08 07:27:14 EST
Verified.