Merge branch 'pr-550'

This commit is contained in:
Juan Martín Sotuyo Dodero
2017-08-15 00:30:17 -03:00
10 changed files with 747 additions and 12 deletions

View File

@ -484,6 +484,10 @@ public final class MethodTypeResolution {
return isSubtypeable(parameter, argument.getTypeDefinition());
}
public static boolean isSubtypeable(Class<?> parameter, Class<?> argument) {
return isSubtypeable(JavaTypeDefinition.forClass(parameter), JavaTypeDefinition.forClass(argument));
}
/**
* Subtypeability rules.
* https://docs.oracle.com/javase/specs/jls/se7/html/jls-4.html#jls-4.10
@ -496,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());
@ -528,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<>();

View File

@ -12,8 +12,10 @@ import java.lang.reflect.WildcardType;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class JavaTypeDefinition implements TypeDefinition {
// contains TypeDefs where only the clazz field is used
@ -21,11 +23,14 @@ public class JavaTypeDefinition implements TypeDefinition {
private final Class<?> clazz;
private final List<JavaTypeDefinition> genericArgs;
// cached because calling clazz.getTypeParameters().length create a new array every time
private final int typeParameterCount;
private final boolean isGeneric;
private final JavaTypeDefinition enclosingClass;
private JavaTypeDefinition(final Class<?> clazz) {
this.clazz = clazz;
this.typeParameterCount = clazz.getTypeParameters().length;
final TypeVariable<?>[] typeParameters;
// the anonymous class can't have generics, but we may be binding generics from super classes
@ -64,10 +69,7 @@ public class JavaTypeDefinition implements TypeDefinition {
final JavaTypeDefinition newDef = new JavaTypeDefinition(clazz);
// We can only cache types without generics, since their values are context-based
if (!newDef.isGeneric) {
CLASS_TYPE_DEF_CACHE.put(clazz, newDef);
}
CLASS_TYPE_DEF_CACHE.put(clazz, newDef);
return newDef;
}
@ -80,9 +82,7 @@ public class JavaTypeDefinition implements TypeDefinition {
// With generics there is no cache
final JavaTypeDefinition typeDef = new JavaTypeDefinition(clazz);
for (final JavaTypeDefinition generic : boundGenerics) {
typeDef.genericArgs.add(generic);
}
Collections.addAll(typeDef.genericArgs, boundGenerics);
return typeDef;
}
@ -237,7 +237,7 @@ public class JavaTypeDefinition implements TypeDefinition {
}
public int getTypeParameterCount() {
return clazz.getTypeParameters().length;
return typeParameterCount;
}
public boolean isArrayType() {
@ -252,4 +252,108 @@ public class JavaTypeDefinition implements TypeDefinition {
.append(']').toString();
}
@Override
public boolean equals(Object obj) {
if (obj == null || !(obj instanceof JavaTypeDefinition)) {
return false;
}
// raw vs raw
// we assume that this covers raw types, because they are cached
if (this == obj) {
return true;
}
JavaTypeDefinition otherTypeDef = (JavaTypeDefinition) obj;
if (clazz != otherTypeDef.clazz) {
return false;
}
// This should cover
// raw vs proper
// proper vs raw
// proper vs proper
// Note: we have to force raw types to compute their generic args, class Stuff<? extends List<Stuff>>
// Stuff a;
// Stuff<? extends List<Stuff>> b;
// Stuff<List<Stuff>> c;
// all of the above should be equal
for (int i = 0; i < getTypeParameterCount(); ++i) {
// Note: we assume that cycles can only exist because of raw types
if (!getGenericType(i).equals(otherTypeDef.getGenericType(i))) {
return false;
}
}
return true;
}
@Override
public int hashCode() {
return clazz.hashCode();
}
public Set<JavaTypeDefinition> getSuperTypeSet() {
return getSuperTypeSet(new HashSet<JavaTypeDefinition>());
}
private Set<JavaTypeDefinition> getSuperTypeSet(Set<JavaTypeDefinition> destinationSet) {
destinationSet.add(this);
if (this.clazz != Object.class) {
resolveTypeDefinition(clazz.getGenericSuperclass()).getSuperTypeSet(destinationSet);
for (Type type : clazz.getGenericInterfaces()) {
resolveTypeDefinition(type).getSuperTypeSet(destinationSet);
}
}
return destinationSet;
}
public Set<Class<?>> getErasedSuperTypeSet() {
Set<Class<?>> result = new HashSet<>();
result.add(Object.class);
return getErasedSuperTypeSet(this.clazz, result);
}
private static Set<Class<?>> getErasedSuperTypeSet(Class<?> clazz, Set<Class<?>> destinationSet) {
if (clazz != null) {
destinationSet.add(clazz);
getErasedSuperTypeSet(clazz.getSuperclass(), destinationSet);
for (Class<?> interfaceType : clazz.getInterfaces()) {
getErasedSuperTypeSet(interfaceType, destinationSet);
}
}
return destinationSet;
}
/**
* @return true if clazz is generic and had not been parameterized
*/
public boolean isRawType() {
return isGeneric() && CLASS_TYPE_DEF_CACHE.containsKey(getType());
}
public JavaTypeDefinition getAsSuper(Class<?> superClazz) {
if (clazz == superClazz) { // optimize for same class calls
return this;
}
for (JavaTypeDefinition superTypeDef : getSuperTypeSet()) {
if (superTypeDef.clazz == superClazz) {
return superTypeDef;
}
}
return null;
}
}

