Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -360,17 +360,28 @@ public Type visitTypeVar(Type.TypeVar t, Type other) {
* <p>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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,45 @@ 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<T extends @Nullable Object> {
Nested<T> self() {
return this;
}
Nested<? extends @Nullable T> wildcardUpperTypeVariable() {
throw new RuntimeException();
}
}

Nested<? extends String> testDirect(Nested<? extends String> receiver) {
// BUG: Diagnostic contains: incompatible types
return receiver.wildcardUpperTypeVariable();
}

Nested<? extends String> testWithSelf(Nested<? extends String> receiver) {
// BUG: Diagnostic contains: incompatible types
return receiver.self().wildcardUpperTypeVariable();
}

Nested<? extends String> testWithVar(Nested<? extends String> receiver) {
var local = receiver;
// BUG: Diagnostic contains: incompatible types
return local.wildcardUpperTypeVariable();
}
}
""")
.doTest();
}
Comment on lines +435 to +472

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Add Javadoc for this non-trivial regression test.

Proposed fix
   `@Test`
+  /** Verifies nullable bounds are restored for captured extends wildcards across receiver paths. */
   public void wildcardCaptureRestoresConcreteAnnotatedBound() {

As per coding guidelines, “Add Javadoc to every non-trivial method, including private methods.”

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@Test
public void wildcardCaptureRestoresConcreteAnnotatedBound() {
makeHelper()
.addSourceLines(
"Test.java",
"""
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
@NullMarked
class Test {
static class Nested<T extends @Nullable Object> {
Nested<T> self() {
return this;
}
Nested<? extends @Nullable T> wildcardUpperTypeVariable() {
throw new RuntimeException();
}
}
Nested<? extends String> testDirect(Nested<? extends String> receiver) {
// BUG: Diagnostic contains: incompatible types
return receiver.wildcardUpperTypeVariable();
}
Nested<? extends String> testWithSelf(Nested<? extends String> receiver) {
// BUG: Diagnostic contains: incompatible types
return receiver.self().wildcardUpperTypeVariable();
}
Nested<? extends String> testWithVar(Nested<? extends String> receiver) {
var local = receiver;
// BUG: Diagnostic contains: incompatible types
return local.wildcardUpperTypeVariable();
}
}
""")
.doTest();
}
`@Test`
/** Verifies nullable bounds are restored for captured extends wildcards across receiver paths. */
public void wildcardCaptureRestoresConcreteAnnotatedBound() {
makeHelper()
.addSourceLines(
"Test.java",
"""
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
`@NullMarked`
class Test {
static class Nested<T extends `@Nullable` Object> {
Nested<T> self() {
return this;
}
Nested<? extends `@Nullable` T> wildcardUpperTypeVariable() {
throw new RuntimeException();
}
}
Nested<? extends String> testDirect(Nested<? extends String> receiver) {
// BUG: Diagnostic contains: incompatible types
return receiver.wildcardUpperTypeVariable();
}
Nested<? extends String> testWithSelf(Nested<? extends String> receiver) {
// BUG: Diagnostic contains: incompatible types
return receiver.self().wildcardUpperTypeVariable();
}
Nested<? extends String> testWithVar(Nested<? extends String> receiver) {
var local = receiver;
// BUG: Diagnostic contains: incompatible types
return local.wildcardUpperTypeVariable();
}
}
""")
.doTest();
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@nullaway/src/test/java/com/uber/nullaway/jspecify/WildcardTests.java` around
lines 435 - 472, Add Javadoc to the non-trivial test method
wildcardCaptureRestoresConcreteAnnotatedBound, briefly documenting the
wildcard-capture regression scenario it covers and the expected diagnostic
behavior. Leave the test implementation and assertions unchanged.

Source: Coding guidelines


@Test
public void wildcardCaptureReturnWithTypeVariableUpperBound() {
makeHelper()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -584,14 +584,36 @@ public void nestedWildcardWithCapturedTypeVariableBound() {
import org.jspecify.annotations.*;
@NullMarked
public class Test {
NestedAnnots<? extends String> test(

NestedAnnots<? extends String> testDirect(
NestedAnnots<? extends String> receiver) {
// should reject since return type of wildcardUpperTypeVariable
// is modeled to be NestedAnnots<? extends @Nullable T>, which
// here is incompatible with the return type NestedAnnots<? extends String>
// BUG: Diagnostic contains: incompatible types
return receiver.wildcardUpperTypeVariable();
}

NestedAnnots<? extends String> testWithSelf(
NestedAnnots<? extends String> receiver) {
// should reject since return type of wildcardUpperTypeVariable
// is modeled to be NestedAnnots<? extends @Nullable T>, which
// here is incompatible with the return type NestedAnnots<? extends String>
// BUG: Diagnostic contains: incompatible types
return receiver.self().wildcardUpperTypeVariable();
}


NestedAnnots<? extends String> testWithVar(
NestedAnnots<? extends String> receiver) {
var foo = receiver;
// should reject since return type of wildcardUpperTypeVariable
// is modeled to be NestedAnnots<? extends @Nullable T>, which
// here is incompatible with the return type NestedAnnots<? extends String>
// BUG: Diagnostic contains: incompatible types
return foo.wildcardUpperTypeVariable();
}

}
""")
.doTest();
Expand Down
Loading