diff options
author | emcmanus <emcmanus@google.com> | 2020-01-09 07:50:05 -0800 |
---|---|---|
committer | David P. Baker <dpb@google.com> | 2020-01-10 13:07:06 -0500 |
commit | 13a0b2470affd322d7fa2cad8d41140df91539a9 (patch) | |
tree | fc44bdeed17b52fe2c51837b210ce5f571a17998 /common | |
parent | a69b35ab263e96c0a51742a694f2f3a07763e771 (diff) | |
download | auto-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.java | 66 | ||||
-rw-r--r-- | common/src/test/java/com/google/auto/common/MoreTypesTest.java | 58 |
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; + } } |