Java, typeres: add basic method type inference

This commit is contained in:
Bendegúz Nagy
2017-08-15 23:37:08 +02:00
parent 1426b23ce5
commit bc20be1ed8
7 changed files with 164 additions and 69 deletions

View File

@ -22,25 +22,25 @@ public class ASTExpression extends AbstractJavaTypeNode {
}
public boolean isStandAlonePrimitive() {
if(jjtGetNumChildren() != 1) {
if (jjtGetNumChildren() != 1) {
return false;
}
ASTPrimaryExpression primaryExpression = getFirstChildOfType(ASTPrimaryExpression.class);
if(primaryExpression == null || primaryExpression.jjtGetNumChildren() != 1) {
if (primaryExpression == null || primaryExpression.jjtGetNumChildren() != 1) {
return false;
}
ASTPrimaryPrefix primaryPrefix = primaryExpression.getFirstChildOfType(ASTPrimaryPrefix.class);
if(primaryPrefix == null || primaryPrefix.jjtGetNumChildren() != 1) {
if (primaryPrefix == null || primaryPrefix.jjtGetNumChildren() != 1) {
return false;
}
ASTLiteral literal = primaryPrefix.getFirstChildOfType(ASTLiteral.class);
if(literal == null || literal.jjtGetNumChildren() != 0) {
if (literal == null || literal.jjtGetNumChildren() != 0) {
return false;
}

View File

@ -362,7 +362,7 @@ public class ClassTypeResolver extends JavaParserVisitorAdapter {
// First try: com.package.SomeClass.staticField.otherField
// Second try: com.package.SomeClass.staticField
// Third try: com.package.SomeClass <- found a class!
for (String reducedImage = node.getImage(); ; ) {
for (String reducedImage = node.getImage();;) {
populateType(node, reducedImage);
if (node.getType() != null) {
break; // we found a class!
@ -422,7 +422,13 @@ public class ClassTypeResolver extends JavaParserVisitorAdapter {
Collections.<JavaTypeDefinition>emptyList(),
methodArgsArity, accessingClass);
previousType = getBestMethodReturnType(node.getTypeDefinition(), methods, astArgumentList);
TypeNode enclosingType = getEnclosingTypeDeclaration(node);
if (enclosingType == null) {
return data; // we can't proceed, probably uncompiled sources
}
previousType = getBestMethodReturnType(enclosingType.getTypeDefinition(),
methods, astArgumentList);
} else { // field
previousType = getTypeDefinitionOfVariableFromScope(node.getScope(), dotSplitImage[0],
accessingClass);

View File

@ -4,6 +4,9 @@
package net.sourceforge.pmd.lang.java.typeresolution;
import static net.sourceforge.pmd.lang.java.typeresolution.typeinference.InferenceRuleType.LOOSE_INVOCATION;
import static net.sourceforge.pmd.lang.java.typeresolution.typeinference.InferenceRuleType.SUBTYPE;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
@ -12,7 +15,6 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import com.sun.xml.internal.bind.v2.runtime.reflect.opt.Const;
import net.sourceforge.pmd.lang.ast.Node;
import net.sourceforge.pmd.lang.java.ast.ASTArgumentList;
import net.sourceforge.pmd.lang.java.ast.ASTExpression;
@ -22,11 +24,10 @@ import net.sourceforge.pmd.lang.java.ast.TypeNode;
import net.sourceforge.pmd.lang.java.typeresolution.typedefinition.JavaTypeDefinition;
import net.sourceforge.pmd.lang.java.typeresolution.typeinference.Bound;
import net.sourceforge.pmd.lang.java.typeresolution.typeinference.Constraint;
import net.sourceforge.pmd.lang.java.typeresolution.typeinference.TypeInferenceResolver;
import net.sourceforge.pmd.lang.java.typeresolution.typeinference.TypeInferenceResolver.ResolutionFailedException;
import net.sourceforge.pmd.lang.java.typeresolution.typeinference.Variable;
import static net.sourceforge.pmd.lang.java.typeresolution.typeinference.InferenceRuleType.LOOSE_INVOCATION;
import static net.sourceforge.pmd.lang.java.typeresolution.typeinference.InferenceRuleType.SUBTYPE;
public final class MethodTypeResolution {
private MethodTypeResolution() {}
@ -134,7 +135,7 @@ public final class MethodTypeResolution {
}
}
methodType = parameterizeStrictInvocation(context, methodType.getMethod(), argList);
methodType = parameterizeInvocation(context, methodType.getMethod(), argList);
}
// check subtypeability of each argument to the corresponding parameter
@ -162,17 +163,18 @@ public final class MethodTypeResolution {
}
public static MethodType parameterizeStrictInvocation(JavaTypeDefinition context, Method method,
ASTArgumentList argList) {
public static MethodType parameterizeInvocation(JavaTypeDefinition context, Method method,
ASTArgumentList argList) {
// variables are set up by the call to produceInitialBounds
List<Variable> variables = new ArrayList<>();
List<Bound> initialBounds = new ArrayList<>();
produceInitialBounds(method, context, variables, initialBounds);
List<Constraint> initialConstraints = produceInitialConstraints(method, argList, variables);
List<JavaTypeDefinition> resolvedTypeParameters = TypeInferenceResolver
.inferTypes(produceInitialConstraints(method, argList, variables), initialBounds, variables);
return null;
return getTypeDefOfMethod(context, method, resolvedTypeParameters);
}
public static List<Constraint> produceInitialConstraints(Method method, ASTArgumentList argList,
@ -184,14 +186,16 @@ public final class MethodTypeResolution {
// TODO: add support for variable arity methods
for (int i = 0; i < methodParameters.length; i++) {
int typeParamIndex;
if (methodParameters[i] instanceof TypeVariable
&& (typeParamIndex = JavaTypeDefinition
.getGenericTypeIndex(methodTypeParameters, ((TypeVariable) methodParameters[i]).getName())) != -1) {
int typeParamIndex = -1;
if (methodParameters[i] instanceof TypeVariable) {
typeParamIndex = JavaTypeDefinition
.getGenericTypeIndex(methodTypeParameters, ((TypeVariable) methodParameters[i]).getName());
}
if (typeParamIndex != -1) {
// TODO: we are cheating here, it should be a contraint of the form 'var -> expression' not 'var->type'
result.add(new Constraint(variables.get(typeParamIndex),
((TypeNode) argList.jjtGetChild(i)).getTypeDefinition(), LOOSE_INVOCATION));
result.add(new Constraint(((TypeNode) argList.jjtGetChild(i)).getTypeDefinition(),
variables.get(typeParamIndex), LOOSE_INVOCATION));
}
}
@ -222,10 +226,13 @@ public final class MethodTypeResolution {
// appears in the set; if this results in no proper upper bounds for αl (only dependencies), then the
// bound α <: Object also appears in the set.
int boundVarIndex;
if (bound instanceof TypeVariable
&& (boundVarIndex = JavaTypeDefinition
.getGenericTypeIndex(typeVariables, ((TypeVariable) bound).getName())) != -1) {
int boundVarIndex = -1;
if (bound instanceof TypeVariable) {
boundVarIndex =
JavaTypeDefinition.getGenericTypeIndex(typeVariables, ((TypeVariable) bound).getName());
}
if (boundVarIndex != -1) {
initialBounds.add(new Bound(variables.get(currVarIndex), variables.get(boundVarIndex), SUBTYPE));
} else {
currVarHasNoProperUpperBound = false;
@ -436,13 +443,6 @@ public final class MethodTypeResolution {
// search the class
for (Method method : contextClass.getDeclaredMethods()) {
if (isMethodApplicable(method, methodName, argArity, accessingClass, typeArguments)) {
if (isGeneric(method) && typeArguments.size() == 0) {
// TODO: do generic implicit methods
// this disables invocations which could match generic methods and have no explicit type args
result.clear();
return result;
}
result.add(getTypeDefOfMethod(context, method, typeArguments));
}
}
@ -496,7 +496,7 @@ public final class MethodTypeResolution {
// if the method isn't vararg, then arity matches
&& (method.isVarArgs() || (argArity == getArity(method)))
// isn't generic or arity of type arguments matches that of parameters
&& (!isGeneric(method) || typeArguments == null
&& (!isGeneric(method) || typeArguments.isEmpty()
|| method.getTypeParameters().length == typeArguments.size())) {
return true;

View File

@ -23,6 +23,7 @@ import net.sourceforge.pmd.lang.java.typeresolution.typedefinition.JavaTypeDefin
public final class TypeInferenceResolver {
public static class ResolutionFailedException extends RuntimeException {
}
@ -31,6 +32,38 @@ public final class TypeInferenceResolver {
}
public static List<JavaTypeDefinition> inferTypes(List<Constraint> constraints, List<Bound> bounds,
List<Variable> variables) {
List<Bound> newBounds = new ArrayList<>();
while (!constraints.isEmpty()) {
List<BoundOrConstraint> reduceResult = constraints.get(constraints.size() - 1).reduce();
constraints.remove(constraints.size() - 1);
for (BoundOrConstraint boundOrConstraint : reduceResult) {
if (boundOrConstraint instanceof Bound) {
newBounds.add((Bound) boundOrConstraint);
} else if (boundOrConstraint instanceof Constraint) {
constraints.add((Constraint) boundOrConstraint);
} else {
throw new IllegalStateException("Unknown BoundOrConstraint subclass");
}
}
constraints.addAll(incorporateBounds(bounds, newBounds));
newBounds.clear();
}
Map<Variable, JavaTypeDefinition> resolutionResult = resolveVariables(bounds);
List<JavaTypeDefinition> result = new ArrayList<>();
for (Variable variable : variables) {
result.add(resolutionResult.get(variable));
}
return result;
}
public static JavaTypeDefinition lub(List<JavaTypeDefinition> types) {
for (JavaTypeDefinition type : types) {
if (type.isArrayType()) {
@ -510,6 +543,8 @@ public final class TypeInferenceResolver {
}
}
currentBounds.addAll(newBounds);
return newConstraints;
}

View File

@ -23,15 +23,7 @@ import java.util.List;
import java.util.Set;
import java.util.StringTokenizer;
import net.sourceforge.pmd.lang.java.ast.ASTArgumentList;
import net.sourceforge.pmd.lang.java.ast.AbstractJavaNode;
import net.sourceforge.pmd.lang.java.typeresolution.MethodTypeResolution;
import net.sourceforge.pmd.lang.java.typeresolution.typeinference.Bound;
import net.sourceforge.pmd.lang.java.typeresolution.typeinference.Constraint;
import net.sourceforge.pmd.lang.java.typeresolution.typeinference.Variable;
import net.sourceforge.pmd.typeresolution.testdata.GenericMethods;
import net.sourceforge.pmd.typeresolution.testdata.dummytypes.SuperClassAOther;
import net.sourceforge.pmd.typeresolution.testdata.dummytypes.SuperClassAOther2;
import org.apache.commons.io.IOUtils;
import org.jaxen.JaxenException;
@ -43,6 +35,7 @@ import net.sourceforge.pmd.lang.LanguageVersionHandler;
import net.sourceforge.pmd.lang.ast.Node;
import net.sourceforge.pmd.lang.java.JavaLanguageModule;
import net.sourceforge.pmd.lang.java.ast.ASTAllocationExpression;
import net.sourceforge.pmd.lang.java.ast.ASTArgumentList;
import net.sourceforge.pmd.lang.java.ast.ASTBooleanLiteral;
import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration;
import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceType;
@ -62,11 +55,17 @@ import net.sourceforge.pmd.lang.java.ast.ASTType;
import net.sourceforge.pmd.lang.java.ast.ASTTypeDeclaration;
import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclarator;
import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId;
import net.sourceforge.pmd.lang.java.ast.AbstractJavaNode;
import net.sourceforge.pmd.lang.java.ast.AbstractJavaTypeNode;
import net.sourceforge.pmd.lang.java.ast.TypeNode;
import net.sourceforge.pmd.lang.java.symboltable.VariableNameDeclaration;
import net.sourceforge.pmd.lang.java.typeresolution.ClassTypeResolver;
import net.sourceforge.pmd.lang.java.typeresolution.MethodType;
import net.sourceforge.pmd.lang.java.typeresolution.MethodTypeResolution;
import net.sourceforge.pmd.lang.java.typeresolution.typedefinition.JavaTypeDefinition;
import net.sourceforge.pmd.lang.java.typeresolution.typeinference.Bound;
import net.sourceforge.pmd.lang.java.typeresolution.typeinference.Constraint;
import net.sourceforge.pmd.lang.java.typeresolution.typeinference.Variable;
import net.sourceforge.pmd.typeresolution.testdata.AnonymousClassFromInterface;
import net.sourceforge.pmd.typeresolution.testdata.AnonymousInnerClass;
import net.sourceforge.pmd.typeresolution.testdata.AnoymousExtendingObject;
@ -85,6 +84,7 @@ import net.sourceforge.pmd.typeresolution.testdata.FieldAccessPrimaryGenericSimp
import net.sourceforge.pmd.typeresolution.testdata.FieldAccessShadow;
import net.sourceforge.pmd.typeresolution.testdata.FieldAccessStatic;
import net.sourceforge.pmd.typeresolution.testdata.FieldAccessSuper;
import net.sourceforge.pmd.typeresolution.testdata.GenericMethodsImplicit;
import net.sourceforge.pmd.typeresolution.testdata.InnerClass;
import net.sourceforge.pmd.typeresolution.testdata.Literals;
import net.sourceforge.pmd.typeresolution.testdata.MethodAccessibility;
@ -106,6 +106,8 @@ import net.sourceforge.pmd.typeresolution.testdata.dummytypes.JavaTypeDefinition
import net.sourceforge.pmd.typeresolution.testdata.dummytypes.StaticMembers;
import net.sourceforge.pmd.typeresolution.testdata.dummytypes.SuperClassA;
import net.sourceforge.pmd.typeresolution.testdata.dummytypes.SuperClassA2;
import net.sourceforge.pmd.typeresolution.testdata.dummytypes.SuperClassAOther;
import net.sourceforge.pmd.typeresolution.testdata.dummytypes.SuperClassAOther2;
import net.sourceforge.pmd.typeresolution.testdata.dummytypes.SuperClassB;
import net.sourceforge.pmd.typeresolution.testdata.dummytypes.SuperClassB2;
@ -1489,6 +1491,26 @@ public class ClassTypeResolverTest {
assertEquals("All expressions not tested", index, expressions.size());
}
@Test
public void testMethodTypeInference() throws JaxenException {
ASTCompilationUnit acu = parseAndTypeResolveForClass15(GenericMethodsImplicit.class);
List<AbstractJavaTypeNode> expressions = convertList(
acu.findChildNodesWithXPath("//VariableInitializer/Expression/PrimaryExpression"),
AbstractJavaTypeNode.class);
int index = 0;
// SuperClassA2 a = bar((SuperClassA) null, (SuperClassAOther) null, null, (SuperClassAOther2) null);
assertEquals(SuperClassA2.class, expressions.get(index).getType());
assertEquals(SuperClassA2.class, getChildType(expressions.get(index), 0));
assertEquals(SuperClassA2.class, getChildType(expressions.get(index++), 1));
// Make sure we got them all
assertEquals("All expressions not tested", index, expressions.size());
}
@Test
public void testJavaTypeDefinitionEquals() {
JavaTypeDefinition a = JavaTypeDefinition.forClass(Integer.class);
@ -1545,11 +1567,11 @@ public class ClassTypeResolverTest {
@Test
public void testMethodInitialBounds() throws NoSuchMethodException {
JavaTypeDefinition context = JavaTypeDefinition.forClass(GenericMethods.class,
JavaTypeDefinition context = JavaTypeDefinition.forClass(GenericMethodsImplicit.class,
JavaTypeDefinition.forClass(Thread.class));
List<Variable> variables = new ArrayList<>();
List<Bound> initialBounds = new ArrayList<>();
Method method = GenericMethods.class.getMethod("foo");
Method method = GenericMethodsImplicit.class.getMethod("foo");
MethodTypeResolution.produceInitialBounds(method, context, variables, initialBounds);
assertEquals(initialBounds.size(), 6);
@ -1572,38 +1594,61 @@ public class ClassTypeResolverTest {
@Test
public void testMethodInitialConstraints() throws NoSuchMethodException, JaxenException {
ASTCompilationUnit acu = parseAndTypeResolveForClass15(GenericMethods.class);
ASTCompilationUnit acu = parseAndTypeResolveForClass15(GenericMethodsImplicit.class);
List<AbstractJavaNode> expressions = convertList(
acu.findChildNodesWithXPath("//ArgumentList"),
AbstractJavaNode.class);
assertEquals(expressions.size(), 1);
List<Variable> variables = new ArrayList<>();
for (int i = 0; i < 2; ++i) {
variables.add(new Variable());
}
Method method = GenericMethods.class.getMethod("bar", Object.class, Object.class,
Integer.class, Object.class);
Method method = GenericMethodsImplicit.class.getMethod("bar", Object.class, Object.class,
Integer.class, Object.class);
ASTArgumentList argList = (ASTArgumentList) expressions.get(0);
List<Constraint> constraints = MethodTypeResolution.produceInitialConstraints(method, argList, variables);
assertEquals(constraints.size(), 3);
// A
assertTrue(constraints.contains(new Constraint(variables.get(0),
JavaTypeDefinition.forClass(SuperClassA.class),
LOOSE_INVOCATION)));
assertTrue(constraints.contains(new Constraint(variables.get(0),
JavaTypeDefinition.forClass(SuperClassAOther.class),
assertTrue(constraints.contains(new Constraint(JavaTypeDefinition.forClass(SuperClassA.class),
variables.get(0), LOOSE_INVOCATION)));
assertTrue(constraints.contains(new Constraint(JavaTypeDefinition.forClass(SuperClassAOther.class),
variables.get(0),
LOOSE_INVOCATION)));
// B
assertTrue(constraints.contains(new Constraint(variables.get(1),
JavaTypeDefinition.forClass(SuperClassAOther2.class),
assertTrue(constraints.contains(new Constraint(JavaTypeDefinition.forClass(SuperClassAOther2.class),
variables.get(1),
LOOSE_INVOCATION)));
}
@Test
public void testMethodParameterization() throws JaxenException, NoSuchMethodException {
ASTCompilationUnit acu = parseAndTypeResolveForClass15(GenericMethodsImplicit.class);
List<AbstractJavaNode> expressions = convertList(
acu.findChildNodesWithXPath("//ArgumentList"),
AbstractJavaNode.class);
JavaTypeDefinition context = JavaTypeDefinition.forClass(GenericMethodsImplicit.class,
JavaTypeDefinition.forClass(Thread.class));
Method method = GenericMethodsImplicit.class.getMethod("bar", Object.class, Object.class,
Integer.class, Object.class);
ASTArgumentList argList = (ASTArgumentList) expressions.get(0);
MethodType inferedMethod = MethodTypeResolution.parameterizeInvocation(context, method, argList);
assertEquals(inferedMethod.getParameterTypes().get(0),
JavaTypeDefinition.forClass(SuperClassA2.class));
assertEquals(inferedMethod.getParameterTypes().get(1),
JavaTypeDefinition.forClass(SuperClassA2.class));
assertEquals(inferedMethod.getParameterTypes().get(2),
JavaTypeDefinition.forClass(Integer.class));
assertEquals(inferedMethod.getParameterTypes().get(3),
JavaTypeDefinition.forClass(SuperClassAOther2.class));
}
private Class<?> getChildType(Node node, int childIndex) {
return ((TypeNode) node.jjtGetChild(childIndex)).getType();

View File

@ -1,14 +0,0 @@
package net.sourceforge.pmd.typeresolution.testdata;
import net.sourceforge.pmd.typeresolution.testdata.dummytypes.SuperClassA;
import net.sourceforge.pmd.typeresolution.testdata.dummytypes.SuperClassAOther;
import net.sourceforge.pmd.typeresolution.testdata.dummytypes.SuperClassAOther2;
public class GenericMethods<T> {
public <A, B extends Number & Runnable, C extends D, D extends T> void foo() {
}
public <A, B> void bar(A a, A b, Integer c, B d) {
bar((SuperClassA) null, (SuperClassAOther) null, null, (SuperClassAOther2) null);
}
}

View File

@ -0,0 +1,23 @@
/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.typeresolution.testdata;
import net.sourceforge.pmd.typeresolution.testdata.dummytypes.SuperClassA;
import net.sourceforge.pmd.typeresolution.testdata.dummytypes.SuperClassA2;
import net.sourceforge.pmd.typeresolution.testdata.dummytypes.SuperClassAOther;
import net.sourceforge.pmd.typeresolution.testdata.dummytypes.SuperClassAOther2;
public class GenericMethodsImplicit<T> {
public <A, B extends Number & Runnable, C extends D, D extends T> void foo() {
}
public <A, B> A bar(A a, A b, Integer c, B d) {
return null;
}
void test() {
SuperClassA2 a = bar((SuperClassA) null, (SuperClassAOther) null, (Integer) null, (SuperClassAOther2) null);
}
}