diff --git a/common/src/main/java/com/google/auto/common/MoreTypes.java b/common/src/main/java/com/google/auto/common/MoreTypes.java index c8be62556b..e935a3d95b 100644 --- a/common/src/main/java/com/google/auto/common/MoreTypes.java +++ b/common/src/main/java/com/google/auto/common/MoreTypes.java @@ -26,6 +26,7 @@ import com.google.common.base.Equivalence; import com.google.common.base.Optional; +import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import java.util.HashSet; @@ -88,13 +89,13 @@ public String toString() { * may be preferred in a number of cases: * * */ public static Equivalence equivalence() { @@ -347,10 +348,10 @@ private static boolean equal( /** * Returns the type of the innermost enclosing instance, or null if there is none. This is the - * same as {@link DeclaredType#getEnclosingType()} except that it returns null rather than - * NoType for a static type. We need this because of - * this bug whereby - * the Eclipse compiler returns a value for static classes that is not NoType. + * same as {@link DeclaredType#getEnclosingType()} except that it returns null rather than NoType + * for a static type. We need this because of this bug whereby the Eclipse + * compiler returns a value for static classes that is not NoType. */ private static @Nullable TypeMirror enclosingType(DeclaredType t) { TypeMirror enclosing = t.getEnclosingType(); @@ -836,42 +837,121 @@ public Boolean visitDeclared(DeclaredType type, Void ignored) { } /** - * Returns true if the raw type underlying the given {@link TypeMirror} represents the same raw - * type as the given {@link Class} and throws an IllegalArgumentException if the {@link - * TypeMirror} does not represent a type that can be referenced by a {@link Class} + * Returns {@code true} iff the raw type underlying the given {@link Class} represents the raw + * type of the given {@link TypeMirror} and throws an {@link IllegalArgumentException} if the + * {@link TypeMirror} does not represent a type that can be referenced by a {@link Class}. + * + *

Note: The representation need not be exact. For example, {@linkplain java.util.ArrayList} + * represents {@linkplain List}. */ public static boolean isTypeOf(final Class clazz, TypeMirror type) { - checkNotNull(clazz); - return type.accept(new IsTypeOf(clazz), null); + return type.accept(new isTypeOf(clazz), null); } - private static final class IsTypeOf extends SimpleTypeVisitor8 { + private static final class isTypeOf extends SimpleTypeVisitor8 { private final Class clazz; - IsTypeOf(Class clazz) { - this.clazz = clazz; + isTypeOf(Class clazz) { + this.clazz = Preconditions.checkNotNull(clazz); } @Override - protected Boolean defaultAction(TypeMirror type, Void ignored) { + protected Boolean defaultAction(TypeMirror type, Void ignore) { + return isExactTypeOf(clazz, type); + } + + @Override + public Boolean visitArray(ArrayType array, Void ignore) { + return clazz.isArray() && isTypeOf(clazz.getComponentType(), array.getComponentType()); + } + + @Override + public Boolean visitDeclared(DeclaredType type, Void ignore) { + return isDeclaredTypeOf(clazz, type); + } + + @Override + public Boolean visitTypeVariable(TypeVariable type, Void ignore) { + TypeMirror upperBoundType = type.getUpperBound(); + if (upperBoundType.getKind() != TypeKind.INTERSECTION) { + return isTypeOf(clazz, upperBoundType); + } + + for (TypeMirror UBType : ((IntersectionType) upperBoundType).getBounds()) { + if (isTypeOf(clazz, UBType)) { + return true; + } + } + + return false; + } + + @Override + public Boolean visitWildcard(WildcardType type, Void ignore) { + TypeMirror upperBoundType = type.getExtendsBound(); + return upperBoundType == null || isTypeOf(clazz, upperBoundType); + } + } + + private static boolean isDeclaredTypeOf(final Class clazz, DeclaredType declaredType) { + if (isExactTypeOf(clazz, declaredType)) { + return true; + } + + TypeElement typeElement = MoreElements.asType(declaredType.asElement()); + + for (TypeMirror i : typeElement.getInterfaces()) { + if (isDeclaredTypeOf(clazz, MoreTypes.asDeclared(i))) { + return true; + } + } + + /* For interfaces (including annotation types), + * and java.lang.Object, TypeElement#getSuperclass() returns + * NoType with the NONE kind. + */ + TypeMirror superClassType = typeElement.getSuperclass(); + return (superClassType.getKind() != TypeKind.NONE) + && isDeclaredTypeOf(clazz, MoreTypes.asDeclared(superClassType)); + } + + /** + * Returns {@code true} iff the raw type underlying the given {@link TypeMirror} represents the + * same raw type as the given {@link Class} and throws an {@link IllegalArgumentException} if the + * {@link TypeMirror} does not represent a type that can be referenced by a {@link Class} + */ + public static boolean isExactTypeOf(final Class clazz, TypeMirror type) { + return type.accept(new isExactTypeOf(clazz), null); + } + + private static final class isExactTypeOf extends SimpleTypeVisitor8 { + private final Class clazz; + + isExactTypeOf(Class clazz) { + this.clazz = Preconditions.checkNotNull(clazz); + } + + @Override + protected Boolean defaultAction(TypeMirror type, Void ignore) { throw new IllegalArgumentException(type + " cannot be represented as a Class."); } @Override - public Boolean visitNoType(NoType noType, Void p) { - if (noType.getKind().equals(TypeKind.VOID)) { + public Boolean visitNoType(NoType noType, Void ignore) { + if (noType.getKind() == TypeKind.VOID) { return clazz.equals(Void.TYPE); } + throw new IllegalArgumentException(noType + " cannot be represented as a Class."); } @Override - public Boolean visitError(ErrorType errorType, Void p) { + public Boolean visitError(ErrorType errorType, Void ignore) { return false; } @Override - public Boolean visitPrimitive(PrimitiveType type, Void p) { + public Boolean visitPrimitive(PrimitiveType type, Void ignore) { switch (type.getKind()) { case BOOLEAN: return clazz.equals(Boolean.TYPE); @@ -895,12 +975,12 @@ public Boolean visitPrimitive(PrimitiveType type, Void p) { } @Override - public Boolean visitArray(ArrayType array, Void p) { - return clazz.isArray() && isTypeOf(clazz.getComponentType(), array.getComponentType()); + public Boolean visitArray(ArrayType array, Void ignore) { + return clazz.isArray() && isExactTypeOf(clazz.getComponentType(), array.getComponentType()); } @Override - public Boolean visitDeclared(DeclaredType type, Void ignored) { + public Boolean visitDeclared(DeclaredType type, Void ignore) { TypeElement typeElement = MoreElements.asType(type.asElement()); return typeElement.getQualifiedName().contentEquals(clazz.getCanonicalName()); } @@ -946,8 +1026,8 @@ private static boolean isObjectType(DeclaredType type) { /** * Resolves a {@link VariableElement} parameter to a method or constructor based on the given * container, or a member of a class. For parameters to a method or constructor, the variable's - * enclosing element must be a supertype of the container type. For example, given a - * {@code container} of type {@code Set}, and a variable corresponding to the {@code E e} + * enclosing element must be a supertype of the container type. For example, given a {@code + * container} of type {@code Set}, and a variable corresponding to the {@code E e} * parameter in the {@code Set.add(E e)} method, this will return a TypeMirror for {@code String}. */ public static TypeMirror asMemberOf( diff --git a/common/src/main/java/com/google/auto/common/SuperficialValidation.java b/common/src/main/java/com/google/auto/common/SuperficialValidation.java index 614e26265f..f5727d18ea 100644 --- a/common/src/main/java/com/google/auto/common/SuperficialValidation.java +++ b/common/src/main/java/com/google/auto/common/SuperficialValidation.java @@ -41,8 +41,8 @@ import javax.lang.model.util.SimpleTypeVisitor8; /** - * A utility class that traverses {@link Element} instances and ensures that all type information - * is present and resolvable. + * A utility class that traverses {@link Element} instances and ensures that all type information is + * present and resolvable. * * @author Gregory Kick */ @@ -213,7 +213,7 @@ private static boolean validateAnnotationValues( new SimpleAnnotationValueVisitor8() { @Override protected Boolean defaultAction(Object o, TypeMirror expectedType) { - return MoreTypes.isTypeOf(o.getClass(), expectedType); + return MoreTypes.isExactTypeOf(o.getClass(), expectedType); } @Override @@ -254,42 +254,42 @@ public Boolean visitType(TypeMirror type, TypeMirror ignored) { @Override public Boolean visitBoolean(boolean b, TypeMirror expectedType) { - return MoreTypes.isTypeOf(Boolean.TYPE, expectedType); + return MoreTypes.isExactTypeOf(Boolean.TYPE, expectedType); } @Override public Boolean visitByte(byte b, TypeMirror expectedType) { - return MoreTypes.isTypeOf(Byte.TYPE, expectedType); + return MoreTypes.isExactTypeOf(Byte.TYPE, expectedType); } @Override public Boolean visitChar(char c, TypeMirror expectedType) { - return MoreTypes.isTypeOf(Character.TYPE, expectedType); + return MoreTypes.isExactTypeOf(Character.TYPE, expectedType); } @Override public Boolean visitDouble(double d, TypeMirror expectedType) { - return MoreTypes.isTypeOf(Double.TYPE, expectedType); + return MoreTypes.isExactTypeOf(Double.TYPE, expectedType); } @Override public Boolean visitFloat(float f, TypeMirror expectedType) { - return MoreTypes.isTypeOf(Float.TYPE, expectedType); + return MoreTypes.isExactTypeOf(Float.TYPE, expectedType); } @Override public Boolean visitInt(int i, TypeMirror expectedType) { - return MoreTypes.isTypeOf(Integer.TYPE, expectedType); + return MoreTypes.isExactTypeOf(Integer.TYPE, expectedType); } @Override public Boolean visitLong(long l, TypeMirror expectedType) { - return MoreTypes.isTypeOf(Long.TYPE, expectedType); + return MoreTypes.isExactTypeOf(Long.TYPE, expectedType); } @Override public Boolean visitShort(short s, TypeMirror expectedType) { - return MoreTypes.isTypeOf(Short.TYPE, expectedType); + return MoreTypes.isExactTypeOf(Short.TYPE, expectedType); } }; diff --git a/common/src/test/java/com/google/auto/common/MoreTypesIsTypeOfTest.java b/common/src/test/java/com/google/auto/common/MoreTypesIsTypeOfTest.java index ba8fccebdd..63dc731612 100644 --- a/common/src/test/java/com/google/auto/common/MoreTypesIsTypeOfTest.java +++ b/common/src/test/java/com/google/auto/common/MoreTypesIsTypeOfTest.java @@ -23,8 +23,14 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.testing.compile.CompilationRule; +import java.util.ArrayList; +import java.util.LinkedList; import java.util.List; +import java.util.Map; +import java.util.NavigableMap; +import java.util.RandomAccess; import java.util.SortedMap; +import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; @@ -148,9 +154,81 @@ public void isTypeOf_arrayType() { .isTrue(); } + @Test + // ArrayList is a list because its parent implements List (checking interface and direct ancestry) + public void isTypeOf_listLineage() { + TypeMirror type = + typeUtils.getDeclaredType( + getTypeElementFor(ArrayList.class), getTypeElementFor(String.class).asType()); + assertWithMessage("Mirror:\t" + type + "\nClass:\t" + ArrayList.class.getCanonicalName()) + .that(MoreTypes.isTypeOf(ArrayList.class, type)) + .isTrue(); + assertWithMessage("Mirror:\t" + type + "\nClass:\t" + List.class.getCanonicalName()) + .that(MoreTypes.isTypeOf(List.class, type)) + .isTrue(); + assertWithMessage("Mirror:\t" + type + "\nClass:\t" + String.class.getCanonicalName()) + .that(MoreTypes.isTypeOf(String.class, type)) + .isFalse(); + assertWithMessage("Mirror:\t" + type + "\nClass:\t" + LinkedList.class.getCanonicalName()) + .that(MoreTypes.isTypeOf(LinkedList.class, type)) + .isFalse(); + + type = typeUtils.getArrayType(type); // ArrayList[] + assertWithMessage("Mirror:\t" + type + "\nClass:\t" + ArrayList[].class.getCanonicalName()) + .that(MoreTypes.isTypeOf(ArrayList[].class, type)) + .isTrue(); + assertWithMessage("Mirror:\t" + type + "\nClass:\t" + List[].class.getCanonicalName()) + .that(MoreTypes.isTypeOf(List[].class, type)) + .isTrue(); + assertWithMessage("Mirror:\t" + type + "\nClass:\t" + LinkedList[].class.getCanonicalName()) + .that(MoreTypes.isTypeOf(LinkedList[].class, type)) + .isFalse(); + } + + @Test + // NavigableMap implements SortedMap and SortedMap implements Map (checking interface ancestry) + public void isTypeOf_mapLineage() { + TypeMirror type = getTypeElementFor(SortedMap.class).asType(); + assertWithMessage("Mirror:\t" + type + "\nClass:\t" + SortedMap.class.getCanonicalName()) + .that(MoreTypes.isTypeOf(SortedMap.class, type)) + .isTrue(); + assertWithMessage("Mirror:\t" + type + "\nClass:\t" + Map.class.getCanonicalName()) + .that(MoreTypes.isTypeOf(Map.class, type)) + .isTrue(); + assertWithMessage("Mirror:\t" + type + "\nClass:\t" + NavigableMap.class.getCanonicalName()) + .that(MoreTypes.isTypeOf(NavigableMap.class, type)) + .isFalse(); + + // Testing ancestor that is not a direct parent + type = getTypeElementFor(NavigableMap.class).asType(); + assertWithMessage("Mirror:\t" + type + "\nClass:\t" + Map.class.getCanonicalName()) + .that(MoreTypes.isTypeOf(Map.class, type)) + .isTrue(); + } + + @Test + public void isTypeOf_wildcardCapture() { + TypeMirror type = + typeUtils.getWildcardType( + getTypeElementFor(SortedMap.class).asType(), null); // ? extends SortedMap + assertWithMessage("Mirror:\t" + type + "\nClass:\t" + SortedMap.class.getCanonicalName()) + .that(MoreTypes.isTypeOf(SortedMap.class, type)) + .isTrue(); + assertWithMessage("Mirror:\t" + type + "\nClass:\t" + Map.class.getCanonicalName()) + .that(MoreTypes.isTypeOf(Map.class, type)) + .isTrue(); + assertWithMessage("Mirror:\t" + type + "\nClass:\t" + NavigableMap.class.getCanonicalName()) + .that(MoreTypes.isTypeOf(NavigableMap.class, type)) + .isFalse(); + } + private interface TestType { @SuppressWarnings("unused") > T method0(); + + @SuppressWarnings("unused") + & RandomAccess> void method1( + RANDOM_ACCESS_LIST randomAccessList); } @Test @@ -167,13 +245,49 @@ public void isTypeOf_declaredType() { .isFalse(); } + @Test + public void isTypeOf_typeParameterCapture() { + assertTrue(MoreTypes.isType(getTypeElementFor(TestType.class).asType())); + + // Getting the type parameter of method0 + ExecutableElement executableElement = + MoreElements.asExecutable(getTypeElementFor(TestType.class).getEnclosedElements().get(0)); + TypeMirror type = Iterables.getOnlyElement(executableElement.getTypeParameters()).asType(); + + assertWithMessage("Mirror:\t" + type + "\nClass:\t" + SortedMap.class.getCanonicalName()) + .that(MoreTypes.isTypeOf(SortedMap.class, type)) + .isTrue(); + assertWithMessage("Mirror:\t" + type + "\nClass:\t" + Map.class.getCanonicalName()) + .that(MoreTypes.isTypeOf(Map.class, type)) + .isTrue(); + assertWithMessage("Mirror:\t" + type + "\nClass:\t" + NavigableMap.class.getCanonicalName()) + .that(MoreTypes.isTypeOf(NavigableMap.class, type)) + .isFalse(); + + // Getting parameter type of method1 and checking for intersection type + executableElement = + MoreElements.asExecutable(getTypeElementFor(TestType.class).getEnclosedElements().get(1)); + type = Iterables.getOnlyElement(executableElement.getParameters()).asType(); + + assertWithMessage("Mirror:\t" + type + "\nClass:\t" + List.class.getCanonicalName()) + .that(MoreTypes.isTypeOf(List.class, type)) + .isTrue(); + assertWithMessage("Mirror:\t" + type + "\nClass:\t" + RandomAccess.class.getCanonicalName()) + .that(MoreTypes.isTypeOf(RandomAccess.class, type)) + .isTrue(); + assertWithMessage("Mirror:\t" + type + "\nClass:\t" + ArrayList.class.getCanonicalName()) + .that(MoreTypes.isTypeOf(ArrayList.class, type)) + .isFalse(); + } + @Test public void isTypeOf_fail() { - TypeMirror methodType = - Iterables.getOnlyElement(getTypeElementFor(TestType.class).getEnclosedElements()).asType(); - assertFalse(MoreTypes.isType(methodType)); + assertFalse( + MoreTypes.isType(getTypeElementFor(TestType.class).getEnclosedElements().get(0).asType())); + TypeMirror method1Type = + getTypeElementFor(TestType.class).getEnclosedElements().get(1).asType(); try { - MoreTypes.isTypeOf(List.class, methodType); + MoreTypes.isTypeOf(List.class, method1Type); fail(); } catch (IllegalArgumentException expected) { } diff --git a/common/src/test/java/com/google/auto/common/MoreTypesTest.java b/common/src/test/java/com/google/auto/common/MoreTypesTest.java index b8e84e0859..d71b47e930 100644 --- a/common/src/test/java/com/google/auto/common/MoreTypesTest.java +++ b/common/src/test/java/com/google/auto/common/MoreTypesTest.java @@ -502,13 +502,13 @@ public void testIsTypeOf() { PrimitiveType intType = types.getPrimitiveType(TypeKind.INT); TypeMirror integerType = types.boxedClass(intType).asType(); WildcardType wildcardType = types.getWildcardType(null, null); - expect.that(MoreTypes.isTypeOf(int.class, intType)).isTrue(); - expect.that(MoreTypes.isTypeOf(Integer.class, integerType)).isTrue(); - expect.that(MoreTypes.isTypeOf(Integer.class, intType)).isFalse(); - expect.that(MoreTypes.isTypeOf(int.class, integerType)).isFalse(); - expect.that(MoreTypes.isTypeOf(Integer.class, FAKE_ERROR_TYPE)).isFalse(); + expect.that(MoreTypes.isExactTypeOf(int.class, intType)).isTrue(); + expect.that(MoreTypes.isExactTypeOf(Integer.class, integerType)).isTrue(); + expect.that(MoreTypes.isExactTypeOf(Integer.class, intType)).isFalse(); + expect.that(MoreTypes.isExactTypeOf(int.class, integerType)).isFalse(); + expect.that(MoreTypes.isExactTypeOf(Integer.class, FAKE_ERROR_TYPE)).isFalse(); assertThrows( - IllegalArgumentException.class, () -> MoreTypes.isTypeOf(Integer.class, wildcardType)); + IllegalArgumentException.class, () -> MoreTypes.isExactTypeOf(Integer.class, wildcardType)); } // The type of every field here is such that casting to it provokes an "unchecked" warning.