Yet another generics related "good code is red" issue

Before I submit a JIRA can anyone tell me if I'm doing anything wrong or if perhaps this issue is already known?
I had a quick look at JIRA; I couldn't find this specific one but I may have missed it.

A self-contained example of the problematic code is pasted in at the end of the topic.

The red code is in Test.doTest:
"impl.doStuff(TestEnum.THING);"

When I mouseover, IDEA says "Ambiguous method call. Both doStuff(TestEnum) in TestImpl and doStuff(TestEnum) in TestInterface match".

The code compiles both in idea and using ant/javac with no warnings - only IDEA freaks out. Eclipse also has no problems with this code.

The problem seems to relate to the ">" recursive type definition. If I replace it with "]]>" everything works fine.

It seems to affect multiple releases, and given that the problematic type definition is pretty standard (i.e. the way Sun defines enums), it's probably a good idea make sure IDEA works for it. It's hard to believe noone else has seen this issue - am I doing it wrong, should I be defining the type token differently somehow?

It's really annoying getting "red squigglies" everywhere (the actual error is only in one place, but the highlighting propogates the error notification all the way up to module root.) I have managed to get rid of the red code by turning off the highlighting in the file, but that's eventually going to cause more problems than it fixes :)

Reproduced on IDEA versions:
8650
8637
8526

P.S. Also, the Eclipse developers are laughing at me :(

> void doStuff(E thing);
}

class  TestImpl implements TestInterface {
    @Override
    public > void doStuff(E thing){
        
    }
}

enum TestEnum {
    THING
}
]]>

Edited by: Shorn Tolley on Aug 8, 2008 2:48 PM

Edited by: Shorn Tolley on Aug 8, 2008 2:49 PM

Edited by: Shorn Tolley on Aug 8, 2008 2:51 PM

7 comments
Comment actions Permalink

Before I submit a JIRA can anyone tell me if I'm doing anything wrong or if perhaps this issue is already known?

>

< SNIP >

>

P.S. Also, the Eclipse developers are laughing at me :(


Well you can have the last laugh with the Eclipse developers because while the code may compile, it is syntactically incorrect and is really not using generics. If I want to create a List that holds Strings, I type:

 myList = new ArrayList();
]]>


If I want to implement the Callable interface so that the call method returns a Date object, I code it as:

{
    public Date call() throws Exception
    {
        /* implementation goes here */
    }
}
]]>

In both those cases, I was able to specify a type argument (String for the list and Date for the Callable). However, with the way the code you show is currently written, there is no way to specify a type argument. If you look at the List and Callable interface declarations in the Java API, they have a type parameter:

 extends Collection
public interface Callable 
]]>

The TestInterface interface declaration is not specifying a type parameter. Therefore there is no way to specify for an implementation what type the doStuff method takes as an parameter/argument. Notice in the above example for the Callable, when I implemented it I specified what type the call method would return for my implementation. There's no way to do that for the TestInterface. Doing so would result in a compile error of "Type 'package.name.TestInterface' does not have type parameters":

 //This is an error with the way TestInterface is currently written
]]>


So we need to specify that the TestInterface has a type parameter:

{
    void doStuff(E thing);
}
]]>


If I want to limit E to be Enums, I then write:

>
{
    void doStuff(E thing);
}
]]>


Then when I implement this interface, I specify what Enum that implementation takes in the doStuff method:

{
    @Override
    public void doStuff(TestEnum thing)
    {
        //implementation code goes here...
    }
}
]]>



In total, this gives us:

>
{

    void doStuff(E thing);
}

class TestImpl implements TestInterface
{
    @Override
    public void doStuff(TestEnum thing)
    {
        //implementation code goes here...
    }
}

enum TestEnum
{
    THING
}
]]>


And you will no longer have red highlights as the code is now syntactically correct.

If you wanted to make so that you specify the type of Enum when instantiating a TestImpl, the syntax would be:

 impl = new TestImpl();  
        impl.doStuff(TestEnum.THING);// >
{

    void doStuff(E thing);
}

