Skip to content
Merged
Show file tree
Hide file tree
Changes from 50 commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
ad3a0f3
to use NestedAnnotationInfo and Immutable classes
haewiful Jan 8, 2026
4358508
add nested annotation fields to JSON file using NestedAnnotationInfo …
haewiful Jan 8, 2026
d66e0e4
Merge branch 'uber:master' into nested-annotations
haewiful Jan 8, 2026
c2fb5ff
minor changes
haewiful Jan 8, 2026
4c9211b
add type adaptor for ImmutableList in AstubxGenerator
haewiful Jan 9, 2026
582aade
change comments
haewiful Jan 9, 2026
e7e19be
clean up
haewiful Jan 9, 2026
b21c4d2
make field private
haewiful Jan 9, 2026
0d0f0de
for consistency
haewiful Jan 9, 2026
4958718
coderabbit suggestions
haewiful Jan 9, 2026
d83bc26
copy NestedAnnotationInfo into project
haewiful Jan 11, 2026
981ec0c
Merge branch 'master' into nested-annotations
msridhar Jan 14, 2026
d5cc811
suggested simplification in CreateNestedAnnotationInfoVisitor
haewiful Jan 14, 2026
8606f18
code review comments
haewiful Jan 14, 2026
1ae87d2
modify array element type test
haewiful Jan 14, 2026
42b3f41
change return type and add getter method
haewiful Jan 14, 2026
e8b97f0
javadoc
haewiful Jan 14, 2026
997a76b
Merge branch 'master' into nested-annotations
msridhar Jan 15, 2026
000f06c
Update jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/C…
haewiful Jan 15, 2026
e879332
Update jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/C…
haewiful Jan 15, 2026
ad29bce
Update jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/N…
haewiful Jan 15, 2026
06c4233
Update jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/N…
haewiful Jan 15, 2026
e49e75c
new test case
haewiful Jan 15, 2026
c558007
Merge branch 'master' into nested-annotations
msridhar Jan 15, 2026
268924b
typo
haewiful Jan 18, 2026
700234e
add nested annotation info to astubx
haewiful Jan 29, 2026
0882dd9
Ensure to include both dependencies and own classes in shadowJar
haewiful Jan 29, 2026
b115f7c
test cases for astubx generator
haewiful Jan 29, 2026
7868e51
do not remove needed whitespaces
haewiful Jan 29, 2026
3660b97
apply changes in MethodAnnotationsRecord
haewiful Jan 29, 2026
b6e3499
ensure deterministic iteration order
haewiful Jan 29, 2026
b0eb077
write nested annotation information to astubx
haewiful Jan 29, 2026
fe84633
read nested annotation info from astubx
haewiful Jan 29, 2026
bd4a3ea
load nested annotation info to LibraryModelsHandler
haewiful Jan 29, 2026
682946a
test method for nested annot in parameters
haewiful Jan 29, 2026
747c0ae
test method for nested annot in return types
haewiful Jan 29, 2026
734b83e
add integration tests
haewiful Jan 29, 2026
db71d25
Merge branch 'master' into nested-annot-astubx
haewiful Jan 29, 2026
7b3df3e
Merge branch 'master' into nested-annot-astubx
msridhar Jan 30, 2026
7a534d3
add dependency for the new NestedAnnotationInfo in jdk-javac-plugin
haewiful Jan 30, 2026
3f11f6b
move NestedAnnotationInfo from jdk-javac-plugin to library-model-gene…
haewiful Jan 31, 2026
d6795b1
Merge branch 'master' into nested-annot-astubx
haewiful Jan 31, 2026
86ed8b5
Merge branch 'master' into nested-annot-astubx
msridhar Feb 1, 2026
0b43ef6
revert changes
haewiful Feb 4, 2026
4c1d5e0
test case for wildcard lower bound
haewiful Feb 4, 2026
e0eb40a
add @NullMarked annotation to class
haewiful Feb 4, 2026
766868b
add missed @Nullable annotations
haewiful Feb 4, 2026
f55c97f
put enum names to keysets
haewiful Feb 4, 2026
5d15a86
change the top-level type of nestedAnnotationInfoCache to Map
haewiful Feb 4, 2026
76206f1
Merge branch 'master' into nested-annot-astubx
haewiful Feb 4, 2026
089021b
coderabbit comment
haewiful Feb 4, 2026
e1c6b22
Merge branch 'master' into nested-annot-astubx
msridhar Feb 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import com.google.common.base.Verify;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSetMultimap;
import com.ibm.wala.cfg.ControlFlowGraph;
import com.ibm.wala.classLoader.CodeScanner;
import com.ibm.wala.classLoader.IClass;
Expand Down Expand Up @@ -457,14 +458,18 @@ private void writeModel(DataOutputStream out) throws IOException {
MethodAnnotationsRecord.create(
nullableReturns.contains(sign) ? ImmutableSet.of("Nullable") : ImmutableSet.of(),
ImmutableSet.of(),
ImmutableMap.copyOf(argAnnotation)));
ImmutableMap.copyOf(argAnnotation),
ImmutableSetMultimap.of()));
nullableReturns.remove(sign);
}
for (String nullableReturnMethodSign : Iterator2Iterable.make(nullableReturns.iterator())) {
methodRecords.put(
nullableReturnMethodSign,
MethodAnnotationsRecord.create(
ImmutableSet.of("Nullable"), ImmutableSet.of(), ImmutableMap.of()));
ImmutableSet.of("Nullable"),
ImmutableSet.of(),
ImmutableMap.of(),
ImmutableSetMultimap.of()));
}
StubxWriter.write(
out,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSetMultimap;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializer;
Expand All @@ -11,6 +12,7 @@
import com.uber.nullaway.javacplugin.NullnessAnnotationSerializer.MethodInfo;
import com.uber.nullaway.javacplugin.NullnessAnnotationSerializer.TypeParamInfo;
import com.uber.nullaway.libmodel.MethodAnnotationsRecord;
import com.uber.nullaway.libmodel.NestedAnnotationInfo;
import com.uber.nullaway.libmodel.StubxWriter;
import java.io.DataOutputStream;
import java.io.File;
Expand Down Expand Up @@ -272,17 +274,24 @@ private static void getMethodRecords(
}
} else {
// remove any spaces in Array types
typeSignature = typeSignature.replace(" ", "");
typeSignature = typeSignature.replace(" []", "[]");
}
argumentList[i] = typeSignature;
}
ImmutableSetMultimap.Builder<Integer, NestedAnnotationInfo> nestedAnnotations =
new ImmutableSetMultimap.Builder<>();
for (Map.Entry<Integer, Set<NestedAnnotationInfo>> nestedInfo :
method.nestedAnnotationsList().entrySet()) {
nestedAnnotations.putAll(nestedInfo.getKey(), nestedInfo.getValue());
}
signatureForMethodRecords += String.join(",", argumentList) + ")";
methodRecords.put(
signatureForMethodRecords,
MethodAnnotationsRecord.create(
returnTypeNullness,
nullableTypeParamBuilder.build(),
ImmutableMap.copyOf(argAnnotation)));
ImmutableMap.copyOf(argAnnotation),
nestedAnnotations.build()));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,18 @@
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.MatcherAssert.assertThat;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSetMultimap;
import com.google.errorprone.BugPattern;
import com.google.errorprone.CompilationTestHelper;
import com.google.errorprone.bugpatterns.BugChecker;
import com.uber.nullaway.libmodel.MethodAnnotationsRecord;
import com.uber.nullaway.libmodel.NestedAnnotationInfo;
import com.uber.nullaway.libmodel.NestedAnnotationInfo.Annotation;
import com.uber.nullaway.libmodel.NestedAnnotationInfo.TypePathEntry;
import com.uber.nullaway.libmodel.NestedAnnotationInfo.TypePathEntry.Kind;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
Expand Down Expand Up @@ -66,7 +72,10 @@ public void nullableReturn() {
ImmutableMap.of(
"AnnotationExample:java.lang.String makeUpperCase(java.lang.String)",
MethodAnnotationsRecord.create(
ImmutableSet.of("Nullable"), ImmutableSet.of(), ImmutableMap.of()));
ImmutableSet.of("Nullable"),
ImmutableSet.of(),
ImmutableMap.of(),
ImmutableSetMultimap.of()));
runTest(expectedMethodRecords, ImmutableMap.of(), ImmutableSet.of("AnnotationExample"));
}

