Java 8 language spec flaw/bug

Dan Smith daniel.smith at oracle.com
Fri Aug 22 17:25:00 UTC 2014


I think this has been sorted out, but let me just offer some point-by-point comments.

On Aug 21, 2014, at 10:18 AM, Davin McCall <davmac at bluej.org> wrote:

> I'm concerned with this bug:
> https://bugs.openjdk.java.net/browse/JDK-8044053
> 
> It concerns changes in the language spec (and the compiler) that cause some backwards incompatibility between Java 7 and Java 8. The example in the bug ticket uses the trinary conditional operator but I believe this is  a red herring, that is, the same problem occurs when not using the conditional operator.
> 
> Specifically, the following compiles with Java 7 but not 8:
> 
>   |--- begin ---
>   public  class  Foo  {
> 
>        public  static  void  main(String[]  args)  throws  Exception  {
>            // compiles fine in Java 7 and Java 8:
>            Class<?  extends  CharSequence>  aClass=  ternary(true, String.class, StringBuilder.class);
>            CharSequence  foo=  foo(aClass);
> 
>            // Inlining the variable using 'ternary' method
>            // Compiles with Java 7 but not with 8:
>   	CharSequence  foo2=  foo(ternary(true,  String.class,  StringBuilder.class));
>        }
> 
>        static  <T>  T foo(Class<T>  clazz)  throws  Exception  {
>            return  clazz.newInstance();
>        }
> 
>        static  <T>  T ternary(boolean  cond,  T a,  T b)  {
>            if  (cond)  return  a;
>            else  return  b;
>        }
>   }
>   --- end ---

This is a compiler bug, fixed by JDK-8030741.

The relevant spec is here: http://docs.oracle.com/javase/specs/jls/se8/html/jls-18.html#jls-18.5.2

"Otherwise, if R θ is an inference variable α, and ... T is a reference type, but is not a wildcard-parameterized type, and ... B2 contains two bounds of the forms S1 <: α and S2 <: α, where S1 and S2 have supertypes that are two different parameterizations of the same generic class or interface"

In these circumstances, α is resolved immediately, and capture is performed.

Applied to your example:
R (return type) = T (type parameter of 'ternary')
R θ = t1 (an inference variable substituted for T)
T (target type) = Class<t2> (where t2 is an inference variable substituted for T, the type parameter of 'foo')
B2 = { t1 <: Object, Class<String> <: t1, Class<StringBuilder> <: t1 }
S1 = Class<String>
S2 = Class<StringBuilder>

So t1 must be resolved immediately, producing 'Class<? extends CharSequence & Serializable>'; then we capture this, and get the constraint 'Class<CAP> --> Class<t2>'.

> I'm worried about the comments in the bug, but I'm unable to make comment myself (I don't have an OpenJDK account and it doesn't appear to be possible for general members of the public to sign up). See Dan Smith's comment:
> 
>> In general, the use of context provided by the new strategy is valuable, but in this case it interferes with the opportunity for capture. It might be possible to use a strategy similar to 18.5.2 in order to recognize situations in which eager bottom-up typing and capture is the more useful approach. Ultimately, this comes down to whether we consider the conditional expression to be a poly expression or not (see 15.25.3). (But changing the simple rule, "A reference conditional expression is a poly expression if it appears in an assignment context or an invocation context," to something more complex may not be worthwhile.)
> 
> This is, in my opinion, bogus. The problem is not in how conditional expressions are treated at all - as I have demonstrated above, the problem also occurs with generic methods.

As you've seen, no, the example you provide is a separate bug from 8044053, and is just a compiler bug.  The conditional operator is an essential part of 8044053, and unfortunately that case is not a simple matter of making javac conform to the spec.

> In fact the issue is with section 18.2.3 of the JLS:
> 
>   --- begin ---
> 
> 
>         18.2.3. Subtyping Constraints
> 
>   ...
> 
>   A constraint formula of the form ‹S |<=| T›, where S and T are type
>   arguments (§4.5.1
>   <http://docs.oracle.com/javase/specs/jls/se8/html/jls-4.html#jls-4.5.1>),
>   is reduced as follows:
> 
>   If T is a type:
> 
>     * If S is a type, the constraint reduces to ‹S = T›.
> 
>   --- end ---
> 
> 
> This turns a 'contains' constraint directly into an 'equals' constraint, which is precisely what causes the problem. I can see no justification for this. It should instead imply a sub-type constraint, that is, it should reduce to ‹S |<:| T›.

There's been some discussion about this, but to be clear: you're misunderstanding the meaning of "contains" and/or the meaning of inference variables.

http://docs.oracle.com/javase/specs/jls/se8/html/jls-4.html#jls-4.5.1 defines containment.
http://docs.oracle.com/javase/specs/jls/se8/html/jls-18.html#jls-18.1.1 defines inference variables.

Note that inference variables represent some unknown type, and in particular, an inference variable cannot represent a wildcard.

The only rule in 4.5.1 that applies to two different types (not wildcards) is this:

T <= T

Or, in other words,

S <= T iff S = T

—Dan


More information about the lambda-dev mailing list