JDK-8075793 : Source incompatibility for inference using -source 7
  • Type: Bug
  • Component: tools
  • Sub-Component: javac
  • Affected Version: 9
  • Priority: P3
  • Status: Closed
  • Resolution: Fixed
  • OS: generic
  • CPU: x86_64
  • Submitted: 2015-03-19
  • Updated: 2017-01-04
  • Resolved: 2016-12-15
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 9
9 b150Fixed
Related Reports
Duplicate :  
Relates :  
Relates :  
Relates :  
Description
FULL PRODUCT VERSION :
java version "1.9.0-ea"
Java(TM) SE Runtime Environment (build 1.9.0-ea-b54)
Java HotSpot(TM) 64-Bit Server VM (build 1.9.0-ea-b54, mixed mode)

A DESCRIPTION OF THE PROBLEM :
When compiling existing Java 7 compatible source code that compiles fine on Java 7 with the recent Java 9 snapshot builds, it fails. This is caused by type deduction for diamonds, which seems to behave different (but only if you compile with -source/target 1.7, all later source/target versions compile fine with java 9).

REGRESSION.  Last worked in version 7u76

ADDITIONAL REGRESSION INFORMATION: 
This code compiles perfectly with Java 7 and Java 8, passing "-source 1.7 -target 1.7"

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Use the following class file:

import java.util.*;

class Bug {
  public static <V> Set<V> copy(final Set<? extends V> set) { 
    return new HashSet<>(set);
  }
}

Code compiles perfectly with Java 7 and Java 8. It also compiles with Java 9 when passing the -source/target 1.8 or 1.9. But with -source/-target 1.7 it fails.

The code also compiles fine with build 47, so it must have been introduced between those builds.

This breaks builds of several open source projects with Java 9. Affected are Apache Lucene and Apache Solr, also Elasticsearch has seen this issue.

The same problems also happens with this code:

import java.util.*;

class Bug {
  public static <V> void copy(final Set<? extends V> set) { 
    Set<V> copy = new HashSet<>(set);
  }
}

It looks like the diamond operator of the HashSet is deducted from the HashSet constructor parameter and not as described by JLS from the left side of the assignment.

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
$ javac -source 1.7 -target 1.7 Bug.java
warning: [options] bootstrap class path not set in conjunction with -source 1.7
1 warning
$
ACTUAL -
$ javac -source 1.7 -target 1.7 Bug.java
warning: [options] bootstrap class path not set in conjunction with -source 1.7
Bug.java:5: error: incompatible types: HashSet<CAP#1> cannot be converted to Set<V>
    return new HashSet<>(set);
           ^
  where V is a type-variable:
    V extends Object declared in method <V>copy(Set<? extends V>)
  where CAP#1 is a fresh type-variable:
    CAP#1 extends V from capture of ? extends V
1 error
1 warning
$

REPRODUCIBILITY :
This bug can be reproduced always.

CUSTOMER SUBMITTED WORKAROUND :
Explicitely insert the type into the diamond: return new HashSet<V>(set);



Comments
After seeing this repeated in JDK-8166437 and getting other similar feedback, we've decided to reopen and address this with changed behavior under "-source 7" (and previous).
01-12-2016

After discussion with the rest of the team, the consensus is to not go out of our way to preserve the old, broken behavior. javac 9 is conforming to the language specification, and previous version of javac did not, allowing certain illegal programs to compile. If there were an easy way to switch off the bug fix under "-source 7", we might consider that, but the treatment of wildcards is such a deep problem that there is not a superficial way to preserve the old behavior. See also the suggested JDK 9 release notes in JDK-8039214: "The javac compiler's behavior when handling wildcards and "capture" type variables has been improved for conformance to the language specification. This improves type checking behavior in certain unusual circumstances. It is also a source-incompatible change: certain uses of wildcards that have compiled in the past may fail to compile because of a program's reliance on the javac bug."
12-11-2015

Priority lowered to P3: - Impact = Medium (regression, but conforms to specification) - Likelihood = Medium (not extremely common, not extremely rare) - Workaround = Medium (must change source, but change is straightforward)
27-04-2015