Expand All @@ -92,7 +101,10 @@ public void nullableUpperBound() {
ImmutableMap.of(
"NullableUpperBound:T withAnnotation()",
MethodAnnotationsRecord.create(
ImmutableSet.of("Nullable"), ImmutableSet.of(), ImmutableMap.of()));
ImmutableSet.of("Nullable"),
ImmutableSet.of(),
ImmutableMap.of(),
ImmutableSetMultimap.of()));
ImmutableMap<String, Set<Integer>> expectedNullableUpperBounds =
ImmutableMap.of("NullableUpperBound", ImmutableSet.of(0));
runTest(
Expand Down Expand Up @@ -123,7 +135,10 @@ public void nestedNullableUpperBound() {
ImmutableMap.of(
"ReturnAnnotation.UpperBoundExample:T withAnnotation()",
MethodAnnotationsRecord.create(
ImmutableSet.of("Nullable"), ImmutableSet.of(), ImmutableMap.of()));
ImmutableSet.of("Nullable"),
ImmutableSet.of(),
ImmutableMap.of(),
ImmutableSetMultimap.of()));
ImmutableMap<String, Set<Integer>> expectedNullableUpperBounds =
ImmutableMap.of("ReturnAnnotation.UpperBoundExample", ImmutableSet.of(0));
runTest(
Expand Down Expand Up @@ -181,7 +196,8 @@ public void nullableParameters() {
MethodAnnotationsRecord.create(
ImmutableSet.of(),
ImmutableSet.of(),
ImmutableMap.of(0, ImmutableSet.of("Nullable"))));
ImmutableMap.of(0, ImmutableSet.of("Nullable")),
ImmutableSetMultimap.of()));
runTest(expectedMethodRecords, ImmutableMap.of(), ImmutableSet.of("NullableParameters"));
}

