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 8bd12bc6d1c2..84df05bb76c9 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 @@ -69,18 +69,8 @@ public class DfaPsiUtil { @NotNull public static Nullness getElementNullability(@Nullable PsiType resultType, @Nullable PsiModifierListOwner owner) { - if (resultType != null) { - for (PsiAnnotation annotation : resultType.getAnnotations()) { - String qualifiedName = annotation.getQualifiedName(); - NullableNotNullManager nnn = NullableNotNullManager.getInstance(annotation.getProject()); - if (nnn.getNullables().contains(qualifiedName)) { - return Nullness.NULLABLE; - } - if (nnn.getNotNulls().contains(qualifiedName)) { - return Nullness.NOT_NULL; - } - } - } + Nullness x = getTypeNullability(resultType); + if (x != Nullness.UNKNOWN) return x; if (owner == null || resultType instanceof PsiPrimitiveType) { return Nullness.UNKNOWN; @@ -109,6 +99,23 @@ public class DfaPsiUtil { return Nullness.UNKNOWN; } + @NotNull + public static Nullness getTypeNullability(@Nullable PsiType type) { + if (type != null) { + for (PsiAnnotation annotation : type.getAnnotations()) { + String qualifiedName = annotation.getQualifiedName(); + NullableNotNullManager nnn = NullableNotNullManager.getInstance(annotation.getProject()); + if (nnn.getNullables().contains(qualifiedName)) { + return Nullness.NULLABLE; + } + if (nnn.getNotNulls().contains(qualifiedName)) { + return Nullness.NOT_NULL; + } + } + } + return Nullness.UNKNOWN; + } + /** * Returns the nullness of functional expression parameter * diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/nullable/NullableStuffInspectionBase.java b/java/java-analysis-impl/src/com/intellij/codeInspection/nullable/NullableStuffInspectionBase.java index 08e7257d722f..f1d0858eb5a5 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/nullable/NullableStuffInspectionBase.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/nullable/NullableStuffInspectionBase.java @@ -19,10 +19,12 @@ import com.intellij.codeInsight.AnnotationUtil; import com.intellij.codeInsight.NullableNotNullManager; import com.intellij.codeInsight.daemon.GroupNames; import com.intellij.codeInsight.daemon.impl.analysis.HighlightControlFlowUtil; +import com.intellij.codeInsight.daemon.impl.analysis.JavaGenericsUtil; import com.intellij.codeInsight.intention.AddAnnotationPsiFix; import com.intellij.codeInsight.intention.impl.AddNotNullAnnotationFix; import com.intellij.codeInspection.*; import com.intellij.codeInspection.dataFlow.DfaPsiUtil; +import com.intellij.codeInspection.dataFlow.Nullness; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.WriteExternalException; @@ -390,6 +392,19 @@ public class NullableStuffInspectionBase extends BaseJavaBatchLocalInspectionToo PsiAnnotation annotation = isDeclaredNotNull == null ? isDeclaredNullable : isDeclaredNotNull; reportPrimitiveType(holder, annotation, annotation, parameter); } + if (parameter.getParent() instanceof PsiForeachStatement) { + PsiExpression iteratedValue = ((PsiForeachStatement)parameter.getParent()).getIteratedValue(); + Nullness itemTypeNullability = DfaPsiUtil.getTypeNullability(iteratedValue == null ? null : JavaGenericsUtil.getCollectionItemType(iteratedValue)); + if (isDeclaredNotNull != null && itemTypeNullability == Nullness.NULLABLE) { + holder.registerProblem(isDeclaredNotNull, "Loop parameter can be null", + new RemoveAnnotationQuickFix(isDeclaredNotNull, null)); + } + else if (isDeclaredNullable != null && itemTypeNullability == Nullness.NOT_NULL) { + holder.registerProblem(isDeclaredNullable, "Loop parameter is always not-null", + new RemoveAnnotationQuickFix(isDeclaredNullable, null)); + } + } + return new Annotated(isDeclaredNotNull != null,isDeclaredNullable != null); } diff --git a/java/java-tests/testData/inspection/nullableProblems/ForeachParameterNullability.java b/java/java-tests/testData/inspection/nullableProblems/ForeachParameterNullability.java new file mode 100644 index 000000000000..5d4268a5c4d3 --- /dev/null +++ b/java/java-tests/testData/inspection/nullableProblems/ForeachParameterNullability.java @@ -0,0 +1,21 @@ +import typeUse.*; +import java.util.*; + +class JC { + + public static Collection<@Nullable Object> getNullableStuff() { + return Collections.emptyList(); + } + public static Collection<@NotNull Object> getNotNullStuff() { + return Collections.emptyList(); + } + + void usage() { + for (@NotNull Object o : getNullableStuff()) { + System.out.println(o.getClass()); + } + for (@Nullable Object o : getNotNullStuff()) { + System.out.println(o.getClass()); + } + } +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/codeInspection/DataFlowInspection8Test.java b/java/java-tests/testSrc/com/intellij/codeInspection/DataFlowInspection8Test.java index 169861bee57c..cd4dec7f967c 100644 --- a/java/java-tests/testSrc/com/intellij/codeInspection/DataFlowInspection8Test.java +++ b/java/java-tests/testSrc/com/intellij/codeInspection/DataFlowInspection8Test.java @@ -26,6 +26,7 @@ import com.intellij.testFramework.IdeaTestUtil; import com.intellij.testFramework.LightProjectDescriptor; import com.intellij.testFramework.PsiTestUtil; import com.intellij.testFramework.fixtures.DefaultLightProjectDescriptor; +import com.intellij.testFramework.fixtures.JavaCodeInsightTestFixture; import org.jetbrains.annotations.NotNull; /** @@ -128,9 +129,13 @@ public class DataFlowInspection8Test extends DataFlowInspectionTestCase { } private void setupCustomAnnotations() { - myFixture.addClass("package foo;\n\nimport java.lang.annotation.*;\n\n@Target({ElementType.TYPE_USE}) public @interface Nullable { }"); - myFixture.addClass("package foo;\n\nimport java.lang.annotation.*;\n\n@Target({ElementType.TYPE_USE}) public @interface NotNull { }"); - setCustomAnnotations(getProject(), myFixture.getTestRootDisposable(), "foo.NotNull", "foo.Nullable"); + setupTypeUseAnnotations("foo", myFixture); + } + + static void setupTypeUseAnnotations(String pkg, JavaCodeInsightTestFixture fixture) { + fixture.addClass("package " + pkg + ";\n\nimport java.lang.annotation.*;\n\n@Target({ElementType.TYPE_USE}) public @interface Nullable { }"); + fixture.addClass("package " + pkg + ";\n\nimport java.lang.annotation.*;\n\n@Target({ElementType.TYPE_USE}) public @interface NotNull { }"); + setCustomAnnotations(fixture.getProject(), fixture.getTestRootDisposable(), pkg + ".NotNull", pkg + ".Nullable"); } static void setCustomAnnotations(Project project, Disposable parentDisposable, String notNull, String nullable) { diff --git a/java/java-tests/testSrc/com/intellij/codeInspection/NullableStuffInspectionTest.java b/java/java-tests/testSrc/com/intellij/codeInspection/NullableStuffInspectionTest.java index 96e934844885..4f95f5cd5494 100644 --- a/java/java-tests/testSrc/com/intellij/codeInspection/NullableStuffInspectionTest.java +++ b/java/java-tests/testSrc/com/intellij/codeInspection/NullableStuffInspectionTest.java @@ -202,4 +202,9 @@ public class NullableStuffInspectionTest extends LightCodeInsightFixtureTestCase doTest(); } + public void testForeachParameterNullability() { + DataFlowInspection8Test.setupTypeUseAnnotations("typeUse", myFixture); + doTest(); + } + } \ No newline at end of file