Oracle JDK and Eclipse JDT compilers disagree! Which is compiling this incorrectly? Unusual generics and inferrence
Asked Answered
O

1

4

I have a piece of code which is compiling inconsistently between Oracle JDK 7 and Eclipse JDT 7, but since I'm not sure about which compiler is making the mistake(s) I thought I should ask for opinions here before submitting any bug reports.

This is the simplest test I could come up with to demonstrate the inconsistency:

interface Foo<S extends Foo<S, T>, T> {
    // should this compile?
    public <X extends Foo<S, Y>, Y> Y method1();

    // what about this?
    public <X extends Foo<? extends S, Y>, Y> Y method2();
}

Oracle JDK gives an error on method1 but not method2, whereas Eclipse has no problem with either method. I'm not even certain that either method should compile...

If neither method should even compile to begin with then the following point is moot, but I feel like both compilers are making a mistake if we add the following code:

interface Bar extends Foo<Bar, Integer> {
}

class Bug {
    void bug() {
        Bar bar = null;
        Double bubble;

        // these fail as expected...
        bar.<Foo<Bar, Double>, Double> method1();
        bar.<Foo<? extends Bar, Double>, Double> method2();

        // ...but these don't even though the inferred parametrisations should be
        // the same as above
        Double bobble = bar.method1();
        Double babble = bar.method2();
    }
}

When we provide explicit parametrisations for method1 and method2, I can find none which result in a valid invocation which will return a Double (i.e. I can find no valid parametrisation for X when Y is parametrised with Double). This is the behaviour I expect, Y here should only be parametrisable with Integer as far as I can see.

When we let the compiler infer the parametrisation, though, both Oracle JDK and the Eclipse JDT allow the call with Y inferred to be Double. If you hover over the invocations in Eclipse it even shows the parametrisation to be exactly the same as our manual one which fails, so why the different behaviour?

(The reason for assignment to the fresh variables bobble and babble at this point is that the hover text shows different parametrisations - replacing Double with Object for some reason - if we assign to bubble again. It still compiles both invocations and assigns to the Doubles though, so I don't know why this is.)

So, this is perhaps another fairly vague question from me, but can anybody here shed some light onto this for me?

EDIT:

Bug report with Eclipse: https://bugs.eclipse.org/bugs/show_bug.cgi?id=398011

Orchidaceous answered 20/12, 2012 at 21:13 Comment(10)
i can only imagine the application of this codeDrayage
@Drayage - The original code is indeed very silly and overcomplicated ;). Everything has been mutated quite a bit here, but idea of the methods in their original form was a sort of 'magic cast' to get the type of T when we have a reference to Foo with only S parametrised properly. In our example that would look something like: Foo<Bar, ?> fooBar = null; Integer something = fooBar.method1();Orchidaceous
@EliasVasylenko - But have you tried it in NetBeans, or across JDK 5/6/7? I wrote some convoluted generics code that I got compiling in the JDK and Eclipse but found it failed in the NetBeans editor. Lesson learnt - don't try to nest too many parameterized types.Looksee
@Looksee - The lesson should be to make them as convoluted as you like so long as it's legal, and submit bug reports if you need to! Not tried in netbeans, tried in JDK 6 and got same behaviour as 7Orchidaceous
@EliasVasylenko - I have some sympathy with that view but I would recommend listening to the 2011 Devoxx talk on Java (32 through 35) by the guy who put generics in there. It is obviously complicated enough that compiler writers interpret the requirements differently. I take a pragmatic view - if you want your stuff to compile across multiple compilers you have to write to the lowest common denominator and Java isn't unique in that. That said, if you raise bugs post them here so people can track them.Looksee
@Looksee - Fair enough, and if I can't get my code compiling on at least the majority of compilers I'll concede :p. For personal projects I do enjoy playing around a lot with generics, though, and I have found the Eclipse team has been quick to address these types of bugs in the past. I will of course post here if I end up submitting any!Orchidaceous
Wow. If this is written just for fun and to flex your generics muscles, then more power to you! However, if you expect this code to go into production and be maintained for years to come by people other than you (or even you 5 years from now), then I would stay away from this. Make your code simpler and easier to maintain and everyone will be happy.Vernal
That being said, it's worth filing a bug on both compilers (JDK and Oracle).Vernal
The part involving X is completely pointless, as X isn't used anywhere. Just remove it. And are you sure that Y shouldn't be the inherited T?Koto
I'm very sure, the purpose is to 'capture' the type of T from X when T is parametrised with a wildcard, as I described in a comment above.Orchidaceous
G
1

It seems that the compiler does some kind of simplification. Foo.method1 and Foo.method2 are declared with two parameters, X and Y, one of which can be determined during inferring, but X is not used at all.

So when you call Double bobble = bar.method1() X should be calculated as extends Foo<Bar, Double>, but the compiler decides to drop this parameter because it is not used.

When you explicitly specify method parameters then compiler has to check their correctness and fails as expected.

If you change either of these methods to accept arguments of type X then you won't get this ambiguous situation because you'll provide some information for the compiler which will be used to determine actual X.

Eclipse had several such bugs when its compiler shows no errors at all, but javac starts complaining about incorrect method calls. The best way to avoid such errors is using explicit parameters, in this case most compilers behave almost identically. So if you have problems with explicit parameters it's better re-design your classes.

Genovevagenre answered 12/1, 2013 at 0:35 Comment(1)
The purpose of Y is that it is not parametriseable for certain parametrisations of X, giving a sort of bounding on X by proxy... Your explanation of Y being ignored sounds sensible enough (still sounds like a mistake to me though), but when you hover over the invocation in eclipse it tells you the inferred parametrisation is Foo<? extends Bar, Double> which is plain wrong. For an example where X cannot be ignored but the problem still exists add the following method definition and invocation: public <X extends Foo<? extends S, Y>, Y> X method3(); ... Foo<?, Double> a = bar.method3();Orchidaceous

© 2022 - 2024 — McMap. All rights reserved.