aboutsummaryrefslogtreecommitdiff
path: root/common
diff options
context:
space:
mode:
authoremcmanus <emcmanus@google.com>2020-01-09 07:50:05 -0800
committerDavid P. Baker <dpb@google.com>2020-01-10 13:07:06 -0500
commit13a0b2470affd322d7fa2cad8d41140df91539a9 (patch)
treefc44bdeed17b52fe2c51837b210ce5f571a17998 /common
parenta69b35ab263e96c0a51742a694f2f3a07763e771 (diff)
downloadauto-13a0b2470affd322d7fa2cad8d41140df91539a9.tar.gz
Add MoreTypes.isConversionFromObjectUnchecked. This method tells, for a given type, whether casting Object to that type will elicit an "unchecked" warning from the compiler.
RELNOTES=Added MoreTypes.isConversionFromObjectUnchecked to test whether casting to a type will elicit an "unchecked" compiler warning. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=288896286
Diffstat (limited to 'common')
-rw-r--r--common/src/main/java/com/google/auto/common/MoreTypes.java66
-rw-r--r--common/src/test/java/com/google/auto/common/MoreTypesTest.java58
2 files changed, 123 insertions, 1 deletions
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 7a389218..e09680bc 100644
--- a/common/src/main/java/com/google/auto/common/MoreTypes.java
+++ b/common/src/main/java/com/google/auto/common/MoreTypes.java
@@ -949,5 +949,71 @@ public final class MoreTypes {
}
}
+ /**
+ * Returns true if casting {@code Object} to the given type will elicit an unchecked warning from
+ * the compiler. Only type variables and parameterized types such as {@code List<String>} produce
+ * such warnings. There will be no warning if the type's only type parameters are simple
+ * wildcards, as in {@code Map<?, ?>}.
+ */
+ public static boolean isConversionFromObjectUnchecked(TypeMirror type) {
+ return new CastingUncheckedVisitor().visit(type, null);
+ }
+
+ /**
+ * Visitor that tells whether a type is erased, in the sense of {@link #castIsUnchecked}. Each
+ * visitX method returns true if its input parameter is true or if the type being visited is
+ * erased.
+ */
+ private static class CastingUncheckedVisitor extends SimpleTypeVisitor8<Boolean, Void> {
+ CastingUncheckedVisitor() {
+ super(false);
+ }
+
+ @Override
+ public Boolean visitUnknown(TypeMirror t, Void p) {
+ // We don't know whether casting is unchecked for this mysterious type but assume it is,
+ // so we will insert a possibly unnecessary @SuppressWarnings("unchecked").
+ return true;
+ }
+
+ @Override
+ public Boolean visitArray(ArrayType t, Void p) {
+ return visit(t.getComponentType(), p);
+ }
+
+ @Override
+ public Boolean visitDeclared(DeclaredType t, Void p) {
+ return t.getTypeArguments().stream().anyMatch(CastingUncheckedVisitor::uncheckedTypeArgument);
+ }
+
+ @Override
+ public Boolean visitTypeVariable(TypeVariable t, Void p) {
+ return true;
+ }
+
+ // If a type has a type argument, then casting to the type is unchecked, except if the argument
+ // is <?> or <? extends Object>. The same applies to all type arguments, so casting to Map<?, ?>
+ // does not produce an unchecked warning for example.
+ private static boolean uncheckedTypeArgument(TypeMirror arg) {
+ if (arg.getKind().equals(TypeKind.WILDCARD)) {
+ WildcardType wildcard = asWildcard(arg);
+ if (wildcard.getExtendsBound() == null || isJavaLangObject(wildcard.getExtendsBound())) {
+ // This is <?>, unless there's a super bound, in which case it is <? super Foo> and
+ // is erased.
+ return (wildcard.getSuperBound() != null);
+ }
+ }
+ return true;
+ }
+
+ private static boolean isJavaLangObject(TypeMirror type) {
+ if (type.getKind() != TypeKind.DECLARED) {
+ return false;
+ }
+ TypeElement typeElement = asTypeElement(type);
+ return typeElement.getQualifiedName().contentEquals("java.lang.Object");
+ }
+ }
+
private MoreTypes() {}
}
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 5b8d33f8..3cd360db 100644
--- a/common/src/test/java/com/google/auto/common/MoreTypesTest.java
+++ b/common/src/test/java/com/google/auto/common/MoreTypesTest.java
@@ -28,6 +28,7 @@ import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.testing.EquivalenceTester;
+import com.google.common.truth.Expect;
import com.google.testing.compile.CompilationRule;
import java.lang.annotation.Annotation;
import java.util.List;
@@ -55,7 +56,8 @@ import org.junit.runners.JUnit4;
@RunWith(JUnit4.class)
public class MoreTypesTest {
- @Rule public CompilationRule compilationRule = new CompilationRule();
+ @Rule public final CompilationRule compilationRule = new CompilationRule();
+ @Rule public final Expect expect = Expect.create();
@Test
public void equivalence() {
@@ -442,4 +444,58 @@ public class MoreTypesTest {
return null;
}
};
+
+ @Test
+ public void testIsConversionFromObjectUnchecked_yes() {
+ Elements elements = compilationRule.getElements();
+ TypeElement unchecked = elements.getTypeElement(Unchecked.class.getCanonicalName());
+ for (VariableElement field : ElementFilter.fieldsIn(unchecked.getEnclosedElements())) {
+ TypeMirror type = field.asType();
+ expect
+ .withMessage("Casting to %s is unchecked", type)
+ .that(MoreTypes.isConversionFromObjectUnchecked(type))
+ .isTrue();
+ }
+ }
+
+ @Test
+ public void testIsConversionFromObjectUnchecked_no() {
+ Elements elements = compilationRule.getElements();
+ TypeElement notUnchecked = elements.getTypeElement(NotUnchecked.class.getCanonicalName());
+ for (VariableElement field : ElementFilter.fieldsIn(notUnchecked.getEnclosedElements())) {
+ TypeMirror type = field.asType();
+ expect
+ .withMessage("Casting to %s is not unchecked", type)
+ .that(MoreTypes.isConversionFromObjectUnchecked(type))
+ .isFalse();
+ }
+ }
+
+ // The type of every field here is such that casting to it provokes an "unchecked" warning.
+ @SuppressWarnings("unused")
+ private static class Unchecked<T> {
+ private List<String> listOfString;
+ private List<? extends CharSequence> listOfExtendsCharSequence;
+ private List<? super CharSequence> listOfSuperCharSequence;
+ private List<T> listOfT;
+ private List<T[]> listOfArrayOfT;
+ private T t;
+ private T[] arrayOfT;
+ private List<T>[] arrayOfListOfT;
+ private Map<?, String> mapWildcardToString;
+ private Map<String, ?> mapStringToWildcard;
+ }
+
+ // The type of every field here is such that casting to it doesn't provoke an "unchecked" warning.
+ @SuppressWarnings("unused")
+ private static class NotUnchecked {
+ private String string;
+ private int integer;
+ private String[] arrayOfString;
+ private int[] arrayOfInt;
+ private Thread.State threadStateEnum;
+ private List<?> listOfWildcard;
+ private List<? extends Object> listOfWildcardExtendsObject;
+ private Map<?, ?> mapWildcardToWildcard;
+ }
}