[java-inspections] When analyzing annotation conflict, ignore container annotation if non-container is present

Fixes IDEA-369220 JSpecify support - wrong warning when Nullable method is declared in NullMarked scope and overridden


(cherry picked from commit 50163bfda72bd1f583dda644c668b78859fac022)

IJ-CR-159281

GitOrigin-RevId: fc33b9c9f8ae165baba5a70c00b91267e57d68b1
This commit is contained in:
Tagir Valeev
2025-04-02 18:55:19 +02:00
committed by intellij-monorepo-bot
parent 28143970c8
commit a1141237c8
4 changed files with 38 additions and 7 deletions

View File

@@ -262,8 +262,9 @@ public class NullableStuffInspectionBase extends AbstractBaseJavaLocalInspection
anno != annotation && manager.getAnnotationNullability(anno.getQualifiedName()).filter(n -> n != nullability).isPresent();
PsiAnnotation oppositeAnno = ContainerUtil.find(owner.getAnnotations(), filter);
if (oppositeAnno == null && listOwner != null) {
oppositeAnno = manager.findExplicitNullabilityAnnotation(
NullabilityAnnotationInfo result = manager.findNullabilityAnnotationInfo(
listOwner, ContainerUtil.filter(Nullability.values(), n -> n != nullability));
oppositeAnno = result == null || result.isContainer() ? null : result.getAnnotation();
}
if (oppositeAnno != null &&
Objects.equals(AnnotationUtil.getRelatedType(annotation), AnnotationUtil.getRelatedType(oppositeAnno))) {
@@ -693,8 +694,13 @@ public class NullableStuffInspectionBase extends AbstractBaseJavaLocalInspection
static @NotNull Annotated from(@NotNull PsiModifierListOwner owner) {
NullableNotNullManager manager = NullableNotNullManager.getInstance(owner.getProject());
return new Annotated(manager.findExplicitNullabilityAnnotation(owner, Collections.singleton(Nullability.NOT_NULL)),
manager.findExplicitNullabilityAnnotation(owner, Collections.singleton(Nullability.NULLABLE)));
NullabilityAnnotationInfo notNullInfo = manager.findNullabilityAnnotationInfo(owner, Collections.singleton(Nullability.NOT_NULL));
NullabilityAnnotationInfo nullableInfo = manager.findNullabilityAnnotationInfo(owner, Collections.singleton(Nullability.NULLABLE));
PsiAnnotation nullableAnno = notNullInfo == null || (notNullInfo.isContainer() && nullableInfo != null && !nullableInfo.isContainer())
? null : notNullInfo.getAnnotation();
PsiAnnotation notNullAnno = nullableInfo == null || (nullableInfo.isContainer() && notNullInfo != null && !notNullInfo.isContainer())
? null : nullableInfo.getAnnotation();
return new Annotated(nullableAnno, notNullAnno);
}
}

View File

@@ -208,11 +208,11 @@ public abstract class NullableNotNullManager {
}
/**
* @return an annotation (if any) with the given nullability semantics on the given declaration or its type. In case of conflicts,
* @return the annotation info (if any) with the given nullability semantics on the given declaration or its type. In case of conflicts,
* type annotations are preferred.
*/
public @Nullable PsiAnnotation findExplicitNullabilityAnnotation(@NotNull PsiModifierListOwner owner,
@NotNull Collection<Nullability> nullabilities) {
public @Nullable NullabilityAnnotationInfo findNullabilityAnnotationInfo(@NotNull PsiModifierListOwner owner,
@NotNull Collection<Nullability> nullabilities) {
NullabilityAnnotationDataHolder holder = getAllNullabilityAnnotationsWithNickNames();
Set<String> filteredSet =
holder.qualifiedNames().stream().filter(qName -> nullabilities.contains(holder.getNullability(qName))).collect(Collectors.toSet());
@@ -229,7 +229,7 @@ public abstract class NullableNotNullManager {
}
};
NullabilityAnnotationInfo result = findPlainAnnotation(owner, false, filtered);
return result == null || !nullabilities.contains(result.getNullability()) ? null : result.getAnnotation();
return result == null || !nullabilities.contains(result.getNullability()) ? null : result;
}
private @Nullable NullabilityAnnotationInfo findPlainAnnotation(

View File

@@ -0,0 +1,18 @@
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
class Main {
@NullMarked
interface Api {
@Nullable
<T> T call();
}
@NullMarked
static class Impl implements Api {
@Nullable
public <T> T call() {
return null;
}
}
}

View File

@@ -422,6 +422,13 @@ public class NullableStuffInspectionTest extends LightJavaCodeInsightFixtureTest
doTest();
}
public void testOverriddenWithNullMarked() {
myInspection.REPORT_ANNOTATION_NOT_PROPAGATED_TO_OVERRIDERS = true;
DataFlowInspectionTestCase.addJSpecifyNullMarked(myFixture);
DataFlowInspectionTestCase.setupTypeUseAnnotations("org.jspecify.annotations", myFixture);
doTest();
}
public void testNullableParameterOverride() {
doTest();
}