JDK-6721089 : ternary conditional operator does not handle generics
  • Type: Enhancement
  • Component: tools
  • Sub-Component: javac
  • Affected Version: 6
  • Priority: P4
  • Status: Resolved
  • Resolution: Fixed
  • OS: windows_xp
  • CPU: x86
  • Submitted: 2008-07-01
  • Updated: 2013-05-31
  • Resolved: 2013-05-31
Related Reports
Relates :  
Relates :  
Relates :  
Description
FULL PRODUCT VERSION :
Tested on both Java5 and Java6

$ java -version
java version "1.6.0_05"
Java(TM) SE Runtime Environment (build 1.6.0_05-b13)
Java HotSpot(TM) 64-Bit Server VM (build 10.0-b19, mixed mode)

$ java -version
java version "1.5.0_15"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_15-b04)
Java HotSpot(TM) 64-Bit Server VM (build 1.5.0_15-b04, mixed mode)



ADDITIONAL OS VERSION INFORMATION :
Microsoft Windows [Version 5.2.3790]
(running in a Cygwin shell)

A DESCRIPTION OF THE PROBLEM :
When using the ternary operator (conditional assignment) generics are not
properly handled.  This code gives an error

import java.util.*;
public class TestGenerics {
    public static void main(String[] args) throws Exception {
        boolean b = true;
        List<String> list = b
            ? Collections.emptyList()
            : Collections.emptyList();
    }
}

$ javac TestGenerics.java
TestGenerics.java:6: incompatible types
found   : java.util.List<java.lang.Object>
required: java.util.List<java.lang.String>
            ? Collections.emptyList()

On the other hand this works

        List<String> list = null;
        if (b)
            list = Collections.emptyList();
        else
            list = Collections.emptyList();

I don't see why these should be different.

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Compile the above code with javac from JDK 1.6.0

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
Code should be valid
ACTUAL -
$ javac TestGenerics.java
TestGenerics.java:6: incompatible types
found   : java.util.List<java.lang.Object>
required: java.util.List<java.lang.String>
            ? Collections.emptyList()

REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
import java.util.*;
public class TestGenerics {
    public static void main(String[] args) throws Exception {
        boolean b = true;
        List<String> list = b
            ? Collections.emptyList()
            : Collections.emptyList();
    }
}
---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
Use if/else

Comments
EVALUATION Expanding the scope of type-inference is something that we will consider for 8.
26-10-2010

WORK AROUND A useful workaround to this is to specify explicitely the actual parameter types when invoking Collections.emptyList() as in the following code: import java.util.*; public class TestGenerics { public static void main(String[] args) throws Exception { boolean b = true; List<String> list = b ? Collections.<String>emptyList() : Collections.<String>emptyList(); } }
01-07-2008

EVALUATION The described behaviour is not buggy; let's see why. COnsider the following program: import java.util.*; public class TestGenerics { List<String> test1(boolean b) { return b ? Collections.emptyList() : Collections.emptyList(); } List<String> test2(boolean b) { if (b) return Collections.emptyList(); else return Collections.emptyList(); } } As described, the compiler reports an error in test1(). This is due to a bad interplay between the way in which javac computes the type of a ternary expression and type-inference. Note that this behaviour is present also in other java compiler. There are two pieces of info in the JLS that makes the compiler the way it is today; the first one is given in the description of the semantic of the conditional operator (section 15.25). The submitter here seems to assume that the type of a conditional expression is the type on the LHS of an assigment (List<String> in this case). This is not true: as stated by the JLS, the type of a conditional expression: "The type of the conditional expression is the result of applying capture conversion (��5.1.10) to lub(T1, T2) (��15.12.2.7). " It's important to understand why it's necessary to apply lub. Consider the following example: class A {} class B extends A{} class C extends A{} class Foo<X> Foo<? extends A> l = b ? new Foo<B>() : new Foo<C>() In this case we have that LHS is of type Foo<? extends A> while the RHS is of type lub(Foo<B>, Foo<C>), that is Foo<? extends B&C>. The second issue that contributes to this strange behaviour is how inference from return-type is defined (JLS, section 15.12.2.8). "If the method result occurs in a context where it will be subject to assignment conversion (��5.2) to a type S, then let R be the declared result type of the method, and let R' = R[T1 = B(T1) ... Tn = B(Tn)] where B(Ti) is the type inferred for Ti in the previous section, or Ti if no type was inferred. " The tricky part here is: does the original ternary expression constitute an assignment context? In the JLS this is *not* crystal-clear. After reading 15.25, 15.12.2.8 and 5.2) it seems like an assigment conversion is only defined when the result of a method is directly assigned to some variable. In this case the assignment is indirect (via the ternary expression). In other word, the submitted code behaves like the following: class TestGenerics { void test1() { test2(Collections.emptyList(), Collections.emptyList()); } <T> void test2(List<String> l1, List<String> l2) {} } That is, given the fact that no assignment context involving the return type of the generic method exists, javac assumes that the method's type-variable is instantiated to Object - and this instantiation makes the mothod inapplicable (as, in the submitted example, the inferred type for the method's type-variable made the type of the ternary expression unconvertible to the type of the LHS in the assigment). Despite this shouldn't regarded as being a javac bug (javac is simply following what JLS mandate), I think it's worth investigating a bit more about as to the possibility of re-defining the semantic of ternary expression so that either it's clearly stated that they forms an assignment context or they avoid resorting to lub (the latter seems less likely to happen to me). For the above reason I''ll file an RFE against the JLS that I hope will be addressed in time for 7.
01-07-2008