Evaluation: javac 5, 6, 7, and 8 did the wrong thing here; JDK-8039214 fixed it, but there are programs that rely on the broken behavior. Simplified test: class C<T> {} <T> C<T> m(C<? extends T> x) { return null; } void test(C<? extends Number> arg) { C<Number> c = m(arg); } Type checking behavior is clear, per JLS 3 and JLS 7: - The type of 'arg' is C<CAP#1>, where CAP#1 extends Number (6.5.6.1) - Inference determines that T has lower bound CAP#1 (15.12.2.7) - The type of T is inferred as CAP#1 (15.12.2.7) - The type of the method invocation ('m(arg)') is C<CAP#1> (15.12.2.6) - C<CAP#1> cannot be assigned to C<Number> (4.10.2) Traditionally, javac has performed some unspecified "simplifications" that led to T being inferred as 'Number' rather than 'CAP#1'. These unspecified behaviors break programs that should compile, but also allow programs like these to be compiled when the spec disallows them (see JDK-8039214). For the class of programs described by this bug, changes made to inference in JLS 8 make these JLS/javac inconsistencies moot, because both strategies get the same answer. But under -source 7, the distinction is apparent, and fixing JDK-8039214 exposes these programs' dependency on incorrect type checking behavior. Broadly, our choices: - Not A Bug, expect users to fix their code - Freeze the -source 7 type checking implementation, and stop fixing bugs in it (hard to separate JDK-8039214 from many other bugs...) - Retroactively "specify" javac's legacy behavior as part of inference in 5-7, re-implement it, and hope this doesn't cause subtle breakage elsewhere
14-04-2015

Added one more source which reproduce the bug. It's strange, but in case of single parameter compiler apply parameter's type-parameter to resulting type. I.e. if we have method defined as "<E> Foo<E> method(Bar<? extends E> b)" and call it with parameter of type "Bar<? extends String>", compiler mistakenly treats method return type as "Foo<? extends String>" instead of "Foo<String>". Foo<? extends String> could not be converted to Foo<String>, it's error. Note that double parameter methods ("<E> Foo<E> method(Bar<? extends E> b1, Bar<? extends E> b2)") does not have such problem.
26-03-2015

The problem is that the type-system changes in the patch for JDK-8039214 probably occur regardless of selected source version - so there are bad interactions when a source other than 8 (featuring inference improvements) is selected.
24-03-2015

Here is further investigation/observation from Uwe Schindler On 3/24/2015 7:53 PM, Uwe Schindler wrote: > Hi, > > I did some investigation: It is the following issue that breaks this: > https://bugs.openjdk.java.net/browse/JDK-8039214 "Inference should not map capture variables to their upper bounds" > > This is the commit: > http://hg.openjdk.java.net/jdk9/dev/langtools/rev/414b82835861 > > The comments here actually say that there might be backwards breaks with existing code. BUT: The strange thing is that this only affect -source/target 1.7, not for newer code. So this breaks only Java 7 backwards compilation! This looks like a regression in the JDK7 specific backwards compiler which seems to handle the types in a wrong way. Maybe some of the changes in this commit have to also be applied to the backwards compiler for Java 1.7 code (I have no idea if there is a separate instance of the 1.7 compiler in the code base, just a guess). > > To me it looks really wrong what is happening here for diamonds and such static wrapper methods like Collections.unmodifiable*()! > > In fact, both bugs are the same, so I think it's safe to append my second test case to the already existing issue as well. > > Uwe
24-03-2015

Uwe Schindler provided another testcase 'Bug2.java' (see attached) - this is another variant of the same bug that affects simple method calls (without diamond), so it is not only the constructor with diamond affected.
24-03-2015

1) Compiled the attached test case (Bug.java) with source/target 1.7 and could confirm the issue. The issue seems appeared with JDK 9 ea b53 and onwards. 2) On a Windows 7 (64-bit) and Linux (32-bit) system, checked this for JDK 7u80, 8u40, 9 ea (b47-55). The test compiled successfully with all except JDK 9 ea b53-b55. **************************************************************************************** 7u80: OK 8u40: OK 9 ea b47: OK 9 ea b48: OK 9 ea b49: Ok 9 ea b50: OK 9 ea b52: OK 9 ea b53: FAIL 9 ea b54: FAIL 9 ea b55: FAIL ****************************************************************************************** 3) Output of the testcase with 9 ea b47: >javac -source 1.7 -target 1.7 Bug.java warning: [options] bootstrap class path not set in conjunction with -source 1.7 1 warning Output of the testcase with 9 ea b55: >javac -source 1.7 -target 1.7 Bug.java warning: [options] bootstrap class path not set in conjunction with -source 1.7 Bug.java:5: error: incompatible types: HashSet<CAP#1> cannot be converted to Set<V> return new HashSet<>(set); ^ where V is a type-variable: V extends Object declared in method <V>copy(Set<? extends V>) where CAP#1 is a fresh type-variable: CAP#1 extends V from capture of ? extends V 1 error 1 warning
24-03-2015