From 2c8a328e782c0c802d170b9983edab62dadc0bf0 Mon Sep 17 00:00:00 2001 From: Andrey Vlasovskikh Date: Wed, 17 Aug 2011 20:50:04 +0400 Subject: [PATCH] Added support for not isinstance and not None type assertions (PY-2140) --- .../controlflow/PyTypeAssertionEvaluator.java | 94 +++++++++++++++---- .../python/psi/PyElementVisitor.java | 4 + .../psi/impl/PyPrefixExpressionImpl.java | 10 +- .../python/psi/types/PyUnionType.java | 10 ++ .../codeInsight/controlflow/assignment2.txt | 11 ++- .../codeInsight/controlflow/break.txt | 11 ++- .../codeInsight/controlflow/continue.txt | 11 ++- .../codeInsight/controlflow/ifelsereturn.txt | 27 +++--- .../codeInsight/controlflow/manyifs.txt | 23 ++--- .../codeInsight/controlflow/return.txt | 11 ++- .../controlflow/sliceassignment.txt | 11 ++- .../codeInsight/controlflow/tryfinally.txt | 28 +++--- .../PyTypeCheckerInspection/test.py | 32 +++++++ .../com/jetbrains/python/PyTypeTest.java | 14 ++- 14 files changed, 212 insertions(+), 85 deletions(-) diff --git a/python/src/com/jetbrains/python/codeInsight/controlflow/PyTypeAssertionEvaluator.java b/python/src/com/jetbrains/python/codeInsight/controlflow/PyTypeAssertionEvaluator.java index ab5f2027d2d9..220a9131756a 100644 --- a/python/src/com/jetbrains/python/codeInsight/controlflow/PyTypeAssertionEvaluator.java +++ b/python/src/com/jetbrains/python/codeInsight/controlflow/PyTypeAssertionEvaluator.java @@ -1,14 +1,11 @@ package com.jetbrains.python.codeInsight.controlflow; -import com.intellij.psi.PsiElement; import com.intellij.util.Function; import com.intellij.util.containers.CollectionFactory; import com.jetbrains.python.PyNames; +import com.jetbrains.python.PyTokenTypes; import com.jetbrains.python.psi.*; -import com.jetbrains.python.psi.types.PyClassType; -import com.jetbrains.python.psi.types.PyType; -import com.jetbrains.python.psi.types.PyUnionType; -import com.jetbrains.python.psi.types.TypeEvalContext; +import com.jetbrains.python.psi.types.*; import java.util.ArrayList; import java.util.List; @@ -19,19 +16,21 @@ import java.util.Stack; */ public class PyTypeAssertionEvaluator extends PyRecursiveElementVisitor { private Stack myStack = CollectionFactory.stack(); + private boolean myPositive = true; public List getDefinitions() { return myStack; } @Override - public void visitPyBinaryExpression(PyBinaryExpression node) { - PsiElement child = node.getFirstChild(); - while (child != null) { - if (child instanceof PyExpression) { - child.accept(this); - } - child = child.getNextSibling(); + public void visitPyPrefixExpression(PyPrefixExpression node) { + if (myPositive && node.getOperator() == PyTokenTypes.NOT_KEYWORD) { + myPositive = !myPositive; + super.visitPyPrefixExpression(node); + myPositive = !myPositive; + } + else { + super.visitPyPrefixExpression(node); } } @@ -42,12 +41,14 @@ public class PyTypeAssertionEvaluator extends PyRecursiveElementVisitor { if (args.length == 2 && args[0] instanceof PyReferenceExpression) { final PyReferenceExpression target = (PyReferenceExpression)args[0]; final PyExpression typeElement = args[1]; + final boolean positive = myPositive; if (!processTuple(target, typeElement)) { pushAssertion(target, new Function() { @Override public PyType fun(TypeEvalContext context) { - final PyType t = context.getType(typeElement); - return t instanceof PyClassType ? ((PyClassType)t).toInstance() : t; + final List types = new ArrayList(); + types.add(context.getType(typeElement)); + return createAssertionType(context.getType(target), types, positive, context); } }); } @@ -55,21 +56,60 @@ public class PyTypeAssertionEvaluator extends PyRecursiveElementVisitor { } } - private boolean processTuple(PyReferenceExpression target, PyExpression typeElement) { + @Override + public void visitPyReferenceExpression(final PyReferenceExpression node) { + if (node.getParent() instanceof PyIfPart) { + pushAssertion(node, new Function() { + @Override + public PyType fun(TypeEvalContext context) { + final List types = new ArrayList(); + types.add(PyNoneType.INSTANCE); + return createAssertionType(context.getType(node), types, false, context); + } + }); + return; + } + super.visitPyReferenceExpression(node); + } + + @Override + public void visitPyBinaryExpression(PyBinaryExpression node) { + if (node.isOperator("isnot")) { + final PyExpression lhs = node.getLeftExpression(); + final PyExpression rhs = node.getRightExpression(); + if (lhs instanceof PyReferenceExpression && rhs instanceof PyReferenceExpression) { + final PyReferenceExpression target = (PyReferenceExpression)lhs; + if (PyNames.NONE.equals(rhs.getName())) { + pushAssertion(target, new Function() { + @Override + public PyType fun(TypeEvalContext context) { + final List types = new ArrayList(); + types.add(PyNoneType.INSTANCE); + return createAssertionType(context.getType(target), types, false, context); + } + }); + return; + } + } + } + super.visitPyBinaryExpression(node); + } + + private boolean processTuple(final PyReferenceExpression target, PyExpression typeElement) { boolean pushed = false; if (typeElement instanceof PyParenthesizedExpression) { final PyExpression contained = ((PyParenthesizedExpression)typeElement).getContainedExpression(); if (contained instanceof PyTupleExpression) { final PyTupleExpression tuple = (PyTupleExpression)contained; + final boolean positive = myPositive; pushAssertion(target, new Function() { @Override public PyType fun(TypeEvalContext context) { final List types = new ArrayList(); for (PyExpression e : tuple.getElements()) { - final PyType t = context.getType(e); - types.add(t instanceof PyClassType ? ((PyClassType)t).toInstance() : t); + types.add(context.getType(e)); } - return PyUnionType.union(types); + return createAssertionType(context.getType(target), types, positive, context); } }); pushed = true; @@ -78,6 +118,24 @@ public class PyTypeAssertionEvaluator extends PyRecursiveElementVisitor { return pushed; } + private static PyType createAssertionType(PyType initial, List types, boolean positive, TypeEvalContext context) { + final List members = new ArrayList(); + for (PyType t : types) { + members.add(t instanceof PyClassType ? ((PyClassType)t).toInstance() : t); + } + final PyType union = PyUnionType.union(members); + if (positive) { + return union; + } + else if (initial instanceof PyUnionType) { + return ((PyUnionType)initial).exclude(union, context); + } + else if (PyTypeChecker.match(union, initial, context)) { + return null; + } + return initial; + } + private void pushAssertion(PyReferenceExpression element, Function getType) { myStack.push(new Assertion(element, getType)); } diff --git a/python/src/com/jetbrains/python/psi/PyElementVisitor.java b/python/src/com/jetbrains/python/psi/PyElementVisitor.java index a576ade506bd..1d102df30cbf 100644 --- a/python/src/com/jetbrains/python/psi/PyElementVisitor.java +++ b/python/src/com/jetbrains/python/psi/PyElementVisitor.java @@ -38,6 +38,10 @@ public class PyElementVisitor extends PsiElementVisitor { visitPyExpression(node); } + public void visitPyPrefixExpression(final PyPrefixExpression node) { + visitPyExpression(node); + } + public void visitPySequenceExpression(final PySequenceExpression node) { visitPyExpression(node); } diff --git a/python/src/com/jetbrains/python/psi/impl/PyPrefixExpressionImpl.java b/python/src/com/jetbrains/python/psi/impl/PyPrefixExpressionImpl.java index c63b3fe86465..c28d740e966a 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyPrefixExpressionImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyPrefixExpressionImpl.java @@ -7,10 +7,7 @@ import com.intellij.psi.PsiReference; import com.jetbrains.python.PyElementTypes; import com.jetbrains.python.PyNames; import com.jetbrains.python.PyTokenTypes; -import com.jetbrains.python.psi.Callable; -import com.jetbrains.python.psi.PyElementType; -import com.jetbrains.python.psi.PyExpression; -import com.jetbrains.python.psi.PyPrefixExpression; +import com.jetbrains.python.psi.*; import com.jetbrains.python.psi.resolve.PyResolveContext; import com.jetbrains.python.psi.types.PyType; import com.jetbrains.python.psi.types.TypeEvalContext; @@ -45,6 +42,11 @@ public class PyPrefixExpressionImpl extends PyElementImpl implements PyPrefixExp return (PyElementType)op.getNode().getElementType(); } + @Override + protected void acceptPyVisitor(PyElementVisitor pyVisitor) { + pyVisitor.visitPyPrefixExpression(this); + } + @Override public PsiReference getReference() { return getReference(PyResolveContext.noImplicits()); diff --git a/python/src/com/jetbrains/python/psi/types/PyUnionType.java b/python/src/com/jetbrains/python/psi/types/PyUnionType.java index feae5506f5f1..13f44ce64636 100644 --- a/python/src/com/jetbrains/python/psi/types/PyUnionType.java +++ b/python/src/com/jetbrains/python/psi/types/PyUnionType.java @@ -135,6 +135,16 @@ public class PyUnionType implements PyType { return myMembers; } + public PyType exclude(PyType t, TypeEvalContext context) { + final List members = new ArrayList(); + for (PyType m : getMembers()) { + if (!PyTypeChecker.match(t, m, context)) { + members.add(m); + } + } + return union(members); + } + private static PyType unit(@Nullable PyType type) { if (type instanceof PyUnionType) { Set members = new LinkedHashSet(); diff --git a/python/testData/codeInsight/controlflow/assignment2.txt b/python/testData/codeInsight/controlflow/assignment2.txt index 0d46808a467f..4531c455bec4 100644 --- a/python/testData/codeInsight/controlflow/assignment2.txt +++ b/python/testData/codeInsight/controlflow/assignment2.txt @@ -2,9 +2,10 @@ 1(2) element: PyAssignmentStatement 2(3) WRITE ACCESS: sibling 3(4) element: PyIfStatement -4(5,9) READ ACCESS: sibling +4(5,10) READ ACCESS: sibling 5(6) element: PyStatementList. Condition: sibling:true -6(7) element: PyAssignmentStatement -7(8) element: PySubscriptionExpression -8(9) READ ACCESS: sibling -9() element: null \ No newline at end of file +6(7) WRITETYPE ACCESS: sibling +7(8) element: PyAssignmentStatement +8(9) element: PySubscriptionExpression +9(10) READ ACCESS: sibling +10() element: null \ No newline at end of file diff --git a/python/testData/codeInsight/controlflow/break.txt b/python/testData/codeInsight/controlflow/break.txt index 8faec1e5d6e0..4b74c0ae9cb5 100644 --- a/python/testData/codeInsight/controlflow/break.txt +++ b/python/testData/codeInsight/controlflow/break.txt @@ -1,10 +1,11 @@ 0(1) element: null 1(2) element: PyWhileStatement -2(3,9) READ ACCESS: foo +2(3,10) READ ACCESS: foo 3(4) element: PyStatementList. Condition: foo:true 4(5) element: PyIfStatement -5(6,8) READ ACCESS: condition +5(6,9) READ ACCESS: condition 6(7) element: PyStatementList. Condition: condition:true -7(9) element: PyBreakStatement -8(1) element: PyPrintStatement -9() element: null \ No newline at end of file +7(8) WRITETYPE ACCESS: condition +8(10) element: PyBreakStatement +9(1) element: PyPrintStatement +10() element: null \ No newline at end of file diff --git a/python/testData/codeInsight/controlflow/continue.txt b/python/testData/codeInsight/controlflow/continue.txt index ea1ced2ff3f9..531e228f1682 100644 --- a/python/testData/codeInsight/controlflow/continue.txt +++ b/python/testData/codeInsight/controlflow/continue.txt @@ -1,10 +1,11 @@ 0(1) element: null 1(2) element: PyWhileStatement -2(3,9) READ ACCESS: foo +2(3,10) READ ACCESS: foo 3(4) element: PyStatementList. Condition: foo:true 4(5) element: PyIfStatement -5(6,8) READ ACCESS: condition +5(6,9) READ ACCESS: condition 6(7) element: PyStatementList. Condition: condition:true -7(1) element: PyContinueStatement -8(1) element: PyPrintStatement -9() element: null \ No newline at end of file +7(8) WRITETYPE ACCESS: condition +8(1) element: PyContinueStatement +9(1) element: PyPrintStatement +10() element: null \ No newline at end of file diff --git a/python/testData/codeInsight/controlflow/ifelsereturn.txt b/python/testData/codeInsight/controlflow/ifelsereturn.txt index 14ea1cc87a83..be3de5e39d0f 100644 --- a/python/testData/codeInsight/controlflow/ifelsereturn.txt +++ b/python/testData/codeInsight/controlflow/ifelsereturn.txt @@ -1,16 +1,17 @@ 0(1) element: null 1(2) element: PyIfStatement -2(3,12) READ ACCESS: markup +2(3,13) READ ACCESS: markup 3(4) element: PyStatementList. Condition: markup:true -4(5) element: PyTryExceptStatement -5(6,8) element: PyTryPart -6(7,8) element: PyReturnStatement -7(8,15) READ ACCESS: renderer -8(9) element: PyExceptPart -9(10) READ ACCESS: KeyError -10(11) element: PyRaiseStatement -11(15) READ ACCESS: Error -12(13) element: PyElsePart. Condition: markup:false -13(14) element: PyReturnStatement -14(15) READ ACCESS: body -15() element: null \ No newline at end of file +4(5) WRITETYPE ACCESS: markup +5(6) element: PyTryExceptStatement +6(7,9) element: PyTryPart +7(8,9) element: PyReturnStatement +8(9,16) READ ACCESS: renderer +9(10) element: PyExceptPart +10(11) READ ACCESS: KeyError +11(12) element: PyRaiseStatement +12(16) READ ACCESS: Error +13(14) element: PyElsePart. Condition: markup:false +14(15) element: PyReturnStatement +15(16) READ ACCESS: body +16() element: null \ No newline at end of file diff --git a/python/testData/codeInsight/controlflow/manyifs.txt b/python/testData/codeInsight/controlflow/manyifs.txt index b26712011b2a..3206af0e248d 100644 --- a/python/testData/codeInsight/controlflow/manyifs.txt +++ b/python/testData/codeInsight/controlflow/manyifs.txt @@ -6,8 +6,8 @@ 5(6,9) READ ACCESS: b 6(7) element: PyStatementList. Condition: a == b:true 7(8) element: PyAssignmentStatement -8(24) WRITE ACCESS: var -9(10,21) element: PyIfPartElif. Condition: a == b:false +8(25) WRITE ACCESS: var +9(10,22) element: PyIfPartElif. Condition: a == b:false 10(11) READ ACCESS: aa 11(12) READ ACCESS: bb 12(13) element: PyIfPartElif. Condition: aa == bb:true @@ -15,13 +15,14 @@ 14(15) READ ACCESS: same_changet_expression 15(16) WRITE ACCESS: bbb 16(17) element: PyIfStatement -17(18,24) READ ACCESS: bbb +17(18,25) READ ACCESS: bbb 18(19) element: PyStatementList. Condition: bbb:true -19(20) element: PyAssignmentStatement -20(24) WRITE ACCESS: var -21(22) element: PyElsePart. Condition: aa == bb:false -22(23) element: PyAssignmentStatement -23(24) WRITE ACCESS: var -24(25) element: PyReturnStatement -25(26) READ ACCESS: var -26() element: null \ No newline at end of file +19(20) WRITETYPE ACCESS: bbb +20(21) element: PyAssignmentStatement +21(25) WRITE ACCESS: var +22(23) element: PyElsePart. Condition: aa == bb:false +23(24) element: PyAssignmentStatement +24(25) WRITE ACCESS: var +25(26) element: PyReturnStatement +26(27) READ ACCESS: var +27() element: null \ No newline at end of file diff --git a/python/testData/codeInsight/controlflow/return.txt b/python/testData/codeInsight/controlflow/return.txt index cf5c12aa98cf..1d310e3b16b1 100644 --- a/python/testData/codeInsight/controlflow/return.txt +++ b/python/testData/codeInsight/controlflow/return.txt @@ -1,10 +1,11 @@ 0(1) element: null 1(2) element: PyWhileStatement -2(3,9) READ ACCESS: foo +2(3,10) READ ACCESS: foo 3(4) element: PyStatementList. Condition: foo:true 4(5) element: PyIfStatement -5(6,8) READ ACCESS: condition +5(6,9) READ ACCESS: condition 6(7) element: PyStatementList. Condition: condition:true -7(9) element: PyReturnStatement -8(1) element: PyPrintStatement -9() element: null \ No newline at end of file +7(8) WRITETYPE ACCESS: condition +8(10) element: PyReturnStatement +9(1) element: PyPrintStatement +10() element: null \ No newline at end of file diff --git a/python/testData/codeInsight/controlflow/sliceassignment.txt b/python/testData/codeInsight/controlflow/sliceassignment.txt index 0d46808a467f..4531c455bec4 100644 --- a/python/testData/codeInsight/controlflow/sliceassignment.txt +++ b/python/testData/codeInsight/controlflow/sliceassignment.txt @@ -2,9 +2,10 @@ 1(2) element: PyAssignmentStatement 2(3) WRITE ACCESS: sibling 3(4) element: PyIfStatement -4(5,9) READ ACCESS: sibling +4(5,10) READ ACCESS: sibling 5(6) element: PyStatementList. Condition: sibling:true -6(7) element: PyAssignmentStatement -7(8) element: PySubscriptionExpression -8(9) READ ACCESS: sibling -9() element: null \ No newline at end of file +6(7) WRITETYPE ACCESS: sibling +7(8) element: PyAssignmentStatement +8(9) element: PySubscriptionExpression +9(10) READ ACCESS: sibling +10() element: null \ No newline at end of file diff --git a/python/testData/codeInsight/controlflow/tryfinally.txt b/python/testData/codeInsight/controlflow/tryfinally.txt index 9ce68183d0a2..c85cfbfe2a61 100644 --- a/python/testData/codeInsight/controlflow/tryfinally.txt +++ b/python/testData/codeInsight/controlflow/tryfinally.txt @@ -6,21 +6,23 @@ 5(6,9) element: PyTryPart 6(7,9) element: PyAssignmentStatement 7(8,9) READ ACCESS: open -8(9,16) WRITE ACCESS: status +8(9,17) WRITE ACCESS: status 9(10) element: PyFinallyPart 10(11) element: PyIfStatement 11(12) READ ACCESS: status -12(13,25) READ ACCESS: None +12(13,27) READ ACCESS: None 13(14) element: PyStatementList. Condition: status is not None:true -14(15) element: PyPrintStatement -15(25) READ ACCESS: status -16(17) element: PyFinallyPart -17(18) element: PyIfStatement -18(19) READ ACCESS: status -19(20,23) READ ACCESS: None -20(21) element: PyStatementList. Condition: status is not None:true -21(22) element: PyPrintStatement -22(23) READ ACCESS: status -23(24) element: PyExpressionStatement +14(15) WRITETYPE ACCESS: status +15(16) element: PyPrintStatement +16(27) READ ACCESS: status +17(18) element: PyFinallyPart +18(19) element: PyIfStatement +19(20) READ ACCESS: status +20(21,25) READ ACCESS: None +21(22) element: PyStatementList. Condition: status is not None:true +22(23) WRITETYPE ACCESS: status +23(24) element: PyPrintStatement 24(25) READ ACCESS: status -25() element: null \ No newline at end of file +25(26) element: PyExpressionStatement +26(27) READ ACCESS: status +27() element: null \ No newline at end of file diff --git a/python/testData/inspections/PyTypeCheckerInspection/test.py b/python/testData/inspections/PyTypeCheckerInspection/test.py index 4678541d802f..1ffc1b7469c0 100644 --- a/python/testData/inspections/PyTypeCheckerInspection/test.py +++ b/python/testData/inspections/PyTypeCheckerInspection/test.py @@ -342,3 +342,35 @@ def test_isinstance_implicit_self_types(): x = 1 if isinstance(x, unicode): x.encode('UTF-8') #pass + +def test_not_none(): + def test(x): + """ + :type x: int or str or list + """ + def f1(): + """ + :rtype: int or None + """ + def f2(): + """ + :rtype: int or str or None + """ + x1 = f1() + x2 = f2() + x3 = 1 + test(x1) + test(x2) + test(x3) + if x1: + test(x1) + if x2: + test(x2) + if x3: + test(x3) + if x1 is not None: + test(x1) + elif x2 is not None: + test(x2) + elif x3 is not None: + test(x3) diff --git a/python/testSrc/com/jetbrains/python/PyTypeTest.java b/python/testSrc/com/jetbrains/python/PyTypeTest.java index 7406e8399f28..87637d888c21 100644 --- a/python/testSrc/com/jetbrains/python/PyTypeTest.java +++ b/python/testSrc/com/jetbrains/python/PyTypeTest.java @@ -249,6 +249,19 @@ public class PyTypeTest extends PyLightFixtureTestCase { " expr = x"); } + // PY-2140 + public void testNotIsInstance() { + doTest("list", + "def f(c):\n" + + " def g():\n" + + " '''\n" + + " :rtype: int or str or list\n" + + " '''\n" + + " x = g()\n" + + " if not isinstance(x, (str, long)):\n" + + " expr = x"); + } + private PyExpression parseExpr(String text) { myFixture.configureByText(PythonFileType.INSTANCE, text); return myFixture.findElementByText("expr", PyExpression.class); @@ -271,4 +284,3 @@ public class PyTypeTest extends PyLightFixtureTestCase { } } } -