Expand All @@ -208,7 +224,8 @@ public void nullableParametersInNullUnmarkedClass() {
MethodAnnotationsRecord.create(
ImmutableSet.of(),
ImmutableSet.of(),
ImmutableMap.of(0, ImmutableSet.of("Nullable"))));
ImmutableMap.of(0, ImmutableSet.of("Nullable")),
ImmutableSetMultimap.of()));
runTest(expectedMethodRecords, ImmutableMap.of(), ImmutableSet.of());
}

Expand Down Expand Up @@ -237,7 +254,8 @@ public void nullableArrayTypeParameter() {
MethodAnnotationsRecord.create(
ImmutableSet.of(),
ImmutableSet.of(),
ImmutableMap.of(0, ImmutableSet.of("Nullable"))));
ImmutableMap.of(0, ImmutableSet.of("Nullable")),
ImmutableSetMultimap.of()));
runTest(expectedMethodRecords, ImmutableMap.of(), ImmutableSet.of("NullableParameters"));
}

Expand All @@ -261,7 +279,8 @@ public void genericParameter() {
MethodAnnotationsRecord.create(
ImmutableSet.of(),
ImmutableSet.of(),
ImmutableMap.of(0, ImmutableSet.of("Nullable"))));
ImmutableMap.of(0, ImmutableSet.of("Nullable")),
ImmutableSetMultimap.of()));
runTest(expectedMethodRecords, ImmutableMap.of(), ImmutableSet.of("Generic"));
}

