IDEA-385435 [java]: Add inspection detecting effectively non-null param overriding not-annotated one

Merge-request: IJ-MR-190382
Merged-by: Marcin Mikosik <marcin.mikosik@jetbrains.com>

GitOrigin-RevId: 0a755a3eb1749b7971fe42dea0bceaa2df3a6bb7
This commit is contained in:
Marcin Mikosik
2026-02-13 10:13:39 +00:00
committed by intellij-monorepo-bot
parent a498b868a2
commit 38de0d9ee7
5 changed files with 100 additions and 5 deletions
@@ -1145,7 +1145,7 @@ public class NullableStuffInspectionBase extends AbstractBaseJavaLocalInspection
"inspection.nullable.problems.parameter.overrides.NotNull", getPresentableAnnoName(notNullSuper));
}
if (isNotNullParameterOverridingNonAnnotated(nullableManager, parameter, superParameters)) {
NullabilityAnnotationInfo info = nullableManager.findOwnNullabilityInfo(parameter);
NullabilityAnnotationInfo info = nullableManager.findEffectiveNullabilityInfo(parameter);
assert info != null;
PsiAnnotation notNullAnnotation = info.getAnnotation();
boolean physical = PsiTreeUtil.isAncestor(parameter, notNullAnnotation, true);
@@ -1193,8 +1193,8 @@ public class NullableStuffInspectionBase extends AbstractBaseJavaLocalInspection
PsiParameter parameter,
List<? extends PsiParameter> superParameters) {
if (!REPORT_NOTNULL_PARAMETERS_OVERRIDES_NOT_ANNOTATED) return false;
NullabilityAnnotationInfo info = nullableManager.findOwnNullabilityInfo(parameter);
return info != null && info.getNullability() == Nullability.NOT_NULL && !info.isInferred() &&
NullabilityAnnotationInfo info = nullableManager.findEffectiveNullabilityInfo(parameter);
return info != null && info.getNullability() == Nullability.NOT_NULL && info.getInheritedFrom() == null && !info.isInferred() &&
ContainerUtil.exists(superParameters, sp -> isSuperNotAnnotated(nullableManager, parameter, sp));
}
@@ -7,8 +7,8 @@ class Test {
@ParametersAreNonnullByDefault
static class X extends XX {
// Do not warn as ParametersAreNonnullByDefault does not work for overridden parameters
void get(Object x) {
// Warn: parameter is effectively non-null via @ParametersAreNonnullByDefault
void get(Object <warning descr="Parameter annotated @ParametersAreNonnullByDefault should not override non-annotated parameter">x</warning>) {
}
}
@@ -0,0 +1,15 @@
import javax.annotation.ParametersAreNonnullByDefault;
import javax.annotation.Nullable;
class Test {
static class XX {
void get(@Nullable Object t) {}
}
@ParametersAreNonnullByDefault
static class X extends XX {
void get(Object <warning descr="Parameter annotated @ParametersAreNonnullByDefault must not override @Nullable parameter">x</warning>) {
}
}
}
@@ -0,0 +1,66 @@
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
// Direct inheritance tests
class UnmarkedSuper {
void method(String param) {}
void methodWithTwoParams(String first, String second) {}
}
@NullMarked
class NullMarkedSubclass extends UnmarkedSuper {
@Override
void method(String <warning descr="Parameter annotated @NullMarked should not override non-annotated parameter">param</warning>) {}
@Override
void methodWithTwoParams(String <warning descr="Parameter annotated @NullMarked should not override non-annotated parameter">first</warning>,
String <warning descr="Parameter annotated @NullMarked should not override non-annotated parameter">second</warning>) {}
}
@NullMarked
class NullMarkedSubclassWithNullable extends UnmarkedSuper {
@Override
void method(@Nullable String param) {}
}
@NullMarked
class NullMarkedSuper {
void method(String param) {}
}
@NullMarked
class NullMarkedSubclass2 extends NullMarkedSuper {
@Override
void method(String param) {}
}
// Indirect inheritance test
interface UnmarkedInterface {
void interfaceMethod(String param);
}
@NullMarked
class NullMarkedBase {
public void interfaceMethod(String param) {} // effectively non-null via @NullMarked
}
abstract class IndirectSubclass extends NullMarkedBase implements <warning descr="Non-null parameter 'param' in method 'interfaceMethod' from 'NullMarkedBase' should not override non-annotated parameter from 'UnmarkedInterface'">UnmarkedInterface</warning> {}
// Multi-level inheritance test
class UnmarkedBase2 {
void chainMethod(String param) {}
}
class UnmarkedIntermediate extends UnmarkedBase2 {
@Override
void chainMethod(String param) {}
}
@NullMarked
class NullMarkedChainEnd extends UnmarkedIntermediate {
@Override
void chainMethod(String <warning descr="Parameter annotated @NullMarked should not override non-annotated parameter">param</warning>) {}
}
@@ -144,6 +144,13 @@ public class NullableStuffInspectionTest extends LightJavaCodeInsightFixtureTest
doTest();
}
public void testNullMarkedParameterOverridesNotAnnotated() {
myInspection.REPORT_NOTNULL_PARAMETERS_OVERRIDES_NOT_ANNOTATED = true;
addJSpecifyNullMarked(myFixture);
setupTypeUseAnnotations("org.jspecify.annotations", myFixture);
doTest();
}
public void testNotNullByDefaultParameterOverridesNotAnnotated() {
myInspection.REPORT_NOTNULL_PARAMETERS_OVERRIDES_NOT_ANNOTATED = true;
addJavaxNullabilityAnnotations(myFixture);
@@ -151,6 +158,13 @@ public class NullableStuffInspectionTest extends LightJavaCodeInsightFixtureTest
doTest();
}
public void testNotNullByDefaultParameterOverridesNullable() {
myInspection.REPORT_NOTNULL_PARAMETERS_OVERRIDES_NOT_ANNOTATED = true;
addJavaxNullabilityAnnotations(myFixture);
DataFlowInspectionTest.addJavaxDefaultNullabilityAnnotations(myFixture);
doTest();
}
public void testNullableCalledWithNullUnderNotNullByDefault() {
addJavaxNullabilityAnnotations(myFixture);
DataFlowInspectionTest.addJavaxDefaultNullabilityAnnotations(myFixture);