Skip to content

Commit

Permalink
Merge branch 'pr-550'
Browse files Browse the repository at this point in the history
  • Loading branch information
jsotuyod committed Aug 15, 2017
2 parents 888dd23 + 98d43ee commit f3aa28c
Show file tree
Hide file tree
Showing 10 changed files with 747 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -484,6 +484,10 @@ public static boolean isSubtypeable(JavaTypeDefinition parameter, ASTExpression
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
Expand All @@ -496,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 @@ -528,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 @@ -12,20 +12,25 @@
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
private static final Map<Class<?>, JavaTypeDefinition> CLASS_TYPE_DEF_CACHE = new HashMap<>();

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
Expand Down Expand Up @@ -64,10 +69,7 @@ public static JavaTypeDefinition forClass(final Class<?> clazz) {

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;
}
Expand All @@ -80,9 +82,7 @@ public static JavaTypeDefinition forClass(final Class<?> clazz, final JavaTypeDe
// 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;
}
Expand Down Expand Up @@ -237,7 +237,7 @@ public boolean hasSameErasureAs(JavaTypeDefinition def) {
}

public int getTypeParameterCount() {
return clazz.getTypeParameters().length;
return typeParameterCount;
}

public boolean isArrayType() {
Expand All @@ -252,4 +252,108 @@ public String toString() {
.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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -134,4 +135,36 @@ public InferenceRuleType ruleType() {
}

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;
}
}
Loading

0 comments on commit f3aa28c

Please sign in to comment.