[java-inspection] IDEA-392353 JSpecify type parameter nullability is not inherited correctly

(also fix IDEA-392423)

(cherry picked from commit ed6427f21243d69e90b671a38cb34fe8511dce61)

GitOrigin-RevId: 3e4b277c9be6bdb0954663e0c3a679907e13c5b5
This commit is contained in:
Mikhail Pyltsin
2026-08-06 16:08:57 +00:00
committed by intellij-monorepo-bot
parent e713c2f69d
commit 896e2014ab
3 changed files with 46 additions and 1 deletions
@@ -129,7 +129,26 @@ public final class JavaClassSupersImpl extends JavaClassSupers {
if (paramCandidate instanceof PsiTypeParameter && paramCandidate != parameter) {
targetType = outer.substituteWithBoundsPromotion((PsiTypeParameter)paramCandidate);
if (targetType != null && !innerType.getNullability().equals(TypeNullability.UNKNOWN)) {
targetType = targetType.withNullability(innerType.getNullability());
TypeNullability instantiated;
if (PsiUtil.resolveClassInClassTypeOnly(targetType) == paramCandidate) {
// interface Super<T extends @Nullable Object> { void take(@NullnessUnspecified T t); }
// interface Sub<U extends @Nullable Object> extends Super<U> {}
//
// For the `Super` substitutor of `Sub`, the outer substitutor leaves `U` as it is, and then there is
// nothing to instantiate the usage with: the bare `U` it hands back is not written anywhere in code, so it
// has no nullability of its own to report, and instantiating with it would erase the nullable bound of `U`.
instantiated = innerType.getNullability();
}
else {
//initialized with the known type
// Bar<String> getNonNullBar() { <- with String
// return new Bar<>();
// }
//simplified without taking into account the nullability of the captured type
instantiated = innerType.getNullability().instantiatedWith(targetType.getNullability());
}
targetType = targetType.withNullability(instantiated);
}
}
else {
@@ -0,0 +1,19 @@
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
@NullMarked
class ReturnDerivedWithNullableBound {
Bar<String> getNonNullBar() {
return new Bar<>();
}
Foo<String> getNonNullFoo() {
return getNonNullBar();
}
static class Foo<T extends @Nullable Object> {
}
static class Bar<T extends @Nullable Object> extends Foo<T> {
}
}
@@ -455,6 +455,13 @@ public class NullableStuffInspectionTest extends LightJavaCodeInsightFixtureTest
setupTypeUseAnnotations("org.jspecify.annotations", myFixture);
doTest();
}
public void testReturnDerivedWithNullableBound() {
myInspection.REPORT_NOT_NULL_TO_NULLABLE_CONFLICTS_IN_ASSIGNMENTS = true;
addJSpecifyNullMarked(myFixture);
setupTypeUseAnnotations("org.jspecify.annotations", myFixture);
doTest();
}
public void testNullableParameterOverride() {
doTest();