diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StateMerger.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StateMerger.java index 1fa65263de27..86c87a7af046 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StateMerger.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StateMerger.java @@ -20,9 +20,9 @@ import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.util.Condition; import com.intellij.openapi.util.UnorderedPair; import com.intellij.psi.JavaTokenType; +import com.intellij.util.Function; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.MultiMap; -import gnu.trove.THashSet; import org.jetbrains.annotations.Nullable; import java.util.*; @@ -56,32 +56,25 @@ class StateMerger { for (DfaMemoryStateImpl state : ContainerUtil.concat(statesByFact.get(fact), statesWithNegations)) { statesByUnrelatedFacts.putValue(getUnrelatedFacts(fact, state), state); } - - Set removedStates = ContainerUtil.newIdentityTroveSet(); - List result = ContainerUtil.newArrayList(); + + Replacements replacements = new Replacements(states); for (Set key : statesByUnrelatedFacts.keySet()) { - Collection group = statesByUnrelatedFacts.get(key); - if (group.size() > 1) { - DfaMemoryStateImpl copy = group.iterator().next().createCopy(); - fact.removeFromState(copy); - if (fact.myType == FactType.equality) { - restoreOtherInequalities(fact, group, copy); + final Collection group = statesByUnrelatedFacts.get(key); + final Set unknowns = getAllUnknownVariables(group); + replacements.stripAndMerge(group, new Function() { + @Override + public DfaMemoryStateImpl fun(DfaMemoryStateImpl original) { + DfaMemoryStateImpl copy = withUnknownVariables(original, unknowns); + fact.removeFromState(copy); + if (fact.myType == FactType.equality) { + restoreOtherInequalities(fact, group, copy); + } + return copy; } - mergeUnknowns(copy, group); - - removedStates.addAll(group); - result.add(copy); - } + }); } - if (!result.isEmpty()) { - for (DfaMemoryStateImpl state : states) { - if (!removedStates.contains(state)) { - result.add(state); - } - } - return result; - } + if (replacements.hasMerges()) return replacements.getMergeResult(); } return null; } @@ -129,26 +122,20 @@ class StateMerger { return otherInequalities; } - private static void mergeUnknowns(DfaMemoryStateImpl mergedState, Collection complementaryStates) { - for (DfaMemoryStateImpl removedState : complementaryStates) { - for (DfaVariableValue unknownVar : removedState.getUnknownVariables()) { - mergedState.doFlush(unknownVar, true); - } + private static Set getAllUnknownVariables(Collection complementary) { + final Set toFlush = ContainerUtil.newLinkedHashSet(); + for (DfaMemoryStateImpl removedState : complementary) { + toFlush.addAll(removedState.getUnknownVariables()); } + return toFlush; } - private static List getMergeResult(List statesBeforeMerge, - final THashSet mergedStates, - DfaMemoryStateImpl mergeResult) { - List result = ContainerUtil.newArrayList(); - result.add(mergeResult); - result.addAll(ContainerUtil.filter(statesBeforeMerge, new Condition() { - @Override - public boolean value(DfaMemoryStateImpl state) { - return !mergedStates.contains(state); - } - })); - return result; + private static DfaMemoryStateImpl withUnknownVariables(DfaMemoryStateImpl original, Set toFlush) { + DfaMemoryStateImpl copy = original.createCopy(); + for (DfaVariableValue value : toFlush) { + copy.doFlush(value, true); + } + return copy; } @Nullable @@ -159,6 +146,7 @@ class StateMerger { byHash.putValue(state.getPartialHashCode(false, true), state); } + Replacements replacements = new Replacements(states); for (Integer key : byHash.keySet()) { Collection similarStates = byHash.get(key); if (similarStates.size() < 2) continue; @@ -171,16 +159,11 @@ class StateMerger { return state1.equalsByRelations(state2) && state1.equalsByVariableStates(state2); } }); - if (complementary.size() > 1) { - DfaMemoryStateImpl copy = state1.createCopy(); - mergeUnknowns(copy, complementary); - return getMergeResult(states, ContainerUtil.newIdentityTroveSet(complementary), copy); - } + if (mergeUnknowns(replacements, complementary)) break; } - } - return null; + return replacements.getMergeResult(); } @Nullable @@ -191,10 +174,12 @@ class StateMerger { byHash.putValue(state.getPartialHashCode(false, false), state); } + Replacements replacements = new Replacements(states); for (Integer key : byHash.keySet()) { Collection similarStates = byHash.get(key); if (similarStates.size() < 2) continue; + groupLoop: for (final DfaMemoryStateImpl state1 : similarStates) { ProgressManager.checkCanceled(); for (final DfaVariableValue var : state1.getChangedVariables()) { @@ -205,25 +190,33 @@ class StateMerger { List complementary = ContainerUtil.filter(similarStates, new Condition() { @Override public boolean value(DfaMemoryStateImpl state2) { - return state1.equalsByRelations(state2) && + return state1.equalsByRelations(state2) && areEquivalentModuloVar(state1, state2, var) && areVarStatesEqualModuloNullability(state1, state2, var); } }); - if (complementary.size() > 1) { - DfaMemoryStateImpl copy = state1.createCopy(); - mergeUnknowns(copy, complementary); - return getMergeResult(states, ContainerUtil.newIdentityTroveSet(complementary), copy); - } + if (mergeUnknowns(replacements, complementary)) break groupLoop; } - } - } - return null; + return replacements.getMergeResult(); } - + + private static boolean mergeUnknowns(Replacements replacements, List complementary) { + if (complementary.size() < 2) return false; + + final Set toFlush = getAllUnknownVariables(complementary); + if (toFlush.isEmpty()) return false; + + return replacements.stripAndMerge(complementary, new Function() { + @Override + public DfaMemoryStateImpl fun(DfaMemoryStateImpl original) { + return withUnknownVariables(original, toFlush); + } + }); + } + private boolean areEquivalentModuloVar(DfaMemoryStateImpl state1, DfaMemoryStateImpl state2, DfaVariableValue var) { DfaMemoryStateImpl copy1 = copyWithoutVar(state1, var); DfaMemoryStateImpl copy2 = copyWithoutVar(state2, var); @@ -380,4 +373,51 @@ class StateMerger { } } } + + private static class Replacements { + private final List myAllStates; + private final Set myRemovedStates = ContainerUtil.newIdentityTroveSet(); + private final List myMerged = ContainerUtil.newArrayList(); + + Replacements(List allStates) { + myAllStates = allStates; + } + + boolean hasMerges() { return !myMerged.isEmpty(); } + + @Nullable + List getMergeResult() { + if (hasMerges()) { + List result = ContainerUtil.newArrayList(myMerged); + for (DfaMemoryStateImpl state : myAllStates) { + if (!myRemovedStates.contains(state)) { + result.add(state); + } + } + return result; + } + return null; + } + + boolean stripAndMerge(Collection group, + Function stripper) { + if (group.size() <= 1) return false; + + boolean hasMerges = false; + MultiMap strippedToOriginals = MultiMap.create(); + for (DfaMemoryStateImpl original : group) { + strippedToOriginals.putValue(stripper.fun(original), original); + } + for (Map.Entry> entry : strippedToOriginals.entrySet()) { + Collection merged = entry.getValue(); + if (merged.size() > 1) { + myRemovedStates.addAll(merged); + myMerged.add(entry.getKey()); + hasMerges = true; + } + } + return hasMerges; + } + } + } diff --git a/java/java-tests/testData/codeInsight/completion/normal/DontCastPartiallyInstanceofedQualifier.java b/java/java-tests/testData/codeInsight/completion/normal/DontCastPartiallyInstanceofedQualifier.java new file mode 100644 index 000000000000..ce2b3df46e79 --- /dev/null +++ b/java/java-tests/testData/codeInsight/completion/normal/DontCastPartiallyInstanceofedQualifier.java @@ -0,0 +1,16 @@ +public abstract class Zzza { + + abstract Object getFoo(); + + void foo(Zzza other) { + int a; + if (other.getFoo() instanceof String) { + a = 1; + } else { + a = 2; + } + other.getFoo().subst + } + + +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/completion/NormalCompletionDfaTest.groovy b/java/java-tests/testSrc/com/intellij/codeInsight/completion/NormalCompletionDfaTest.groovy index 4c3cec03ce17..eaa0975f37d0 100644 --- a/java/java-tests/testSrc/com/intellij/codeInsight/completion/NormalCompletionDfaTest.groovy +++ b/java/java-tests/testSrc/com/intellij/codeInsight/completion/NormalCompletionDfaTest.groovy @@ -34,6 +34,7 @@ class NormalCompletionDfaTest extends LightFixtureCompletionTestCase { void testCastTooComplexInstanceofedQualifier() { doAntiTest() } void testDontCastInstanceofedQualifier() { doTest(); } + void testDontCastPartiallyInstanceofedQualifier() { doAntiTest(); } void testQualifierCastingWithUnknownAssignments() { doTest(); } void testQualifierCastingBeforeLt() { doTest(); } void testCastQualifierForPrivateFieldReference() { doTest(); }