Refactor DFA completion

1. Separate visitors for normal and smart completion, as problems are quite different
2. Remove custom memory state (exception assignment filtering) and custom DfaInstanceofValue. Instead, custom variable descriptor is used for complex expressions under instanceof or cast
3. Track TypeConstraint always; move to PsiType only when returning result

Should fix many subtle cases when cast-completion was not available
Fixes IDEA-242322 Dfa cast-completion is missing under boolean flag

GitOrigin-RevId: 1246e6ea84f8c359cc13fc805d32e0eef364ee1b
This commit is contained in:
Tagir Valeev
2020-06-02 15:22:34 +07:00
committed by intellij-monorepo-bot
parent 002caf8f57
commit 97e4d3f847
10 changed files with 289 additions and 382 deletions

View File

@@ -0,0 +1,40 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.codeInsight.guess.impl;
import com.intellij.codeInspection.dataFlow.ControlFlowAnalyzer;
import com.intellij.codeInspection.dataFlow.DfaMemoryStateImpl;
import com.intellij.codeInspection.dataFlow.types.DfReferenceType;
import com.intellij.codeInspection.dataFlow.types.DfType;
import com.intellij.codeInspection.dataFlow.value.DfaValueFactory;
import com.intellij.codeInspection.dataFlow.value.DfaVariableValue;
import com.intellij.psi.PsiLambdaExpression;
import com.intellij.psi.PsiParameter;
import org.jetbrains.annotations.NotNull;
/**
* A memory state that may ignore type constraint known from assignment.
* Useful for completion. E.g. if {@code Collection<?> c = new ArrayList<>();} was created
* it might be undesired to suggest List-specific methods on e.g. {@code c.add} completion.
*/
public class AssignmentFilteringMemoryState extends DfaMemoryStateImpl {
/**
* @param factory factory to use
*/
public AssignmentFilteringMemoryState(DfaValueFactory factory) {
super(factory);
}
public AssignmentFilteringMemoryState(AssignmentFilteringMemoryState toCopy) {
super(toCopy);
}
@Override
protected DfType filterDfTypeOnAssignment(DfaVariableValue var, @NotNull DfType dfType) {
if (ControlFlowAnalyzer.isTempVariable(var) || (!(dfType instanceof DfReferenceType)) ||
var.getPsiVariable() instanceof PsiParameter && var.getPsiVariable().getParent().getParent() instanceof PsiLambdaExpression) {
// Pass type normally for synthetic lambda parameter assignment
return dfType;
}
return ((DfReferenceType)dfType).dropTypeConstraint();
}
}

View File

