1
0
forked from phoedos/pmd

Java, typeres: basic type variable resolution in place

This commit is contained in:
Bendegúz Nagy
2017-08-11 13:41:30 +02:00
parent 7e49521762
commit f51c75fa73
9 changed files with 131 additions and 45 deletions
pmd-java/src
main/java/net/sourceforge/pmd/lang/java/typeresolution
test/java/net/sourceforge/pmd/typeresolution

@ -500,7 +500,21 @@ public final class MethodTypeResolution {
// this covers arrays, simple class/interface cases
if (parameter.getType().isAssignableFrom(argument.getType())) {
return true;
if (!parameter.isGeneric() || parameter.isRawType() || argument.isRawType()) {
return true;
}
// parameter is a non-raw generic type
// argument is a non-generic or a non-raw generic type
// example result: List<String>.getAsSuper(Collection) becomes Collection<String>
JavaTypeDefinition argSuper = argument.getAsSuper(parameter.getType());
// argSuper can't be null because isAssignableFrom check above returned true
// right now we only check if generic arguments are the same
// TODO: add support for wildcard types
// (future note: can't call subtype as it is recursively, infinite types)
return parameter.equals(argSuper);
}
int indexOfParameter = PRIMITIVE_SUBTYPE_ORDER.indexOf(parameter.getType());
@ -532,12 +546,12 @@ public final class MethodTypeResolution {
public static List<JavaTypeDefinition> getMethodExplicitTypeArugments(Node node) {
ASTMemberSelector memberSelector = node.getFirstChildOfType(ASTMemberSelector.class);
if (memberSelector == null) {
return Collections.emptyList(); // empty list
return Collections.emptyList();
}
ASTTypeArguments typeArguments = memberSelector.getFirstChildOfType(ASTTypeArguments.class);
if (typeArguments == null) {
return Collections.emptyList(); // empty list
return Collections.emptyList();
}
List<JavaTypeDefinition> result = new ArrayList<>();

@ -10,7 +10,6 @@ import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@ -329,7 +328,7 @@ public class JavaTypeDefinition implements TypeDefinition {
destinationSet.add(clazz);
getErasedSuperTypeSet(clazz.getSuperclass(), destinationSet);
for(Class<?> interfaceType : clazz.getInterfaces()) {
for (Class<?> interfaceType : clazz.getInterfaces()) {
getErasedSuperTypeSet(interfaceType, destinationSet);
}
}
@ -345,8 +344,12 @@ public class JavaTypeDefinition implements TypeDefinition {
}
public JavaTypeDefinition getAsSuper(Class<?> superClazz) {
for(JavaTypeDefinition superTypeDef : getSuperTypeSet()) {
if(superTypeDef.clazz == superClazz) {
if (clazz == superClazz) { // optimize for same class calls
return this;
}
for (JavaTypeDefinition superTypeDef : getSuperTypeSet()) {
if (superTypeDef.clazz == superClazz) {
return superTypeDef;
}
}

@ -137,11 +137,11 @@ public abstract class BoundOrConstraint {
public abstract List<BoundOrConstraint> reduce();
public void addVariablesToSet(Set<Variable> variables) {
if(leftTypeVariable != null) {
if (leftTypeVariable != null) {
variables.add(leftTypeVariable);
}
if(rightTypeVariable != null) {
if (rightTypeVariable != null) {
variables.add(rightTypeVariable);
}
}

@ -4,9 +4,6 @@
package net.sourceforge.pmd.lang.java.typeresolution.typeinference;
import net.sourceforge.pmd.lang.java.typeresolution.MethodTypeResolution;
import net.sourceforge.pmd.lang.java.typeresolution.typedefinition.JavaTypeDefinition;
import static net.sourceforge.pmd.lang.java.typeresolution.typeinference.InferenceRuleType.EQUALITY;
import static net.sourceforge.pmd.lang.java.typeresolution.typeinference.InferenceRuleType.SUBTYPE;
@ -20,6 +17,9 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import net.sourceforge.pmd.lang.java.typeresolution.MethodTypeResolution;
import net.sourceforge.pmd.lang.java.typeresolution.typedefinition.JavaTypeDefinition;
public final class TypeInferenceResolver {
@ -39,11 +39,14 @@ public final class TypeInferenceResolver {
}
}
// step 1 - EC
Set<Class<?>> erasedCandidateSet = getErasedCandidateSet(types);
// step 2 - MEC
Set<Class<?>> minimalSet = getMinimalErasedCandidateSet(erasedCandidateSet);
List<JavaTypeDefinition> candidates = new ArrayList<>();
// for each element G n MEC
for (Class<?> erasedSupertype : minimalSet) {
JavaTypeDefinition lci = types.get(0).getAsSuper(erasedSupertype);
@ -65,17 +68,21 @@ public final class TypeInferenceResolver {
JavaTypeDefinition result = candidates.get(0);
for (JavaTypeDefinition candidate : candidates) {
if (containsType(candidate, result)) {
if (MethodTypeResolution.isSubtypeable(candidate, result)) {
result = candidate;
} else if (!containsType(result, candidate)) { // TODO: add support for compound types
} else if (!MethodTypeResolution.isSubtypeable(result, candidate)) {
// TODO: add support for compound types
throw new ResolutionFailed();
}
} // else: result contains candidate, nothing else to do
}
return result;
}
private static JavaTypeDefinition intersect(JavaTypeDefinition first, JavaTypeDefinition second) {
/**
* @return the intersection of the two types
*/
public static JavaTypeDefinition intersect(JavaTypeDefinition first, JavaTypeDefinition second) {
if (first.equals(second)) { // two types equal
return first;
} else if (first.getType() == second.getType()) {
@ -90,38 +97,30 @@ public final class TypeInferenceResolver {
}
/**
* @return true, if parameter contains argument
* Merge two types of the same class to something both can be assigned to and is most specific.
*/
public static boolean containsType(JavaTypeDefinition parameter, JavaTypeDefinition argument) {
if (!MethodTypeResolution.isSubtypeable(parameter, argument)) {
return false; // class can't be converted even with unchecked conversion
}
// TODO: wildcards, checking generic arguments properly
if (parameter.equals(argument)) {
// we don't care about List<String> is assigable to Collection<String> or Collection<? extends Object>
return true; // we don't yet care about wildcards like, List<String> is assignable to List<? extends Object>
} else {
return false;
}
}
public static JavaTypeDefinition merge(JavaTypeDefinition first, JavaTypeDefinition second) {
if (first.getType() != second.getType()) {
throw new IllegalStateException("Must be called with typedefinitions of the same class");
}
if (!first.isGeneric()) {
return first;
}
JavaTypeDefinition[] mergedGeneric = new JavaTypeDefinition[first.getTypeParameterCount()];
for (int i = 0; i < first.getTypeParameterCount(); ++i) {
if (containsType(first.getGenericType(i), second.getGenericType(i))) {
if (MethodTypeResolution.isSubtypeable(first.getGenericType(i), second.getGenericType(i))) {
mergedGeneric[i] = first.getGenericType(i);
} else if (containsType(second.getGenericType(i), first.getGenericType(i))) {
} else if (MethodTypeResolution.isSubtypeable(second.getGenericType(i), first.getGenericType(i))) {
mergedGeneric[i] = second.getGenericType(i);
} else {
return JavaTypeDefinition.forClass(Object.class);
// TODO: Generic types of the same class can be merged like so:
// List<Integer> List<Double> -> List<? extends Number> but we don't have wildcards yet
// List<Integer> List<Double> -> List<? extends Number>
// but we don't have wildcards yet
}
}
@ -175,15 +174,47 @@ public final class TypeInferenceResolver {
// Note: since the Combinations class enumerates the power set from least numerous to most numerous sets
// the above requirement is satisfied
List<Variable> variablesToResolve = null;
for (List<Variable> variableSet : new Combinations(uninstantiatedVariables)) {
if (isProperSubsetOfVariables(variableSet, instantiations, variableDependencies, bounds)) {
// TODO: resolve variables
variablesToResolve = variableSet;
break;
}
}
if (variablesToResolve == null) {
throw new ResolutionFailed();
}
// if there are least upper bounds
for (Variable var : variablesToResolve) {
List<JavaTypeDefinition> lowerBounds = getLowerBoundsOf(var, bounds);
// TODO: should call incorporation
instantiations.put(var, lub(lowerBounds));
}
uninstantiatedVariables.removeAll(variablesToResolve);
}
return instantiations;
}
public static List<JavaTypeDefinition> getLowerBoundsOf(Variable var, List<Bound> bounds) {
List<JavaTypeDefinition> result = new ArrayList<>();
for (Bound bound : bounds) {
if (bound.ruleType() == SUBTYPE && bound.rightVariable() == var) {
// TODO: add support for variables depending on other variables
if (bound.isLeftVariable()) {
throw new ResolutionFailed();
}
result.add(bound.leftProper());
}
}
return instantiations;
return result;
}
/**
@ -204,10 +235,11 @@ public final class TypeInferenceResolver {
Map<Variable, Set<Variable>> dependencies,
List<Bound> bounds) {
// search the bounds for an
for (Variable unresolvedVariable : variables) {
for (Variable dependency : dependencies.get(unresolvedVariable)) {
if (!instantiations.containsKey(dependency)
&& unresolvedVariable != dependency
&& !boundsHaveAnEqualityBetween(variables, dependency, bounds)) {
return false;
}
@ -247,7 +279,7 @@ public final class TypeInferenceResolver {
private List<Variable> resultList = new ArrayList<>();
private List<Variable> unmodifyableViewOfResult = Collections.unmodifiableList(resultList);
public Combinations(List<Variable> permuteThis) {
Combinations(List<Variable> permuteThis) {
this.permuteThis = permuteThis;
this.n = permuteThis.size();
this.k = 0;

@ -15,13 +15,11 @@ import java.io.InputStream;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
import java.util.StringTokenizer;
import net.sourceforge.pmd.typeresolution.testdata.dummytypes.JavaTypeDefinitionEquals;
import org.apache.commons.io.IOUtils;
import org.jaxen.JaxenException;
@ -92,6 +90,7 @@ import net.sourceforge.pmd.typeresolution.testdata.SuperExpression;
import net.sourceforge.pmd.typeresolution.testdata.ThisExpression;
import net.sourceforge.pmd.typeresolution.testdata.dummytypes.Converter;
import net.sourceforge.pmd.typeresolution.testdata.dummytypes.GenericClass;
import net.sourceforge.pmd.typeresolution.testdata.dummytypes.JavaTypeDefinitionEquals;
import net.sourceforge.pmd.typeresolution.testdata.dummytypes.StaticMembers;
import net.sourceforge.pmd.typeresolution.testdata.dummytypes.SuperClassA;
import net.sourceforge.pmd.typeresolution.testdata.dummytypes.SuperClassA2;

@ -17,7 +17,6 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.tools.ant.taskdefs.Java;
import org.junit.Test;
import net.sourceforge.pmd.lang.java.typeresolution.typedefinition.JavaTypeDefinition;
@ -28,6 +27,11 @@ import net.sourceforge.pmd.lang.java.typeresolution.typeinference.InferenceRuleT
import net.sourceforge.pmd.lang.java.typeresolution.typeinference.TypeInferenceResolver;
import net.sourceforge.pmd.lang.java.typeresolution.typeinference.Variable;
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 TypeInferenceTest {
private JavaTypeDefinition number = JavaTypeDefinition.forClass(Number.class);
private JavaTypeDefinition integer = JavaTypeDefinition.forClass(Integer.class);
@ -320,10 +324,21 @@ public class TypeInferenceTest {
@Test
public void testLeastUpperBound() {
List<JavaTypeDefinition> lowerBounds = new ArrayList<>();
lowerBounds.add(JavaTypeDefinition.forClass(String.class));
lowerBounds.add(JavaTypeDefinition.forClass(.class));
lowerBounds.add(JavaTypeDefinition.forClass(SuperClassA.class));
lowerBounds.add(JavaTypeDefinition.forClass(SuperClassAOther.class));
lowerBounds.add(JavaTypeDefinition.forClass(SuperClassAOther2.class));
JavaTypeDefinition result = TypeInferenceResolver.lub(lowerBounds);
assertEquals(TypeInferenceResolver.lub(lowerBounds), JavaTypeDefinition.forClass(SuperClassA2.class));
}
@Test
public void testResolution() {
List<Bound> bounds = new ArrayList<>();
bounds.add(new Bound(JavaTypeDefinition.forClass(SuperClassA.class), alpha, SUBTYPE));
bounds.add(new Bound(JavaTypeDefinition.forClass(SuperClassAOther.class), alpha, SUBTYPE));
Map<Variable, JavaTypeDefinition> result = TypeInferenceResolver.resolveVariables(bounds);
assertEquals(result.size(), 1);
assertEquals(result.get(alpha), JavaTypeDefinition.forClass(SuperClassA2.class));
}
private List<Constraint> incorporationResult(Bound firstBound, Bound secondBound) {

@ -1,3 +1,8 @@
/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.typeresolution.testdata.dummytypes;
import java.util.List;

@ -0,0 +1,9 @@
/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.typeresolution.testdata.dummytypes;
public class SuperClassAOther extends SuperClassA2 {
}

@ -0,0 +1,9 @@
/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.typeresolution.testdata.dummytypes;
public class SuperClassAOther2 extends SuperClassA2 {
}