View File

@ -5,6 +5,7 @@
package net.sourceforge.pmd.lang.java.typeresolution.typeinference;
import java.util.List;
import java.util.Set;
import net.sourceforge.pmd.lang.java.typeresolution.typedefinition.JavaTypeDefinition;
@ -134,4 +135,36 @@ public abstract class BoundOrConstraint {
}
public abstract List<BoundOrConstraint> reduce();
public void addVariablesToSet(Set<Variable> variables) {
if (leftTypeVariable != null) {
variables.add(leftTypeVariable);
}
if (rightTypeVariable != null) {
variables.add(rightTypeVariable);
}
}
/**
* @return true, if the left-hand side mentions variables
*/
public boolean leftHasMentionedVariable() {
return leftTypeVariable != null;
}
/**
* @return true, if the right-hand side mentions variales
*/
public boolean rightHasMentionedVariable() {
return rightTypeVariable != null;
}
public Variable getLeftMentionedVariable() {
return leftTypeVariable;
}
public Variable getRightMentionedVariable() {
return rightTypeVariable;
}
}

View File

@ -4,7 +4,9 @@
package net.sourceforge.pmd.typeresolution;
import static junit.framework.TestCase.assertTrue;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
@ -12,8 +14,10 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
import java.util.StringTokenizer;
import org.apache.commons.io.IOUtils;
@ -86,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;
@ -1472,6 +1477,61 @@ public class ClassTypeResolverTest {
assertEquals("All expressions not tested", index, expressions.size());
}
@Test
public void testJavaTypeDefinitionEquals() {
JavaTypeDefinition a = JavaTypeDefinition.forClass(Integer.class);
JavaTypeDefinition b = JavaTypeDefinition.forClass(Integer.class);
// test non-generic types
assertEquals(a, b);
assertNotEquals(a, null);
// test generic arg equality
b = JavaTypeDefinition.forClass(List.class, a);
a = JavaTypeDefinition.forClass(List.class, a);
assertEquals(a, b);
a = JavaTypeDefinition.forClass(List.class, JavaTypeDefinition.forClass(String.class));
assertNotEquals(a, b);
assertNotEquals(b, a);
// test raw vs proper, proper vs raw
a = JavaTypeDefinition.forClass(JavaTypeDefinitionEquals.class);
b = JavaTypeDefinition.forClass(JavaTypeDefinitionEquals.class,
JavaTypeDefinition.forClass(List.class, a));
assertEquals(a, b);
assertEquals(b, a);
}
@Test
public void testJavaTypeDefinitionGetSuperTypeSet() {
JavaTypeDefinition originalTypeDef = JavaTypeDefinition.forClass(List.class,
JavaTypeDefinition.forClass(Integer.class));
Set<JavaTypeDefinition> set = originalTypeDef.getSuperTypeSet();
assertEquals(set.size(), 4);
assertTrue(set.contains(JavaTypeDefinition.forClass(Object.class)));
assertTrue(set.contains(originalTypeDef));
assertTrue(set.contains(JavaTypeDefinition.forClass(Collection.class,
JavaTypeDefinition.forClass(Integer.class))));
assertTrue(set.contains(JavaTypeDefinition.forClass(Iterable.class,
JavaTypeDefinition.forClass(Integer.class))));
}
@Test
public void testJavaTypeDefinitionGetErasedSuperTypeSet() {
JavaTypeDefinition originalTypeDef = JavaTypeDefinition.forClass(List.class,
JavaTypeDefinition.forClass(Integer.class));
Set<Class<?>> set = originalTypeDef.getErasedSuperTypeSet();
assertEquals(set.size(), 4);
assertTrue(set.contains(Object.class));
assertTrue(set.contains(Collection.class));
assertTrue(set.contains(Iterable.class));
assertTrue(set.contains(List.class));
}
private Class<?> getChildType(Node node, int childIndex) {
return ((TypeNode) node.jjtGetChild(childIndex)).getType();

View File

@ -12,8 +12,10 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.junit.Test;
@ -25,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);
@ -278,7 +285,7 @@ public class TypeInferenceTest {
@Test
public void testIncorporationSubtypeAndSubtype() {
List<Constraint> result;
// ### Original rule 4. : S <: α and α <: T imply S <: T
result = incorporationResult(new Bound(s, alpha, EQUALITY), new Bound(alpha, t, SUBTYPE));
assertEquals(result.size(), 1);
@ -291,6 +298,49 @@ public class TypeInferenceTest {
}
@Test
public void testErasedCandidateSet() {
List<JavaTypeDefinition> types = new ArrayList<>();
types.add(JavaTypeDefinition.forClass(List.class));
types.add(JavaTypeDefinition.forClass(Set.class));
Set<Class<?>> erasedCandidate = TypeInferenceResolver.getErasedCandidateSet(types);
assertEquals(erasedCandidate.size(), 3);
assertTrue(erasedCandidate.contains(Object.class));
assertTrue(erasedCandidate.contains(Collection.class));
assertTrue(erasedCandidate.contains(Iterable.class));
}
@Test
public void testMinimalErasedCandidateSet() {
Set<Class<?>> minimalSet = TypeInferenceResolver.getMinimalErasedCandidateSet(
JavaTypeDefinition.forClass(List.class).getErasedSuperTypeSet());
assertEquals(minimalSet.size(), 1);
assertTrue(minimalSet.contains(List.class));
}
@Test
public void testLeastUpperBound() {
List<JavaTypeDefinition> lowerBounds = new ArrayList<>();
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));
}
@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) {
List<Bound> current = new ArrayList<>();
List<Bound> newBounds = new ArrayList<>();

View File

@ -0,0 +1,11 @@
/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.typeresolution.testdata.dummytypes;
import java.util.List;
public class JavaTypeDefinitionEquals<T extends List<JavaTypeDefinitionEquals>> {
}

View File

@ -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 {
}

View File

@ -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 {
}

View File

@ -166,3 +166,5 @@ All existing rules have been updated to reflect these changes. If you have custo
* [#542](https://github.com/pmd/pmd/pull/542): \[java] Metrics abstraction - [Clément Fournier](https://github.com/oowekyala)
* [#545](https://github.com/pmd/pmd/pull/545): \[apex] Apex metrics framework - [Clément Fournier](https://github.com/oowekyala)
* [#548](https://github.com/pmd/pmd/pull/548): \[java] Metrics documentation - [Clément Fournier](https://github.com/oowekyala)
* [#550](https://github.com/pmd/pmd/pull/550): \[java] Add basic resolution to type inference - [Bendegúz Nagy](https://github.com/WinterGrascph)