@@ -1,178 +0,0 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.codeInsight.guess.impl;
import com.intellij.codeInsight.JavaPsiEquivalenceUtil;
import com.intellij.codeInspection.dataFlow.ControlFlowAnalyzer;
import com.intellij.codeInspection.dataFlow.DfaMemoryStateImpl;
import com.intellij.codeInspection.dataFlow.types.DfConstantType;
import com.intellij.codeInspection.dataFlow.types.DfReferenceType;
import com.intellij.codeInspection.dataFlow.types.DfType;
import com.intellij.codeInspection.dataFlow.value.*;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.psi.*;
import com.intellij.util.containers.MultiMap;
import it.unimi.dsi.fastutil.Hash;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenCustomHashMap;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
import java.util.Map;
import java.util.Objects;
/**
* @author peter
*/
public final class ExpressionTypeMemoryState extends DfaMemoryStateImpl {
private static final Logger LOG = Logger.getInstance(ExpressionTypeMemoryState.class);
public static final Hash.Strategy<PsiExpression> EXPRESSION_HASHING_STRATEGY = new Hash.Strategy<PsiExpression>() {
@Override
public int hashCode(PsiExpression object) {
if (object == null) {
return 0;
}
else if (object instanceof PsiReferenceExpression) {
return Objects.hashCode(((PsiReferenceExpression)object).getReferenceName()) * 31 + 1;
}
else if (object instanceof PsiMethodCallExpression) {
return Objects.hashCode(((PsiMethodCallExpression)object).getMethodExpression().getReferenceName()) * 31 + 2;
}
return object.getNode().getElementType().hashCode();
}
@Override
public boolean equals(PsiExpression o1, PsiExpression o2) {
if (o1 == o2) {
return true;
}
if (o1 == null || o2 == null) {
return false;
}
if (JavaPsiEquivalenceUtil.areExpressionsEquivalent(o1, o2)) {
if (hashCode(o1) != hashCode(o2)) {
LOG.error("different hashCodes: " + o1 + "; " + o2 + "; " + hashCode(o1) + "!=" + hashCode(o2));
}
return true;
}
return false;
}
};
private final boolean myHonorAssignments;
// may be shared between memory state instances
private MultiMap<PsiExpression, PsiType> myStates;
public ExpressionTypeMemoryState(final DfaValueFactory factory, boolean honorAssignments) {
super(factory);
myHonorAssignments = honorAssignments;
myStates = MultiMap.createSet(new Object2ObjectOpenCustomHashMap<>(EXPRESSION_HASHING_STRATEGY));
}
private ExpressionTypeMemoryState(ExpressionTypeMemoryState toCopy) {
super(toCopy);
myHonorAssignments = toCopy.myHonorAssignments;
myStates = toCopy.myStates;
}
@Override
protected DfType filterDfTypeOnAssignment(DfaVariableValue var, @NotNull DfType dfType) {
if (myHonorAssignments) return dfType;
if (ControlFlowAnalyzer.isTempVariable(var) || (!(dfType instanceof DfReferenceType)) ||
var.getPsiVariable() instanceof PsiParameter && var.getPsiVariable().getParent().getParent() instanceof PsiLambdaExpression) {
// Pass type normally for synthetic lambda parameter assignment
return dfType;
}
return ((DfReferenceType)dfType).dropTypeConstraint();
}
@NotNull
@Override
public DfaMemoryStateImpl createCopy() {
return new ExpressionTypeMemoryState(this);
}
@Override
public boolean isSuperStateOf(DfaMemoryStateImpl that) {
if (!super.isSuperStateOf(that)) {
return false;
}
MultiMap<PsiExpression, PsiType> thatStates = ((ExpressionTypeMemoryState)that).myStates;
if (thatStates == myStates) return true;
for (Map.Entry<PsiExpression, Collection<PsiType>> entry : myStates.entrySet()) {
Collection<PsiType> thisTypes = entry.getValue();
Collection<PsiType> thatTypes = thatStates.get(entry.getKey());
if (!thatTypes.containsAll(thisTypes)) {
return false;
}
}
return true;
}
@Override
public boolean applyCondition(DfaCondition dfaCond) {
if (dfaCond instanceof DfaRelation) {
DfaRelation rel = (DfaRelation)dfaCond;
DfaValue leftOperand = rel.getLeftOperand();
DfaValue rightOperand = rel.getRightOperand();
RelationType relation = rel.getRelation();
if (leftOperand instanceof DfaInstanceofValue && (relation == RelationType.EQ || relation == RelationType.NE)) {
DfaInstanceofValue value = (DfaInstanceofValue)leftOperand;
Boolean val = DfConstantType.getConstantOfType(rightOperand.getDfType(), Boolean.class);
if (val != null) {
boolean negated = (relation == RelationType.EQ) != val;
if (!negated) {
setExpressionType(value.getExpression(), value.getCastType());
}
return super.applyCondition(negated ? value.getRelation().negate() : value.getRelation());
}
}
}
return super.applyCondition(dfaCond);
}
MultiMap<PsiExpression, PsiType> getStates() {
return myStates;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (!super.equals(o)) return false;
ExpressionTypeMemoryState that = (ExpressionTypeMemoryState)o;
return myStates.equals(that.myStates);
}
@Override
public int hashCode() {
int result = super.hashCode();
result = 31 * result + myStates.hashCode();
return result;
}
@Override
public String toString() {
return super.toString() + " states=[" + myStates + "]";
}
void removeExpressionType(@NotNull PsiExpression expression) {
if (myStates.containsKey(expression)) {
MultiMap<PsiExpression, PsiType> oldStates = myStates;
myStates = MultiMap.createSet(new Object2ObjectOpenCustomHashMap<>(EXPRESSION_HASHING_STRATEGY));
for (Map.Entry<PsiExpression, Collection<PsiType>> entry : oldStates.entrySet()) {
if (!EXPRESSION_HASHING_STRATEGY.equals(entry.getKey(), expression)) {
myStates.putValues(entry.getKey(), entry.getValue());
}
}
}
}
void setExpressionType(@NotNull PsiExpression expression, @NotNull PsiType type) {
if (!myStates.get(expression).contains(type)) {
MultiMap<PsiExpression, PsiType> oldStates = myStates;
myStates = MultiMap.createSet(new Object2ObjectOpenCustomHashMap<>(EXPRESSION_HASHING_STRATEGY));
myStates.putAllValues(oldStates);
myStates.putValue(expression, type);
}
}
}