Expand All @@ -282,13 +301,49 @@ public void parameterizedTypeArray() {
" public List<@Nullable String>[] nullableIdentity(List<@Nullable String>[] listArray) {",
" return listArray;",
" }",
" public List<? extends @Nullable String> wildcardIdentity(List<? super @Nullable String> listArray) {",
" return null;",
" }",
"}")
.doTest();
ImmutableMap<String, MethodAnnotationsRecord> expectedMethodRecords =
ImmutableMap.of(
"ParameterizedTypeArray:java.util.List<java.lang.String>[] nullableIdentity(java.util.List<java.lang.String>[])",
MethodAnnotationsRecord.create(
ImmutableSet.of(), ImmutableSet.of(), ImmutableMap.of()));
ImmutableSet.of(),
ImmutableSet.of(),
ImmutableMap.of(),
ImmutableSetMultimap.of(
-1,
new NestedAnnotationInfo(
Annotation.NULLABLE,
ImmutableList.of(
new TypePathEntry(Kind.ARRAY_ELEMENT, -1),
new TypePathEntry(Kind.TYPE_ARGUMENT, 0))),
0,
new NestedAnnotationInfo(
Annotation.NULLABLE,
ImmutableList.of(
new TypePathEntry(Kind.ARRAY_ELEMENT, -1),
new TypePathEntry(Kind.TYPE_ARGUMENT, 0))))),
"ParameterizedTypeArray:java.util.List<? extends java.lang.String> wildcardIdentity(java.util.List<? super java.lang.String>)",
MethodAnnotationsRecord.create(
ImmutableSet.of(),
ImmutableSet.of(),
ImmutableMap.of(),
ImmutableSetMultimap.of(
-1,
new NestedAnnotationInfo(
Annotation.NULLABLE,
ImmutableList.of(
new TypePathEntry(Kind.TYPE_ARGUMENT, 0),
new TypePathEntry(Kind.WILDCARD_BOUND, 0))),
0,
new NestedAnnotationInfo(
Annotation.NULLABLE,
ImmutableList.of(
new TypePathEntry(Kind.TYPE_ARGUMENT, 0),
new TypePathEntry(Kind.WILDCARD_BOUND, 1))))));
runTest(expectedMethodRecords, ImmutableMap.of(), ImmutableSet.of("ParameterizedTypeArray"));
}

Expand All @@ -315,7 +370,8 @@ public void primitiveTypeReturn() {
MethodAnnotationsRecord.create(
ImmutableSet.of(),
ImmutableSet.of(),
ImmutableMap.of(0, ImmutableSet.of("Nullable"), 1, ImmutableSet.of("Nullable"))));
ImmutableMap.of(0, ImmutableSet.of("Nullable"), 1, ImmutableSet.of("Nullable")),
ImmutableSetMultimap.of()));
runTest(expectedMethodRecords, ImmutableMap.of(), ImmutableSet.of("PrimitiveType"));
}

Expand All @@ -341,7 +397,8 @@ public void voidReturn() {
MethodAnnotationsRecord.create(
ImmutableSet.of(),
ImmutableSet.of(),
ImmutableMap.of(0, ImmutableSet.of("Nullable"), 1, ImmutableSet.of("Nullable"))));
ImmutableMap.of(0, ImmutableSet.of("Nullable"), 1, ImmutableSet.of("Nullable")),
ImmutableSetMultimap.of()));
runTest(expectedMethodRecords, ImmutableMap.of(), ImmutableSet.of("VoidReturn"));
}

Expand All @@ -361,7 +418,10 @@ public void methodTypeParameter() {
ImmutableMap.of(
"Test:void <K,T>nullableTypeVar(K,T)",
MethodAnnotationsRecord.create(
ImmutableSet.of(), ImmutableSet.of(1), ImmutableMap.of()));
ImmutableSet.of(),
ImmutableSet.of(1),
ImmutableMap.of(),
ImmutableSetMultimap.of()));
runTest(expectedMethodRecords, ImmutableMap.of(), ImmutableSet.of("Test"));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -388,4 +388,95 @@ public void loadLibraryModuleMethodTypeParam() {
"}")
.doTest();
}

