IDEA-209947 Better type constraint explanation

GitOrigin-RevId: bc97ff49908ffee155459fb81dc5e46e4fb12c59
This commit is contained in:
Tagir Valeev
2019-04-28 16:46:26 +03:00
committed by intellij-monorepo-bot
parent 05188a3b0b
commit efcbb20422
9 changed files with 124 additions and 10 deletions
@@ -349,23 +349,21 @@ public class TrackingRunner extends StandardDataFlowRunner {
DfaPsiType wanted = operandValue.getFactory().createDfaType(type);
Pair<MemoryStateChange, TypeConstraint> fact = operandHistory.findFact(operandValue, DfaFactType.TYPE_CONSTRAINT);
TypeConstraint constraint = fact.second == null ? TypeConstraint.empty() : fact.second;
boolean stillSatisfied = (isInstance ? constraint.withNotInstanceofValue(wanted) : constraint.withInstanceofValue(wanted)) == null;
while (stillSatisfied) {
String explanation = fact.second == null ? null : fact.second.getAssignabilityExplanation(wanted, isInstance);
while (explanation != null) {
MemoryStateChange causeLocation = fact.first;
if (causeLocation == null) break;
MemoryStateChange prevHistory = causeLocation.myPrevious;
if (prevHistory == null) break;
fact = prevHistory.findFact(operandValue, DfaFactType.TYPE_CONSTRAINT);
TypeConstraint prevConstraint = fact.second == null ? TypeConstraint.empty() : fact.second;
stillSatisfied = (isInstance ? prevConstraint.withNotInstanceofValue(wanted) : prevConstraint.withInstanceofValue(wanted)) == null;
if (!stillSatisfied) {
CauseItem causeItem =
new CauseItem("Type of '" + operand.getText() + "' is " + constraint.getPresentationText(operand.getType()), operand);
String prevExplanation = prevConstraint.getAssignabilityExplanation(wanted, isInstance);
if (prevExplanation == null) {
CauseItem causeItem = new CauseItem(explanation, operand);
causeItem.addChildren(new CauseItem("Type of '" + operand.getText() + "' is known from #ref", causeLocation));
return new CauseItem[]{causeItem};
}
constraint = prevConstraint;
explanation = prevExplanation;
}
return null;
}
@@ -69,6 +69,7 @@ public abstract class TypeConstraint {
public abstract boolean isExact(String typeName);
public abstract String getAssignabilityExplanation(DfaPsiType otherType, boolean expectedAssignable);
static final class Exact extends TypeConstraint {
final @NotNull DfaPsiType myType;
@@ -95,6 +96,21 @@ public abstract class TypeConstraint {
return type.isAssignableFrom(myType) ? null : this;
}
@Override
public String getAssignabilityExplanation(DfaPsiType otherType, boolean expectedAssignable) {
boolean actual = otherType.isAssignableFrom(myType);
if (actual != expectedAssignable) return null;
if (expectedAssignable) {
if (myType == otherType) {
return "An object is already known to be " + myType;
}
return "An object type is exactly " + myType + " which is a subtype of " + otherType;
}
else {
return "An object type is exactly " + myType + " which is not a subtype of " + otherType;
}
}
@NotNull
@Override
TypeConstraint withoutType(@NotNull DfaPsiType type) {
@@ -411,6 +427,31 @@ public abstract class TypeConstraint {
return false;
}
@Override
public String getAssignabilityExplanation(DfaPsiType otherType, boolean expectedAssignable) {
if (expectedAssignable) {
for (DfaPsiType dfaTypeValue : myInstanceofValues) {
if (otherType.isAssignableFrom(dfaTypeValue)) {
return "An object is already known to be " + dfaTypeValue +
(otherType == dfaTypeValue ? "" : " which is a subtype of " + otherType);
}
}
} else {
for (DfaPsiType dfaTypeValue : myNotInstanceofValues) {
if (dfaTypeValue.isAssignableFrom(otherType)) {
return "An object is known to be not " + dfaTypeValue +
(otherType == dfaTypeValue ? "" : " which is a supertype of " + otherType);
}
}
for (DfaPsiType dfaTypeValue : myInstanceofValues) {
if (!otherType.isConvertibleFrom(dfaTypeValue)) {
return "An object is known to be " + dfaTypeValue + " which is definitely incompatible with " + otherType;
}
}
}
return null;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
@@ -0,0 +1,17 @@
/*
Value is always false (s instanceof String)
An object is known to be Number which is definitely incompatible with String (s)
Type of 's' is known from line #10 (s instanceof Number)
*/
import java.util.List;
class Test {
void test(Object s) {
if (s instanceof Number) {
if (s instanceof Integer) {
if (<selection>s instanceof String</selection>){
}
}
}
}
}
@@ -0,0 +1,21 @@
/*
Value is always true (s instanceof RandomAccess)
An object is already known to be ArrayList which is a subtype of RandomAccess (s)
Type of 's' is known from line #12 (s instanceof ArrayList)
*/
import java.util.*;
class Test {
void test(Object s) {
if (s instanceof Map) {
if (s instanceof List) {
if (s instanceof ArrayList) {
if (s instanceof CharSequence) {
if (<selection>s instanceof RandomAccess</selection>){
}
}
}
}
}
}
}
@@ -1,6 +1,6 @@
/*
Value is always false (s instanceof Integer)
Type of 's' is exactly String (s)
An object type is exactly String which is not a subtype of Integer (s)
Type of 's' is known from line #10 (s instanceof String)
*/
import java.util.List;
@@ -0,0 +1,16 @@
/*
Value is always true (s instanceof String)
An object is already known to be String (s)
Type of 's' is known from line #10 ((String)s)
*/
import java.util.List;
class Test {
void test(Object s) {
System.out.println(((String)s).trim());
if (<selection>s instanceof String</selection>) {
}
}
}
@@ -1,6 +1,6 @@
/*
Value is always true (s instanceof CharSequence)
Type of 's' is exactly String (s)
An object type is exactly String which is a subtype of CharSequence (s)
Type of 's' is known from line #10 (s instanceof String)
*/
import java.util.List;
@@ -0,0 +1,17 @@
/*
Value is always false (s instanceof String)
An object is known to be not CharSequence which is a supertype of String (s)
Type of 's' is known from line #10 (s instanceof CharSequence)
*/
import java.util.List;
class Test {
void test(Object s) {
if (!(s instanceof CharSequence)) {
if (s instanceof Integer) {
if (<selection>s instanceof String</selection>){
}
}
}
}
}
@@ -117,4 +117,8 @@ public class DataFlowInspectionTrackerTest extends LightCodeInsightFixtureTestCa
public void testInstanceOfNull() { doTest(); }
public void testInstanceOfConflict() { doTest(); }
public void testInstanceOfRedundant() { doTest(); }
public void testInstanceOfChain() { doTest(); }
public void testInstanceOfChain2() { doTest(); }
public void testNotInstanceOf() { doTest(); }
public void testInstanceOfPreviousCast() { doTest(); }
}