RFR: 8177466: Add compiler support for local variable type-inference

Maurizio Cimadamore maurizio.cimadamore at oracle.com
Mon Sep 18 23:38:49 UTC 2017



On 19/09/17 00:35, Maurizio Cimadamore wrote:
> Uhm - this is a bug in the diagnostic generation for overload 
> resolution - although not a bug in the 'var' support.
>
> I could reproduce same issue with this code:
>
> import java.util.*;
>
> class Test {
>
> void test() {
>    make(new ArrayList<String>(), new ArrayList<Integer>()).add("");
> }
>
> <Z> Z make(Z z1, Z z2) {
>    return null;
> }
> }
Actually, you don't even need intersection types to reproduce; the 
following will do:

  new ArrayList<Integer>().add("");

error: no suitable method found for add(String)
    new ArrayList<Integer>().add("");
                            ^
     method Collection.add(Integer) is not applicable
       (argument mismatch; String cannot be converted to Integer)
     method List.add(Integer) is not applicable
       (argument mismatch; String cannot be converted to Integer)
     method AbstractCollection.add(Integer) is not applicable
       (argument mismatch; String cannot be converted to Integer)
     method AbstractList.add(Integer) is not applicable
       (argument mismatch; String cannot be converted to Integer)
     method ArrayList.add(Integer) is not applicable
       (argument mismatch; String cannot be converted to Integer)

Seems like something is up in the logic that should pick the 'most 
specific' signature to report in the diagnostic.

Maurizio
>
>
> So, I'd suggest that we deal with this as a separate 
> diagnostic-related issue in JDK 10 (I could also reproduce it in 8 and 
> 9).
>
> Maurizio
>
> On 18/09/17 23:44, Sergey Bylokhov wrote:
>> Hi, Maurizio.
>> I am not sure is it expected or not, but in some cases the new 'var' 
>> produce some non-easy to read error messages:
>>
>> var s = true ? new ArrayList<String>() : new ArrayList<Integer>();
>> s.add(new String());
>>
>> testVar.java:9: error: no suitable method found for add(String)
>>         s.add(new String());
>>          ^
>>     method Collection.add(CAP#1) is not applicable
>>       (argument mismatch; String cannot be converted to CAP#1)
>>     method List.add(CAP#1) is not applicable
>>       (argument mismatch; String cannot be converted to CAP#1)
>>     method AbstractCollection.add(CAP#1) is not applicable
>>       (argument mismatch; String cannot be converted to CAP#1)
>>     method AbstractList.add(CAP#1) is not applicable
>>       (argument mismatch; String cannot be converted to CAP#1)
>>     method ArrayList.add(CAP#1) is not applicable
>>       (argument mismatch; String cannot be converted to CAP#1)
>>   where CAP#1 is a fresh type-variable:
>>     CAP#1 extends INT#1 from capture of ? extends INT#1
>>   where INT#1,INT#2 are intersection types:
>>     INT#1 extends Object,Serializable,Comparable<? extends INT#2>
>>     INT#2 extends Object,Serializable,Comparable<?>
>> Note: Some messages have been simplified; recompile with 
>> -Xdiags:verbose to get full output
>>
>>
>> On 9/18/17 09:14, Maurizio Cimadamore wrote:
>>> Hi,
>>> this change adds support for local variable type inference (JEP 286 
>>> [1]). A webrev of the change is available here:
>>>
>>> http://cr.openjdk.java.net/~mcimadamore/8177466
>>>
>>> The patch is relatively straightforward: implicitly typed locals are 
>>> modeled in a similar fashion to implicit lambda parameters: their 
>>> AST node is a JCVariableDecl whose 'vartype' field is not set (e.g. 
>>> null).
>>>
>>> There are few tricky parts to this changeset:
>>>
>>> 1) tweak the parser to give 'var' special meaning depending on the 
>>> version number and context
>>>
>>> 2) Add logic in name resolution to catch bad reference to types 
>>> named 'var'
>>>
>>> 3) add logic to map initializer type back to a suitable variable 
>>> declared type
>>>
>>> As for (1), the parser has been extended so as to special case local 
>>> variables with special name 'var', so that the type will be left out 
>>> of the corresponding AST representing the variable declaration. This 
>>> behavior will only affect latest source version.
>>>
>>> The parser has a number of extra checks to prevent 'var to be used 
>>> in places where it does not belong (according to the spec draft 
>>> [2]); for instance, declaring a class whose name is 'var' is 
>>> rejected in the parser. As a general rule, I tried to implement all 
>>> such checks in the parser, as that gives very early and precise 
>>> feedback about what's wrong with the code. The changes are 
>>> implemented in Parser.java.
>>>
>>> There are however errors which cannot be caught in the parser, and 
>>> that's why (2) is needed. Basically, whenever 'var' is used in a 
>>> position where it could be either a type or a package name, the 
>>> parser can't simply rule that out, so we have to accept the code, 
>>> and give an error if, later on, we discover that 'var' was really 
>>> used in a type position (see changes in Resolve.java).
>>>
>>> As far as (3) is concerned, we need to 'uncapture' captured types 
>>> from initializers. That means that if we have a 'var' like this:
>>>
>>> class Foo {
>>>      void test() {
>>>          var x = getClass().getSuperClass();
>>>      }
>>> }
>>>
>>> The initializer type will be something like Class<? super #CAP>, 
>>> where #CAP <: Foo
>>>
>>> In this case, the compiler will project this type back to the less 
>>> specific type Class<?>, and use that as the declared type for 'x'. 
>>> This logic is defined in Types.java. As this logic is the same logic 
>>> needed by jshell to render type of standalone expressions, jshell 
>>> class VarTypePrinter has been removed and jshell has been rewired to 
>>> point at the (now) official routine in Types. Jshell also needed 
>>> several other tweaks to (i) accept 'var' and (ii) to deal with 
>>> non-denotable types (intersection types and anonymous class types) 
>>> that can be produced by the LVTI machinery (many thanks to Jan for 
>>> doing those changes!)
>>>
>>>
>>> As far as testing is concerned, I wrote several tests to check that 
>>> the parser was behaving as expected; to check the behavior of the 
>>> LVTI inference machinery, I wrote a test harness which leverages 
>>> annotation on 'var' so that we can write down assertions such as:
>>>
>>> @InferredType("java.util.List<? extends java.lang.String>")
>>> var s = extString();
>>>
>>>
>>> Regarding compiler diagnostics, for those interested, a 
>>> comprehensive list of examples of new diagnostics triggered by the 
>>> LVTI compiler can be found here:
>>>
>>> http://cr.openjdk.java.net/~mcimadamore/8177466/lvti-diags.html
>>>
>>> Finally, a finder has been added to detect local variable decls 
>>> whose declared type can be replaced by 'var' - to enable it, the 
>>> hidden option -XDfind=local should be used.
>>>
>>>
>>> Thanks
>>> Maurizio
>>>
>>> [1] - http://openjdk.java.net/jeps/286
>>> [2] - http://cr.openjdk.java.net/~dlsmith/local-var-inference.html
>>>
>>>
>>
>>
>



More information about the compiler-dev mailing list