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

Vicente Romero vicente.romero at oracle.com
Thu Sep 21 00:06:20 UTC 2017

On 09/20/2017 06:54 PM, Maurizio Cimadamore wrote:
> On 20/09/17 22:26, Vicente Romero wrote:
>> On 09/20/2017 05:14 PM, Maurizio Cimadamore wrote:
>>> Thanks for the comments - some responses below
>>> On 20/09/17 20:36, Vicente Romero wrote:
>>>> Hi,
>>>> Another great piece of work! some high level comments:
>>>> at Types:
>>>> * new visitors TypeProjection and CaptureScanner would benefit of 
>>>> having some documentation added, it should be made clear what 
>>>> (!upward) means as there is no explicit reference to the downward 
>>>> projection in the code. Instead of passing a boolean, using an enum 
>>>> could be beneficial for self documentation. Like enum Projection 
>>>> {UPWARD, DOWNWARD}, the enum could have a getInverseProjection() or 
>>>> getComplementProjection() method.
>>> Good idea, I'll play with it a bit
>>>>     - also probably examples of expected output for a given input 
>>>> could be added, but I'm not too strong about this though
>>>>     - also could it be beneficial to include some unit tests for 
>>>> them? I realize that they need to be public for a jtreg test to be 
>>>> able to access them but I think it will be important to check that 
>>>> the upward projection is yielding the
>>>>       expected result. I'm mostly interested in this code path: 
>>>> "Type lower = t.map(this, !upward);" at 
>>>> TypeProjection.mapTypeArgument()
>>> I think a TypeHarness test would be a natural way to unit test this. 
>>> Not sure though if this needs to be done now or as a follow up
>> right it doesn't have to be right now, it can wait.
>>> - after all, the test harness for LVTI is already providing some 
>>> sort of unit testing of this part, look at this:
>>> http://cr.openjdk.java.net/~mcimadamore/8177466_v2/test/langtools/tools/javac/lvti/harness/NonDenotableTest.java.html 
>>>> Attr:
>>>> why at attribLazyConstantValue you do:
>>>>     - variable.sym.type = chk.checkLocalVarType(variable, itype, 
>>>> variable.name); and at visitVarDef:
>>>>    - v.type = chk.checkLocalVarType(tree, 
>>>> tree.init.type.baseType(), tree.name);  // baseType() is invoked in 
>>>> this case
>>> As to why there's a baseType in visitVarDef, I believe it is because 
>>> we can get into that code path if a 'var' is not final but its 
>>> initializer is a constant - e.g. :
>>> var s = "Hello!"
>>> In this case we don't want the type of 's' to be constant.
>>> Regarding attribLazyConstantValue, I think it's probably better to 
>>> use baseType() there too. But I will have to double check if lack of 
>>> baseType is creating issues.
>> ok thanks for the clarification, yes I think that we need to 
>> investigate if you need to use baseType() for lazy evaluation too 
>> which should be triggered by:
>> final var s = "Hello!" right?
> Yes, lazy evaluation would happen for final vars.
>>>> JavacParser:
>>>> general question: can't the parser do an early detection of cases 
>>>> like "var s = () -> {};"?
>>> I think it's maybe doable, but it gets convoluted if you have casts 
>>> and other things before the lambda.
>> right but if there is a cast then there is no issue as there is a 
>> target, we need to discard only the cases when a "raw" lambda or 
>> method reference is used to initialize a variable with no explicit 
>> type. Again if you consider that code won't benefit from this then 
>> let's let it as it is. But it could be worthy trying to discard ill 
>> formed expressions as soon as possible
> You are right - a cast is the dumbest example I could have come up 
> with :-)
> but think of this:
> var s = (cond) ? "Hello!" : () -> { }
> Again, this is all doable, we could do lookahead, or we could look at 
> the parsed expression after the fact and rule it out as there's a 
> lambda in the middle - but it seemed simpler to simply special case it 
> in the Attr visitors. That way, no matter how deeply nested the lambda 
> is, you will get a consistent error message.

right, yes I think that you are right and Attr should be the best place, 
it was just too tempting to put it in the parser :)

> Maurizio


>>>> Thanks,
>>>> Vicente
>> Vicente
>>>> On 09/19/2017 11:04 AM, 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