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

Maurizio Cimadamore maurizio.cimadamore at oracle.com
Wed Sep 20 22:54:38 UTC 2017

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.

>>> 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