View File

@@ -0,0 +1,91 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.codeInsight.guess.impl;
import com.intellij.codeInsight.JavaPsiEquivalenceUtil;
import com.intellij.codeInspection.dataFlow.value.DfaVariableValue;
import com.intellij.codeInspection.dataFlow.value.VariableDescriptor;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.psi.PsiExpression;
import com.intellij.psi.PsiMethodCallExpression;
import com.intellij.psi.PsiReferenceExpression;
import com.intellij.psi.PsiType;
import it.unimi.dsi.fastutil.Hash;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Objects;
public final class ExpressionVariableDescriptor implements VariableDescriptor {
public static final Hash.Strategy<PsiExpression> EXPRESSION_HASHING_STRATEGY = new PsiExpressionStrategy();
private final @NotNull PsiExpression myExpression;
public ExpressionVariableDescriptor(@NotNull PsiExpression expression) {
myExpression = expression;
}
@Override
public boolean isStable() {
return true;
}
public @NotNull PsiExpression getExpression() {
return myExpression;
}
@Override
public @Nullable PsiType getType(@Nullable DfaVariableValue qualifier) {
return myExpression.getType();
}
@Override
public int hashCode() {
return EXPRESSION_HASHING_STRATEGY.hashCode(myExpression);
}
@Override
public boolean equals(Object obj) {
return obj instanceof ExpressionVariableDescriptor &&
EXPRESSION_HASHING_STRATEGY.equals(myExpression, ((ExpressionVariableDescriptor)obj).myExpression);
}
@Override
public String toString() {
return myExpression.getText();
}
private static class PsiExpressionStrategy implements Hash.Strategy<PsiExpression> {
private static final Logger LOG = Logger.getInstance(PsiExpressionStrategy.class);
@Override
public int hashCode(PsiExpression object) {
if (object == null) {
return 0;
}
else if (object instanceof PsiReferenceExpression) {
return Objects.hashCode(((PsiReferenceExpression)object).getReferenceName()) * 31 + 1;
}
else if (object instanceof PsiMethodCallExpression) {
return Objects.hashCode(((PsiMethodCallExpression)object).getMethodExpression().getReferenceName()) * 31 + 2;
}
return object.getNode().getElementType().hashCode();
}
@Override
public boolean equals(PsiExpression o1, PsiExpression o2) {
if (o1 == o2) {
return true;
}
if (o1 == null || o2 == null) {
return false;
}
if (JavaPsiEquivalenceUtil.areExpressionsEquivalent(o1, o2)) {
if (hashCode(o1) != hashCode(o2)) {
LOG.error("different hashCodes: " + o1 + "; " + o2 + "; " + hashCode(o1) + "!=" + hashCode(o2));
}
return true;
}
return false;
}
}
}

View File

