[java-dfa] Support annotation nicknames in getAnnotationNullability()

Fixes IDEA-372080 Invalid null check for array with @Nullable
Also: do not attempt to fill nicknames in dumb mode

GitOrigin-RevId: 7955160a958d9891d382ef2736d4c72fe926dbbf
This commit is contained in:
Tagir Valeev
2025-05-15 16:11:33 +00:00
committed by intellij-monorepo-bot
parent 45b2d9b242
commit 74800bc0b5
4 changed files with 91 additions and 38 deletions
@@ -17,6 +17,7 @@ import com.intellij.openapi.components.State;
import com.intellij.openapi.extensions.ExtensionPointListener;
import com.intellij.openapi.extensions.PluginDescriptor;
import com.intellij.openapi.fileTypes.FileTypeRegistry;
import com.intellij.openapi.project.DumbService;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ProjectFileIndex;
import com.intellij.openapi.roots.ProjectRootManager;
@@ -142,16 +143,7 @@ public class NullableNotNullManagerImpl extends NullableNotNullManager implement
@Override
public @NotNull Optional<Nullability> getAnnotationNullability(String name) {
if (myNotNulls.contains(name)) {
return Optional.of(Nullability.NOT_NULL);
}
if (myNullables.contains(name)) {
return Optional.of(Nullability.NULLABLE);
}
if (myDefaultUnknowns.containsKey(name)) {
return Optional.of(Nullability.UNKNOWN);
}
return Optional.empty();
return Optional.ofNullable(getAllNullabilityAnnotationsWithNickNames().getNullability(name));
}
@Override
@@ -361,41 +353,40 @@ public class NullableNotNullManagerImpl extends NullableNotNullManager implement
@Override
protected @NotNull NullabilityAnnotationDataHolder getAllNullabilityAnnotationsWithNickNames() {
if (DumbService.isDumb(myProject)) {
// Searching for nullability nicknames is not available in the dumb mode
return NullabilityAnnotationDataHolder.fromMap(getNullabilityMap());
}
return CachedValuesManager.getManager(myProject).getCachedValue(myProject, () -> {
Map<String, Nullability> result = new HashMap<>();
for (String qName : myDefaultAll) {
result.put(qName, null);
}
for (String qName : getNotNulls()) {
result.put(qName, Nullability.NOT_NULL);
}
for (String qName : getNullables()) {
result.put(qName, Nullability.NULLABLE);
}
for (String qName : myDefaultUnknowns.keySet()) {
result.put(qName, Nullability.UNKNOWN);
}
Map<String, Nullability> result = getNullabilityMap();
for (PsiClass aClass : getAllNullabilityNickNames()) {
String qName = aClass.getQualifiedName();
if (qName != null) {
result.putIfAbsent(qName, Jsr305Support.getNickNamedNullability(aClass));
}
}
NullabilityAnnotationDataHolder holder = new NullabilityAnnotationDataHolder() {
@Override
public Set<String> qualifiedNames() {
return result.keySet();
}
@Override
public @Nullable Nullability getNullability(String annotation) {
return result.get(annotation);
}
};
NullabilityAnnotationDataHolder holder = NullabilityAnnotationDataHolder.fromMap(result);
return Result.create(holder, PsiModificationTracker.MODIFICATION_COUNT);
});
}
private @NotNull Map<String, Nullability> getNullabilityMap() {
Map<String, Nullability> result = new HashMap<>();
for (String qName : myDefaultAll) {
result.put(qName, null);
}
for (String qName : getNotNulls()) {
result.put(qName, Nullability.NOT_NULL);
}
for (String qName : getNullables()) {
result.put(qName, Nullability.NULLABLE);
}
for (String qName : myDefaultUnknowns.keySet()) {
result.put(qName, Nullability.UNKNOWN);
}
return result;
}
@Override
protected @NotNull Nullability correctNullability(@NotNull Nullability nullability, @NotNull PsiAnnotation annotation) {
if (nullability == Nullability.NOT_NULL && annotation.hasQualifiedName(Jsr305Support.JAVAX_ANNOTATION_NONNULL)) {
@@ -10,10 +10,7 @@ import com.intellij.psi.util.PsiUtil;
import com.intellij.util.ObjectUtils;
import org.jetbrains.annotations.*;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.*;
import java.util.stream.Collectors;
import static com.intellij.codeInsight.AnnotationUtil.*;
@@ -489,5 +486,23 @@ public abstract class NullableNotNullManager {
* @return nullability
*/
@Nullable Nullability getNullability(String annotation);
/**
* @param map from annotation qualified name to nullability
* @return a data holder implementation based on the provided map
*/
static @NotNull NullabilityAnnotationDataHolder fromMap(@NotNull Map<String, Nullability> map) {
return new NullabilityAnnotationDataHolder() {
@Override
public Set<String> qualifiedNames() {
return map.keySet();
}
@Override
public @Nullable Nullability getNullability(String annotation) {
return map.get(annotation);
}
};
}
}
}
@@ -0,0 +1,42 @@
import javax.annotation.Nonnull;
import javax.annotation.meta.TypeQualifierDefault;
import javax.annotation.meta.TypeQualifierNickname;
import javax.annotation.meta.When;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
@Nonnull(when = When.ALWAYS)
@TypeQualifierNickname
@Target({ElementType.TYPE})
@TypeQualifierDefault({
ElementType.METHOD,
ElementType.FIELD,
ElementType.PARAMETER,
ElementType.LOCAL_VARIABLE,
ElementType.TYPE_PARAMETER,
ElementType.TYPE_USE,
})
@interface DefaultNonNull {
}
@Nonnull(when = When.MAYBE)
@TypeQualifierNickname
@Target({
ElementType.METHOD,
ElementType.FIELD,
ElementType.PARAMETER,
ElementType.LOCAL_VARIABLE,
ElementType.TYPE_USE,
})
@interface Nullable {
}
@DefaultNonNull
class Test {
void test(String s) {
if (<warning descr="Condition 's == null' is always 'false'">s == null</warning>) {}
@Nullable String @Nullable [] arr = new String[] {"a", "b", "c", <warning descr="'null' is stored to an array of @NotNull elements">null</warning>};
@Nullable String @Nullable [] arr2 = {"a", "b", "c", null};
@Nullable String @Nullable [] arr3 = new @Nullable String [] {"a", "b", "c", null};
}
}
@@ -179,4 +179,9 @@ public class DataFlowInspection21Test extends DataFlowInspectionTestCase {
setupTypeUseAnnotations("org.jspecify.annotations", myFixture);
doTest();
}
public void testJsr305NicknameAsTypeAnnotation() {
DataFlowInspectionTest.addJavaxNullabilityAnnotations(myFixture);
doTest();
}
}