JDK-8079605 : Unspecified generic type leads to bogus compilation failure in iteration
  • Type: Bug
  • Component: tools
  • Sub-Component: javac
  • Affected Version: 8
  • Priority: P3
  • Status: Closed
  • Resolution: Not an Issue
  • OS: windows_8
  • CPU: x86
  • Submitted: 2015-02-16
  • Updated: 2016-03-16
  • Resolved: 2016-03-16
Related Reports
Relates :  
Description
FULL PRODUCT VERSION :
java version "1.8.0_31"
Java(TM) SE Runtime Environment (build 1.8.0_31-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.31-b07, mixed mode)

ADDITIONAL OS VERSION INFORMATION :
Microsoft Windows [Version 6.3.9600] 


A DESCRIPTION OF THE PROBLEM :
Compiliing this class with javac 1.8.0_31 fails: 

import java.util.Map;
import java.util.Map.Entry;
import java.io.Serializable;

class DoesNotCompile {

  private static Map<String, String> testMethod(Map<Integer, Integer> map) {
	return null;
  }

  public static void main(String[] args) {
	Map map = null;
	for (Entry<String, String> entry : testMethod(map).entrySet()) { }
  }
}

The error message is:
DoesNotCompile.java:14: error: incompatible types: Object cannot be converted to Entry<String,String>
        for (Entry<String, String> entry : testMethod(map).entrySet()) { }

As far as I can see, this is valid java code (compiles with a warning  with javac 1.7.0_51).

Interestingly, the following class does compile (the only difference being that the generic types of the local variable "map" are now specified).

import java.util.Map;
import java.util.Map.Entry;
import java.io.Serializable;

class DoesCompile {

  private static Map<String, String> testMethod(Map<Integer, Integer> map) {
	return null;
  }

  public static void main(String[] args) {
	Map<Integer,Integer> map = null;
	for (Entry<String, String> entry : testMethod(map).entrySet()) { }
  }
}

REGRESSION.  Last worked in version 7u75

ADDITIONAL REGRESSION INFORMATION: 
java version "1.7.0_51"
Java(TM) SE Runtime Environment (build 1.7.0_51-b13)
Java HotSpot(TM) 64-Bit Server VM (build 24.51-b03, mixed mode)

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Try to compile the following source code with javac version 1.8.0_31:

import java.util.Map;
import java.util.Map.Entry;
import java.io.Serializable;

class DoesNotCompile {

  private static Map<String, String> testMethod(Map<Integer, Integer> map) {
	return null;
  }

  public static void main(String[] args) {
	Map map = null;
	for (Entry<String, String> entry : testMethod(map).entrySet()) { }
  }
}


EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
The code should compile (with a warning).
ACTUAL -
Compilation fails:

DoesNotCompile.java:14: error: incompatible types: Object cannot be converted to Entry<String,String>
        for (Entry<String, String> entry : testMethod(map).entrySet()) { }
                                                                   ^
Note: DoesNotCompile.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
1 error

ERROR MESSAGES/STACK TRACES THAT OCCUR :
DoesNotCompile.java:14: error: incompatible types: Object cannot be converted to Entry<String,String>
        for (Entry<String, String> entry : testMethod(map).entrySet()) { }
                                                                   ^
Note: DoesNotCompile.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
1 error

REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
import java.util.Map;
import java.util.Map.Entry;
import java.io.Serializable;

class DoesNotCompile {

  private static Map<String, String> testMethod(Map<Integer, Integer> map) {
	return null;
  }

  public static void main(String[] args) {
	Map map = null;
	for (Entry<String, String> entry : testMethod(map).entrySet()) { }
  }
}
---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
Several workarounds, e.g. specify generic types.


Comments
The fix in JDK-7144506 is what caused this behavior (which is correct) to start. Before that fix, javac would only apply erasure in unchecked calls for generic method calls.
22-01-2016

'Map' is a raw type; when a raw type is passed to a method it triggers unchecked conversions (which should be visible in a warning). When a method is compatible with unchecked warnings, the signature of the method is 'erased', meaning that the method call testMethod(map).entrySet() will return a raw Set. This raw set is then used in a for-each loop; in other words the code is like the following: Set set; for (Entry<String, String> e : set) { } Which is of course not legal code (was not even legal in JDK 7). I think this change in behavior has been caused by the fact that the JDK 8 compiler implementation has been made stricter, to adhere the rules in JLS that require the return type of a method with unchecked call to be fully erased.
22-01-2016

Wrong assignment. Reverting back
03-03-2015