-
Notifications
You must be signed in to change notification settings - Fork 329
Update jdk-javac-plugin to contain nested annotations #1432
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
ad3a0f3
4358508
d66e0e4
c2fb5ff
4c9211b
582aade
e7e19be
b21c4d2
0d0f0de
4958718
d83bc26
981ec0c
d5cc811
8606f18
1ae87d2
42b3f41
e8b97f0
997a76b
000f06c
e879332
ad29bce
06c4233
e49e75c
c558007
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,139 @@ | ||
| package com.uber.nullaway.javacplugin; | ||
|
|
||
| import com.google.common.collect.ImmutableList; | ||
| import com.sun.tools.javac.code.Type; | ||
| import com.sun.tools.javac.code.Types; | ||
| import com.uber.nullaway.javacplugin.NestedAnnotationInfo.Annotation; | ||
| import com.uber.nullaway.javacplugin.NestedAnnotationInfo.TypePathEntry; | ||
| import java.util.ArrayDeque; | ||
| import java.util.HashSet; | ||
| import java.util.List; | ||
| import java.util.Set; | ||
| import javax.lang.model.element.AnnotationMirror; | ||
| import javax.lang.model.element.TypeElement; | ||
| import javax.lang.model.type.TypeMirror; | ||
| import org.jspecify.annotations.NullMarked; | ||
| import org.jspecify.annotations.Nullable; | ||
|
|
||
| /** | ||
| * Visitor that traverses a {@link Type} structure to discover and record nested JSpecify | ||
| * annotations. | ||
| * | ||
| * <p>This visitor records annotations that occur on: | ||
| * | ||
| * <ul> | ||
| * <li>Type arguments of parameterized types (e.g. {@code List<@Nullable String>}) | ||
| * <li>Array element types (e.g. {@code @Nullable String[]}) | ||
| * <li>Wildcard bounds (e.g. {@code ? extends @Nullable T}, {@code ? super @NonNull T}) | ||
| * </ul> | ||
| * | ||
| * <p>After the visitor has completed traversal, callers should invoke {@link | ||
| * #getNestedAnnotationInfoSet()} to retrieve the set of collected {@link NestedAnnotationInfo} | ||
| * instances. | ||
| */ | ||
| @NullMarked | ||
| public class CreateNestedAnnotationInfoVisitor | ||
msridhar marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| extends Types.DefaultTypeVisitor<@Nullable Void, @Nullable Void> { | ||
|
|
||
| private final ArrayDeque<TypePathEntry> path; | ||
| private final Set<NestedAnnotationInfo> nestedAnnotationInfoSet; | ||
|
|
||
| private static final String NULLABLE_QNAME = "org.jspecify.annotations.Nullable"; | ||
| private static final String NONNULL_QNAME = "org.jspecify.annotations.NonNull"; | ||
|
|
||
| public CreateNestedAnnotationInfoVisitor() { | ||
| path = new ArrayDeque<>(); | ||
| nestedAnnotationInfoSet = new HashSet<>(); | ||
| } | ||
|
|
||
| @Override | ||
| public @Nullable Void visitClassType(Type.ClassType classType, @Nullable Void unused) { | ||
| // only processes type arguments | ||
| List<Type> typeArguments = classType.getTypeArguments(); | ||
| if (!typeArguments.isEmpty()) { | ||
| for (int idx = 0; idx < typeArguments.size(); idx++) { | ||
| path.addLast(new TypePathEntry(TypePathEntry.Kind.TYPE_ARGUMENT, idx)); | ||
| Type typeArg = typeArguments.get(idx); | ||
| addNestedAnnotationInfo(typeArg); | ||
| typeArg.accept(this, null); | ||
| path.removeLast(); | ||
| } | ||
| } | ||
| return null; | ||
| } | ||
|
|
||
| @Override | ||
| public @Nullable Void visitArrayType(Type.ArrayType arrayType, @Nullable Void unused) { | ||
| path.addLast(new TypePathEntry(TypePathEntry.Kind.ARRAY_ELEMENT, -1)); | ||
| addNestedAnnotationInfo(arrayType.elemtype); | ||
| arrayType.elemtype.accept(this, null); | ||
| path.removeLast(); | ||
| return null; | ||
| } | ||
|
|
||
| @Override | ||
| public @Nullable Void visitWildcardType(Type.WildcardType wildcardTypet, @Nullable Void unused) { | ||
| // Upper Bound (? extends T) | ||
| if (wildcardTypet.getExtendsBound() != null) { | ||
| path.addLast(new TypePathEntry(TypePathEntry.Kind.WILDCARD_BOUND, 0)); | ||
| Type upperBound = wildcardTypet.getExtendsBound(); | ||
| addNestedAnnotationInfo(upperBound); | ||
| upperBound.accept(this, null); | ||
| path.removeLast(); | ||
| } | ||
|
|
||
| // Lower Bound (? super T) | ||
| if (wildcardTypet.getSuperBound() != null) { | ||
| path.addLast(new TypePathEntry(TypePathEntry.Kind.WILDCARD_BOUND, 1)); | ||
| Type lowerBound = wildcardTypet.getSuperBound(); | ||
| addNestedAnnotationInfo(lowerBound); | ||
| lowerBound.accept(this, null); | ||
| path.removeLast(); | ||
| } | ||
|
|
||
| return null; | ||
| } | ||
|
|
||
| @Override | ||
| public @Nullable Void visitType(Type type, @Nullable Void unused) { | ||
| return null; | ||
| } | ||
|
|
||
| public Set<NestedAnnotationInfo> getNestedAnnotationInfoSet() { | ||
| return nestedAnnotationInfoSet; | ||
| } | ||
|
Comment on lines
+102
to
+104
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Consider returning an unmodifiable view of the set.
🤖 Prompt for AI Agents |
||
|
|
||
| private void addNestedAnnotationInfo(Type type) { | ||
| if (hasNullableAnnotation(type)) { | ||
| nestedAnnotationInfoSet.add(new NestedAnnotationInfo(Annotation.NULLABLE, getTypePath())); | ||
| } else if (hasNonNullAnnotation(type)) { | ||
| nestedAnnotationInfoSet.add(new NestedAnnotationInfo(Annotation.NONNULL, getTypePath())); | ||
| } | ||
| } | ||
|
|
||
| private static boolean hasAnnotation(TypeMirror type, String qname) { | ||
| if (type == null) { | ||
| return false; | ||
| } | ||
| for (AnnotationMirror annotation : type.getAnnotationMirrors()) { | ||
| String qualifiedName = | ||
| ((TypeElement) annotation.getAnnotationType().asElement()).getQualifiedName().toString(); | ||
| if (qualifiedName.equals(qname)) { | ||
| return true; | ||
| } | ||
| } | ||
| return false; | ||
| } | ||
|
|
||
| private boolean hasNullableAnnotation(TypeMirror type) { | ||
| return hasAnnotation(type, NULLABLE_QNAME); | ||
| } | ||
|
|
||
| private boolean hasNonNullAnnotation(TypeMirror type) { | ||
| return hasAnnotation(type, NONNULL_QNAME); | ||
| } | ||
|
|
||
| private ImmutableList<TypePathEntry> getTypePath() { | ||
| return ImmutableList.copyOf(path); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| package com.uber.nullaway.javacplugin; | ||
|
|
||
| import com.google.common.collect.ImmutableList; | ||
| import org.jspecify.annotations.NullMarked; | ||
|
|
||
| /** | ||
| * Class to hold information about a nested nullability annotation within a type, including the type | ||
| * of nullability annotation and the type path to reach it. | ||
| * | ||
| * @param annotation the nullability annotation | ||
| * @param typePath the type path to reach the annotation. If empty, the annotation applies to the | ||
| * outermost type. Otherwise, each entry indicates one step in how to navigate to the nested | ||
| * type. | ||
| */ | ||
| @NullMarked | ||
| public record NestedAnnotationInfo(Annotation annotation, ImmutableList<TypePathEntry> typePath) { | ||
msridhar marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| /** | ||
| * Class for a single entry in a type path, indicating how to navigate the "next step" in the type | ||
| * to eventually reach some target type. | ||
| * | ||
| * @param kind the kind of this type path entry | ||
| * @param index the index associated with the kind. For TYPE_ARGUMENT, this is the type argument | ||
| * index. For WILDCARD_BOUND, this is 0 for the upper bound ({@code ? extends Foo}) and 1 for | ||
| * the lower bound ({@code ? super Foo}). For ARRAY_ELEMENT, this is unused and set to -1. | ||
| */ | ||
| public record TypePathEntry(Kind kind, int index) { | ||
|
|
||
| /** Possible nested type kinds for an entry */ | ||
| public enum Kind { | ||
| ARRAY_ELEMENT, | ||
| TYPE_ARGUMENT, | ||
| WILDCARD_BOUND | ||
| } | ||
| } | ||
|
|
||
| /** Possible annotations for nullability */ | ||
| public enum Annotation { | ||
| NULLABLE, | ||
| NONNULL | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.