13

The following code

class GenericCompilationFailureDemo {
    List<? extends GenericCompilationFailureDemo> newList() { 
        return new ArrayList<GenericCompilationFailureDemo>(); 
    };

    void useList() {
        List<GenericCompilationFailureDemo> list = 
            (List<GenericCompilationFailureDemo>) newList();
    }  

    List<? extends Set<GenericCompilationFailureDemo>> newListOfSpecificSets() { 
        return new ArrayList<Set<GenericCompilationFailureDemo>>(); 
    };

    void useListOfSpecificSets() {
        List<Set<GenericCompilationFailureDemo>> listOfSpecificSets = 
            (List<Set<GenericCompilationFailureDemo>>) newListOfSpecificSets();
    } 

    List<? extends Set<? extends GenericCompilationFailureDemo>> newListOfSets() { 
        return new ArrayList<Set<? extends GenericCompilationFailureDemo>>(); 
    };

    void useListOfSet() {
        List<Set<? extends GenericCompilationFailureDemo>> listOfSets = 
            (List<Set<? extends GenericCompilationFailureDemo>>) newListOfSets();
    }  
}

compiles under Sun JDK 1.6.0_20 (64-bit on Windows Vista, but I don't think that makes any difference) but causes the following compilation failure under Oracle JDK 1.7.0_01 (same platform):

[ERROR] src\main\java\GenericCompilationFailureDemo.java:[56,78] error: inconvertible types

Note that the first two "extends-to-specific-type" casts in useList and useListOfSpecificSets both still succeed under 1.7.0_01, so it would seem it's something to do with the "double generic extends".

Any ideas what might have changed between 6 and 7, and whether the observed behaviour is according to spec or a bug?

edited in response to Sanjay's comment:

@Sanjay: Aha, interesting! Here the output from java -version:

java version "1.7.0_01"
Java(TM) SE Runtime Environment (build 1.7.0_01-b08)
Java HotSpot(TM) 64-Bit Server VM (build 21.1-b02, mixed mode)

And here the result of javac GenericCompilationFailureDemo.java (same code as above with import statements for List, ArrayList and Set):

GenericCompilationFailureDemo.java:30: error: inconvertible types
            (List<Set<? extends GenericCompilationFailureDemo>>) newListOfSets()
;
                                                                              ^
  required: List<Set<? extends GenericCompilationFailureDemo>>
  found:    List<CAP#1>
  where CAP#1 is a fresh type-variable:
    CAP#1 extends Set<? extends GenericCompilationFailureDemo> from capture of ?
 extends Set<? extends GenericCompilationFailureDemo>
Note: GenericCompilationFailureDemo.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
1 error
ROMANIA_engineer
  • 54,432
  • 29
  • 203
  • 199
Andrew Phillips
  • 1,050
  • 9
  • 16
  • 1
    For what it's worth, this code compiles on my JDK 7. Can you post the entire output of `java -version` command along with the "build" string? – Sanjay T. Sharma Dec 26 '11 at 17:58
  • please paste the exact compile errors + what sanjay has suggested. *I believe the generics are supposed to be somewhat simpler in java 7* in some cases... maybe you found a corner case where older, complex, JDK 6 syntax breaks the new compiler . – jayunit100 Dec 26 '11 at 18:10
  • @Sanjay: aha, interesting! See the edited question for responses to your comments – Andrew Phillips Dec 26 '11 at 20:58
  • javac seem to have a bad habit of spontaneously rejecting generic casts that are legal per JLS, see [bug 6790039](http://bugs.sun.com/view_bug.do?bug_id=6790039) and especially the _looooong_ list of _Related Bugs_ mentioned there (16 as of now) - yours might be there in this list :) – gnat Dec 26 '11 at 21:23

1 Answers1

7

This is apparently a javac7 bug. It should be allowed per casting conversion rules [1]

One of the rules allows a narrowing reference conversion ... followed by an unchecked conversion

Casting List<A> => List<B> is allowed by this rule

List<A> => List   // narrowing reference conversion
List => List<B>   // unchecked conversion

That's not all the story though; the spec has further rules to forbid casting like List<String>=>List<Integer>, for they are provably distinct parameterized types. There is no object belonging to the two types at the same time, so compiler think it's better to disallow this apparent programming error. (You can bypass it by explicitly List<String>=>List=>List<Integer>)

The last rule doesn't apply here though; so it looks like a javac7 bug.

Why the last rule doesn't apply: so we are casting List<? extends A> to List<A>. Here capture conversion is applied to List<? extends A> [2] so we are actually casting List<T> to List<A>, where T is a new type variable with upper bound A.

The question is whether List<T> and List<A> are provably distinct parameterized types. My understanding is that it's false (it has to be false for your first two examples to compile). Since T is a type variable, it can take a value to make List<T> and List<A> the same parameterized type (i.e. when T=A). This reasoning should work for any type A.

[1] http://java.sun.com/docs/books/jls/third_edition/html/conversions.html#5.5

[2] http://java.sun.com/docs/books/jls/third_edition/html/expressions.html#341306

irreputable
  • 44,725
  • 9
  • 65
  • 93
  • _You can bypass it by explicitly List=>List=>List)_ I verified that, indeed - but "hacking generics" was not something I really wanted to commit ;-) Thanks for the explanation! Is there anyone within Oracle you'd suggest to contact (e.g. the compiler-dev list or so?) to see whether it's worth filing a bug for this? – Andrew Phillips Dec 26 '11 at 22:40
  • 1
    Just posted to compiler-dev. Curious to see what kind of responses we'll get ;-) – Andrew Phillips Dec 27 '11 at 00:54
  • 1
    @AndrewPhillips Hey can u tell us about that response :) I'm curious to see that hacking free solution. – chAmi Feb 27 '14 at 06:27