diff --git a/nullaway/src/main/java/com/uber/nullaway/generics/TypeSubstitutionUtils.java b/nullaway/src/main/java/com/uber/nullaway/generics/TypeSubstitutionUtils.java index 436277ea5c..111e43d21b 100644 --- a/nullaway/src/main/java/com/uber/nullaway/generics/TypeSubstitutionUtils.java +++ b/nullaway/src/main/java/com/uber/nullaway/generics/TypeSubstitutionUtils.java @@ -360,17 +360,28 @@ public Type visitTypeVar(Type.TypeVar t, Type other) { *
The corresponding type {@code other} may be an ordinary wildcard because javac can
* capture-convert {@code t} without capture-converting {@code other}. In such cases, the
* annotation on the bound of {@code other} should be restored to the bound of the wildcard
- * corresponding to {@code t}.
+ * corresponding to {@code t}. Alternatively, nested capture conversion can make {@code t} a
+ * captured {@code extends} wildcard while the same position in {@code other} is a non-wildcard
+ * type. In that case, annotations from {@code other} must be restored to the backing wildcard's
+ * upper bound, since wildcard-aware checks use that bound rather than annotations directly on
+ * the captured type.
*/
@Override
public Type visitCapturedType(Type.CapturedType t, Type other) {
Type updated = updateDirectNullabilityAnnotationsForType(t, other);
Type.WildcardType otherWildcard = GenericsUtils.asWildcard(other);
- if (otherWildcard == null) {
+ Type.WildcardType updatedWildcard;
+ if (otherWildcard != null) {
+ updatedWildcard = (Type.WildcardType) t.wildcard.accept(this, otherWildcard);
+ } else if (t.wildcard.kind == BoundKind.EXTENDS) {
+ Type updatedBound = t.wildcard.type.accept(this, other);
+ if (updatedBound == t.wildcard.type) {
+ return updated;
+ }
+ updatedWildcard = TYPE_METADATA_BUILDER.createWildcardType(t.wildcard, updatedBound);
+ } else {
return updated;
}
- Type.WildcardType updatedWildcard =
- (Type.WildcardType) t.wildcard.accept(this, otherWildcard);
if (updatedWildcard == t.wildcard) {
return updated;
}
diff --git a/nullaway/src/test/java/com/uber/nullaway/jspecify/WildcardTests.java b/nullaway/src/test/java/com/uber/nullaway/jspecify/WildcardTests.java
index 951bd9bbd3..34fe87d32c 100644
--- a/nullaway/src/test/java/com/uber/nullaway/jspecify/WildcardTests.java
+++ b/nullaway/src/test/java/com/uber/nullaway/jspecify/WildcardTests.java
@@ -432,6 +432,51 @@ static void test(Holder extends Box<@Nullable String>> holder) {
.doTest();
}
+ @Test
+ public void annotationRestoredFromUpperBoundToCapturedWildcard() {
+ makeHelper()
+ .addSourceLines(
+ "Test.java",
+ """
+ import org.jspecify.annotations.NullMarked;
+ import org.jspecify.annotations.Nullable;
+ @NullMarked
+ class Test {
+ static class Nested