RFR: 8177466: Add compiler support for local variable type-inference
Sergey Bylokhov
Sergey.Bylokhov at oracle.com
Tue Sep 19 00:21:36 UTC 2017
It is also interesting how the new 'var' should work in case of
conditional expression:
String s = true ? new String() : new int[1]; // incompatible types: bad
type in conditional expression
int[] s = true ? new String() : new int[1]; // incompatible types:
bad type in conditional expression
var s = true ? new String() : new int[1]; // works fine
Is it expected that this code compiles?
One related example:
var s = true ? new String() : new int[1];
s[0]=123;
testVar.java:11: error: array required, but Serializable found
s[0]=123;
^
1 error
Looks like a surprise - 's' is of type "Serializable"?
On 9/18/17 15: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
>>
>>
>
>
--
Best regards, Sergey.
More information about the compiler-dev
mailing list