JDK-8174249 : Regression in generic method unchecked calls
  • Type: Bug
  • Component: tools
  • Sub-Component: javac
  • Affected Version: 9
  • Priority: P3
  • Status: Closed
  • Resolution: Fixed
  • Submitted: 2017-02-09
  • Updated: 2017-03-13
  • Resolved: 2017-02-09
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 10 JDK 9
10Fixed 9 b157Fixed
Related Reports
Relates :  
Relates :  
Relates :  
Relates :  
Description
This code stopped compiling after the fix for JDK-8078093:

import java.util.ArrayList;
import java.util.Collection;

public class Foo {
    static <T> T foo(Class<T> c, Collection<? super T> baz) {
	return null;
    }

    static void bar(String c) {

    }

    @SuppressWarnings("unchecked")
    public static void main(String[] args) {
	// this works
	bar(foo(String.class, new ArrayList<String>()));

	// this works with a warning
	String s = foo(String.class, new ArrayList());
	bar(s);

	// this causes an error on JDK9
	bar(foo(String.class, new ArrayList()));
    }
}

Moreover, the error issued is weird-looking:

Foo.java:23: error: method bar in class Foo cannot be applied to given types;
                bar(foo(String.class, new ArrayList()));
                ^
  required: String
  found: String
  reason: argument mismatch; Object cannot be converted to String
1 error


Comments
The reason as to why the weird diagnostic (expected String, found String) is generated has to do with recovery of deferred arguments. When overload resolution fails, because Object is found and String was expected, an error message is issued; this error needs to mention the types of the actual and formal arguments; but here, the type of the actual is deferred, so javac attempts to 'recover' its type, by checking the nested method call in an empty assignment context. That checking produces a type String, and that type is then reported back to the user as the 'actual' type - despite that's not really the type used by javac during overload.
09-02-2017

The situation is a tad convoluted; basically there's a long-standing javac discrepancy which allowed javac to erase the return type of an unchecked call *after* some type inference has been performed. This behavior is well described in JDK-8135087. In the above example, that means that, since the first actual argument to 'foo' has type Class<String> and the first formal is Class<T>, javac is able to infer that T = String - which gives a fully instantiated 'foo' with signature: String foo(Class<String> c, Collection<? super String> baz) Then, as an unchecked conversion has been required for this method call (for the second argument), javac proceeds to erase the signature - but there's nothing to do! That is, erasure(String) = String, so the invocation type of the inner most call has a return type 'String'. That's different from what the spec says - as the spec wants to erase the *declared signature* of the method if an unchecked conversion occurs - in which case the invocation return type would be just Object (and a compiler error would be issued). That said, JDK-8078093 accidentally introduced a change in this area - in method context the current javac implementation would erase the declared signature; while this behavior is compatible with what the spec say, it's completely inconsistent with what javac does in assignment context (as shown in the example above), and it's also inconsistent with almost everything else javac does (hence the weird error you get 'expected: String - found: String'). This is just a plain bug - the fix for JDK-8078093 was never meant to alter behavior of type-checking with unchecked calls and generic methods. In the short term we should restore the original behavior (as with JDK 8).
09-02-2017