Skip to content

Commit 13a0b24

Browse files
eamonnmcmanusnetdpb
authored andcommitted
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
1 parent a69b35a commit 13a0b24

File tree

2 files changed

+123
-1
lines changed

2 files changed

+123
-1
lines changed

common/src/main/java/com/google/auto/common/MoreTypes.java

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -949,5 +949,71 @@ protected T defaultAction(TypeMirror e, Void v) {
949949
}
950950
}
951951

952+
/**
953+
* Returns true if casting {@code Object} to the given type will elicit an unchecked warning from
954+
* the compiler. Only type variables and parameterized types such as {@code List<String>} produce
955+
* such warnings. There will be no warning if the type's only type parameters are simple
956+
* wildcards, as in {@code Map<?, ?>}.
957+
*/
958+
public static boolean isConversionFromObjectUnchecked(TypeMirror type) {
959+
return new CastingUncheckedVisitor().visit(type, null);
960+
}
961+
962+
/**
963+
* Visitor that tells whether a type is erased, in the sense of {@link #castIsUnchecked}. Each
964+
* visitX method returns true if its input parameter is true or if the type being visited is
965+
* erased.
966+
*/
967+
private static class CastingUncheckedVisitor extends SimpleTypeVisitor8<Boolean, Void> {
968+
CastingUncheckedVisitor() {
969+
super(false);
970+
}
971+
972+
@Override
973+
public Boolean visitUnknown(TypeMirror t, Void p) {
974+
// We don't know whether casting is unchecked for this mysterious type but assume it is,
975+
// so we will insert a possibly unnecessary @SuppressWarnings("unchecked").
976+
return true;
977+
}
978+
979+
@Override
980+
public Boolean visitArray(ArrayType t, Void p) {
981+
return visit(t.getComponentType(), p);
982+
}
983+
984+
@Override
985+
public Boolean visitDeclared(DeclaredType t, Void p) {
986+
return t.getTypeArguments().stream().anyMatch(CastingUncheckedVisitor::uncheckedTypeArgument);
987+
}
988+
989+
@Override
990+
public Boolean visitTypeVariable(TypeVariable t, Void p) {
991+
return true;
992+
}
993+
994+
// If a type has a type argument, then casting to the type is unchecked, except if the argument
995+
// is <?> or <? extends Object>. The same applies to all type arguments, so casting to Map<?, ?>
996+
// does not produce an unchecked warning for example.
997+
private static boolean uncheckedTypeArgument(TypeMirror arg) {
998+
if (arg.getKind().equals(TypeKind.WILDCARD)) {
999+
WildcardType wildcard = asWildcard(arg);
1000+
if (wildcard.getExtendsBound() == null || isJavaLangObject(wildcard.getExtendsBound())) {
1001+
// This is <?>, unless there's a super bound, in which case it is <? super Foo> and
1002+
// is erased.
1003+
return (wildcard.getSuperBound() != null);
1004+
}
1005+
}
1006+
return true;
1007+
}
1008+
1009+
private static boolean isJavaLangObject(TypeMirror type) {
1010+
if (type.getKind() != TypeKind.DECLARED) {
1011+
return false;
1012+
}
1013+
TypeElement typeElement = asTypeElement(type);
1014+
return typeElement.getQualifiedName().contentEquals("java.lang.Object");
1015+
}
1016+
}
1017+
9521018
private MoreTypes() {}
9531019
}

common/src/test/java/com/google/auto/common/MoreTypesTest.java

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import com.google.common.collect.ImmutableSet;
2929
import com.google.common.collect.Iterables;
3030
import com.google.common.testing.EquivalenceTester;
31+
import com.google.common.truth.Expect;
3132
import com.google.testing.compile.CompilationRule;
3233
import java.lang.annotation.Annotation;
3334
import java.util.List;
@@ -55,7 +56,8 @@
5556

5657
@RunWith(JUnit4.class)
5758
public class MoreTypesTest {
58-
@Rule public CompilationRule compilationRule = new CompilationRule();
59+
@Rule public final CompilationRule compilationRule = new CompilationRule();
60+
@Rule public final Expect expect = Expect.create();
5961

6062
@Test
6163
public void equivalence() {
@@ -442,4 +444,58 @@ public List<? extends AnnotationMirror> getAnnotationMirrors() {
442444
return null;
443445
}
444446
};
447+
448+
@Test
449+
public void testIsConversionFromObjectUnchecked_yes() {
450+
Elements elements = compilationRule.getElements();
451+
TypeElement unchecked = elements.getTypeElement(Unchecked.class.getCanonicalName());
452+
for (VariableElement field : ElementFilter.fieldsIn(unchecked.getEnclosedElements())) {
453+
TypeMirror type = field.asType();
454+
expect
455+
.withMessage("Casting to %s is unchecked", type)
456+
.that(MoreTypes.isConversionFromObjectUnchecked(type))
457+
.isTrue();
458+
}
459+
}
460+
461+
@Test
462+
public void testIsConversionFromObjectUnchecked_no() {
463+
Elements elements = compilationRule.getElements();
464+
TypeElement notUnchecked = elements.getTypeElement(NotUnchecked.class.getCanonicalName());
465+
for (VariableElement field : ElementFilter.fieldsIn(notUnchecked.getEnclosedElements())) {
466+
TypeMirror type = field.asType();
467+
expect
468+
.withMessage("Casting to %s is not unchecked", type)
469+
.that(MoreTypes.isConversionFromObjectUnchecked(type))
470+
.isFalse();
471+
}
472+
}
473+
474+
// The type of every field here is such that casting to it provokes an "unchecked" warning.
475+
@SuppressWarnings("unused")
476+
private static class Unchecked<T> {
477+
private List<String> listOfString;
478+
private List<? extends CharSequence> listOfExtendsCharSequence;
479+
private List<? super CharSequence> listOfSuperCharSequence;
480+
private List<T> listOfT;
481+
private List<T[]> listOfArrayOfT;
482+
private T t;
483+
private T[] arrayOfT;
484+
private List<T>[] arrayOfListOfT;
485+
private Map<?, String> mapWildcardToString;
486+
private Map<String, ?> mapStringToWildcard;
487+
}
488+
489+
// The type of every field here is such that casting to it doesn't provoke an "unchecked" warning.
490+
@SuppressWarnings("unused")
491+
private static class NotUnchecked {
492+
private String string;
493+
private int integer;
494+
private String[] arrayOfString;
495+
private int[] arrayOfInt;
496+
private Thread.State threadStateEnum;
497+
private List<?> listOfWildcard;
498+
private List<? extends Object> listOfWildcardExtendsObject;
499+
private Map<?, ?> mapWildcardToWildcard;
500+
}
445501
}

0 commit comments

Comments
 (0)