diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaUtil.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaUtil.java index f8f0b17b4e22..ae54ff7817cb 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaUtil.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaUtil.java @@ -166,7 +166,7 @@ public class DfaUtil { if (rc == RunnerResult.OK) { if (hasNulls.get()) { - return Nullness.NULLABLE; + return InferenceFromSourceUtil.suppressNullable(method) ? Nullness.UNKNOWN : Nullness.NULLABLE; } if (hasNotNulls.get() && !hasUnknowns.get()) { return Nullness.NOT_NULL; diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/InferenceFromSourceUtil.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/InferenceFromSourceUtil.java index 998abf2318d2..bbe5573e4d8c 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/InferenceFromSourceUtil.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/InferenceFromSourceUtil.java @@ -72,4 +72,15 @@ public class InferenceFromSourceUtil { return false; } + + static boolean suppressNullable(PsiMethod method) { + if (method.getParameterList().getParametersCount() == 0) return false; + + for (MethodContract contract : ControlFlowAnalyzer.getMethodContracts(method)) { + if (contract.returnValue == MethodContract.ValueConstraint.NULL_VALUE) { + return true; + } + } + return false; + } } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/NullityInference.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/NullityInference.java index ab02b5bf7648..2edf50ea1637 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/NullityInference.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/NullityInference.java @@ -117,7 +117,7 @@ public class NullityInference { }); if (hasNulls.get()) { - return Nullness.NULLABLE; + return InferenceFromSourceUtil.suppressNullable(method) ? Nullness.UNKNOWN : Nullness.NULLABLE; } if (hasErrors.get() || hasUnknowns.get() || delegates.size() > 1) { diff --git a/java/java-tests/testSrc/com/intellij/codeInspection/NullityInferenceFromSourceTestCase.groovy b/java/java-tests/testSrc/com/intellij/codeInspection/NullityInferenceFromSourceTestCase.groovy index ec897f29a759..849d6eb2763b 100644 --- a/java/java-tests/testSrc/com/intellij/codeInspection/NullityInferenceFromSourceTestCase.groovy +++ b/java/java-tests/testSrc/com/intellij/codeInspection/NullityInferenceFromSourceTestCase.groovy @@ -87,10 +87,29 @@ String bar() { return "z"; } assert inferNullity(parse('Object foo() { return () -> { return null; }; }')) == NOT_NULL } + void "test in presence of explicit null contract"() { + assert inferNullity(parse(''' +@Contract("null->null") +Object foo(Object o) { if (o == null) return null; return 2; } +''')) == UNKNOWN + } + void "test in presence of inferred null contract"() { + assert inferNullity(parse(''' +Object foo(Object o) { if (o == null) return null; return 2; } +''')) == UNKNOWN + } + + void "test in presence of fail contract"() { + assert inferNullity(parse(''' +@Contract("null->fail") +Object foo(Object o) { if (o == null) return o.hashCode(); return 2; } +''')) == NOT_NULL + } + protected abstract Nullness inferNullity(PsiMethod method) protected PsiMethod parse(String method) { - return myFixture.addClass("final class Foo { $method }").methods[0] + return myFixture.addClass("import org.jetbrains.annotations.*; final class Foo { $method }").methods[0] } static class LightInferenceTest extends NullityInferenceFromSourceTestCase {