RFR: 8177466: Add compiler support for local variable type-inference
Maurizio Cimadamore
maurizio.cimadamore at oracle.com
Mon Sep 18 16:14:36 UTC 2017
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