From c4e72cac0bed6a6795cb53867bb77d45a0fb43b5 Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Tue, 18 Mar 2025 12:55:55 +0100 Subject: [PATCH] [java-dfa] IDEA-358087 Local variable nullability annotations are ignored with default qualifier GitOrigin-RevId: e640517083910df691b58415f7ba706be37223ec --- .../codeInsight/NullableNotNullManager.java | 32 +++++++++++++++++-- .../CheckerLocalVariableOverridesDefault.java | 10 ++++++ .../DataFlowInspection16Test.java | 5 +++ 3 files changed, 44 insertions(+), 3 deletions(-) create mode 100644 java/java-tests/testData/inspection/dataFlow/fixture/CheckerLocalVariableOverridesDefault.java 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 21fbda713781..7dad00de0e14 100644 --- a/java/java-psi-api/src/com/intellij/codeInsight/NullableNotNullManager.java +++ b/java/java-psi-api/src/com/intellij/codeInsight/NullableNotNullManager.java @@ -7,6 +7,7 @@ import com.intellij.openapi.util.Ref; import com.intellij.psi.*; import com.intellij.psi.util.InheritanceUtil; import com.intellij.psi.util.PsiUtil; +import com.intellij.util.ObjectUtils; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -262,8 +263,13 @@ public abstract class NullableNotNullManager { return new NullabilityAnnotationInfo(memberAnno.annotation, nullability, memberAnno.owner == owner ? null : memberAnno.owner, false); } if (type instanceof PsiPrimitiveType) return null; - if (owner instanceof PsiLocalVariable) return null; - return findAnnotationInTypeHierarchy(type, annotations); + NullabilityAnnotationInfo inHierarchy = findAnnotationInTypeHierarchy(type, annotations); + if (inHierarchy != null && + owner instanceof PsiLocalVariable && + !canAnnotateLocals(inHierarchy.getAnnotation().getQualifiedName())) { + return null; + } + return inHierarchy; } protected @NotNull Nullability correctNullability(@NotNull Nullability nullability, @NotNull PsiAnnotation annotation) { @@ -435,10 +441,18 @@ public abstract class NullableNotNullManager { } } if (!(eachType instanceof PsiClassType)) return true; + PsiClassType classType = (PsiClassType)eachType; PsiClass targetClass = PsiUtil.resolveClassInClassTypeOnly(eachType); if (!(targetClass instanceof PsiTypeParameter)) return false; if (targetClass.getExtendsListTypes().length == 0) { - NullabilityAnnotationInfo info = findNullabilityDefault(targetClass, PsiAnnotation.TargetType.TYPE_PARAMETER); + PsiAnnotation.TargetType[] targetType; + PsiModifierListOwner owner = getOwner(classType); + if (owner != null) { + targetType = AnnotationTargetUtil.getTargetsForLocation(owner.getModifierList()); + } else { + targetType = new PsiAnnotation.TargetType[]{PsiAnnotation.TargetType.TYPE_PARAMETER}; + } + NullabilityAnnotationInfo info = findNullabilityDefault(targetClass, targetType); if (info != null) { result.set(info); return false; @@ -448,6 +462,18 @@ public abstract class NullableNotNullManager { }); return result.get(); } + + private static PsiModifierListOwner getOwner(@NotNull PsiClassType classType) { + PsiJavaCodeReferenceElement context = ObjectUtils.tryCast(classType.getPsiContext(), PsiJavaCodeReferenceElement.class); + if (context != null) { + PsiTypeElement typeElement = ObjectUtils.tryCast(context.getParent(), PsiTypeElement.class); + if (typeElement != null) { + PsiModifierListOwner owner = ObjectUtils.tryCast(typeElement.getParent(), PsiModifierListOwner.class); + return owner; + } + } + return null; + } protected interface NullabilityAnnotationDataHolder { /** diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/CheckerLocalVariableOverridesDefault.java b/java/java-tests/testData/inspection/dataFlow/fixture/CheckerLocalVariableOverridesDefault.java new file mode 100644 index 000000000000..7be50653ea6a --- /dev/null +++ b/java/java-tests/testData/inspection/dataFlow/fixture/CheckerLocalVariableOverridesDefault.java @@ -0,0 +1,10 @@ +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.framework.qual.DefaultQualifier; + +@DefaultQualifier(NonNull.class) +final class Main { + public static void main(final String[] args) { + final @Nullable String foo = null; + } +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspection16Test.java b/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspection16Test.java index 4a09f85e3913..38247e85ac1f 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspection16Test.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspection16Test.java @@ -47,6 +47,11 @@ public class DataFlowInspection16Test extends DataFlowInspectionTestCase { doTest(); } + public void testCheckerLocalVariableOverridesDefault() { + addCheckerAnnotations(myFixture); + doTest(); + } + public void testRecordComponentAnnotate() { doTest(); IntentionAction intention = myFixture.findSingleIntention("Annotate record component 'member' as '@Nullable'");