@Test
public void loadLibraryModuleMethodReturnTypeNestedAnnotation() {
compilationHelper
.setArgs(
Arrays.asList(
"-d",
temporaryFolder.getRoot().getAbsolutePath(),
"-XepOpt:NullAway:AnnotatedPackages=com.uber",
"-XepOpt:NullAway:JarInferEnabled=true",
"-XepOpt:NullAway:JSpecifyMode=true",
"-XDaddTypeAnnotationsToSymbol=true"))
.addSourceLines(
"Test.java",
"package com.uber;",
"import org.jspecify.annotations.Nullable;",
"import com.uber.nullaway.jdkannotations.ReturnAnnotation;",
"import java.util.*;",
"class Test {",
" void testCall() {",
" // -- Type argument nullability",
" // BUG: Diagnostic contains: incompatible types",
" List<String> typeArg1 = ReturnAnnotation.nestedAnnotTypeArg();",
" List<@Nullable String> typeArg2 = ReturnAnnotation.nestedAnnotTypeArg();",
" // -- Array Element nullability",
" // BUG: Diagnostic contains: incompatible types",
" String[] arrayElement1 = ReturnAnnotation.nestedAnnotArrayElement();",
" // BUG: Diagnostic contains: incompatible types",
" String @Nullable [] arrayElement2 = ReturnAnnotation.nestedAnnotArrayElement();",
" @Nullable String [] arrayElement3 = ReturnAnnotation.nestedAnnotArrayElement();",
" // -- mixed type nullability",
" // BUG: Diagnostic contains: incompatible types",
" List<Integer>[] mixed1 = ReturnAnnotation.nestedAnnotMixed();",
" // BUG: Diagnostic contains: incompatible types",
" @Nullable List<Integer>[] mixed2 = ReturnAnnotation.nestedAnnotMixed();",
" // BUG: Diagnostic contains: incompatible types",
" List<@Nullable Integer>[] mixed3 = ReturnAnnotation.nestedAnnotMixed();",
" @Nullable List<@Nullable Integer>[] mixed4 = ReturnAnnotation.nestedAnnotMixed();",
" }",
"}")
.doTest();
}

@Test
public void loadLibraryModuleMethodParameterTypeNestedAnnotation() {
compilationHelper
.setArgs(
Arrays.asList(
"-d",
temporaryFolder.getRoot().getAbsolutePath(),
"-XepOpt:NullAway:AnnotatedPackages=com.uber",
"-XepOpt:NullAway:JarInferEnabled=true",
"-XepOpt:NullAway:JSpecifyMode=true",
"-XDaddTypeAnnotationsToSymbol=true"))
.addSourceLines(
"Test.java",
"package com.uber;",
"import org.jspecify.annotations.Nullable;",
"import com.uber.nullaway.jdkannotations.ParameterAnnotation;",
"import java.util.*;",
"class Test {",
" void testCall() {",
" // -- Type argument",
" List<@Nullable String> nullableTypeArg = new ArrayList<>();",
" nullableTypeArg.add(\"string\");",
" nullableTypeArg.add(null);",
" List<String> nonNullTypeArg = new ArrayList<>(2);",
" // -- Array type",
" @Nullable String[] nullableArray = new String[] {\"populated\", \"value\", null};",
" String[] nonNullArray = new String[] {\"populated\", \"value\"};",
" // -- Multiple nested annotations",
" List<@Nullable Integer> innerList = new ArrayList<>();",
" innerList.add(null);",
" innerList.add(4);",
" @Nullable List<@Nullable Integer>[] nullableMixed = (@Nullable List<@Nullable Integer>[]) new List<?>[3];",
" nullableMixed[0] = innerList;",
" nullableMixed[0] = null;",
" List<@Nullable Integer>[] nonNullArrayMixed = (List<@Nullable Integer>[]) new List<?>[3];",
" @Nullable List<Integer>[] nonNullTypeArgMixed = (@Nullable List<Integer>[]) new List<?>[3];",
" // === test calls",
" ParameterAnnotation.nestedAnnotations(nullableTypeArg, nullableArray, nullableMixed);",
" // BUG: Diagnostic contains: incompatible types",
" ParameterAnnotation.nestedAnnotations(nonNullTypeArg, nullableArray, nullableMixed);",
" ParameterAnnotation.nestedAnnotations(nullableTypeArg, nonNullArray, nullableMixed);",
" ParameterAnnotation.nestedAnnotations(nullableTypeArg, nullableArray, nonNullArrayMixed);",
" // BUG: Diagnostic contains: incompatible types",
" ParameterAnnotation.nestedAnnotations(nullableTypeArg, nullableArray, nonNullTypeArgMixed);",
" }",
"}")
.doTest();
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.uber.nullaway.jdkannotations;

import java.util.List;
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
Expand Down Expand Up @@ -64,4 +65,9 @@ public static void takesNonNullGenericArray(Generic<String>[] objects) {
}

public static <T> void nonNullTypeParam(T t) {}

public static void nestedAnnotations(
List<@Nullable String> typeArg,
@Nullable String[] array,
@Nullable List<@Nullable Integer>[] mixed) {}
}
Loading