From 4b457ac3e78dce639694efddac09e87fe019fc60 Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Tue, 18 Nov 2025 17:38:42 +0100 Subject: [PATCH] IDEA-341719 [java-analysis] Nullability annotation on type param doesn't apply to uses of type param GitOrigin-RevId: 6a15d9e40e8d132e01c6a1b0b08a30ac3d33189d --- .../annoPackages/CheckerFrameworkSupport.java | 6 ++++-- .../codeInsight/NullableNotNullManager.java | 13 ++++++++++-- .../util/JavaTypeNullabilityUtil.java | 7 ++++++- .../DefaultNotNullTypeParameterOverrides.java | 21 +++++++++++++++++++ .../codeInspection/CheckerNullityTest.java | 2 +- .../NullableStuffInspectionTest.java | 4 ++++ 6 files changed, 47 insertions(+), 6 deletions(-) create mode 100644 java/java-tests/testData/inspection/nullableProblems/DefaultNotNullTypeParameterOverrides.java diff --git a/java/java-impl/src/com/intellij/codeInsight/annoPackages/CheckerFrameworkSupport.java b/java/java-impl/src/com/intellij/codeInsight/annoPackages/CheckerFrameworkSupport.java index 6b662bca416a..9c3ac258a236 100644 --- a/java/java-impl/src/com/intellij/codeInsight/annoPackages/CheckerFrameworkSupport.java +++ b/java/java-impl/src/com/intellij/codeInsight/annoPackages/CheckerFrameworkSupport.java @@ -42,8 +42,10 @@ final class CheckerFrameworkSupport implements AnnotationPackageSupport { // DefaultQualifier is not applicable to type parameter declarations if (context instanceof PsiTypeParameter) return false; // DefaultQualifier is not applicable to type parameter uses - return !(PsiUtil.getTypeByPsiElement(context) instanceof PsiClassType classType) || - !(classType.resolve() instanceof PsiTypeParameter); + return !(PsiUtil.getTypeByPsiElement(context) instanceof PsiClassType classType && + classType.resolve() instanceof PsiTypeParameter || + context instanceof PsiJavaCodeReferenceElement ref && + ref.resolve() instanceof PsiTypeParameter); }); } return ContextNullabilityInfo.EMPTY; diff --git a/java/java-psi-api/src/com/intellij/codeInsight/NullableNotNullManager.java b/java/java-psi-api/src/com/intellij/codeInsight/NullableNotNullManager.java index b9cef61f966e..3edffda37dbc 100644 --- a/java/java-psi-api/src/com/intellij/codeInsight/NullableNotNullManager.java +++ b/java/java-psi-api/src/com/intellij/codeInsight/NullableNotNullManager.java @@ -200,7 +200,7 @@ public abstract class NullableNotNullManager { for (PsiParameter parameter: superParameters) { NullabilityAnnotationInfo plain = findPlainAnnotation(parameter, false, annotations); // Plain not null annotation is not inherited - if (plain != null) return null; + if (plain != null && !plain.isContainer()) return null; NullabilityAnnotationInfo defaultInfo = findContainerAnnotation(parameter); if (defaultInfo != null) { return defaultInfo.getNullability() == Nullability.NOT_NULL ? defaultInfo.withInheritedFrom(parameter) : null; @@ -259,6 +259,11 @@ public abstract class NullableNotNullManager { Nullability origNullability = holder.getNullability(annotation); return nullabilities.contains(origNullability) ? origNullability : null; } + + @Override + public boolean isWantedNullability(@NotNull Nullability nullability) { + return nullabilities.contains(nullability); + } }; NullabilityAnnotationInfo result = findPlainAnnotation(owner, false, filtered); return result == null || !nullabilities.contains(result.getNullability()) ? null : result; @@ -296,7 +301,7 @@ public abstract class NullableNotNullManager { } if (type == null || type instanceof PsiPrimitiveType) return null; NullabilityAnnotationInfo info = type.getNullability().toNullabilityAnnotationInfo(); - return info != null && annotations.getNullability(info.getAnnotation().getQualifiedName()) != null ? info : null; + return info != null && annotations.isWantedNullability(info.getNullability()) ? info : null; } protected @NotNull Nullability correctNullability(@NotNull Nullability nullability, @NotNull PsiAnnotation annotation) { @@ -479,6 +484,10 @@ public abstract class NullableNotNullManager { * @return nullability */ @Nullable Nullability getNullability(String annotation); + + default boolean isWantedNullability(@NotNull Nullability nullability) { + return true; + } /** * @param map from annotation qualified name to nullability diff --git a/java/java-psi-api/src/com/intellij/util/JavaTypeNullabilityUtil.java b/java/java-psi-api/src/com/intellij/util/JavaTypeNullabilityUtil.java index 42f61cf7fc6e..8c0b58ed79fa 100644 --- a/java/java-psi-api/src/com/intellij/util/JavaTypeNullabilityUtil.java +++ b/java/java-psi-api/src/com/intellij/util/JavaTypeNullabilityUtil.java @@ -82,7 +82,12 @@ public final class JavaTypeNullabilityUtil { PsiElement context = classType.getPsiContext(); return context instanceof PsiJavaCodeReferenceElement && context.getParent() instanceof PsiTypeElement && - context.getParent().getParent() instanceof PsiLocalVariable; + isLocalVariable(context.getParent().getParent()); + } + + private static boolean isLocalVariable(PsiElement element) { + return element instanceof PsiLocalVariable || + element instanceof PsiParameter && !(element.getParent() instanceof PsiParameterList); } /** diff --git a/java/java-tests/testData/inspection/nullableProblems/DefaultNotNullTypeParameterOverrides.java b/java/java-tests/testData/inspection/nullableProblems/DefaultNotNullTypeParameterOverrides.java new file mode 100644 index 000000000000..973ba6f62c0d --- /dev/null +++ b/java/java-tests/testData/inspection/nullableProblems/DefaultNotNullTypeParameterOverrides.java @@ -0,0 +1,21 @@ +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.NotNullByDefault; + +interface Test { + @NotNull + T test(); +} +@NotNullByDefault +class TestImpl implements Test { + + final T value; + + public TestImpl(final T value) { + this.value = value; + } + + @Override + public T test() { + return this.value; + } +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/java/codeInspection/CheckerNullityTest.java b/java/java-tests/testSrc/com/intellij/java/codeInspection/CheckerNullityTest.java index 17073aaea547..5ea84b737047 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInspection/CheckerNullityTest.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInspection/CheckerNullityTest.java @@ -144,7 +144,7 @@ public class CheckerNullityTest extends LightJavaCodeInsightFixtureTestCase { import org.checkerframework.framework.qual.*; @DefaultQualifier(NonNull.class) - interface Test { + interface Test { String test(X x); X test(String x); }"""); diff --git a/java/java-tests/testSrc/com/intellij/java/codeInspection/NullableStuffInspectionTest.java b/java/java-tests/testSrc/com/intellij/java/codeInspection/NullableStuffInspectionTest.java index aec6118caed7..7e372396f436 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInspection/NullableStuffInspectionTest.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInspection/NullableStuffInspectionTest.java @@ -520,4 +520,8 @@ public class NullableStuffInspectionTest extends LightJavaCodeInsightFixtureTest addJSpecifyNullMarked(myFixture); doTest(); } + + public void testDefaultNotNullTypeParameterOverrides() { + doTest(); + } } \ No newline at end of file