PY-20889 Fixed: Unresolved attribute after isinstance check in boolean expression

Consider `and` and `or` expressions in PyControlFlowBuilder and use PyTypeAssertionEvaluator to make assertion for right expression.
This commit is contained in:
Semyon Proshev
2017-09-29 19:12:46 +03:00
parent 3506acb0de
commit 8d3d94ee88
10 changed files with 147 additions and 29 deletions
@@ -25,6 +25,7 @@ import com.intellij.psi.PsiNamedElement;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.QualifiedName;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.PyTokenTypes;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.impl.ParamHelper;
import com.jetbrains.python.psi.impl.PyAugAssignmentStatementNavigator;
@@ -390,6 +391,29 @@ public class PyControlFlowBuilder extends PyRecursiveElementVisitor {
}
}
@Override
public void visitPyBinaryExpression(PyBinaryExpression node) {
final PyElementType operator = node.getOperator();
if (operator == PyTokenTypes.AND_KEYWORD || operator == PyTokenTypes.OR_KEYWORD) {
myBuilder.startNode(node);
final PyExpression left = node.getLeftExpression();
final PyTypeAssertionEvaluator assertionEvaluator = new PyTypeAssertionEvaluator(operator == PyTokenTypes.AND_KEYWORD);
if (left != null) {
left.accept(this);
left.accept(assertionEvaluator);
}
final PyExpression right = node.getRightExpression();
if (right != null) {
InstructionBuilder.addAssertInstructions(myBuilder, assertionEvaluator);
right.accept(this);
myBuilder.addPendingEdge(node, myBuilder.prevInstruction);
}
}
else {
super.visitPyBinaryExpression(node);
}
}
@Override
public void visitPyWhileStatement(final PyWhileStatement node) {
final Instruction instruction = myBuilder.startNode(node);
@@ -0,0 +1,7 @@
class A:
pass
var = object()
if isinstance(var, A) and var:
pass
@@ -0,0 +1,17 @@
0(1) element: null
1(2) element: PyClass: A
2(3) WRITE ACCESS: A
3(4) element: PyAssignmentStatement
4(5) READ ACCESS: object
5(6) WRITE ACCESS: var
6(7) element: PyIfStatement
7(8) element: PyBinaryExpression
8(9) READ ACCESS: isinstance
9(10) READ ACCESS: var
10(11) READ ACCESS: A
11(12) ASSERTTYPE ACCESS: var
12(13,15) READ ACCESS: var
13(14) element: PyStatementList. Condition: isinstance(var, A) and var:true
14(16) ASSERTTYPE ACCESS: var
15(16) ASSERTTYPE ACCESS: var
16() element: null
@@ -1,16 +1,17 @@
0(1) element: null
1(2) element: PyForStatement
2(3,15) READ ACCESS: self
2(3,16) READ ACCESS: self
3(4) element: PyTupleExpression
4(5) WRITE ACCESS: start
5(6) WRITE ACCESS: end
6(7) WRITE ACCESS: name
7(8) element: PyIfStatement
8(9) READ ACCESS: hour
9(10) READ ACCESS: start
10(11) READ ACCESS: hour
11(12,3,15) READ ACCESS: end
12(13) element: PyStatementList. Condition: hour >= start and hour < end:true
13(14) element: PyReturnStatement
14(15) READ ACCESS: name
15() element: null
8(9) element: PyBinaryExpression
9(10) READ ACCESS: hour
10(11) READ ACCESS: start
11(12) READ ACCESS: hour
12(13,3,16) READ ACCESS: end
13(14) element: PyStatementList. Condition: hour >= start and hour < end:true
14(15) element: PyReturnStatement
15(16) READ ACCESS: name
16() element: null
@@ -20,22 +20,24 @@
19(11,20) READ ACCESS: method
20(21) WRITE ACCESS: methodList
21(22) element: PyAssignmentStatement
22(23) READ ACCESS: collapse
23(24) element: PyLambdaExpression
24(25) element: PyLambdaExpression
25(26) WRITE ACCESS: processFunc
26(27) element: PyPrintStatement
27(28) element: PyListCompExpression
28(29) element: PyReferenceExpression: methodList
29(30,40) READ ACCESS: methodList
30(31) element: PyTargetExpression: method
31(32) WRITE ACCESS: method
32(33) element: PyBinaryExpression
33(34) READ ACCESS: method
34(35) READ ACCESS: spacing
35(36) READ ACCESS: processFunc
36(37) READ ACCESS: str
37(38) READ ACCESS: getattr
38(39) READ ACCESS: object
39(30,40) READ ACCESS: method
40() element: null
22(23) element: PyBinaryExpression
23(24) element: PyBinaryExpression
24(25) READ ACCESS: collapse
25(26) element: PyLambdaExpression
26(27) element: PyLambdaExpression
27(28) WRITE ACCESS: processFunc
28(29) element: PyPrintStatement
29(30) element: PyListCompExpression
30(31) element: PyReferenceExpression: methodList
31(32,42) READ ACCESS: methodList
32(33) element: PyTargetExpression: method
33(34) WRITE ACCESS: method
34(35) element: PyBinaryExpression
35(36) READ ACCESS: method
36(37) READ ACCESS: spacing
37(38) READ ACCESS: processFunc
38(39) READ ACCESS: str
39(40) READ ACCESS: getattr
40(41) READ ACCESS: object
41(32,42) READ ACCESS: method
42() element: null
@@ -0,0 +1,11 @@
class A:
pass
class B:
pass
if isinstance(var, A):
if isinstance(var, B) or var:
pass
@@ -0,0 +1,23 @@
0(1) element: null
1(2) element: PyClass: A
2(3) WRITE ACCESS: A
3(4) element: PyClass: B
4(5) WRITE ACCESS: B
5(6) element: PyIfStatement
6(7) READ ACCESS: isinstance
7(8) READ ACCESS: var
8(9,21) READ ACCESS: A
9(10) element: PyStatementList. Condition: isinstance(var, A):true
10(11) ASSERTTYPE ACCESS: var
11(12) element: PyIfStatement
12(13) element: PyBinaryExpression
13(14) READ ACCESS: isinstance
14(15) READ ACCESS: var
15(16) READ ACCESS: B
16(17) ASSERTTYPE ACCESS: var
17(18,20) READ ACCESS: var
18(19) element: PyStatementList. Condition: isinstance(var, B) or var:true
19(22) ASSERTTYPE ACCESS: var
20(22) ASSERTTYPE ACCESS: var
21(22) ASSERTTYPE ACCESS: var
22() element: null
@@ -0,0 +1,18 @@
class A:
def foo(self):
pass
class B:
def bar(self):
pass
var = object()
if isinstance(var, A) and var.foo():
pass
if isinstance(var, A):
if isinstance(var, B) or var.<warning descr="Unresolved attribute reference 'bar' for class 'A'">bar</warning>():
pass
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2013 JetBrains s.r.o.
* Copyright 2000-2017 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.
@@ -248,6 +248,16 @@ public class PyControlFlowBuilderTest extends LightMarkedTestCase {
doTest();
}
// PY-20889
public void testAndBooleanExpression() {
doTest();
}
// PY-20889
public void testOrBooleanExpression() {
doTest();
}
private void doTestFirstStatement() {
final String testName = getTestName(false).toLowerCase();
configureByFile(testName + ".py");
@@ -647,6 +647,11 @@ public class PyUnresolvedReferencesInspectionTest extends PyInspectionTestCase {
doTest();
}
// PY-20889
public void testTypeAssertionInBooleanOperations() {
doTest();
}
@NotNull
@Override
protected Class<? extends PyInspection> getInspectionClass() {