mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-04-20 13:31:28 +07:00
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:
committed by
intellij-monorepo-bot
parent
002caf8f57
commit
97e4d3f847
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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'
|
||||
|
||||
Reference in New Issue
Block a user