mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[java-dfa] Support type hierarchy for parameters
Fixes IDEA-371133 Missing warning when passing null to parameter of non-nullable generic type GitOrigin-RevId: dff653e1e85d7633b744dc2ffbcdb140ac24fe4e
This commit is contained in:
committed by
intellij-monorepo-bot
parent
9345b37078
commit
b628e41173
@@ -119,15 +119,16 @@ public final class DfaPsiUtil {
|
||||
if (DumbService.isDumb(owner.getProject())) return Nullability.UNKNOWN;
|
||||
NullabilityAnnotationInfo fromAnnotation = getNullabilityFromAnnotation(owner, ignoreParameterNullabilityInference);
|
||||
if (fromAnnotation != null) {
|
||||
if (fromAnnotation.getNullability() != Nullability.NOT_NULL &&
|
||||
owner instanceof PsiMethod method) {
|
||||
PsiType type = method.getReturnType();
|
||||
PsiAnnotationOwner annotationOwner = fromAnnotation.getAnnotation().getOwner();
|
||||
if (PsiUtil.resolveClassInClassTypeOnly(type) instanceof PsiTypeParameter &&
|
||||
annotationOwner instanceof PsiType && annotationOwner != type) {
|
||||
// Nullable/Unknown from type hierarchy: should check the instantiation, as it could be more concrete
|
||||
Nullability fromType = getNullabilityFromType(resultType, owner);
|
||||
if (fromType != null) return fromType;
|
||||
if (fromAnnotation.getNullability() != Nullability.NOT_NULL) {
|
||||
PsiType type = PsiUtil.getTypeByPsiElement(owner);
|
||||
if (type != null) {
|
||||
PsiAnnotationOwner annotationOwner = fromAnnotation.getAnnotation().getOwner();
|
||||
if (PsiUtil.resolveClassInClassTypeOnly(type) instanceof PsiTypeParameter &&
|
||||
annotationOwner instanceof PsiType && annotationOwner != type) {
|
||||
// Nullable/Unknown from type hierarchy: should check the instantiation, as it could be more concrete
|
||||
Nullability fromType = getNullabilityFromType(resultType, owner);
|
||||
if (fromType != null) return fromType;
|
||||
}
|
||||
}
|
||||
}
|
||||
return fromAnnotation.getNullability();
|
||||
@@ -260,7 +261,40 @@ public final class DfaPsiUtil {
|
||||
}
|
||||
|
||||
private static @Nullable NullabilityAnnotationInfo getTypeOwnNullability(@NotNull PsiType eachType, boolean local) {
|
||||
for (PsiAnnotation annotation : eachType.getAnnotations()) {
|
||||
NullabilityAnnotationInfo info = getNullabilityFromAnnotations(eachType.getAnnotations());
|
||||
if (info != null) return info;
|
||||
if (eachType instanceof PsiClassType classType && !local) {
|
||||
PsiElement context = classType.getPsiContext();
|
||||
if (context != null) {
|
||||
NullableNotNullManager manager = NullableNotNullManager.getInstance(context.getProject());
|
||||
if (context instanceof PsiJavaCodeReferenceElement ref && ref.getParent() instanceof PsiTypeElement typeElement) {
|
||||
// Due to the absence of nullability type inference, the annotation that presents directly at context
|
||||
// could be removed from eachType. In this case, using context annotation would be wrong, so we just return null
|
||||
info = getNullabilityFromAnnotations(typeElement.getApplicableAnnotations());
|
||||
}
|
||||
NullabilityAnnotationInfo typeUseNullability = manager.findDefaultTypeUseNullability(context);
|
||||
if (typeUseNullability != null) {
|
||||
if (info != null && info.getNullability() != typeUseNullability.getNullability()) {
|
||||
return null;
|
||||
}
|
||||
return typeUseNullability;
|
||||
}
|
||||
PsiClass declaration = PsiUtil.resolveClassInClassTypeOnly(classType);
|
||||
if (declaration instanceof PsiTypeParameter typeParameter && typeParameter.getExtendsList().getReferenceElements().length == 0) {
|
||||
// If there's no bound, we assume an implicit `extends Object` bound, which is subject to default annotation if any.
|
||||
typeUseNullability = manager.findDefaultTypeUseNullability(declaration);
|
||||
if (info != null && typeUseNullability != null && info.getNullability() != typeUseNullability.getNullability()) {
|
||||
return null;
|
||||
}
|
||||
return typeUseNullability;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static @Nullable NullabilityAnnotationInfo getNullabilityFromAnnotations(PsiAnnotation[] annotations) {
|
||||
for (PsiAnnotation annotation : annotations) {
|
||||
String qualifiedName = annotation.getQualifiedName();
|
||||
NullableNotNullManager nnn = NullableNotNullManager.getInstance(annotation.getProject());
|
||||
Optional<Nullability> optionalNullability = nnn.getAnnotationNullability(qualifiedName);
|
||||
@@ -270,21 +304,6 @@ public final class DfaPsiUtil {
|
||||
return new NullabilityAnnotationInfo(annotation, nullability, false);
|
||||
}
|
||||
}
|
||||
if (eachType instanceof PsiClassType classType && !local) {
|
||||
PsiElement context = classType.getPsiContext();
|
||||
if (context != null) {
|
||||
NullableNotNullManager manager = NullableNotNullManager.getInstance(context.getProject());
|
||||
NullabilityAnnotationInfo typeUseNullability = manager.findDefaultTypeUseNullability(context);
|
||||
if (typeUseNullability != null) {
|
||||
return typeUseNullability;
|
||||
}
|
||||
PsiClass declaration = PsiUtil.resolveClassInClassTypeOnly(classType);
|
||||
if (declaration instanceof PsiTypeParameter typeParameter && typeParameter.getExtendsList().getReferenceElements().length == 0) {
|
||||
// If there's no bound, we assume an implicit `extends Object` bound, which is subject to default annotation if any.
|
||||
return manager.findDefaultTypeUseNullability(declaration);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
import org.jspecify.annotations.NotNull;
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
class Demo {
|
||||
@NullMarked
|
||||
static class Test {
|
||||
static <T extends @Nullable Object> void test(T value) {
|
||||
}
|
||||
}
|
||||
|
||||
void test() {
|
||||
Test.<@NotNull String>test(<warning descr="Passing 'null' argument to parameter annotated as @NotNull">null</warning>);
|
||||
Test.<@Nullable String>test(null);
|
||||
}
|
||||
}
|
||||
@@ -28,8 +28,7 @@ class FromDemo {
|
||||
|
||||
public void test(@Nullable Integer t1, @Nullable Integer t2) {
|
||||
Integer o = new FromDemo().oneOfTwo(t1, t2);
|
||||
// TODO: should not warn
|
||||
if (<warning descr="Condition 'o == null' is always 'false'">o == null</warning>) {
|
||||
if (o == null) {
|
||||
System.out.println("1");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,6 +162,12 @@ public class DataFlowInspection21Test extends DataFlowInspectionTestCase {
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testJSpecifyCallExplicitTypeParameters() {
|
||||
addJSpecifyNullMarked(myFixture);
|
||||
setupTypeUseAnnotations("org.jspecify.annotations", myFixture);
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testJSpecifyGetOrDefault() {
|
||||
addJSpecifyNullMarked(myFixture);
|
||||
setupTypeUseAnnotations("org.jspecify.annotations", myFixture);
|
||||
|
||||
Reference in New Issue
Block a user