[java-analysis] Support @UnknownNullability annotation

Fixes IDEA-266098 Constant conditions & exceptions: allow for more fine-grained control regarding NPE

GitOrigin-RevId: 32a7b59a0f2c73b6b293b21a8fc0f837a64c3a89
This commit is contained in:
Tagir Valeev
2021-04-15 08:59:00 +03:00
committed by intellij-monorepo-bot
parent 63ecb6d3e1
commit 3fa435048c
4 changed files with 47 additions and 5 deletions
@@ -17,8 +17,10 @@ class JetBrainsAnnotationSupport implements AnnotationPackageSupport {
return Collections.singletonList(AnnotationUtil.NOT_NULL);
case NULLABLE:
return Collections.singletonList(AnnotationUtil.NULLABLE);
case UNKNOWN:
return Collections.singletonList(AnnotationUtil.UNKNOWN_NULLABILITY);
default:
return Collections.emptyList();
throw new IllegalStateException("Unexpected value: " + nullability);
}
}
}
@@ -21,6 +21,7 @@ import java.util.stream.Stream;
@ApiStatus.NonExtendable
public class AnnotationUtil {
public static final String NULLABLE = "org.jetbrains.annotations.Nullable";
public static final String UNKNOWN_NULLABILITY = "org.jetbrains.annotations.UnknownNullability";
public static final String NOT_NULL = "org.jetbrains.annotations.NotNull";
public static final String NON_NLS = "org.jetbrains.annotations.NonNls";
@@ -220,7 +221,7 @@ public class AnnotationUtil {
AnnotationAndOwner result = findAnnotationAndOwnerInHierarchy(listOwner, annotationNames, skipExternal);
return result == null ? null : result.annotation;
}
static final class AnnotationAndOwner {
final @NotNull PsiModifierListOwner owner;
final @NotNull PsiAnnotation annotation;
@@ -0,0 +1,33 @@
import org.jetbrains.annotations.*;
class UnknownNullabilityTest {
static final String getString() {
return "foo";
}
static final @UnknownNullability String getString2() {
return "foo";
}
static final String getStringNullable() {
return Math.random() > 0.5 ? "foo" : null;
}
static final @UnknownNullability String getStringNullable2() {
return Math.random() > 0.5 ? "foo" : null;
}
void check() {
if (<warning descr="Condition 'getString() == null' is always 'false'">getString() == null</warning>) {}
if (getString2() == null) {}
if (getStringNullable() == null) {}
if (getStringNullable2() == null) {}
}
void deref() {
if (Math.random() > 0.5) getString().trim();
if (Math.random() > 0.5) getString2().trim();
if (Math.random() > 0.5) getStringNullable().<warning descr="Method invocation 'trim' may produce 'NullPointerException'">trim</warning>();
if (Math.random() > 0.5) getStringNullable2().trim();
}
}
@@ -188,7 +188,7 @@ public class DataFlowInspection8Test extends DataFlowInspectionTestCase {
public void testStreamInlining() { doTest(); }
public void testStreamCollectInlining() {
setupTypeUseAnnotations("foo", myFixture);
doTest();
doTest();
}
public void testStreamCollectorInlining() { doTest(); }
public void testStreamToMapInlining() { doTest(); }
@@ -203,7 +203,7 @@ public class DataFlowInspection8Test extends DataFlowInspectionTestCase {
public void testStreamAnyMatchIsNull() { doTest(); }
public void testStreamCustomSumMethod() { doTest(); }
public void testStreamReduceLogicalAnd() { doTest(); }
public void testMapGetWithValueNullability() { doTestWithCustomAnnotations(); }
public void testInferNestedForeachNullability() { doTestWithCustomAnnotations(); }
@@ -251,7 +251,7 @@ public class DataFlowInspection8Test extends DataFlowInspectionTestCase {
doTest();
}
public void testConflictsInInferredTypes() {
public void testConflictsInInferredTypes() {
setupAmbiguousAnnotations("foo", myFixture);
doTest();
}
@@ -330,4 +330,10 @@ public class DataFlowInspection8Test extends DataFlowInspectionTestCase {
doTest();
}
public void testConstantInClosure() { doTest(); }
public void testUnknownNullability() {
myFixture.addClass("package org.jetbrains.annotations;\nimport java.lang.annotation.*;\n" +
"@Target(ElementType.TYPE_USE)\n" +
"public @interface UnknownNullability { }");
doTestWith(insp -> insp.SUGGEST_NULLABLE_ANNOTATIONS = false);
}
}