Don't use resolve while evaluating expressions for CFG

This commit is contained in:
Semyon Proshev
2018-03-07 19:21:54 +03:00
parent 2922e896ec
commit 222cb0d70e
5 changed files with 95 additions and 11 deletions
@@ -448,7 +448,7 @@ public class PyControlFlowBuilder extends PyRecursiveElementVisitor {
boolean isStaticallyTrue = false;
if (condition != null) {
condition.accept(this);
isStaticallyTrue = PyEvaluator.evaluateAsBoolean(condition, false);
isStaticallyTrue = PyEvaluator.evaluateAsBooleanNoResolve(condition, false);
}
final Instruction head = myBuilder.prevInstruction;
final PyElsePart elsePart = node.getElsePart();
@@ -481,7 +481,7 @@ public class PyControlFlowBuilder extends PyRecursiveElementVisitor {
}
final Instruction head = myBuilder.prevInstruction;
final PyElsePart elsePart = node.getElsePart();
if (elsePart == null && !PyEvaluator.evaluateAsBoolean(source, false)) {
if (elsePart == null && !PyEvaluator.evaluateAsBooleanNoResolve(source, false)) {
myBuilder.addPendingEdge(node, myBuilder.prevInstruction);
}
final PyStatementList list = forPart.getStatementList();
@@ -816,7 +816,7 @@ public class PyControlFlowBuilder extends PyRecursiveElementVisitor {
super.visitPyAssertStatement(node);
final PyExpression[] args = node.getArguments();
// assert False
if (args.length >= 1 && !PyEvaluator.evaluateAsBoolean(args[0], true)) {
if (args.length >= 1 && !PyEvaluator.evaluateAsBooleanNoResolve(args[0], true)) {
abruptFlow(node);
return;
}
@@ -120,8 +120,8 @@ public class PyTypeAssertionEvaluator extends PyRecursiveElementVisitor {
}
}
final Object leftValue = new PyEvaluator().evaluate(lhs);
final Object rightValue = new PyEvaluator().evaluate(rhs);
final Object leftValue = PyEvaluator.evaluateNoResolve(lhs, Object.class);
final Object rightValue = PyEvaluator.evaluateNoResolve(rhs, Object.class);
if (leftValue instanceof Boolean && rightValue instanceof Boolean) {
return;
@@ -55,6 +55,11 @@ public class PyEvaluator {
*/
private boolean myEvaluateKeys = true;
/**
* if true, references will be resolved
*/
private boolean myEnableResolve = true;
public void setNamespace(@Nullable Map<String, Object> namespace) {
myNamespace = namespace;
}
@@ -67,6 +72,10 @@ public class PyEvaluator {
myEvaluateKeys = evaluateKeys;
}
public void enableResolve(boolean enableResolve) {
myEnableResolve = enableResolve;
}
@Nullable
@Contract("null -> null")
public Object evaluate(@Nullable PyExpression expression) {
@@ -242,6 +251,9 @@ public class PyEvaluator {
if (myNamespace != null) {
return myNamespace.get(expression.getReferencedName());
}
if (!myEnableResolve) {
return null;
}
final ResolveResult[] results = expression.getReference(PyResolveContext.noImplicits()).multiResolve(false);
if (results.length != 1) {
return null;
@@ -332,9 +344,9 @@ public class PyEvaluator {
/**
* Shortcut that evaluates expression with default params and casts it to particular type (if possible)
*
* @param <T> expected type
* @param expression expression to evaluate
* @param resultType expected type
* @param <T> expected type
* @return value if expression is evaluated to this type, null otherwise
*/
@Nullable
@@ -342,6 +354,21 @@ public class PyEvaluator {
return PyUtil.as(new PyEvaluator().evaluate(expression), resultType);
}
/**
* Shortcut that evaluates expression with default params and disabled resolve and casts it to particular type (if possible)
*
* @param <T> expected type
* @param expression expression to evaluate
* @param resultType expected type
* @return value if expression is evaluated to this type, null otherwise
*/
@Nullable
public static <T> T evaluateNoResolve(@Nullable PyExpression expression, @NotNull Class<T> resultType) {
final PyEvaluator evaluator = new PyEvaluator();
evaluator.enableResolve(false);
return PyUtil.as(evaluator.evaluate(expression), resultType);
}
/**
* Shortcut that evaluates expression and tries to determine if `bool` will return true for it
*
@@ -350,10 +377,39 @@ public class PyEvaluator {
*/
@Nullable
public static Boolean evaluateAsBoolean(@Nullable PyExpression expression) {
return evaluateAsBoolean(prepareEvaluatorForBoolean(true), expression);
}
/**
* Shortcut that evaluates expression and tries to determine if `bool` will return true for it
*
* @param expression expression to evaluate
* @return true if expression is evaluated to value so `bool` returns true for it
*/
@Nullable
public static Boolean evaluateAsBooleanNoResolve(@Nullable PyExpression expression) {
return evaluateAsBoolean(prepareEvaluatorForBoolean(false), expression);
}
public static boolean evaluateAsBoolean(@Nullable PyExpression expression, boolean defaultValue) {
return ObjectUtils.notNull(evaluateAsBoolean(expression), defaultValue);
}
public static boolean evaluateAsBooleanNoResolve(@Nullable PyExpression expression, boolean defaultValue) {
return ObjectUtils.notNull(evaluateAsBooleanNoResolve(expression), defaultValue);
}
@NotNull
private static PyEvaluator prepareEvaluatorForBoolean(boolean enableResolve) {
final PyEvaluator evaluator = new PyEvaluator();
evaluator.setEvaluateCollectionItems(false);
evaluator.setEvaluateKeys(false);
evaluator.enableResolve(enableResolve);
return evaluator;
}
@Nullable
private static Boolean evaluateAsBoolean(@NotNull PyEvaluator evaluator, @Nullable PyExpression expression) {
final Object result = evaluator.evaluate(expression);
if (result instanceof Boolean) {
@@ -374,8 +430,4 @@ public class PyEvaluator {
return null;
}
public static boolean evaluateAsBoolean(@Nullable PyExpression expression, boolean defaultValue) {
return ObjectUtils.notNull(evaluateAsBoolean(expression), defaultValue);
}
}
@@ -2,7 +2,7 @@
1(2) element: PyAssignmentStatement
2(3) WRITE ACCESS: tests
3(4) element: PyForStatement
4(5) READ ACCESS: tests
4(5,17) READ ACCESS: tests
5(6) element: PyTargetExpression: t
6(7) WRITE ACCESS: t
7(8) element: PyTryExceptStatement
@@ -281,6 +281,38 @@ public class PyEvaluatorTest extends PyTestCase {
assertFalse(PyEvaluator.evaluateAsBoolean(parseExpression("[]")));
}
public void testEvaluateAsBooleanNoResolve() {
assertTrue(PyEvaluator.evaluateAsBooleanNoResolve(parseExpression("True")));
assertFalse(PyEvaluator.evaluateAsBooleanNoResolve(parseExpression("False")));
assertTrue(PyEvaluator.evaluateAsBooleanNoResolve(parseExpression("\'a\'")));
assertFalse(PyEvaluator.evaluateAsBooleanNoResolve(parseExpression("\'\'")));
assertTrue(PyEvaluator.evaluateAsBooleanNoResolve(parseExpression("1")));
assertFalse(PyEvaluator.evaluateAsBooleanNoResolve(parseExpression("0")));
assertTrue(PyEvaluator.evaluateAsBooleanNoResolve(parseExpression("{1: 0}")));
assertFalse(PyEvaluator.evaluateAsBooleanNoResolve(parseExpression("{}")));
assertTrue(PyEvaluator.evaluateAsBooleanNoResolve(parseExpression("[1]")));
assertFalse(PyEvaluator.evaluateAsBooleanNoResolve(parseExpression("[]")));
assertNull(PyEvaluator.evaluateAsBooleanNoResolve(parseText("a = True\nexpr = a")));
assertNull(PyEvaluator.evaluateAsBooleanNoResolve(parseText("a = False\nexpr = a")));
assertNull(PyEvaluator.evaluateAsBooleanNoResolve(parseText("a = \'a\'\nexpr = a")));
assertNull(PyEvaluator.evaluateAsBooleanNoResolve(parseText("a = \'\'\nexpr = a")));
assertNull(PyEvaluator.evaluateAsBooleanNoResolve(parseText("a = 1\nexpr = a")));
assertNull(PyEvaluator.evaluateAsBooleanNoResolve(parseText("a = 0\nexpr = a")));
assertNull(PyEvaluator.evaluateAsBooleanNoResolve(parseText("a = {1: 0}\nexpr = a")));
assertNull(PyEvaluator.evaluateAsBooleanNoResolve(parseText("a = {}\nexpr = a")));
assertNull(PyEvaluator.evaluateAsBooleanNoResolve(parseText("a = [1]\nexpr = a")));
assertNull(PyEvaluator.evaluateAsBooleanNoResolve(parseText("a = []\nexpr = a")));
}
@NotNull
private <T> T byExpression(@NotNull String expression, @NotNull Class<T> cls) {
final Object value = new PyEvaluator().evaluate(parseExpression(expression));