diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaPsiUtil.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaPsiUtil.java index e927625d364c..82a53b9758a9 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaPsiUtil.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaPsiUtil.java @@ -245,19 +245,7 @@ public final class DfaPsiUtil { public static @Nullable NullabilityAnnotationInfo getTypeNullabilityInfo(@Nullable PsiType type) { if (type == null || type instanceof PsiPrimitiveType) return null; - - TypeNullability nullability = type.getNullability(); - NullabilitySource source = nullability.source(); - if (source instanceof NullabilitySource.ExtendsBound extendsBound) { - source = extendsBound.boundSource(); - } - if (source instanceof NullabilitySource.ExplicitAnnotation ea) { - return new NullabilityAnnotationInfo(ea.annotation(), nullability.nullability(), false); - } - if (source instanceof NullabilitySource.ContainerAnnotation ca) { - return new NullabilityAnnotationInfo(ca.annotation(), nullability.nullability(), true); - } - return null; + return type.getNullability().toNullabilityAnnotationInfo(); } private static boolean shouldIgnoreAnnotation(PsiAnnotation annotation) { 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 22edd3a12dec..e560522d666a 100644 --- a/java/java-psi-api/src/com/intellij/codeInsight/NullableNotNullManager.java +++ b/java/java-psi-api/src/com/intellij/codeInsight/NullableNotNullManager.java @@ -3,11 +3,15 @@ package com.intellij.codeInsight; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Ref; import com.intellij.psi.*; -import com.intellij.psi.util.*; -import com.intellij.util.ObjectUtils; -import org.jetbrains.annotations.*; +import com.intellij.psi.util.CachedValueProvider; +import com.intellij.psi.util.CachedValuesManager; +import com.intellij.psi.util.PsiModificationTracker; +import com.intellij.psi.util.PsiUtil; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.Unmodifiable; import java.util.*; import java.util.stream.Collectors; @@ -259,14 +263,9 @@ public abstract class NullableNotNullManager { } return new NullabilityAnnotationInfo(memberAnno.annotation, nullability, memberAnno.owner == owner ? null : memberAnno.owner, false); } - if (type instanceof PsiPrimitiveType) return null; - NullabilityAnnotationInfo inHierarchy = findAnnotationInTypeHierarchy(type, annotations); - if (inHierarchy != null && - owner instanceof PsiLocalVariable && - !canAnnotateLocals(inHierarchy.getAnnotation().getQualifiedName())) { - return null; - } - return inHierarchy; + if (type == null || type instanceof PsiPrimitiveType) return null; + NullabilityAnnotationInfo info = type.getNullability().toNullabilityAnnotationInfo(); + return info != null && annotations.getNullability(info.getAnnotation().getQualifiedName()) != null ? info : null; } protected @NotNull Nullability correctNullability(@NotNull Nullability nullability, @NotNull PsiAnnotation annotation) { @@ -432,63 +431,6 @@ public abstract class NullableNotNullManager { .getNullability(annotation.getQualifiedName()) != null; } - /** - * @param type type to check - * @param qualifiedNames annotation qualified names of TYPE_USE annotations to look for - * @return found type annotation, or null if not found. For type parameter types upper bound annotations are also checked - */ - @Contract("null, _ -> null") - private @Nullable NullabilityAnnotationInfo findAnnotationInTypeHierarchy(@Nullable PsiType type, - @NotNull NullabilityAnnotationDataHolder qualifiedNames) { - if (type == null) return null; - Ref result = Ref.create(null); - InheritanceUtil.processSuperTypes(type, true, eachType -> { - for (PsiAnnotation annotation : eachType.getAnnotations()) { - String qualifiedName = annotation.getQualifiedName(); - if (qualifiedNames.qualifiedNames().contains(qualifiedName)) { - Nullability nullability = qualifiedNames.getNullability(qualifiedName); - if (nullability != null) { - nullability = correctNullability(nullability, annotation); - result.set(new NullabilityAnnotationInfo(annotation, nullability, false)); - } - return false; - } - } - 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) { - 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; - } - } - return true; - }); - 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 { /** * @return qualified names of all recognized annotations diff --git a/java/java-psi-api/src/com/intellij/codeInsight/TypeNullability.java b/java/java-psi-api/src/com/intellij/codeInsight/TypeNullability.java index 4d62bb505f92..0c0e513826ec 100644 --- a/java/java-psi-api/src/com/intellij/codeInsight/TypeNullability.java +++ b/java/java-psi-api/src/com/intellij/codeInsight/TypeNullability.java @@ -6,6 +6,7 @@ import com.intellij.psi.PsiTypeParameter; import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.Arrays; import java.util.Collection; @@ -172,4 +173,24 @@ public final class TypeNullability { public String toString() { return myNullability + " (" + mySource + ")"; } + + /** + * @return this object in the form of {@link NullabilityAnnotationInfo} if conversion is possible, null otherwise. + */ + public @Nullable NullabilityAnnotationInfo toNullabilityAnnotationInfo() { + NullabilitySource source = source(); + if (source instanceof NullabilitySource.ExtendsBound) { + source = ((NullabilitySource.ExtendsBound)source).boundSource(); + } + if (source instanceof NullabilitySource.MultiSource) { + source = ((NullabilitySource.MultiSource)source).sources().iterator().next(); + } + if (source instanceof NullabilitySource.ExplicitAnnotation) { + return new NullabilityAnnotationInfo(((NullabilitySource.ExplicitAnnotation)source).annotation(), nullability(), false); + } + if (source instanceof NullabilitySource.ContainerAnnotation) { + return new NullabilityAnnotationInfo(((NullabilitySource.ContainerAnnotation)source).annotation(), nullability(), true); + } + return null; + } } diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/JSpecifyIntersectionBound.java b/java/java-tests/testData/inspection/dataFlow/fixture/JSpecifyIntersectionBound.java new file mode 100644 index 000000000000..2d4fc7aec145 --- /dev/null +++ b/java/java-tests/testData/inspection/dataFlow/fixture/JSpecifyIntersectionBound.java @@ -0,0 +1,13 @@ +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +@NullMarked +class Test2 { + T test(@Nullable T t) { + return t; + } + + T test2(@Nullable T t) { + return t; + } +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspection21Test.java b/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspection21Test.java index b071fb825a26..88c174f50db7 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspection21Test.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspection21Test.java @@ -205,6 +205,12 @@ public class DataFlowInspection21Test extends DataFlowInspectionTestCase { doTest(); } + public void testJSpecifyIntersectionBound() { + addJSpecifyNullMarked(myFixture); + setupTypeUseAnnotations("org.jspecify.annotations", myFixture); + doTest(); + } + public void testPassthroughGenericParameter() { doTestWith((dfi, cvi) -> dfi.TREAT_UNKNOWN_MEMBERS_AS_NULLABLE = true); }