@@ -4,12 +4,7 @@ package com.intellij.codeInsight.guess.impl;
import com.intellij.codeInsight.guess.GuessManager;
import com.intellij.codeInspection.dataFlow.*;
import com.intellij.codeInspection.dataFlow.instructions.*;
import com.intellij.codeInspection.dataFlow.types.DfReferenceType;
import com.intellij.codeInspection.dataFlow.types.DfType;
import com.intellij.codeInspection.dataFlow.value.DfaCondition;
import com.intellij.codeInspection.dataFlow.value.DfaInstanceofValue;
import com.intellij.codeInspection.dataFlow.value.DfaValue;
import com.intellij.codeInspection.dataFlow.value.RelationType;
import com.intellij.codeInspection.dataFlow.value.*;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.*;
@@ -27,7 +22,6 @@ import com.intellij.util.containers.MultiMap;
import com.siyeh.ig.callMatcher.CallMatcher;
import com.siyeh.ig.psiutils.ExpressionUtils;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenCustomHashMap;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -118,39 +112,80 @@ public final class GuessManagerImpl extends GuessManager {
@NotNull
@Override
public MultiMap<PsiExpression, PsiType> getControlFlowExpressionTypes(@NotNull PsiExpression forPlace, boolean honorAssignments) {
MultiMap<PsiExpression, PsiType> typeMap = buildDataflowTypeMap(forPlace, false, honorAssignments);
return typeMap != null ? typeMap : MultiMap.empty();
}
@Nullable
private static MultiMap<PsiExpression, PsiType> buildDataflowTypeMap(PsiExpression forPlace, boolean onlyForPlace, boolean honorAssignments) {
PsiType type = forPlace.getType();
PsiElement scope = DfaPsiUtil.getTopmostBlockInSameClass(forPlace);
if (scope == null) {
PsiFile file = forPlace.getContainingFile();
if (!(file instanceof PsiCodeFragment)) {
return MultiMap.empty();
}
scope = file;
}
DataFlowRunner runner = new DataFlowRunner(scope.getProject()) {
@NotNull
@Override
protected DfaMemoryState createMemoryState() {
return new ExpressionTypeMemoryState(getFactory(), honorAssignments);
}
};
DataFlowRunner runner = createRunner(honorAssignments, scope);
TypeConstraint initial = type == null ? null : TypeConstraints.instanceOf(type);
final ExpressionTypeInstructionVisitor visitor = new ExpressionTypeInstructionVisitor(forPlace, onlyForPlace, initial);
final ExpressionTypeInstructionVisitor visitor = new ExpressionTypeInstructionVisitor(forPlace);
if (runner.analyzeMethodWithInlining(scope, visitor) == RunnerResult.OK) {
return visitor.getResult();
}
return MultiMap.empty();
}
@Nullable
private static PsiType getTypeFromDataflow(PsiExpression forPlace, boolean honorAssignments) {
PsiType type = forPlace.getType();
TypeConstraint initial = type == null ? TypeConstraints.TOP : TypeConstraints.instanceOf(type);
PsiElement scope = DfaPsiUtil.getTopmostBlockInSameClass(forPlace);
if (scope == null) {
PsiFile file = forPlace.getContainingFile();
if (!(file instanceof PsiCodeFragment)) {
return null;
}
scope = file;
}
DataFlowRunner runner = createRunner(honorAssignments, scope);
class Visitor extends CastTrackingVisitor {
TypeConstraint constraint = TypeConstraints.BOTTOM;
@Override
protected void beforeExpressionPush(@NotNull DfaValue value,
@NotNull PsiExpression expression,
@Nullable TextRange range,
@NotNull DfaMemoryState state) {
if (expression == forPlace && range == null) {
if (!(value instanceof DfaVariableValue) || ((DfaVariableValue)value).isFlushableByCalls()) {
value = runner.getFactory().getVarFactory().createVariableValue(new ExpressionVariableDescriptor(expression));
}
constraint = constraint.join(TypeConstraint.fromDfType(state.getDfType(value)));
}
super.beforeExpressionPush(value, expression, range, state);
}
@Override
boolean isInteresting(@NotNull DfaValue value, @NotNull PsiExpression expression) {
return (!(value instanceof DfaVariableValue) || ((DfaVariableValue)value).isFlushableByCalls()) &&
ExpressionVariableDescriptor.EXPRESSION_HASHING_STRATEGY.equals(expression, forPlace);
}
}
final Visitor visitor = new Visitor();
if (runner.analyzeMethodWithInlining(scope, visitor) == RunnerResult.OK) {
return visitor.constraint.meet(initial).getPsiType(scope.getProject());
}
return null;
}
@NotNull
private static DataFlowRunner createRunner(boolean honorAssignments, PsiElement scope) {
return honorAssignments ? new DataFlowRunner(scope.getProject()) : new DataFlowRunner(scope.getProject()) {
@NotNull
@Override
protected DfaMemoryState createMemoryState() {
return new AssignmentFilteringMemoryState(getFactory());
}
};
}
private static PsiElement getTopmostBlock(PsiElement scope) {
assert scope.isValid();
PsiElement lastScope = scope;
@@ -334,8 +369,16 @@ public final class GuessManagerImpl extends GuessManager {
}
}
if (result == null) {
MultiMap<PsiExpression, PsiType> fromDfa = buildDataflowTypeMap(expr, true, honorAssignments);
result = flattenConjuncts(expr, fromDfa != null ? fromDfa.get(expr) : Collections.emptyList());
PsiType psiType = getTypeFromDataflow(expr, honorAssignments);
if (psiType instanceof PsiIntersectionType) {
result = ContainerUtil.mapNotNull(((PsiIntersectionType)psiType).getConjuncts(), type -> DfaPsiUtil.tryGenerify(expr, type));
}
else if (psiType != null) {
result = Collections.singletonList(DfaPsiUtil.tryGenerify(expr, psiType));
}
else {
result = Collections.emptyList();
}
}
result = ContainerUtil.filter(result, t -> {
PsiClass typeClass = PsiUtil.resolveClassInType(t);
@@ -368,15 +411,6 @@ public final class GuessManagerImpl extends GuessManager {
});
}
@NotNull
private static List<PsiType> flattenConjuncts(@NotNull PsiExpression expr, Collection<PsiType> conjuncts) {
if (!conjuncts.isEmpty()) {
Set<PsiType> flatTypes = PsiIntersectionType.flatten(conjuncts.toArray(PsiType.EMPTY_ARRAY), new LinkedHashSet<>());
return ContainerUtil.mapNotNull(flatTypes, type -> DfaPsiUtil.tryGenerify(expr, type));
}
return Collections.emptyList();
}
private static class GuessTypeVisitor extends JavaElementVisitor {
private static final CallMatcher OBJECT_GET_CLASS =
CallMatcher.exactInstanceCall(CommonClassNames.JAVA_LANG_OBJECT, "getClass").parameterCount(0);
@@ -409,7 +443,7 @@ public final class GuessManagerImpl extends GuessManager {
@Override
public void visitAssignmentExpression(PsiAssignmentExpression expression) {
if (ExpressionTypeMemoryState.EXPRESSION_HASHING_STRATEGY.equals(expression.getLExpression(), myPlace)) {
if (ExpressionVariableDescriptor.EXPRESSION_HASHING_STRATEGY.equals(expression.getLExpression(), myPlace)) {
handleAssignment(expression.getRExpression());
}
super.visitAssignmentExpression(expression);
@@ -427,7 +461,7 @@ public final class GuessManagerImpl extends GuessManager {
@Override
public void visitTypeCastExpression(PsiTypeCastExpression expression) {
PsiExpression operand = expression.getOperand();
if (operand != null && ExpressionTypeMemoryState.EXPRESSION_HASHING_STRATEGY.equals(operand, myPlace)) {
if (operand != null && ExpressionVariableDescriptor.EXPRESSION_HASHING_STRATEGY.equals(operand, myPlace)) {
myNeedDfa = true;
}
super.visitTypeCastExpression(expression);
@@ -437,7 +471,7 @@ public final class GuessManagerImpl extends GuessManager {
public void visitMethodCallExpression(PsiMethodCallExpression call) {
if (OBJECT_GET_CLASS.test(call)) {
PsiExpression qualifier = ExpressionUtils.getEffectiveQualifier(call.getMethodExpression());
if (qualifier != null && ExpressionTypeMemoryState.EXPRESSION_HASHING_STRATEGY.equals(qualifier, myPlace)) {
if (qualifier != null && ExpressionVariableDescriptor.EXPRESSION_HASHING_STRATEGY.equals(qualifier, myPlace)) {
myNeedDfa = true;
}
}
@@ -446,7 +480,7 @@ public final class GuessManagerImpl extends GuessManager {
@Override
public void visitInstanceOfExpression(PsiInstanceOfExpression expression) {
if (ExpressionTypeMemoryState.EXPRESSION_HASHING_STRATEGY.equals(expression.getOperand(), myPlace)) {
if (ExpressionVariableDescriptor.EXPRESSION_HASHING_STRATEGY.equals(expression.getOperand(), myPlace)) {
myNeedDfa = true;
}
super.visitInstanceOfExpression(expression);
@@ -460,127 +494,74 @@ public final class GuessManagerImpl extends GuessManager {
return !mySpecificType.equals(rawType);
}
}
private static final class ExpressionTypeInstructionVisitor extends StandardInstructionVisitor {
private final TypeConstraint myInitial;
private MultiMap<PsiExpression, PsiType> myResult;
private final PsiElement myForPlace;
private TypeConstraint myConstraint = null;
private final boolean myOnlyForPlace;
private ExpressionTypeInstructionVisitor(@NotNull PsiElement forPlace,
boolean onlyForPlace,
TypeConstraint initial) {
myOnlyForPlace = onlyForPlace;
myForPlace = PsiUtil.skipParenthesizedExprUp(forPlace);
myInitial = initial;
}
MultiMap<PsiExpression, PsiType> getResult() {
if (myConstraint != null && myForPlace instanceof PsiExpression) {
PsiType type = myConstraint.getPsiType(myForPlace.getProject());
if (type instanceof PsiIntersectionType) {
myResult.putValues((PsiExpression)myForPlace, Arrays.asList(((PsiIntersectionType)type).getConjuncts()));
}
else if (type != null) {
myResult.putValue((PsiExpression)myForPlace, type);
}
}
return myResult;
}
@Contract("null -> false")
private boolean isInteresting(PsiExpression expression) {
if (expression == null) return false;
return !myOnlyForPlace ||
(myForPlace instanceof PsiExpression &&
ExpressionTypeMemoryState.EXPRESSION_HASHING_STRATEGY.equals((PsiExpression)myForPlace, expression));
}
@Override
public DfaInstructionState[] visitInstanceof(InstanceofInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) {
PsiExpression psiOperand = instruction.getLeft();
if (!isInteresting(psiOperand) || instruction.isClassObjectCheck()) {
return super.visitInstanceof(instruction, runner, memState);
}
DfaValue type = memState.pop();
DfaValue operand = memState.pop();
DfaCondition relation = operand.cond(RelationType.IS, type);
memState.push(new DfaInstanceofValue(runner.getFactory(), psiOperand, Objects.requireNonNull(instruction.getCastType()), relation));
return new DfaInstructionState[]{new DfaInstructionState(runner.getInstruction(instruction.getIndex() + 1), memState)};
}
abstract static class CastTrackingVisitor extends StandardInstructionVisitor {
@Override
public DfaInstructionState[] visitTypeCast(TypeCastInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) {
PsiExpression psiOperand = instruction.getCasted();
if (isInteresting(psiOperand)) {
((ExpressionTypeMemoryState)memState).setExpressionType(psiOperand, instruction.getCastTo());
}
DfaValue value = memState.pop();
memState.push(adjustValue(runner, value, instruction.getCasted()));
return super.visitTypeCast(instruction, runner, memState);
}
@Override
public DfaInstructionState[] visitAssign(AssignInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) {
PsiExpression left = instruction.getLExpression();
PsiExpression right = instruction.getRExpression();
if (left != null && right != null) {
((ExpressionTypeMemoryState)memState).removeExpressionType(left);
}
return super.visitAssign(instruction, runner, memState);
public DfaInstructionState[] visitInstanceof(InstanceofInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) {
DfaValue dfaRight = memState.pop();
DfaValue dfaLeft = memState.pop();
memState.push(adjustValue(runner, dfaLeft, instruction.getLeft()));
memState.push(dfaRight);
return super.visitInstanceof(instruction, runner, memState);
}
@Override
public DfaInstructionState[] visitMethodCall(MethodCallInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) {
if (myForPlace == instruction.getCallExpression()) {
addToResult(((ExpressionTypeMemoryState)memState).getStates());
private DfaValue adjustValue(DataFlowRunner runner, DfaValue value, @Nullable PsiExpression expression) {
if (expression != null && isInteresting(value, expression)) {
value = runner.getFactory().getVarFactory().createVariableValue(new ExpressionVariableDescriptor(expression));
}
DfaInstructionState[] states = super.visitMethodCall(instruction, runner, memState);
if (myForPlace == instruction.getCallExpression()) {
addConstraints(states);
}
return states;
return value;
}
@Override
public DfaInstructionState[] visitPush(ExpressionPushingInstruction<?> instruction,
DataFlowRunner runner,
DfaMemoryState memState,
DfaValue value) {
if (myForPlace == instruction.getExpression()) {
addToResult(((ExpressionTypeMemoryState)memState).getStates());
}
DfaInstructionState[] states = super.visitPush(instruction, runner, memState, value);
if (myForPlace == instruction.getExpression()) {
addConstraints(states);
}
return states;
boolean isInteresting(@NotNull DfaValue value, @NotNull PsiExpression expression) {
return true;
}
}
private static final class ExpressionTypeInstructionVisitor extends CastTrackingVisitor {
private final Map<DfaVariableValue, TypeConstraint> myResult = new HashMap<>();
private final PsiElement myForPlace;
private ExpressionTypeInstructionVisitor(@NotNull PsiElement forPlace) {
myForPlace = PsiUtil.skipParenthesizedExprUp(forPlace);
}
private void addConstraints(DfaInstructionState[] states) {
for (DfaInstructionState state : states) {
DfaMemoryState memoryState = state.getMemoryState();
if (myConstraint == TypeConstraints.TOP) return;
DfType type = memoryState.getDfType(memoryState.peek());
TypeConstraint constraint = type instanceof DfReferenceType ? ((DfReferenceType)type).getConstraint() : myInitial;
if (constraint != null) {
myConstraint = myConstraint == null ? constraint : myConstraint.join(constraint);
}
}
}
private void addToResult(MultiMap<PsiExpression, PsiType> map) {
if (myResult == null) {
myResult = MultiMap.createSet(new Object2ObjectOpenCustomHashMap<>(ExpressionTypeMemoryState.EXPRESSION_HASHING_STRATEGY));
myResult.putAllValues(map);
}
else {
final Iterator<PsiExpression> iterator = myResult.keySet().iterator();
while (iterator.hasNext()) {
PsiExpression psiExpression = iterator.next();
if (!myResult.get(psiExpression).equals(map.get(psiExpression))) {
iterator.remove();
MultiMap<PsiExpression, PsiType> getResult() {
MultiMap<PsiExpression, PsiType> result = MultiMap.createSet(
new Object2ObjectOpenCustomHashMap<>(ExpressionVariableDescriptor.EXPRESSION_HASHING_STRATEGY));
Project project = myForPlace.getProject();
myResult.forEach((value, constraint) -> {
if (value.getDescriptor() instanceof ExpressionVariableDescriptor) {
PsiExpression expression = ((ExpressionVariableDescriptor)value.getDescriptor()).getExpression();
PsiType type = constraint.getPsiType(project);
if (type instanceof PsiIntersectionType) {
result.putValues(expression, Arrays.asList(((PsiIntersectionType)type).getConjuncts()));
}
else if (type != null) {
result.putValue(expression, type);
}
}
});
return result;
}
@Override
protected void beforeExpressionPush(@NotNull DfaValue value,
@NotNull PsiExpression expression,
@Nullable TextRange range,
@NotNull DfaMemoryState state) {
if (range == null && myForPlace == expression) {
((DfaMemoryStateImpl)state).forRecordedVariableTypes((var, dfType) -> {
myResult.merge(var, TypeConstraint.fromDfType(dfType), TypeConstraint::join);
});
}
super.beforeExpressionPush(value, expression, range, state);
}
}
}

View File

@@ -1235,7 +1235,7 @@ public class DfaMemoryStateImpl implements DfaMemoryState {
return canonicalized == var ? null : myVariableTypes.get(canonicalized);
}
void forRecordedVariableTypes(BiConsumer<? super DfaVariableValue, ? super DfType> consumer) {
public void forRecordedVariableTypes(BiConsumer<? super DfaVariableValue, ? super DfType> consumer) {
myVariableTypes.forEach(consumer);
}

View File

@@ -1,54 +0,0 @@
/*
* Copyright 2000-2009 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.codeInspection.dataFlow.value;
import com.intellij.psi.PsiExpression;
import com.intellij.psi.PsiType;
import org.jetbrains.annotations.NotNull;
/**
* Used in DFA-aware completion only
*/
public class DfaInstanceofValue extends DfaValue {
private final @NotNull PsiExpression myExpression;
private final @NotNull PsiType myCastType;
private final @NotNull DfaCondition myRelation;
public DfaInstanceofValue(@NotNull DfaValueFactory factory,
@NotNull PsiExpression expression,
@NotNull PsiType castType,
@NotNull DfaCondition relation) {
super(factory);
myExpression = expression;
myCastType = castType;
myRelation = relation;
}
@NotNull
public DfaCondition getRelation() {
return myRelation;
}
@NotNull
public PsiExpression getExpression() {
return myExpression;
}
@NotNull
public PsiType getCastType() {
return myCastType;
}
}

View File

@@ -72,12 +72,6 @@ public final class DfaRelation extends DfaCondition {
else if (dfaRight instanceof DfaTypeValue && dfaLeft.getDfType() instanceof DfConstantType) {
return createConstBasedRelation((DfaTypeValue)dfaRight, relationType, dfaLeft);
}
if (dfaLeft instanceof DfaInstanceofValue && dfaRight.getDfType() instanceof DfConstantType) {
return new DfaRelation(dfaLeft, dfaRight, relationType);
}
if (dfaLeft.getDfType() instanceof DfConstantType && dfaRight instanceof DfaInstanceofValue) {
return new DfaRelation(dfaRight, dfaLeft, relationType);
}
return null;
}

View File

@@ -0,0 +1,16 @@
class Test {
interface A {
void foo();
}
interface B {}
void test(Object obj) {
boolean isA = obj instanceof A;
if (!isA && !(obj instanceof B)) {
return;
}
if (isA) {
obj.fo<caret>
}
}
}

View File

@@ -0,0 +1,16 @@
class Test {
interface A {
void foo();
}
interface B {}
void test(Object obj) {
boolean isA = obj instanceof A;
if (!isA && !(obj instanceof B)) {
return;
}
if (isA) {
((A) obj).foo();<caret>
}
}
}

View File

@@ -73,6 +73,7 @@ class NormalCompletionDfaTest extends NormalCompletionTestCase {
void testInstanceOfDisjunctionCircular() { doTest() }
void testAfterGetClass() { doTest() }
void testNoCastForCompatibleCapture() { doTest() }
void testBooleanFlagDfa() { doTest() }
void testComplexInstanceOfDfa() {
configureByTestName()
myFixture.assertPreferredCompletionItems 0, 'methodFromX', 'methodFromX2', 'methodFromY', 'methodFromY2'