Skip to content

Commit

Permalink
Java, typeres: basic type variable resolution in place
Browse files Browse the repository at this point in the history
  • Loading branch information
Bendegúz Nagy committed Aug 11, 2017
1 parent 7e49521 commit f51c75f
Show file tree
Hide file tree
Showing 9 changed files with 130 additions and 44 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -500,7 +500,21 @@ public static boolean isSubtypeable(JavaTypeDefinition parameter, JavaTypeDefini

// 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());
Expand Down Expand Up @@ -532,12 +546,12 @@ public static JavaTypeDefinition boxPrimitive(JavaTypeDefinition def) {
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<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
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;
Expand Down Expand Up @@ -329,7 +328,7 @@ private static Set<Class<?>> getErasedSuperTypeSet(Class<?> clazz, Set<Class<?>>
destinationSet.add(clazz);
getErasedSuperTypeSet(clazz.getSuperclass(), destinationSet);

for(Class<?> interfaceType : clazz.getInterfaces()) {
for (Class<?> interfaceType : clazz.getInterfaces()) {
getErasedSuperTypeSet(interfaceType, destinationSet);
}
}
Expand All @@ -345,8 +344,12 @@ public boolean isRawType() {
}

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;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,11 +137,11 @@ public InferenceRuleType ruleType() {
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);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -20,6 +17,9 @@
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 {

Expand All @@ -39,11 +39,14 @@ public static JavaTypeDefinition lub(List<JavaTypeDefinition> types) {
}
}

// 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);

Expand All @@ -65,17 +68,21 @@ public static JavaTypeDefinition lub(List<JavaTypeDefinition> types) {
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()) {
Expand All @@ -90,38 +97,30 @@ private static JavaTypeDefinition intersect(JavaTypeDefinition first, JavaTypeDe
}

/**
* @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
}
}

Expand Down Expand Up @@ -175,17 +174,49 @@ public static Map<Variable, JavaTypeDefinition> resolveVariables(List<Bound> bou

// 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 result;
}

/**
* Given a set of inference variables to resolve, let V be the union of this set and all variables upon which
* the resolution of at least one variable in this set depends.
Expand All @@ -204,10 +235,11 @@ public static boolean isProperSubsetOfVariables(List<Variable> variables,
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;
}
Expand Down Expand Up @@ -247,7 +279,7 @@ private static class Combinations implements Iterable<List<Variable>> {
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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,11 @@
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;

Expand Down Expand Up @@ -92,6 +90,7 @@
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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
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;
Expand All @@ -28,6 +27,11 @@
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);
Expand Down Expand Up @@ -320,10 +324,21 @@ public void testMinimalErasedCandidateSet() {
@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));

assertEquals(TypeInferenceResolver.lub(lowerBounds), JavaTypeDefinition.forClass(SuperClassA2.class));
}

JavaTypeDefinition result = TypeInferenceResolver.lub(lowerBounds);
@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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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 {
}
Original file line number Diff line number Diff line change
@@ -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 {
}

0 comments on commit f51c75f

Please sign in to comment.