diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/typedefinition/JavaTypeDefinition.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/typedefinition/JavaTypeDefinition.java index a104ed42c7..a22e8ea862 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/typedefinition/JavaTypeDefinition.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/typedefinition/JavaTypeDefinition.java @@ -336,4 +336,21 @@ public class JavaTypeDefinition implements TypeDefinition { 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) { + for(JavaTypeDefinition superTypeDef : getSuperTypeSet()) { + if(superTypeDef.clazz == superClazz) { + return superTypeDef; + } + } + + return null; + } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/typeinference/TypeInferenceResolver.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/typeinference/TypeInferenceResolver.java index 1f38a90673..c8aed1bc51 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/typeinference/TypeInferenceResolver.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/typeinference/TypeInferenceResolver.java @@ -23,19 +23,120 @@ import java.util.Set; public final class TypeInferenceResolver { + public static class ResolutionFailed extends RuntimeException { + + } + private TypeInferenceResolver() { } - public static Set> getErasedCandidateSet(List>> erasedSuperTypeSets) { - Set> result = new HashSet<>(); - - if (!erasedSuperTypeSets.isEmpty()) { - result.addAll(erasedSuperTypeSets.get(0)); + public static JavaTypeDefinition lub(List types) { + for (JavaTypeDefinition type : types) { + if (type.isArrayType()) { + // TODO: add support for array types + return JavaTypeDefinition.forClass(Object.class); + } } - for (Set> superTypeSet : erasedSuperTypeSets) { - result.retainAll(superTypeSet); + Set> erasedCandidateSet = getErasedCandidateSet(types); + Set> minimalSet = getMinimalErasedCandidateSet(erasedCandidateSet); + + List candidates = new ArrayList<>(); + + for (Class erasedSupertype : minimalSet) { + JavaTypeDefinition lci = types.get(0).getAsSuper(erasedSupertype); + + for (JavaTypeDefinition type : types) { + if (lci == null) { + throw new ResolutionFailed(); + } + + lci = intersect(lci, type.getAsSuper(erasedSupertype)); + } + + candidates.add(lci); + } + + if (candidates.isEmpty()) { + throw new ResolutionFailed(); + } + + JavaTypeDefinition result = candidates.get(0); + + for (JavaTypeDefinition candidate : candidates) { + if (containsType(candidate, result)) { + result = candidate; + } else if (!containsType(result, candidate)) { // TODO: add support for compound types + throw new ResolutionFailed(); + } + } + + return result; + } + + private static JavaTypeDefinition intersect(JavaTypeDefinition first, JavaTypeDefinition second) { + if (first.equals(second)) { // two types equal + return first; + } else if (first.getType() == second.getType()) { + if (!first.isRawType() && !second.isRawType()) { // are generic + return merge(first, second); + } else { // one of them is raw + return JavaTypeDefinition.forClass(first.getType()); + } + } + + throw new ResolutionFailed(); + } + + /** + * @return true, if parameter contains argument + */ + 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 is assigable to Collection or Collection + return true; // we don't yet care about wildcards like, List is assignable to List + } 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"); + } + + JavaTypeDefinition[] mergedGeneric = new JavaTypeDefinition[first.getTypeParameterCount()]; + + for (int i = 0; i < first.getTypeParameterCount(); ++i) { + if (containsType(first.getGenericType(i), second.getGenericType(i))) { + mergedGeneric[i] = first.getGenericType(i); + } else if (containsType(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 List -> List but we don't have wildcards yet + } + } + + return JavaTypeDefinition.forClass(first.getType(), mergedGeneric); + } + + public static Set> getErasedCandidateSet(List erasedSuperTypeSets) { + Set> result = null; + + if (!erasedSuperTypeSets.isEmpty()) { + result = erasedSuperTypeSets.get(0).getErasedSuperTypeSet(); + } + + for (int i = 1; i < erasedSuperTypeSets.size(); ++i) { + result.retainAll(erasedSuperTypeSets.get(i).getErasedSuperTypeSet()); } return result; diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/TypeInferenceTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/TypeInferenceTest.java index 0bcd6e314a..edcb1b80cd 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/TypeInferenceTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/TypeInferenceTest.java @@ -17,6 +17,7 @@ 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; @@ -295,11 +296,11 @@ public class TypeInferenceTest { @Test public void testErasedCandidateSet() { - List>> superTypeSets = new ArrayList<>(); - superTypeSets.add(JavaTypeDefinition.forClass(List.class).getErasedSuperTypeSet()); - superTypeSets.add(JavaTypeDefinition.forClass(Set.class).getErasedSuperTypeSet()); + List types = new ArrayList<>(); + types.add(JavaTypeDefinition.forClass(List.class)); + types.add(JavaTypeDefinition.forClass(Set.class)); - Set> erasedCandidate = TypeInferenceResolver.getErasedCandidateSet(superTypeSets); + Set> erasedCandidate = TypeInferenceResolver.getErasedCandidateSet(types); assertEquals(erasedCandidate.size(), 3); assertTrue(erasedCandidate.contains(Object.class)); @@ -316,6 +317,15 @@ public class TypeInferenceTest { assertTrue(minimalSet.contains(List.class)); } + @Test + public void testLeastUpperBound() { + List lowerBounds = new ArrayList<>(); + lowerBounds.add(JavaTypeDefinition.forClass(String.class)); + lowerBounds.add(JavaTypeDefinition.forClass(.class)); + + JavaTypeDefinition result = TypeInferenceResolver.lub(lowerBounds); + } + private List incorporationResult(Bound firstBound, Bound secondBound) { List current = new ArrayList<>(); List newBounds = new ArrayList<>();