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

Maurizio Cimadamore maurizio.cimadamore at oracle.com
Wed Sep 20 00:38:56 UTC 2017



On 20/09/17 01:20, Sergey Bylokhov wrote:
> Just thoughts on a subject...
>
> Its seems that most of "surprising" behavior comes from the reassigning,
> The new "var" adds one new feature: it will be possible to have a 
> reference to the anonymous class and call the methods defined in it. 
> So it is not simply a syntactic sugar.
You touch on an important point - the type inferred for 'var' might be 
non-denotable - when that happens, re-assignment is going to give 
'surprising' results, where the level of surprise might vary depending 
on what the expectations for this feature are.

This issue was discussed here:

http://mail.openjdk.java.net/pipermail/amber-spec-experts/2017-April/000023.html

As you can see, there's a multitude of options in the design space when 
defining what 'var' means. The currently implemented approach (and spec 
draft) chose this point:

* forbid 'null'
* normalize captured types
* leave intersection types and anonymous classes as is

(note that for the last two bullets - there might occur recursively 
inside other types, such as arrays or parameterized types).

I would suggest that we try and keep this thread confined to the code 
review - that said, your feedback is greatly appreciated, and I suggest 
you to direct it to the amber-spec-experts mailing list.

Thanks
Maurizio
>
> var probablySerializable = new Serializable() {
>     public void test() {
>     }
> };
> .....
> probablySerializable.test();
>
>
> And because of that the reassigned will not work:
> var probablySerializable = new Serializable() {
>     public void test() {
>     }
> };
> probablySerializable = new Serializable() {
> };
>
> testVar.java:13: error: incompatible types: <anonymous Serializable> 
> cannot be converted to <anonymous Serializable>
>         name = new Serializable() {
>
>
> On 9/19/17 13:24, Maurizio Cimadamore wrote:
>> The type of this is not Component<?, ?> but Component<T, D>. So, when 
>> you use an explicit type, you force the type of 'cp1' to be less 
>> sharp, which then works for the followup assignment. But if you use 
>> 'var', the compiler will just infer whatever type the initializer 
>> has, which in this case is Component<T, D>. And the type of 
>> c2.getContainer (Component<#CAP1, #CAP2>) is not compatible with 
>> Component<T, D>.
>>
>> Maurizio
>>
>>
>> On 19/09/17 17:44, Sergey Bylokhov wrote:
>>> Hi, Maurizio.
>>> One more question about generics, in the code below the "case1" 
>>> compiles successfully, but the "case2" produce an error:
>>>
>>> public class testVar {
>>>
>>>     class Component<T extends Object, D extends Object> {
>>>
>>>         final Container<?, ?> getContainer() {
>>>             return null;
>>>         }
>>>
>>>         void windowToLocal() {
>>>             // case1
>>>             Component<?, ?> cp1 = this;
>>>             cp1 = cp1.getContainer();
>>>             // case2
>>>             var cp2 = this;
>>>             cp2 = cp2.getContainer();
>>>         }
>>>     }
>>>     class Container<T extends Object, D extends Object>
>>>             extends Component<T, D> {
>>>
>>>     }
>>> }
>>>
>>> //error
>>> testVar.java:13: error: incompatible types: 
>>> testVar.Container<CAP#1,CAP#2> cannot be converted to 
>>> testVar.Component<T,D>
>>>             cp2 = cp2.getContainer();
>>>                                   ^
>>>   where T,D are type-variables:
>>>     T extends Object declared in class testVar.Component
>>>     D extends Object declared in class testVar.Component
>>>   where CAP#1,CAP#2 are fresh type-variables:
>>>     CAP#1 extends Object from capture of ?
>>>     CAP#2 extends Object from capture of ?
>>> 1 error
>>>
>>> On 9/19/17 08:04, Maurizio Cimadamore wrote:
>>>> Hi,
>>>> I have put together a slightly updated webrev:
>>>>
>>>> * as pointed out, one diagnostic used to say '9' instead of '10' 
>>>> (this will likely need to change again because of new release 
>>>> cadence, but good enough for now)
>>>> * the previous webrev contained a spurious added file (TypeHarness) 
>>>> which was caused by a glitch in the lvti branch in the amber repo.
>>>>
>>>> New webrev:
>>>>
>>>> http://cr.openjdk.java.net/~mcimadamore/8177466_v2/
>>>>
>>>> New diags:
>>>>
>>>> http://cr.openjdk.java.net/~mcimadamore/8177466_v2/diags.html
>>>>
>>>> Maurizio
>>>>
>>>>
>>>> On 18/09/17 17: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