diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inliner/StreamChainInliner.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inliner/StreamChainInliner.java index b9c1a4e64c59..ee192f28b6f7 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inliner/StreamChainInliner.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inliner/StreamChainInliner.java @@ -16,6 +16,7 @@ package com.intellij.codeInspection.dataFlow.inliner; import com.intellij.codeInsight.Nullability; +import com.intellij.codeInsight.daemon.impl.analysis.JavaGenericsUtil; import com.intellij.codeInspection.dataFlow.*; import com.intellij.codeInspection.dataFlow.value.DfaConstValue; import com.intellij.codeInspection.dataFlow.value.DfaUnknownValue; @@ -27,6 +28,7 @@ import com.intellij.psi.util.PsiUtil; import com.intellij.util.ArrayUtil; import com.siyeh.ig.callMatcher.CallMapper; import com.siyeh.ig.callMatcher.CallMatcher; +import com.siyeh.ig.psiutils.ExpectedTypeUtils; import com.siyeh.ig.psiutils.MethodCallUtils; import com.siyeh.ig.psiutils.StreamApiUtil; import org.jetbrains.annotations.NotNull; @@ -49,6 +51,9 @@ public class StreamChainInliner implements CallInliner { private static final CallMatcher MATCH_TERMINAL = instanceCall(JAVA_UTIL_STREAM_BASE_STREAM, "anyMatch", "allMatch", "noneMatch").parameterCount(1); private static final CallMatcher SUM_TERMINAL = instanceCall(JAVA_UTIL_STREAM_BASE_STREAM, "sum", "count").parameterCount(0); + private static final CallMatcher TO_ARRAY_TERMINAL = anyOf( + instanceCall(JAVA_UTIL_STREAM_STREAM, "toArray").parameterCount(0), + instanceCall(JAVA_UTIL_STREAM_STREAM, "toArray").parameterTypes("java.util.function.IntFunction")); private static final CallMatcher OPTIONAL_TERMINAL = anyOf(instanceCall(JAVA_UTIL_STREAM_BASE_STREAM, "min", "max").parameterCount(0), instanceCall(JAVA_UTIL_STREAM_BASE_STREAM, "reduce").parameterCount(1), @@ -117,6 +122,7 @@ public class StreamChainInliner implements CallInliner { .register(SUM_TERMINAL, SumTerminalStep::new) .register(MIN_MAX_TERMINAL, MinMaxTerminalStep::new) .register(OPTIONAL_TERMINAL, OptionalTerminalStep::new) + .register(TO_ARRAY_TERMINAL, ToArrayStep::new) .register(COLLECT_TERMINAL, StreamChainInliner::createTerminalFromCollector); private static final Step NULL_TERMINAL_STEP = new Step(null, null, null) { @@ -558,7 +564,25 @@ public class StreamChainInliner implements CallInliner { @Override boolean expectNotNull() { - return myImmutable; + if (myImmutable) return true; + PsiType collectionType = ExpectedTypeUtils.findExpectedType(myCall, false); + PsiType itemType = JavaGenericsUtil.getCollectionItemType(collectionType, myCall.getResolveScope()); + return DfaPsiUtil.getTypeNullability(itemType) == Nullability.NOT_NULL; + } + } + + static class ToArrayStep extends ToCollectionStep { + ToArrayStep(@NotNull PsiMethodCallExpression call) { + super(call, ArrayUtil.getFirstElement(call.getArgumentList().getExpressions()), false); + } + + @Override + protected void pushInitialValue(CFGBuilder builder) { + if (myFunction == null) { + super.pushInitialValue(builder); + } else { + builder.pushUnknown().invokeFunction(1, myFunction, Nullability.NOT_NULL); + } } } diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/StreamInlining.java b/java/java-tests/testData/inspection/dataFlow/fixture/StreamInlining.java index 639061d83385..12f61c7c0f70 100644 --- a/java/java-tests/testData/inspection/dataFlow/fixture/StreamInlining.java +++ b/java/java-tests/testData/inspection/dataFlow/fixture/StreamInlining.java @@ -235,4 +235,7 @@ public class StreamInlining { } } + void testToArray(List list) { + list.stream().toArray(size -> null); + } } diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/StreamTypeAnnoInlining.java b/java/java-tests/testData/inspection/dataFlow/fixture/StreamTypeAnnoInlining.java new file mode 100644 index 000000000000..46a5eb730d72 --- /dev/null +++ b/java/java-tests/testData/inspection/dataFlow/fixture/StreamTypeAnnoInlining.java @@ -0,0 +1,44 @@ +import foo.NotNull; +import foo.Nullable; + +import java.util.*; +import java.util.function.*; +import java.util.stream.*; + +public class StreamTypeAnnoInlining { + void testToArray() { + @NotNull Object @NotNull[] foo0 = Stream.of("a", "b") + .map(x-> "a".equals(x) ? null : x.toUpperCase()) + .toArray(); + + // IDEA-194697 + @NotNull String @NotNull[] foo = Stream.of("a", "b") + .map(x-> "a".equals(x) ? null : x.toUpperCase()) + .toArray(String[]::new); + + @NotNull String @NotNull[] foo1 = Stream.of("b", "c") + .map(x-> "a".equals(x) ? null : x.toUpperCase()) + .toArray(String[]::new); + + @NotNull String @NotNull[] foo2 = Stream.of("a", "b") + .map(x-> "a".equals(x) ? null : x.toUpperCase()) + .filter(Objects::nonNull) + .toArray(String[]::new); + } + + void testFromList(List<@NotNull String> list) { + list.stream().filter(Objects::nonNull).forEach(System.out::println); + } + + List<@NotNull String> testFromNullableToNotNull(List<@Nullable String> list) { + return list.stream().map(x -> x).collect(Collectors.toList()); + } + + List<@Nullable String> testFromNullableToNullable(List<@Nullable String> list) { + return list.stream().map(x -> x).collect(Collectors.toList()); + } + + List<@NotNull String> testFromUnknownToNotNull(List list) { + return list.stream().map(x -> x).collect(Collectors.toList()); + } +} diff --git a/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspection8Test.java b/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspection8Test.java index 5fe222db30ff..5bb73e9b089a 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspection8Test.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspection8Test.java @@ -197,6 +197,10 @@ public class DataFlowInspection8Test extends DataFlowInspectionTestCase { public void testStreamCollectorInlining() { doTest(); } public void testStreamComparatorInlining() { doTest(); } public void testStreamKnownSource() { doTest(); } + public void testStreamTypeAnnoInlining() { + setupTypeUseAnnotations("foo", myFixture); + doTest(); + } public void testMapGetWithNotNullKeys() { doTestWithCustomAnnotations(); } public void testInferNestedForeachNullability() { doTestWithCustomAnnotations(); }