Rework "var" support for java10 using void production for LocalVariableType

This commit is contained in:
Andreas Dangel
2018-05-28 21:13:11 +02:00
parent 0b0479d7c0
commit a46288547d
11 changed files with 102 additions and 106 deletions

View File

@ -1879,14 +1879,16 @@ void Initializer() :
/*
* Type, name and expression syntax follows.
* Type is the same as "UnannType" in JLS
*
* See https://docs.oracle.com/javase/specs/jls/se10/html/jls-8.html#jls-UnannType
*/
void Type():
{
Token t;
}
{
LOOKAHEAD( { jdkVersion >= 10 && isKeyword("var") } ) t=<IDENTIFIER> {jjtThis.setImage(t.image); jjtThis.setTypeInferred(true); }
| LOOKAHEAD(2) ReferenceType()
LOOKAHEAD(2) ReferenceType()
| PrimitiveType()
}
@ -2338,15 +2340,25 @@ void BlockStatement():
LOOKAHEAD( (Annotation())* ["final"|"abstract"] "class") (Annotation())* ClassOrInterfaceDeclaration(0)
}
/*
* See https://docs.oracle.com/javase/specs/jls/se10/html/jls-14.html#jls-14.4
*/
void LocalVariableDeclaration() :
{}
{
( "final" {jjtThis.setFinal(true);} | Annotation() )*
Type()
LocalVariableType()
VariableDeclarator()
( "," VariableDeclarator() )*
}
void LocalVariableType() #void :
{}
{
LOOKAHEAD( { jdkVersion >= 10 && isKeyword("var") } ) <IDENTIFIER>
| Type()
}
void EmptyStatement() :
{}
{
@ -2502,7 +2514,7 @@ void Resources() :
void Resource() :
{}
{
LOOKAHEAD(2) ( ( "final" {jjtThis.setFinal(true);} | Annotation() )* Type() VariableDeclaratorId() "=" Expression() )
LOOKAHEAD(2) ( ( "final" {jjtThis.setFinal(true);} | Annotation() )* LocalVariableType() VariableDeclaratorId() "=" Expression() )
|
Name() {checkForBadConciseTryWithResourcesUsage();}
}

View File

@ -37,31 +37,49 @@ public class ASTLocalVariableDeclaration extends AbstractJavaAccessNode implemen
return false;
}
/**
* If true, this local variable declaration represents a declaration,
* which makes use of local variable type inference, e.g. java10 "var".
* You can receive the inferred type via {@link ASTVariableDeclarator#getType()}.
*
* @see ASTVariableDeclaratorId#isTypeInferred()
*/
public boolean isTypeInferred() {
return getTypeNode() == null;
}
public boolean isArray() {
if (getTypeNode().isTypeInferred()) {
// TODO: this is wrong, if the inferred type actually denotes a array
return false;
}
return checkType() + checkDecl() > 0;
return getArrayDepth() > 0;
}
public int getArrayDepth() {
return checkType() + checkDecl();
return getArrayDimensionOnType() + getArrayDimensionOnDeclaratorId();
}
/**
* Gets the type node for this variable declaration statement.
* With Java10 and local variable type inference, there might be
* no type node at all.
* @return The type node or <code>null</code>
* @see #isTypeInferred()
*/
public ASTType getTypeNode() {
return getFirstChildOfType(ASTType.class);
}
private int checkType() {
return getTypeNode().getArrayDepth();
private int getArrayDimensionOnType() {
ASTType typeNode = getTypeNode();
if (typeNode != null) {
return typeNode.getArrayDepth();
}
return 0;
}
private ASTVariableDeclaratorId getDecl() {
return (ASTVariableDeclaratorId) jjtGetChild(jjtGetNumChildren() - 1).jjtGetChild(0);
}
private int checkDecl() {
private int getArrayDimensionOnDeclaratorId() {
return getDecl().getArrayDepth();
}

View File

@ -23,8 +23,6 @@ public class ASTType extends AbstractJavaTypeNode {
super(p, id);
}
private boolean typeInferred;
/**
* Accept the visitor. *
*/
@ -34,10 +32,6 @@ public class ASTType extends AbstractJavaTypeNode {
}
public String getTypeImage() {
if (isTypeInferred()) {
return null;
}
ASTClassOrInterfaceType refType = getFirstDescendantOfType(ASTClassOrInterfaceType.class);
if (refType != null) {
return refType.getImage();
@ -56,20 +50,4 @@ public class ASTType extends AbstractJavaTypeNode {
public boolean isArray() {
return getArrayDepth() > 0;
}
void setTypeInferred(boolean typeInferred) {
this.typeInferred = typeInferred;
}
/**
* If true, this type represents a type, that has been inferred.
* It can be e.g. a local variable declaration, which
* uses the java10 "var" type inference feature.
* The method {@link #getType()} will return the correct type, if PMD could determine it.
*
* @see ASTVariableDeclaratorId#isTypeInferred()
*/
public boolean isTypeInferred() {
return typeInferred;
}
}

View File

@ -150,17 +150,17 @@ public class ASTVariableDeclaratorId extends AbstractJavaTypeNode implements Dim
}
private boolean isLocalVariableTypeInferred() {
ASTType type = null;
boolean hasType = true;
if (jjtGetParent() instanceof ASTResource) {
// covers "var" in try-with-resources
type = jjtGetParent().getFirstChildOfType(ASTType.class);
hasType = jjtGetParent().getFirstChildOfType(ASTType.class) != null;
} else if (getNthParent(2) instanceof ASTLocalVariableDeclaration) {
// covers "var" as local variables and in for statements
type = getNthParent(2).getFirstChildOfType(ASTType.class);
hasType = getNthParent(2).getFirstChildOfType(ASTType.class) != null;
}
return type != null && type.isTypeInferred();
return !hasType;
}
/**
@ -171,7 +171,7 @@ public class ASTVariableDeclaratorId extends AbstractJavaTypeNode implements Dim
// TODO unreliable, not typesafe and not useful, should be deprecated
public Node getTypeNameNode() {
ASTType type = getTypeNode();
return type == null || type.isTypeInferred() ? null : getTypeNode().jjtGetChild(0);
return type == null ? null : getTypeNode().jjtGetChild(0);
}

View File

@ -48,8 +48,10 @@ public class CheckResultSetRule extends AbstractJavaRule {
@Override
public Object visit(ASTLocalVariableDeclaration node, Object data) {
ASTClassOrInterfaceType type = node.getFirstChildOfType(ASTType.class)
.getFirstDescendantOfType(ASTClassOrInterfaceType.class);
ASTClassOrInterfaceType type = null;
if (!node.isTypeInferred()) {
type = node.getFirstChildOfType(ASTType.class).getFirstDescendantOfType(ASTClassOrInterfaceType.class);
}
if (type != null && (type.getType() != null && "java.sql.ResultSet".equals(type.getType().getName())
|| "ResultSet".equals(type.getImage()))) {
ASTVariableDeclarator declarator = node.getFirstChildOfType(ASTVariableDeclarator.class);

View File

@ -119,7 +119,7 @@ public class CouplingBetweenObjectsRule extends AbstractJavaRule {
private void handleASTTypeChildren(Node node) {
for (int x = 0; x < node.jjtGetNumChildren(); x++) {
Node sNode = node.jjtGetChild(x);
if (sNode instanceof ASTType && !((ASTType) sNode).isTypeInferred()) {
if (sNode instanceof ASTType) {
Node nameNode = sNode.jjtGetChild(0);
checkVariableType(nameNode, nameNode.getImage());
}

View File

@ -121,7 +121,7 @@ public class CloseResourceRule extends AbstractJavaRule {
for (ASTLocalVariableDeclaration var : vars) {
ASTType type = var.getTypeNode();
if (!type.isTypeInferred() && type.jjtGetChild(0) instanceof ASTReferenceType) {
if (!var.isTypeInferred() && type != null && type.jjtGetChild(0) instanceof ASTReferenceType) {
ASTReferenceType ref = (ASTReferenceType) type.jjtGetChild(0);
if (ref.jjtGetChild(0) instanceof ASTClassOrInterfaceType) {
ASTClassOrInterfaceType clazz = (ASTClassOrInterfaceType) ref.jjtGetChild(0);

View File

@ -64,7 +64,7 @@ public class MoreThanOneLoggerRule extends AbstractJavaRule {
return super.visit(node, data);
}
ASTType type = node.jjtGetParent().getFirstChildOfType(ASTType.class);
if (type != null && !type.isTypeInferred()) {
if (type != null) {
Node reftypeNode = type.jjtGetChild(0);
if (reftypeNode instanceof ASTReferenceType) {
Node classOrIntType = reftypeNode.jjtGetChild(0);

View File

@ -100,8 +100,10 @@ public class UselessOperationOnImmutableRule extends AbstractJavaRule {
*/
private ASTVariableDeclaratorId getDeclaration(ASTLocalVariableDeclaration node) {
ASTType type = node.getTypeNode();
if (MAP_CLASSES.keySet().contains(type.getTypeImage())) {
return node.getFirstDescendantOfType(ASTVariableDeclaratorId.class);
if (type != null) {
if (MAP_CLASSES.keySet().contains(type.getTypeImage())) {
return node.getFirstDescendantOfType(ASTVariableDeclaratorId.class);
}
}
return null;
}

View File

@ -535,7 +535,7 @@ public class ClassTypeResolver extends JavaParserVisitorAdapter {
if (entry.getKey().getImage().equals(image)) {
ASTType typeNode = entry.getKey().getDeclaratorId().getTypeNode();
if (typeNode == null || typeNode.isTypeInferred()) {
if (typeNode == null) {
// TODO : Type is inferred, ie, this is a lambda such as (var) -> var.equals(other) or a local var
return null;
}
@ -627,16 +627,15 @@ public class ClassTypeResolver extends JavaParserVisitorAdapter {
return data;
}
private void populateVariableDeclaratorFromType(ASTLocalVariableDeclaration node) {
ASTType type = node.getTypeNode();
// also assign this type to VariableDeclarator and VariableDeclaratorId
private void populateVariableDeclaratorFromType(ASTLocalVariableDeclaration node, JavaTypeDefinition typeDefinition) {
// assign this type to VariableDeclarator and VariableDeclaratorId
TypeNode var = node.getFirstChildOfType(ASTVariableDeclarator.class);
if (var != null) {
var.setTypeDefinition(type.getTypeDefinition());
var.setTypeDefinition(typeDefinition);
var = var.getFirstChildOfType(ASTVariableDeclaratorId.class);
}
if (var != null) {
var.setTypeDefinition(type.getTypeDefinition());
var.setTypeDefinition(typeDefinition);
}
}
@ -645,14 +644,13 @@ public class ClassTypeResolver extends JavaParserVisitorAdapter {
super.visit(node, data);
// resolve "var" types: Upward projection of the type of the initializer expression
ASTType type = node.getTypeNode();
if (type != null && type.isTypeInferred()) {
if (type == null) {
// no type node -> type is inferred
ASTVariableInitializer initializer = node.getFirstDescendantOfType(ASTVariableInitializer.class);
if (initializer != null && initializer.jjtGetChild(0) instanceof ASTExpression) {
// only Expression is allowed, ArrayInitializer is not allowed in combination with "var".
ASTExpression expression = (ASTExpression) initializer.jjtGetChild(0);
type.setTypeDefinition(expression.getTypeDefinition());
populateVariableDeclaratorFromType(node);
populateVariableDeclaratorFromType(node, expression.getTypeDefinition());
}
}
return data;
@ -665,24 +663,25 @@ public class ClassTypeResolver extends JavaParserVisitorAdapter {
if (node.jjtGetChild(0) instanceof ASTLocalVariableDeclaration) {
ASTLocalVariableDeclaration localVariableDeclaration = (ASTLocalVariableDeclaration) node.jjtGetChild(0);
ASTType type = localVariableDeclaration.getTypeNode();
if (type != null && type.isTypeInferred()) {
if (type == null) {
// no type node -> type is inferred
ASTExpression expression = node.getFirstChildOfType(ASTExpression.class);
if (expression != null && expression.getTypeDefinition() != null) {
// see https://docs.oracle.com/javase/specs/jls/se10/html/jls-14.html#jls-14.14.2
// if the type is an array, then take the component type
// if the type is Iterable<X>, then take X as type
// if the type is Iterable, take Object as type
JavaTypeDefinition typeDefinition = expression.getTypeDefinition();
if (typeDefinition.isArrayType()) {
type.setTypeDefinition(typeDefinition.getComponentType());
} else if (typeDefinition.isGeneric() && typeDefinition.getGenericType(0) != null) {
type.setTypeDefinition(typeDefinition.getGenericType(0));
JavaTypeDefinition typeDefinitionIterable = expression.getTypeDefinition();
JavaTypeDefinition typeDefinition = null;
if (typeDefinitionIterable.isArrayType()) {
typeDefinition = typeDefinitionIterable.getComponentType();
} else if (typeDefinitionIterable.isGeneric() && typeDefinitionIterable.getGenericType(0) != null) {
typeDefinition = typeDefinitionIterable.getGenericType(0);
} else {
type.setTypeDefinition(JavaTypeDefinition.forClass(Object.class));
typeDefinition = JavaTypeDefinition.forClass(Object.class);
}
populateVariableDeclaratorFromType(localVariableDeclaration, typeDefinition);
}
populateVariableDeclaratorFromType(localVariableDeclaration);
}
}
return data;
@ -693,9 +692,9 @@ public class ClassTypeResolver extends JavaParserVisitorAdapter {
super.visit(node, data);
// resolve "var" types: the type of the initializer expression
ASTType type = node.getTypeNode();
if (type != null && type.isTypeInferred()) {
if (type == null) {
// no type node -> type is inferred
ASTExpression initializer = node.getFirstChildOfType(ASTExpression.class);
type.setTypeDefinition(initializer.getTypeDefinition());
if (node.getVariableDeclaratorId() != null) {
node.getVariableDeclaratorId().setTypeDefinition(initializer.getTypeDefinition());

View File

@ -5,11 +5,9 @@
package net.sourceforge.pmd.lang.java.ast;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import java.io.FileInputStream;
import java.io.IOException;
@ -35,12 +33,6 @@ public class Java10Test {
}
}
private static void assertVarType(ASTType type) {
assertEquals("var", type.getImage());
assertEquals(0, type.jjtGetNumChildren());
assertTrue(type.isTypeInferred());
}
@Test
public void testLocalVarInferenceBeforeJava10() {
// note, it can be parsed, but we'll have a ReferenceType of "var"
@ -63,7 +55,6 @@ public class Java10Test {
// in that case, we don't have a class named "var", so the type will be null
assertNull(classType.getType());
assertNull(type.getType());
assertFalse(type.isTypeInferred());
// check the type of the variable initializer's expression
ASTExpression initExpression = localVars.get(0)
@ -81,27 +72,25 @@ public class Java10Test {
assertEquals(3, localVars.size());
// first: var list = new ArrayList<String>();
ASTType type = localVars.get(0).getTypeNode();
assertVarType(type);
assertSame("type should be ArrayList", ArrayList.class, type.getType());
assertEquals("type should be ArrayList<String>", JavaTypeDefinition.forClass(ArrayList.class, JavaTypeDefinition.forClass(String.class)),
type.getTypeDefinition());
assertNull(localVars.get(0).getTypeNode());
ASTVariableDeclarator varDecl = localVars.get(0).getFirstChildOfType(ASTVariableDeclarator.class);
assertEquals("type should be equal", type.getTypeDefinition(), varDecl.getTypeDefinition());
assertSame("type should be ArrayList", ArrayList.class, varDecl.getType());
assertEquals("type should be ArrayList<String>", JavaTypeDefinition.forClass(ArrayList.class, JavaTypeDefinition.forClass(String.class)),
varDecl.getTypeDefinition());
ASTVariableDeclaratorId varId = varDecl.getFirstChildOfType(ASTVariableDeclaratorId.class);
assertEquals("type should be equal", type.getTypeDefinition(), varId.getTypeDefinition());
assertEquals("type should be equal", varDecl.getTypeDefinition(), varId.getTypeDefinition());
// second: var stream = list.stream();
ASTType type2 = localVars.get(1).getTypeNode();
assertVarType(type2);
assertNull(localVars.get(1).getTypeNode());
//ASTVariableDeclarator varDecl2 = localVars.get(1).getFirstChildOfType(ASTVariableDeclarator.class);
// TODO: return type of method call is unknown
//assertEquals("type should be Stream<String>", JavaTypeDefinition.forClass(Stream.class, JavaTypeDefinition.forClass(String.class)),
// type2.getTypeDefinition());
// assertEquals("type should be Stream<String>", JavaTypeDefinition.forClass(Stream.class, JavaTypeDefinition.forClass(String.class)),
// varDecl2.getTypeDefinition());
// third: var s = "Java 10";
ASTType type3 = localVars.get(2).getTypeNode();
assertVarType(type3);
assertEquals("type should be String", JavaTypeDefinition.forClass(String.class), type3.getTypeDefinition());
assertNull(localVars.get(2).getTypeNode());
ASTVariableDeclarator varDecl3 = localVars.get(2).getFirstChildOfType(ASTVariableDeclarator.class);
assertEquals("type should be String", JavaTypeDefinition.forClass(String.class), varDecl3.getTypeDefinition());
ASTArgumentList argumentList = compilationUnit.getFirstDescendantOfType(ASTArgumentList.class);
ASTExpression expression3 = argumentList.getFirstChildOfType(ASTExpression.class);
@ -115,9 +104,9 @@ public class Java10Test {
List<ASTLocalVariableDeclaration> localVars = compilationUnit.findDescendantsOfType(ASTLocalVariableDeclaration.class);
assertEquals(1, localVars.size());
ASTType type = localVars.get(0).getTypeNode();
assertVarType(type);
assertSame("type should be int", Integer.TYPE, type.getType());
assertNull(localVars.get(0).getTypeNode());
ASTVariableDeclarator varDecl = localVars.get(0).getFirstChildOfType(ASTVariableDeclarator.class);
assertSame("type should be int", Integer.TYPE, varDecl.getType());
}
@Test
@ -127,9 +116,9 @@ public class Java10Test {
List<ASTLocalVariableDeclaration> localVars = compilationUnit.findDescendantsOfType(ASTLocalVariableDeclaration.class);
assertEquals(1, localVars.size());
ASTType type = localVars.get(0).getTypeNode();
assertVarType(type);
assertSame("type should be String", String.class, type.getType());
assertNull(localVars.get(0).getTypeNode());
ASTVariableDeclarator varDecl = localVars.get(0).getFirstChildOfType(ASTVariableDeclarator.class);
assertSame("type should be String", String.class, varDecl.getType());
}
@Test
@ -139,17 +128,15 @@ public class Java10Test {
List<ASTLocalVariableDeclaration> localVars = compilationUnit.findDescendantsOfType(ASTLocalVariableDeclaration.class);
assertEquals(4, localVars.size());
ASTType type2 = localVars.get(1).getTypeNode();
assertVarType(type2);
assertSame("type should be String", String.class, type2.getType());
assertNull(localVars.get(1).getTypeNode());
ASTVariableDeclarator varDecl2 = localVars.get(1).getFirstChildOfType(ASTVariableDeclarator.class);
assertSame("type should be String", String.class, varDecl2.getType());
ASTVariableDeclaratorId varId2 = varDecl2.getFirstChildOfType(ASTVariableDeclaratorId.class);
assertSame("type should be String", String.class, varId2.getType());
ASTType type4 = localVars.get(3).getTypeNode();
assertVarType(type4);
assertSame("type should be int", Integer.TYPE, type4.getType());
assertNull(localVars.get(3).getTypeNode());
ASTVariableDeclarator varDecl4 = localVars.get(3).getFirstChildOfType(ASTVariableDeclarator.class);
assertSame("type should be int", Integer.TYPE, varDecl4.getType());
}
@Test
@ -159,9 +146,7 @@ public class Java10Test {
List<ASTResource> resources = compilationUnit.findDescendantsOfType(ASTResource.class);
assertEquals(1, resources.size());
ASTType type = resources.get(0).getTypeNode();
assertVarType(type);
assertSame("type should be FileInputStream", FileInputStream.class, type.getType());
assertNull(resources.get(0).getTypeNode());
ASTVariableDeclaratorId varId = resources.get(0).getVariableDeclaratorId();
assertSame("type should be FileInputStream", FileInputStream.class, varId.getType());
}