forked from phoedos/pmd
Merge branch 'pr-550'
This commit is contained in:
@ -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<>();
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -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();
|
||||
|
@ -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<>();
|
||||
|
@ -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>> {
|
||||
}
|
@ -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 {
|
||||
}
|
@ -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)
|
||||
|
||||
|
Reference in New Issue
Block a user