Skip to content

Commit

Permalink
Add MoreTypes.isConversionFromObjectUnchecked. This method tells, for…
Browse files Browse the repository at this point in the history
… 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
  • Loading branch information
eamonnmcmanus authored and netdpb committed Jan 10, 2020
1 parent a69b35a commit 13a0b24
Show file tree
Hide file tree
Showing 2 changed files with 123 additions and 1 deletion.
66 changes: 66 additions & 0 deletions common/src/main/java/com/google/auto/common/MoreTypes.java
Original file line number Diff line number Diff line change
Expand Up @@ -949,5 +949,71 @@ protected T defaultAction(TypeMirror e, Void v) {
}
}

/**
* 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() {}
}
58 changes: 57 additions & 1 deletion common/src/test/java/com/google/auto/common/MoreTypesTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
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;
Expand Down Expand Up @@ -55,7 +56,8 @@

@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() {
Expand Down Expand Up @@ -442,4 +444,58 @@ public List<? extends AnnotationMirror> getAnnotationMirrors() {
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;
}
}

0 comments on commit 13a0b24

Please sign in to comment.