From 60648a941fe381ba90d2c31d0260c9e4134c471e Mon Sep 17 00:00:00 2001 From: Manu Sridharan Date: Tue, 14 Nov 2023 17:32:10 -0800 Subject: [PATCH] Create com.uber.nullaway.generics package (#855) Fixes #817. This is a refactoring with no semantic changes. --- .../java/com/uber/nullaway/ErrorBuilder.java | 2 +- .../main/java/com/uber/nullaway/NullAway.java | 1 + .../AccessPathNullnessPropagation.java | 2 +- .../generics/CompareNullabilityVisitor.java | 85 +++++++ .../GenericTypePrettyPrintingVisitor.java | 75 ++++++ .../{ => generics}/GenericsChecks.java | 236 +----------------- .../PreservedAnnotationTreeVisitor.java | 94 +++++++ 7 files changed, 267 insertions(+), 228 deletions(-) create mode 100644 nullaway/src/main/java/com/uber/nullaway/generics/CompareNullabilityVisitor.java create mode 100644 nullaway/src/main/java/com/uber/nullaway/generics/GenericTypePrettyPrintingVisitor.java rename nullaway/src/main/java/com/uber/nullaway/{ => generics}/GenericsChecks.java (78%) create mode 100644 nullaway/src/main/java/com/uber/nullaway/generics/PreservedAnnotationTreeVisitor.java diff --git a/nullaway/src/main/java/com/uber/nullaway/ErrorBuilder.java b/nullaway/src/main/java/com/uber/nullaway/ErrorBuilder.java index fb7aee6f70..e60b798a9d 100755 --- a/nullaway/src/main/java/com/uber/nullaway/ErrorBuilder.java +++ b/nullaway/src/main/java/com/uber/nullaway/ErrorBuilder.java @@ -92,7 +92,7 @@ public class ErrorBuilder { * expression into a @NonNull target, and this parameter is the Symbol for that target. * @return the error description */ - Description createErrorDescription( + public Description createErrorDescription( ErrorMessage errorMessage, Description.Builder descriptionBuilder, VisitorState state, diff --git a/nullaway/src/main/java/com/uber/nullaway/NullAway.java b/nullaway/src/main/java/com/uber/nullaway/NullAway.java index ff4ee80a1b..744bdddc42 100644 --- a/nullaway/src/main/java/com/uber/nullaway/NullAway.java +++ b/nullaway/src/main/java/com/uber/nullaway/NullAway.java @@ -92,6 +92,7 @@ import com.uber.nullaway.ErrorMessage.MessageTypes; import com.uber.nullaway.dataflow.AccessPathNullnessAnalysis; import com.uber.nullaway.dataflow.EnclosingEnvironmentNullness; +import com.uber.nullaway.generics.GenericsChecks; import com.uber.nullaway.handlers.Handler; import com.uber.nullaway.handlers.Handlers; import java.util.ArrayList; diff --git a/nullaway/src/main/java/com/uber/nullaway/dataflow/AccessPathNullnessPropagation.java b/nullaway/src/main/java/com/uber/nullaway/dataflow/AccessPathNullnessPropagation.java index 36f35e4513..e6d62cae42 100644 --- a/nullaway/src/main/java/com/uber/nullaway/dataflow/AccessPathNullnessPropagation.java +++ b/nullaway/src/main/java/com/uber/nullaway/dataflow/AccessPathNullnessPropagation.java @@ -35,9 +35,9 @@ import com.sun.tools.javac.code.TypeTag; import com.uber.nullaway.CodeAnnotationInfo; import com.uber.nullaway.Config; -import com.uber.nullaway.GenericsChecks; import com.uber.nullaway.NullabilityUtil; import com.uber.nullaway.Nullness; +import com.uber.nullaway.generics.GenericsChecks; import com.uber.nullaway.handlers.Handler; import com.uber.nullaway.handlers.Handler.NullnessHint; import java.util.HashMap; 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 0000000000..bd34553ab7 --- /dev/null +++ b/nullaway/src/main/java/com/uber/nullaway/generics/CompareNullabilityVisitor.java @@ -0,0 +1,85 @@ +package com.uber.nullaway.generics; + +import com.google.errorprone.VisitorState; +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; + +/** + * 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 { + private final VisitorState state; + + CompareNullabilityVisitor(VisitorState state) { + this.state = state; + } + + @Override + public Boolean visitClassType(Type.ClassType lhsType, Type rhsType) { + 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. + rhsType = (Type.ClassType) types.asSuper(rhsType, lhsType.tsym); + // This is impossible, considering the fact that standard Java subtyping succeeds before + // running NullAway + if (rhsType == null) { + throw new RuntimeException("Did not find supertype of " + rhsType + " matching " + lhsType); + } + List lhsTypeArguments = lhsType.getTypeArguments(); + List rhsTypeArguments = rhsType.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 " + rhsType + " 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 lhsAnnotations = lhsTypeArgument.getAnnotationMirrors(); + // To ensure that we are checking only jspecify nullable annotations + for (Attribute.TypeCompound annotation : lhsAnnotations) { + if (annotation.getAnnotationType().toString().equals(GenericsChecks.NULLABLE_NAME)) { + isLHSNullableAnnotated = true; + break; + } + } + boolean isRHSNullableAnnotated = false; + List rhsAnnotations = rhsTypeArgument.getAnnotationMirrors(); + // To ensure that we are checking only jspecify nullable annotations + for (Attribute.TypeCompound annotation : rhsAnnotations) { + if (annotation.getAnnotationType().toString().equals(GenericsChecks.NULLABLE_NAME)) { + 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) { + Type.ArrayType arrRhsType = (Type.ArrayType) rhsType; + return lhsType.getComponentType().accept(this, arrRhsType.getComponentType()); + } + + @Override + public Boolean visitType(Type t, Type type) { + return true; + } +} diff --git a/nullaway/src/main/java/com/uber/nullaway/generics/GenericTypePrettyPrintingVisitor.java b/nullaway/src/main/java/com/uber/nullaway/generics/GenericTypePrettyPrintingVisitor.java new file mode 100644 index 0000000000..b6c2f9c991 --- /dev/null +++ b/nullaway/src/main/java/com/uber/nullaway/generics/GenericTypePrettyPrintingVisitor.java @@ -0,0 +1,75 @@ +package com.uber.nullaway.generics; + +import static java.util.stream.Collectors.joining; + +import com.google.errorprone.VisitorState; +import com.google.errorprone.util.ASTHelpers; +import com.sun.tools.javac.code.Attribute; +import com.sun.tools.javac.code.BoundKind; +import com.sun.tools.javac.code.Type; +import com.sun.tools.javac.code.Types; + +/** + * A visitor that pretty prints a generic type including its type-use nullability annotations, for + * use in error messages. + * + *

This code is a modified and extended version of code in {@link + * com.google.errorprone.util.Signatures} + */ +final class GenericTypePrettyPrintingVisitor extends Types.DefaultTypeVisitor { + + private final VisitorState state; + + GenericTypePrettyPrintingVisitor(VisitorState state) { + this.state = state; + } + + @Override + public String visitWildcardType(Type.WildcardType t, Void unused) { + // NOTE: we have not tested this code yet as we do not yet support wildcard types + StringBuilder sb = new StringBuilder(); + sb.append(t.kind); + if (t.kind != BoundKind.UNBOUND) { + sb.append(t.type.accept(this, null)); + } + return sb.toString(); + } + + @Override + public String visitClassType(Type.ClassType t, Void s) { + StringBuilder sb = new StringBuilder(); + Type enclosingType = t.getEnclosingType(); + if (!ASTHelpers.isSameType(enclosingType, Type.noType, state)) { + sb.append(enclosingType.accept(this, null)).append('.'); + } + for (Attribute.TypeCompound compound : t.getAnnotationMirrors()) { + sb.append('@'); + sb.append(compound.type.accept(this, null)); + sb.append(' '); + } + sb.append(t.tsym.getSimpleName()); + if (t.getTypeArguments().nonEmpty()) { + sb.append('<'); + sb.append( + t.getTypeArguments().stream().map(a -> a.accept(this, null)).collect(joining(", "))); + sb.append(">"); + } + return sb.toString(); + } + + @Override + public String visitCapturedType(Type.CapturedType t, Void s) { + return t.wildcard.accept(this, null); + } + + @Override + public String visitArrayType(Type.ArrayType t, Void unused) { + // TODO properly print cases like int @Nullable[] + return t.elemtype.accept(this, null) + "[]"; + } + + @Override + public String visitType(Type t, Void s) { + return t.toString(); + } +} diff --git a/nullaway/src/main/java/com/uber/nullaway/GenericsChecks.java b/nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java similarity index 78% rename from nullaway/src/main/java/com/uber/nullaway/GenericsChecks.java rename to nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java index 4870366771..37a56f6d3d 100644 --- a/nullaway/src/main/java/com/uber/nullaway/GenericsChecks.java +++ b/nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java @@ -1,8 +1,7 @@ -package com.uber.nullaway; +package com.uber.nullaway.generics; import static com.google.common.base.Verify.verify; import static com.uber.nullaway.NullabilityUtil.castToNonNull; -import static java.util.stream.Collectors.joining; import com.google.common.base.Preconditions; import com.google.errorprone.VisitorState; @@ -11,7 +10,6 @@ import com.google.errorprone.util.ASTHelpers; import com.sun.source.tree.AnnotatedTypeTree; import com.sun.source.tree.AnnotationTree; -import com.sun.source.tree.ArrayTypeTree; import com.sun.source.tree.AssignmentTree; import com.sun.source.tree.ConditionalExpressionTree; import com.sun.source.tree.ExpressionTree; @@ -22,16 +20,15 @@ import com.sun.source.tree.ParameterizedTypeTree; import com.sun.source.tree.Tree; import com.sun.source.tree.VariableTree; -import com.sun.source.util.SimpleTreeVisitor; import com.sun.source.util.TreePath; import com.sun.tools.javac.code.Attribute; -import com.sun.tools.javac.code.BoundKind; import com.sun.tools.javac.code.Symbol; import com.sun.tools.javac.code.Type; -import com.sun.tools.javac.code.TypeMetadata; -import com.sun.tools.javac.code.Types; -import java.util.ArrayList; -import java.util.Collections; +import com.uber.nullaway.Config; +import com.uber.nullaway.ErrorBuilder; +import com.uber.nullaway.ErrorMessage; +import com.uber.nullaway.NullAway; +import com.uber.nullaway.Nullness; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -41,10 +38,9 @@ /** Methods for performing checks related to generic types and nullability. */ public final class GenericsChecks { - private static final String NULLABLE_NAME = "org.jspecify.annotations.Nullable"; + static final String NULLABLE_NAME = "org.jspecify.annotations.Nullable"; - private static final Supplier NULLABLE_TYPE_SUPPLIER = - Suppliers.typeFromString(NULLABLE_NAME); + static final Supplier NULLABLE_TYPE_SUPPLIER = Suppliers.typeFromString(NULLABLE_NAME); /** Do not instantiate; all methods should be static */ private GenericsChecks() {} @@ -486,159 +482,6 @@ public static void compareGenericTypeParameterNullabilityForCall( } } - /** - * Visitor that is called from compareNullabilityAnnotations which recursively compares the - * Nullability annotations for the nested generic type arguments. 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 static class CompareNullabilityVisitor extends Types.DefaultTypeVisitor { - private final VisitorState state; - - CompareNullabilityVisitor(VisitorState state) { - this.state = state; - } - - @Override - public Boolean visitClassType(Type.ClassType lhsType, Type rhsType) { - 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. - rhsType = (Type.ClassType) types.asSuper(rhsType, lhsType.tsym); - // This is impossible, considering the fact that standard Java subtyping succeeds before - // running NullAway - if (rhsType == null) { - throw new RuntimeException("Did not find supertype of " + rhsType + " matching " + lhsType); - } - List lhsTypeArguments = lhsType.getTypeArguments(); - List rhsTypeArguments = rhsType.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 " + rhsType + " 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 lhsAnnotations = lhsTypeArgument.getAnnotationMirrors(); - // To ensure that we are checking only jspecify nullable annotations - for (Attribute.TypeCompound annotation : lhsAnnotations) { - if (annotation.getAnnotationType().toString().equals(NULLABLE_NAME)) { - isLHSNullableAnnotated = true; - break; - } - } - boolean isRHSNullableAnnotated = false; - List rhsAnnotations = rhsTypeArgument.getAnnotationMirrors(); - // To ensure that we are checking only jspecify nullable annotations - for (Attribute.TypeCompound annotation : rhsAnnotations) { - if (annotation.getAnnotationType().toString().equals(NULLABLE_NAME)) { - 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) { - Type.ArrayType arrRhsType = (Type.ArrayType) rhsType; - return lhsType.getComponentType().accept(this, arrRhsType.getComponentType()); - } - - @Override - public Boolean visitType(Type t, Type type) { - return true; - } - } - - /** - * Visitor For getting the preserved Annotation Type for the nested generic type arguments within - * the ParameterizedTypeTree originally passed to TypeWithPreservedAnnotations method, since these - * nested arguments may not always be ParameterizedTypeTrees and may be of different types for - * e.g. ArrayTypeTree. - */ - public static class PreservedAnnotationTreeVisitor extends SimpleTreeVisitor { - - private final VisitorState state; - - PreservedAnnotationTreeVisitor(VisitorState state) { - this.state = state; - } - - @Override - public Type visitArrayType(ArrayTypeTree tree, Void p) { - Type elemType = tree.getType().accept(this, null); - return new Type.ArrayType(elemType, castToNonNull(ASTHelpers.getType(tree)).tsym); - } - - @Override - public Type visitParameterizedType(ParameterizedTypeTree tree, Void p) { - Type.ClassType type = (Type.ClassType) ASTHelpers.getType(tree); - Preconditions.checkNotNull(type); - Type nullableType = NULLABLE_TYPE_SUPPLIER.get(state); - List typeArguments = tree.getTypeArguments(); - List newTypeArgs = new ArrayList<>(); - for (int i = 0; i < typeArguments.size(); i++) { - AnnotatedTypeTree annotatedType = null; - Tree curTypeArg = typeArguments.get(i); - // If the type argument has an annotation, it will either be an AnnotatedTypeTree, or a - // ParameterizedTypeTree in the case of a nested generic type - if (curTypeArg instanceof AnnotatedTypeTree) { - annotatedType = (AnnotatedTypeTree) curTypeArg; - } else if (curTypeArg instanceof ParameterizedTypeTree - && ((ParameterizedTypeTree) curTypeArg).getType() instanceof AnnotatedTypeTree) { - annotatedType = (AnnotatedTypeTree) ((ParameterizedTypeTree) curTypeArg).getType(); - } - List annotations = - annotatedType != null ? annotatedType.getAnnotations() : Collections.emptyList(); - boolean hasNullableAnnotation = false; - for (AnnotationTree annotation : annotations) { - if (ASTHelpers.isSameType( - nullableType, ASTHelpers.getType(annotation.getAnnotationType()), state)) { - hasNullableAnnotation = true; - break; - } - } - // construct a TypeMetadata object containing a nullability annotation if needed - com.sun.tools.javac.util.List nullableAnnotationCompound = - hasNullableAnnotation - ? com.sun.tools.javac.util.List.from( - Collections.singletonList( - new Attribute.TypeCompound( - nullableType, com.sun.tools.javac.util.List.nil(), null))) - : com.sun.tools.javac.util.List.nil(); - TypeMetadata typeMetadata = - new TypeMetadata(new TypeMetadata.Annotations(nullableAnnotationCompound)); - Type currentTypeArgType = curTypeArg.accept(this, null); - Type newTypeArgType = currentTypeArgType.cloneWithMetadata(typeMetadata); - newTypeArgs.add(newTypeArgType); - } - Type.ClassType finalType = - new Type.ClassType( - type.getEnclosingType(), com.sun.tools.javac.util.List.from(newTypeArgs), type.tsym); - return finalType; - } - - /** By default, just use the type computed by javac */ - @Override - protected Type defaultAction(Tree node, Void unused) { - return castToNonNull(ASTHelpers.getType(node)); - } - } - /** * Checks that type parameter nullability is consistent between an overriding method and the * corresponding overridden method. @@ -733,7 +576,7 @@ private static Type getTypeForSymbol(Symbol symbol, VisitorState state) { } } - static Nullness getGenericMethodReturnTypeNullness( + public static Nullness getGenericMethodReturnTypeNullness( Symbol.MethodSymbol method, @Nullable Type enclosingType, VisitorState state, Config config) { if (enclosingType == null) { // we have no additional information from generics, so return NONNULL (presence of a @Nullable @@ -999,65 +842,6 @@ private static Nullness getTypeNullness(Type type, Config config) { * uses simple names rather than fully-qualified names, and retains all type-use annotations. */ public static String prettyTypeForError(Type type, VisitorState state) { - return type.accept(new PrettyTypeVisitor(state), null); - } - - /** This code is a modified version of code in {@link com.google.errorprone.util.Signatures} */ - private static final class PrettyTypeVisitor extends Types.DefaultTypeVisitor { - - private final VisitorState state; - - PrettyTypeVisitor(VisitorState state) { - this.state = state; - } - - @Override - public String visitWildcardType(Type.WildcardType t, Void unused) { - // NOTE: we have not tested this code yet as we do not yet support wildcard types - StringBuilder sb = new StringBuilder(); - sb.append(t.kind); - if (t.kind != BoundKind.UNBOUND) { - sb.append(t.type.accept(this, null)); - } - return sb.toString(); - } - - @Override - public String visitClassType(Type.ClassType t, Void s) { - StringBuilder sb = new StringBuilder(); - Type enclosingType = t.getEnclosingType(); - if (!ASTHelpers.isSameType(enclosingType, Type.noType, state)) { - sb.append(enclosingType.accept(this, null)).append('.'); - } - for (Attribute.TypeCompound compound : t.getAnnotationMirrors()) { - sb.append('@'); - sb.append(compound.type.accept(this, null)); - sb.append(' '); - } - sb.append(t.tsym.getSimpleName()); - if (t.getTypeArguments().nonEmpty()) { - sb.append('<'); - sb.append( - t.getTypeArguments().stream().map(a -> a.accept(this, null)).collect(joining(", "))); - sb.append(">"); - } - return sb.toString(); - } - - @Override - public String visitCapturedType(Type.CapturedType t, Void s) { - return t.wildcard.accept(this, null); - } - - @Override - public String visitArrayType(Type.ArrayType t, Void unused) { - // TODO properly print cases like int @Nullable[] - return t.elemtype.accept(this, null) + "[]"; - } - - @Override - public String visitType(Type t, Void s) { - return t.toString(); - } + return type.accept(new GenericTypePrettyPrintingVisitor(state), null); } } diff --git a/nullaway/src/main/java/com/uber/nullaway/generics/PreservedAnnotationTreeVisitor.java b/nullaway/src/main/java/com/uber/nullaway/generics/PreservedAnnotationTreeVisitor.java new file mode 100644 index 0000000000..e5971d5c3a --- /dev/null +++ b/nullaway/src/main/java/com/uber/nullaway/generics/PreservedAnnotationTreeVisitor.java @@ -0,0 +1,94 @@ +package com.uber.nullaway.generics; + +import static com.uber.nullaway.NullabilityUtil.castToNonNull; + +import com.google.common.base.Preconditions; +import com.google.errorprone.VisitorState; +import com.google.errorprone.util.ASTHelpers; +import com.sun.source.tree.AnnotatedTypeTree; +import com.sun.source.tree.AnnotationTree; +import com.sun.source.tree.ArrayTypeTree; +import com.sun.source.tree.ParameterizedTypeTree; +import com.sun.source.tree.Tree; +import com.sun.source.util.SimpleTreeVisitor; +import com.sun.tools.javac.code.Attribute; +import com.sun.tools.javac.code.Type; +import com.sun.tools.javac.code.TypeMetadata; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Visitor For getting the preserved Annotation Types for the nested generic type arguments within a + * ParameterizedTypeTree. This is required primarily since javac does not preserve annotations on + * generic type arguments in its types for NewClassTrees. We need a visitor since the nested + * arguments may appear on different kinds of type trees, e.g., ArrayTypeTrees. + */ +public class PreservedAnnotationTreeVisitor extends SimpleTreeVisitor { + + private final VisitorState state; + + PreservedAnnotationTreeVisitor(VisitorState state) { + this.state = state; + } + + @Override + public Type visitArrayType(ArrayTypeTree tree, Void p) { + Type elemType = tree.getType().accept(this, null); + return new Type.ArrayType(elemType, castToNonNull(ASTHelpers.getType(tree)).tsym); + } + + @Override + public Type visitParameterizedType(ParameterizedTypeTree tree, Void p) { + Type.ClassType type = (Type.ClassType) ASTHelpers.getType(tree); + Preconditions.checkNotNull(type); + Type nullableType = GenericsChecks.NULLABLE_TYPE_SUPPLIER.get(state); + List typeArguments = tree.getTypeArguments(); + List newTypeArgs = new ArrayList<>(); + for (int i = 0; i < typeArguments.size(); i++) { + AnnotatedTypeTree annotatedType = null; + Tree curTypeArg = typeArguments.get(i); + // If the type argument has an annotation, it will either be an AnnotatedTypeTree, or a + // ParameterizedTypeTree in the case of a nested generic type + if (curTypeArg instanceof AnnotatedTypeTree) { + annotatedType = (AnnotatedTypeTree) curTypeArg; + } else if (curTypeArg instanceof ParameterizedTypeTree + && ((ParameterizedTypeTree) curTypeArg).getType() instanceof AnnotatedTypeTree) { + annotatedType = (AnnotatedTypeTree) ((ParameterizedTypeTree) curTypeArg).getType(); + } + List annotations = + annotatedType != null ? annotatedType.getAnnotations() : Collections.emptyList(); + boolean hasNullableAnnotation = false; + for (AnnotationTree annotation : annotations) { + if (ASTHelpers.isSameType( + nullableType, ASTHelpers.getType(annotation.getAnnotationType()), state)) { + hasNullableAnnotation = true; + break; + } + } + // construct a TypeMetadata object containing a nullability annotation if needed + com.sun.tools.javac.util.List nullableAnnotationCompound = + hasNullableAnnotation + ? com.sun.tools.javac.util.List.from( + Collections.singletonList( + new Attribute.TypeCompound( + nullableType, com.sun.tools.javac.util.List.nil(), null))) + : com.sun.tools.javac.util.List.nil(); + TypeMetadata typeMetadata = + new TypeMetadata(new TypeMetadata.Annotations(nullableAnnotationCompound)); + Type currentTypeArgType = curTypeArg.accept(this, null); + Type newTypeArgType = currentTypeArgType.cloneWithMetadata(typeMetadata); + newTypeArgs.add(newTypeArgType); + } + Type.ClassType finalType = + new Type.ClassType( + type.getEnclosingType(), com.sun.tools.javac.util.List.from(newTypeArgs), type.tsym); + return finalType; + } + + /** By default, just use the type computed by javac */ + @Override + protected Type defaultAction(Tree node, Void unused) { + return castToNonNull(ASTHelpers.getType(node)); + } +}