//Instead of specifying a type, we carry the parameter forward
class TestImpl> implements TestInterface
{
    @Override
    public void doStuff(E thing)
    {
        //implmentation code goes here...
    }
}

enum TestEnum
{
    THING
}
]]>



The syntax you had before the doStuff method is used when you want to specify something about a method parameter that itself uses generics. For example, if I want to create a method that takes a List, but only a list of Enums, I would then write:

> void fancyMethod(List listOfEnums)
    {
        
    }
}

class ExampleUsage 
{
    public void go()
    {
        Example example = new Example();
        List enumList = new ArrayList();
        example.fancyMethod(enumList); //  stringList = new ArrayList();
        example.fancyMethod(stringList); //


I hope that helps. And let us know how the Eclipse developers take it when you gloat that IDEA -- and a pre-beta version at that -- was catching a very subtle usage problem that that Eclipse simply ignored.

0
Comment actions Permalink

Mark,
you suggest to rewrite the code, which might be ok, but it does not make a bug in IDEA highlighting a feature:)

0
Comment actions Permalink

Hi Mark,

The example given is a simplification of an existing application.
The application uses Spring, and the equivalent of TestInterface/TestImpl code is actually registered with the container as a Spring bean. (IDEA is now ridiculously good at Spring, BTW - truly kick-arse)

Spring does not support generified beans (at least, not well) - i.e. using a generic class, as you suggest.
Besides which; I wouldn't want to declare my spring bean as a generic class anyway, because the bean in question is actually used for loading descriptions of enums from the database for reference data used throughout the application. I've no wish to define hundreds (literally) of instantiations of the spring bean just to manipulate reference data. I really would prefer to use a generic method. The actual type definition is a little more complicated than the example (it uses multiple bounds), I can post it next week if you'd like to have a crack at a generic method version?

Also, the code appears to work fine (both compilation and execution); if it is a simple misuse of the generics functionality, like an unchecked cast or generic array-creation, for example - shouldn't this at least be shown as generic misuse warning rather than an error?

P.S. We're talking about Eclipse developers here; they're impervious to logic - otherwise they'd be using IDEA ;)

0
Comment actions Permalink

Mark Vedder wrote:

The TestInterface interface declaration is not specifying a type
parameter. Therefore there is no way to specify in an implementation
what type the doStuff method returns. Notice in the above example for
the Callable, when I implemented it I specified what type the call
method would return for my implementation. There's no way to do that
for the TestInterface. Doing so would result in a compile error of
"Type 'package.name.TestInterface' does not have type parameters":


I may be missing something here, but as far as I understand it doStuff
returns nothing:

> void doStuff(E thing); The generics part of the method declaration is not associated with the interface type in which the method is declared. It is meant to ensure that whatever type the "thing" parameter has, that type satisfies certain constraints. In this case, it must have some type that extends Enum. So we can't call doStuff("hello"), since String does not extend Enum]]>. But we can call doStuff with anything that satisfies
the type constraint, and this has nothing to do with any parameters to
TestInterface.

Similarly:

class MyNonGenericClass {
void addToList(List list, T element) { list.add(element); } } The type parameter has nothing to do with the enclosing class, but it still has the effect of ensuring that the list and element types are related in the right way. Alternatively: void addToList(List super T> list, T element) { list.add(element); } or void addToList2(List super T>]]> list, T element) {
list.add(element);
}

0
Comment actions Permalink

So much for answering forum posts at 3:30 in the morning when suffering from insomnia ;) It looks like I did misread some things in Shorn's initial post, and misinterpreted the intent of the code... Sorry :( I had just answered a bunch of questions in a Java Beginner's forum, so my brain was still in the "get back to basics mode" when I looked at this post. errare humanum est

>


>Mark,
>you suggest to rewrite the code, which might be ok, but it does not make a bug in IDEA highlighting a feature:)
>

Agreed. When I looked at it last night, I was thinking IDEA was legitimately highlighting a misuse of generics. But now I see that's not the case.


>


