aboutsummaryrefslogtreecommitdiff
path: root/nullaway/src/main/java/com/uber/nullaway/generics/CompareNullabilityVisitor.java
diff options
context:
space:
mode:
Diffstat (limited to 'nullaway/src/main/java/com/uber/nullaway/generics/CompareNullabilityVisitor.java')
-rw-r--r--nullaway/src/main/java/com/uber/nullaway/generics/CompareNullabilityVisitor.java95
1 files changed, 95 insertions, 0 deletions
diff --git a/nullaway/src/main/java/com/uber/nullaway/generics/CompareNullabilityVisitor.java b/nullaway/src/main/java/com/uber/nullaway/generics/CompareNullabilityVisitor.java
new file mode 100644
index 0000000..de86547
--- /dev/null
+++ b/nullaway/src/main/java/com/uber/nullaway/generics/CompareNullabilityVisitor.java
@@ -0,0 +1,95 @@
+package com.uber.nullaway.generics;
+
+import com.google.errorprone.VisitorState;
+import com.google.errorprone.util.ASTHelpers;
+import com.sun.tools.javac.code.Attribute;
+import com.sun.tools.javac.code.Type;
+import com.sun.tools.javac.code.Types;
+import java.util.List;
+import javax.lang.model.type.NullType;
+
+/**
+ * Visitor that checks equality of nullability annotations for all nested generic type arguments
+ * within a type. Compares the Type it is called upon, i.e. the LHS type and the Type passed as an
+ * argument, i.e. The RHS type.
+ */
+public class CompareNullabilityVisitor extends Types.DefaultTypeVisitor<Boolean, Type> {
+ private final VisitorState state;
+
+ CompareNullabilityVisitor(VisitorState state) {
+ this.state = state;
+ }
+
+ @Override
+ public Boolean visitClassType(Type.ClassType lhsType, Type rhsType) {
+ if (rhsType instanceof NullType) {
+ return true;
+ }
+ Types types = state.getTypes();
+ // The base type of rhsType may be a subtype of lhsType's base type. In such cases, we must
+ // compare lhsType against the supertype of rhsType with a matching base type.
+ Type rhsTypeAsSuper = types.asSuper(rhsType, lhsType.tsym);
+ // This is impossible, considering the fact that standard Java subtyping succeeds before
+ // running NullAway
+ if (rhsTypeAsSuper == null) {
+ throw new RuntimeException("Did not find supertype of " + rhsType + " matching " + lhsType);
+ }
+ List<Type> lhsTypeArguments = lhsType.getTypeArguments();
+ List<Type> rhsTypeArguments = rhsTypeAsSuper.getTypeArguments();
+ // This is impossible, considering the fact that standard Java subtyping succeeds before
+ // running NullAway
+ if (lhsTypeArguments.size() != rhsTypeArguments.size()) {
+ throw new RuntimeException(
+ "Number of types arguments in " + rhsTypeAsSuper + " does not match " + lhsType);
+ }
+ for (int i = 0; i < lhsTypeArguments.size(); i++) {
+ Type lhsTypeArgument = lhsTypeArguments.get(i);
+ Type rhsTypeArgument = rhsTypeArguments.get(i);
+ boolean isLHSNullableAnnotated = false;
+ List<Attribute.TypeCompound> lhsAnnotations = lhsTypeArgument.getAnnotationMirrors();
+ // To ensure that we are checking only jspecify nullable annotations
+ Type jspecifyNullableType = GenericsChecks.JSPECIFY_NULLABLE_TYPE_SUPPLIER.get(state);
+ for (Attribute.TypeCompound annotation : lhsAnnotations) {
+ if (ASTHelpers.isSameType(
+ (Type) annotation.getAnnotationType(), jspecifyNullableType, state)) {
+ isLHSNullableAnnotated = true;
+ break;
+ }
+ }
+ boolean isRHSNullableAnnotated = false;
+ List<Attribute.TypeCompound> rhsAnnotations = rhsTypeArgument.getAnnotationMirrors();
+ for (Attribute.TypeCompound annotation : rhsAnnotations) {
+ if (ASTHelpers.isSameType(
+ (Type) annotation.getAnnotationType(), jspecifyNullableType, state)) {
+ isRHSNullableAnnotated = true;
+ break;
+ }
+ }
+ if (isLHSNullableAnnotated != isRHSNullableAnnotated) {
+ return false;
+ }
+ // nested generics
+ if (!lhsTypeArgument.accept(this, rhsTypeArgument)) {
+ return false;
+ }
+ }
+ // If there is an enclosing type (for non-static inner classes), its type argument nullability
+ // should also match. When there is no enclosing type, getEnclosingType() returns a NoType
+ // object, which gets handled by the fallback visitType() method
+ return lhsType.getEnclosingType().accept(this, rhsType.getEnclosingType());
+ }
+
+ @Override
+ public Boolean visitArrayType(Type.ArrayType lhsType, Type rhsType) {
+ if (rhsType instanceof NullType) {
+ return true;
+ }
+ Type.ArrayType arrRhsType = (Type.ArrayType) rhsType;
+ return lhsType.getComponentType().accept(this, arrRhsType.getComponentType());
+ }
+
+ @Override
+ public Boolean visitType(Type t, Type type) {
+ return true;
+ }
+}