From 8d3d94ee88bb69965a8f110160bc7d6959410fed Mon Sep 17 00:00:00 2001 From: Semyon Proshev Date: Thu, 27 Jul 2017 18:40:09 +0300 Subject: [PATCH] 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. --- .../controlflow/PyControlFlowBuilder.java | 24 +++++++++++ .../controlflow/andbooleanexpression.py | 7 ++++ .../controlflow/andbooleanexpression.txt | 17 ++++++++ .../codeInsight/controlflow/forreturn.txt | 19 ++++----- .../codeInsight/controlflow/function.txt | 40 ++++++++++--------- .../controlflow/orbooleanexpression.py | 11 +++++ .../controlflow/orbooleanexpression.txt | 23 +++++++++++ .../typeAssertionInBooleanOperations.py | 18 +++++++++ .../python/PyControlFlowBuilderTest.java | 12 +++++- .../PyUnresolvedReferencesInspectionTest.java | 5 +++ 10 files changed, 147 insertions(+), 29 deletions(-) create mode 100644 python/testData/codeInsight/controlflow/andbooleanexpression.py create mode 100644 python/testData/codeInsight/controlflow/andbooleanexpression.txt create mode 100644 python/testData/codeInsight/controlflow/orbooleanexpression.py create mode 100644 python/testData/codeInsight/controlflow/orbooleanexpression.txt create mode 100644 python/testData/inspections/PyUnresolvedReferencesInspection/typeAssertionInBooleanOperations.py diff --git a/python/src/com/jetbrains/python/codeInsight/controlflow/PyControlFlowBuilder.java b/python/src/com/jetbrains/python/codeInsight/controlflow/PyControlFlowBuilder.java index f23015089168..79673cdd4a10 100644 --- a/python/src/com/jetbrains/python/codeInsight/controlflow/PyControlFlowBuilder.java +++ b/python/src/com/jetbrains/python/codeInsight/controlflow/PyControlFlowBuilder.java @@ -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); diff --git a/python/testData/codeInsight/controlflow/andbooleanexpression.py b/python/testData/codeInsight/controlflow/andbooleanexpression.py new file mode 100644 index 000000000000..1e6dfe20bde4 --- /dev/null +++ b/python/testData/codeInsight/controlflow/andbooleanexpression.py @@ -0,0 +1,7 @@ +class A: + pass + + +var = object() +if isinstance(var, A) and var: + pass \ No newline at end of file diff --git a/python/testData/codeInsight/controlflow/andbooleanexpression.txt b/python/testData/codeInsight/controlflow/andbooleanexpression.txt new file mode 100644 index 000000000000..0af02b823a90 --- /dev/null +++ b/python/testData/codeInsight/controlflow/andbooleanexpression.txt @@ -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 \ No newline at end of file diff --git a/python/testData/codeInsight/controlflow/forreturn.txt b/python/testData/codeInsight/controlflow/forreturn.txt index c189bc10beb5..b3b0d98c5f7b 100644 --- a/python/testData/codeInsight/controlflow/forreturn.txt +++ b/python/testData/codeInsight/controlflow/forreturn.txt @@ -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 \ No newline at end of file +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 \ No newline at end of file diff --git a/python/testData/codeInsight/controlflow/function.txt b/python/testData/codeInsight/controlflow/function.txt index 6b674b44d6de..a89cd5395f88 100644 --- a/python/testData/codeInsight/controlflow/function.txt +++ b/python/testData/codeInsight/controlflow/function.txt @@ -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 \ No newline at end of file +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 \ No newline at end of file diff --git a/python/testData/codeInsight/controlflow/orbooleanexpression.py b/python/testData/codeInsight/controlflow/orbooleanexpression.py new file mode 100644 index 000000000000..379dde4398d2 --- /dev/null +++ b/python/testData/codeInsight/controlflow/orbooleanexpression.py @@ -0,0 +1,11 @@ +class A: + pass + + +class B: + pass + + +if isinstance(var, A): + if isinstance(var, B) or var: + pass \ No newline at end of file diff --git a/python/testData/codeInsight/controlflow/orbooleanexpression.txt b/python/testData/codeInsight/controlflow/orbooleanexpression.txt new file mode 100644 index 000000000000..2099f3866885 --- /dev/null +++ b/python/testData/codeInsight/controlflow/orbooleanexpression.txt @@ -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 \ No newline at end of file diff --git a/python/testData/inspections/PyUnresolvedReferencesInspection/typeAssertionInBooleanOperations.py b/python/testData/inspections/PyUnresolvedReferencesInspection/typeAssertionInBooleanOperations.py new file mode 100644 index 000000000000..81de998ff127 --- /dev/null +++ b/python/testData/inspections/PyUnresolvedReferencesInspection/typeAssertionInBooleanOperations.py @@ -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.bar(): + pass \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/PyControlFlowBuilderTest.java b/python/testSrc/com/jetbrains/python/PyControlFlowBuilderTest.java index 90a46a31a4a5..3d7b6aaebd71 100644 --- a/python/testSrc/com/jetbrains/python/PyControlFlowBuilderTest.java +++ b/python/testSrc/com/jetbrains/python/PyControlFlowBuilderTest.java @@ -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"); diff --git a/python/testSrc/com/jetbrains/python/inspections/PyUnresolvedReferencesInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/PyUnresolvedReferencesInspectionTest.java index a10fb89616e0..07c979683ee6 100644 --- a/python/testSrc/com/jetbrains/python/inspections/PyUnresolvedReferencesInspectionTest.java +++ b/python/testSrc/com/jetbrains/python/inspections/PyUnresolvedReferencesInspectionTest.java @@ -647,6 +647,11 @@ public class PyUnresolvedReferencesInspectionTest extends PyInspectionTestCase { doTest(); } + // PY-20889 + public void testTypeAssertionInBooleanOperations() { + doTest(); + } + @NotNull @Override protected Class getInspectionClass() {