>Also, the code appears to work fine (both compilation and execution); if it is a simple misuse of the generics
>functionality, like an unchecked cast or generic array-creation, for example - shouldn't this at least be shown
>as generic misuse warning rather than an error?
>


I was thinking that no where was the parameter type being defined and kind of became fixated on that. I simply did not see that the intent was to limit the doStuff method parameter to an Enum rather than a specific Enum. D'oh! Again, I was kind of stuck in the mode of answer basic Java questions and as a result had tunnel vision. Sorry about that.

>


>Spring does not support generified beans (at least, not well)
>

Yeah, definitely one of the things I'd like to see better support for.

>


>P.S. We're talking about Eclipse developers here; they're impervious to logic - otherwise they'd be using IDEA ;)
>

I hear ya there. So I guess pointing out to them that it is an EAP version wouldn't do much good since that's too logical. ;)


Mark Vedder wrote:

The TestInterface interface declaration is not specifying a type
parameter. Therefore there is no way to specify in an implementation
what type the doStuff method returns. Notice in the above example for
the Callable, when I implemented it I specified what type the call
method would return for my implementation. There's no way to do that
for the TestInterface. Doing so would result in a compile error of
"Type 'package.name.TestInterface' does not have type parameters":


I may be missing something here, but as far as I understand it doStuff
returns nothing:

<E extends Enum<E>> void doStuff(E thing);


Yup. I misspoke when I mentioned the doStuff return type. I meant to say its method parameter. But again, its all moot at this point since I now see I was going down the wrong track with tunnel vision ;)

0
Comment actions Permalink

Eugene,

Is this an issue you guys already know about, or should I create a JIRA issue with the given example?

I wouldn't like to see this one slip through to an 8.0 release - there's almost nothing I dislike more than smug Eclipse developers.

0
Comment actions Permalink

Fixed in the next EAP.

--
regards,
Alexey Kudravtsev
Software Developer
JetBrains, Inc, http://www.jetbrains.com
"Develop with pleasure!"

"Shorn Tolley" <ideaforum-shorn@sneakemail.com> wrote in message
news:14553832.216891218170943831.JavaMail.jive@app4.labs.intellij.net...

Before I submit a JIRA can anyone tell me if I'm doing anything wrong or
if perhaps this issue is already known?
I had a quick look at JIRA; I couldn't find this specific one but I may
have missed it.

>

A self-contained example of the problematic code is pasted in at the end
of the topic.

>

The red code is in Test.doTest:
"impl.doStuff(TestEnum.THING);"

>

When I mouseover, IDEA says "Ambiguous method call. Both
doStuff(TestEnum) in TestImpl and doStuff(TestEnum) in TestInterface
match".

>

The code compiles both in idea and using ant/javac with no warnings - only
IDEA freaks out. Eclipse also has no problems with this code.

>

The problem seems to relate to the "<E extends Enum<E>>" recursive type
definition. If I replace it with "<E extends TestEnum>" everything works
fine.

>

It seems to affect multiple releases, and given that the problematic type
definition is pretty standard (i.e. the way Sun defines enums), it's
probably a good idea make sure IDEA works for it. It's hard to believe
noone else has seen this issue - am I doing it wrong, should I be defining
the type token differently somehow?

>

It's really annoying getting "red squigglies" everywhere (the actual error
is only in one place, but the highlighting propogates the error
notification all the way up to module root.) I have managed to get rid of
the red code by turning off the highlighting in the file, but that's
eventually going to cause more problems than it fixes :)

>

Reproduced on IDEA versions:
8650
8637
8526

>
>

---- 8< -----
package gekko.services.reference;

>

public class Test {

>

public void doTest(){
TestImpl impl = new TestImpl();
impl.doStuff(TestEnum.THING);
}
}

>

interface TestInterface {

>

<E extends Enum<E>> void doStuff(E thing);
}

>

class TestImpl implements TestInterface {
@Override
public <E extends Enum<E>> void doStuff(E thing){

>

}
}

>

enum TestEnum {
THING
}



0

Please sign in to leave a comment.