From 7b6b2da7b5e035b7897d60d4a3c072215d8e88f5 Mon Sep 17 00:00:00 2001 From: Andrey Vlasovskikh Date: Wed, 18 Sep 2013 20:50:18 +0400 Subject: [PATCH 1/6] Test for types in nested tuple unpacking inside 'for' loops (PY-9334) --- python/testSrc/com/jetbrains/python/PyTypeTest.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/python/testSrc/com/jetbrains/python/PyTypeTest.java b/python/testSrc/com/jetbrains/python/PyTypeTest.java index c5dcfbcd64ea..e7287daba115 100644 --- a/python/testSrc/com/jetbrains/python/PyTypeTest.java +++ b/python/testSrc/com/jetbrains/python/PyTypeTest.java @@ -752,6 +752,15 @@ public class PyTypeTest extends PyTestCase { " expr = x if isinstance(x, str) else 10\n"); } + // PY-9334 + public void testEnumerateListOfTuples() { + doTest("str", + "def f():\n" + + " xs = [('foo', [])]\n" + + " for i, (expr, v) in enumerate(xs):\n" + + " print(expr)\n"); + } + private static TypeEvalContext getTypeEvalContext(@NotNull PyExpression element) { return TypeEvalContext.userInitiated(element.getContainingFile()).withTracing(); } From f5b6204149c4e4314a9070d366095b5d1464d89e Mon Sep 17 00:00:00 2001 From: Andrey Vlasovskikh Date: Thu, 19 Sep 2013 16:55:17 +0400 Subject: [PATCH 2/6] Fixed type inference for nested tuples in 'for' loop targets (PY-9334) --- .../psi/impl/PyTargetExpressionImpl.java | 32 +++++++++++++++---- .../com/jetbrains/python/PyTypeTest.java | 5 ++- 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/python/src/com/jetbrains/python/psi/impl/PyTargetExpressionImpl.java b/python/src/com/jetbrains/python/psi/impl/PyTargetExpressionImpl.java index 212606c03bc0..536442afe9d1 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyTargetExpressionImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyTargetExpressionImpl.java @@ -290,9 +290,28 @@ public class PyTargetExpressionImpl extends PyPresentableElementImpl= 0) { + return tupleType.getElementType(index); + } + for (int i = 0; i < count; i++) { + PyExpression element = elements[i]; + while (element instanceof PyParenthesizedExpression) { + element = ((PyParenthesizedExpression)element).getContainedExpression(); + } + if (element instanceof PyTupleExpression) { + final PyType elementType = tupleType.getElementType(i); + if (elementType instanceof PyTupleType) { + final PyType result = getTypeFromTupleAssignment((PyTupleExpression)element, (PyTupleType)elementType); + if (result != null) { + return result; + } + } + } + } } return null; } @@ -319,12 +338,11 @@ public class PyTargetExpressionImpl extends PyPresentableElementImpl Date: Thu, 19 Sep 2013 17:08:04 +0200 Subject: [PATCH 3/6] XDebugger: @Nullable XBreakpoint.getProperties() --- .../python/debugger/PyDebugProcess.java | 14 ++++++++++---- .../debugger/PyExceptionBreakpointType.java | 18 ++++++++++++------ 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/python/src/com/jetbrains/python/debugger/PyDebugProcess.java b/python/src/com/jetbrains/python/debugger/PyDebugProcess.java index 6c197885bac5..f1ef38a2a38c 100644 --- a/python/src/com/jetbrains/python/debugger/PyDebugProcess.java +++ b/python/src/com/jetbrains/python/debugger/PyDebugProcess.java @@ -544,16 +544,22 @@ public class PyDebugProcess extends XDebugProcess implements IPyDebugProcess, Pr } public void addExceptionBreakpoint(XBreakpoint breakpoint) { - myRegisteredExceptionBreakpoints.put(breakpoint.getProperties().getException(), breakpoint); + ExceptionBreakpointProperties properties = breakpoint.getProperties(); + if (properties == null) return; + + myRegisteredExceptionBreakpoints.put(properties.getException(), breakpoint); if (isConnected()) { - myDebugger.addExceptionBreakpoint(breakpoint.getProperties()); + myDebugger.addExceptionBreakpoint(properties); } } public void removeExceptionBreakpoint(XBreakpoint breakpoint) { - myRegisteredExceptionBreakpoints.remove(breakpoint.getProperties().getException()); + ExceptionBreakpointProperties properties = breakpoint.getProperties(); + if (properties == null) return; + + myRegisteredExceptionBreakpoints.remove(properties.getException()); if (isConnected()) { - myDebugger.removeExceptionBreakpoint(breakpoint.getProperties()); + myDebugger.removeExceptionBreakpoint(properties); } } diff --git a/python/src/com/jetbrains/python/debugger/PyExceptionBreakpointType.java b/python/src/com/jetbrains/python/debugger/PyExceptionBreakpointType.java index c19e1d2f8273..d30aad7eac9c 100644 --- a/python/src/com/jetbrains/python/debugger/PyExceptionBreakpointType.java +++ b/python/src/com/jetbrains/python/debugger/PyExceptionBreakpointType.java @@ -225,18 +225,24 @@ public class PyExceptionBreakpointType @Override public void saveTo(@NotNull XBreakpoint breakpoint) { - breakpoint.getProperties().setNotifyOnTerminate(myNotifyOnTerminateCheckBox.isSelected()); + PyExceptionBreakpointProperties properties = breakpoint.getProperties(); + if (properties == null) return; + + properties.setNotifyOnTerminate(myNotifyOnTerminateCheckBox.isSelected()); - breakpoint.getProperties().setNotifyAlways(myNotifyOnRaiseCheckBox.isSelected() && myAlwaysRadio.isSelected()); - breakpoint.getProperties().setNotifyOnlyOnFirst(myNotifyOnRaiseCheckBox.isSelected() && myOnlyOnFirstRadio.isSelected()); + properties.setNotifyAlways(myNotifyOnRaiseCheckBox.isSelected() && myAlwaysRadio.isSelected()); + properties.setNotifyOnlyOnFirst(myNotifyOnRaiseCheckBox.isSelected() && myOnlyOnFirstRadio.isSelected()); } @Override public void loadFrom(@NotNull XBreakpoint breakpoint) { - myNotifyOnTerminateCheckBox.setSelected(breakpoint.getProperties().isNotifyOnTerminate()); + PyExceptionBreakpointProperties properties = breakpoint.getProperties(); + if (properties == null) return; + + myNotifyOnTerminateCheckBox.setSelected(properties.isNotifyOnTerminate()); - boolean always = breakpoint.getProperties().isNotifyAlways(); - boolean onFirst = breakpoint.getProperties().isNotifyOnlyOnFirst(); + boolean always = properties.isNotifyAlways(); + boolean onFirst = properties.isNotifyOnlyOnFirst(); setNotifyOnRaiseSelected(always || onFirst); From a56383ff3b4df96452ae5ccabc040aaed1b971d4 Mon Sep 17 00:00:00 2001 From: Andrey Vlasovskikh Date: Thu, 19 Sep 2013 20:53:16 +0400 Subject: [PATCH 4/6] Moved and inverted hasCustomDecorators() to PyUtil --- python/src/com/jetbrains/python/psi/PyUtil.java | 15 +++++++++++++++ .../resolve/CompletionVariantsProcessor.java | 17 +---------------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/python/src/com/jetbrains/python/psi/PyUtil.java b/python/src/com/jetbrains/python/psi/PyUtil.java index 3ce5aebb1a3f..13781403a5bc 100644 --- a/python/src/com/jetbrains/python/psi/PyUtil.java +++ b/python/src/com/jetbrains/python/psi/PyUtil.java @@ -44,6 +44,7 @@ import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil; import com.jetbrains.python.codeInsight.stdlib.PyNamedTupleType; import com.jetbrains.python.psi.impl.PyBuiltinCache; import com.jetbrains.python.psi.impl.PyPsiUtils; +import com.jetbrains.python.psi.impl.PyQualifiedName; import com.jetbrains.python.psi.resolve.PyResolveContext; import com.jetbrains.python.psi.resolve.QualifiedResolveResult; import com.jetbrains.python.psi.types.*; @@ -663,6 +664,20 @@ public class PyUtil { return !isListComprehension || isAtLeast30; } + public static boolean hasCustomDecorators(@NotNull PyDecoratable decoratable) { + PyDecoratorList decoratorList = decoratable.getDecoratorList(); + if (decoratorList == null) { + return false; + } + for (PyDecorator decorator : decoratorList.getDecorators()) { + PyQualifiedName name = decorator.getQualifiedName(); + if (name == null || (!PyNames.CLASSMETHOD.equals(name.toString()) && !PyNames.STATICMETHOD.equals(name.toString()))) { + return true; + } + } + return false; + } + public static class KnownDecoratorProviderHolder { public static PyKnownDecoratorProvider[] KNOWN_DECORATOR_PROVIDERS = Extensions.getExtensions(PyKnownDecoratorProvider.EP_NAME); diff --git a/python/src/com/jetbrains/python/psi/resolve/CompletionVariantsProcessor.java b/python/src/com/jetbrains/python/psi/resolve/CompletionVariantsProcessor.java index 0dd3b7157f49..f3c2f6cc2273 100644 --- a/python/src/com/jetbrains/python/psi/resolve/CompletionVariantsProcessor.java +++ b/python/src/com/jetbrains/python/psi/resolve/CompletionVariantsProcessor.java @@ -48,7 +48,7 @@ public class CompletionVariantsProcessor extends VariantsProcessor { if (!myPlainNamesOnly) { if (!mySuppressParentheses && object instanceof PyFunction && ((PyFunction)object).getProperty() == null && - hasNoCustomDecorators((PyFunction)object) && + !PyUtil.hasCustomDecorators((PyFunction)object) && !isSingleArgDecoratorCall(myContext, (PyFunction)object)) { item = item.withInsertHandler(PyFunctionInsertHandler.INSTANCE); final TypeEvalContext context = TypeEvalContext.userInitiated(myContext != null ? myContext.getContainingFile() : null); @@ -105,21 +105,6 @@ public class CompletionVariantsProcessor extends VariantsProcessor { return item; } - private static boolean hasNoCustomDecorators(PyFunction function) { - PyDecoratorList decoratorList = function.getDecoratorList(); - if (decoratorList == null) { - return true; - } - for (PyDecorator decorator : decoratorList.getDecorators()) { - PyQualifiedName name = decorator.getQualifiedName(); - if (name == null || (!PyNames.CLASSMETHOD.equals(name.toString()) && !PyNames.STATICMETHOD.equals(name.toString()))) { - return false; - } - } - - return true; - } - private static boolean isSingleArgDecoratorCall(PsiElement elementInCall, PyFunction callee) { // special case hack to avoid the need of patching generator3.py PyClass containingClass = callee.getContainingClass(); From df135fc4fb997dff7c92a5a6af5acdca3b361706 Mon Sep 17 00:00:00 2001 From: Andrey Vlasovskikh Date: Thu, 19 Sep 2013 20:57:12 +0400 Subject: [PATCH 5/6] Disabled call arguments inspection for decorated functions (PY-10601) --- .../inspections/PyArgumentListInspection.java | 13 ++++++++++++- .../decoratedChangedParameters.py | 10 ++++++++++ .../inspections/PyArgumentListInspectionTest.java | 5 +++++ 3 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 python/testData/inspections/PyArgumentListInspection/decoratedChangedParameters.py diff --git a/python/src/com/jetbrains/python/inspections/PyArgumentListInspection.java b/python/src/com/jetbrains/python/inspections/PyArgumentListInspection.java index b6c5b7be30a2..d412d23e2ea6 100644 --- a/python/src/com/jetbrains/python/inspections/PyArgumentListInspection.java +++ b/python/src/com/jetbrains/python/inspections/PyArgumentListInspection.java @@ -10,7 +10,10 @@ import com.jetbrains.python.PyNames; import com.jetbrains.python.PyTokenTypes; import com.jetbrains.python.psi.*; import com.jetbrains.python.psi.resolve.PyResolveContext; -import com.jetbrains.python.psi.types.*; +import com.jetbrains.python.psi.types.PyABCUtil; +import com.jetbrains.python.psi.types.PyType; +import com.jetbrains.python.psi.types.PyTypeChecker; +import com.jetbrains.python.psi.types.TypeEvalContext; import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NotNull; @@ -88,6 +91,14 @@ public class PyArgumentListInspection extends PyInspection { public static void inspectPyArgumentList(PyArgumentList node, ProblemsHolder holder, final TypeEvalContext context, int implicitOffset) { if (node.getParent() instanceof PyClass) return; // class Foo(object) is also an arg list CallArgumentsMapping result = node.analyzeCall(PyResolveContext.noImplicits().withTypeEvalContext(context), implicitOffset); + final PyCallExpression.PyMarkedCallee callee = result.getMarkedCallee(); + if (callee != null) { + final Callable callable = callee.getCallable(); + // Decorate functions may have different parameter lists. We don't match arguments with parameters of decorators yet + if (callable instanceof PyFunction && PyUtil.hasCustomDecorators((PyFunction)callable)) { + return; + } + } highlightIncorrectArguments(holder, result, context); highlightMissingArguments(node, holder, result); highlightStarArgumentTypeMismatch(node, holder, context); diff --git a/python/testData/inspections/PyArgumentListInspection/decoratedChangedParameters.py b/python/testData/inspections/PyArgumentListInspection/decoratedChangedParameters.py new file mode 100644 index 000000000000..83f7873005f4 --- /dev/null +++ b/python/testData/inspections/PyArgumentListInspection/decoratedChangedParameters.py @@ -0,0 +1,10 @@ +def fill(f): + return lambda: f('test') + + +@fill +def test(x): + return x + + +test() diff --git a/python/testSrc/com/jetbrains/python/inspections/PyArgumentListInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/PyArgumentListInspectionTest.java index 1c441fcb3533..b9e9cb53b3d3 100644 --- a/python/testSrc/com/jetbrains/python/inspections/PyArgumentListInspectionTest.java +++ b/python/testSrc/com/jetbrains/python/inspections/PyArgumentListInspectionTest.java @@ -133,4 +133,9 @@ public class PyArgumentListInspectionTest extends PyTestCase { public void testFloatConstructor() { doTest(); } + + // PY-10601 + public void testDecoratedChangedParameters() { + doTest(); + } } From bc2140236b3c635e0122dd893b869a4a9a2ce4c5 Mon Sep 17 00:00:00 2001 From: Andrey Vlasovskikh Date: Thu, 19 Sep 2013 22:04:28 +0400 Subject: [PATCH 6/6] Fixed false positive attribute assignment warning for classes that inherit '__slots__', but don't define their own slots (PY-10158) --- .../src/com/jetbrains/python/psi/PyClass.java | 8 +++++++- .../PyUnresolvedReferencesInspection.java | 3 +++ .../python/psi/impl/PyClassImpl.java | 19 ++++++++++++------- .../psi/impl/stubs/PyClassElementType.java | 2 +- .../PyUnresolvedReferencesInspection/slots.py | 2 +- 5 files changed, 24 insertions(+), 10 deletions(-) diff --git a/python/psi-api/src/com/jetbrains/python/psi/PyClass.java b/python/psi-api/src/com/jetbrains/python/psi/PyClass.java index 80ac706fa490..c24871db9f6d 100644 --- a/python/psi-api/src/com/jetbrains/python/psi/PyClass.java +++ b/python/psi-api/src/com/jetbrains/python/psi/PyClass.java @@ -181,6 +181,12 @@ public interface PyClass extends PsiNameIdentifierOwner, PyStatement, NameDefine boolean isSubclass(@NotNull String superClassQName); + /** + * Returns the aggregated list of names defined in __slots__ attributes of the class and its ancestors. + */ + @Nullable + List getSlots(); + /** * Returns the list of names in the class' __slots__ attribute, or null if the class * does not define such an attribute. @@ -188,7 +194,7 @@ public interface PyClass extends PsiNameIdentifierOwner, PyStatement, NameDefine * @return the list of names or null. */ @Nullable - List getSlots(); + List getOwnSlots(); @Nullable String getDocStringValue(); diff --git a/python/src/com/jetbrains/python/inspections/PyUnresolvedReferencesInspection.java b/python/src/com/jetbrains/python/inspections/PyUnresolvedReferencesInspection.java index 4f7c75af671d..a3c9ba7d5127 100644 --- a/python/src/com/jetbrains/python/inspections/PyUnresolvedReferencesInspection.java +++ b/python/src/com/jetbrains/python/inspections/PyUnresolvedReferencesInspection.java @@ -128,6 +128,9 @@ public class PyUnresolvedReferencesInspection extends PyInspection { if (type instanceof PyClassType) { final PyClass pyClass = ((PyClassType)type).getPyClass(); if (pyClass.isNewStyleClass()) { + if (pyClass.getOwnSlots() == null) { + return; + } final List slots = pyClass.getSlots(); final String attrName = node.getReferencedName(); if (slots != null && !slots.contains(attrName) && !slots.contains(PyNames.DICT)) { diff --git a/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java b/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java index a8e506f39301..89ea4d51e1d2 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java @@ -246,20 +246,25 @@ public class PyClassImpl extends PyPresentableElementImpl implement @Override public List getSlots() { - List slots = getOwnSlots(); - if (slots != null) { - return slots; + final Set result = new LinkedHashSet(); + boolean found = false; + final List ownSlots = getOwnSlots(); + if (ownSlots != null) { + found = true; + result.addAll(ownSlots); } for (PyClass cls : getAncestorClasses()) { - slots = ((PyClassImpl)cls).getOwnSlots(); - if (slots != null) { - return slots; + final List ancestorSlots = cls.getOwnSlots(); + if (ancestorSlots != null) { + found = true; + result.addAll(ancestorSlots); } } - return null; + return found ? new ArrayList(result) : null; } @Nullable + @Override public List getOwnSlots() { final PyClassStub stub = getStub(); if (stub != null) { diff --git a/python/src/com/jetbrains/python/psi/impl/stubs/PyClassElementType.java b/python/src/com/jetbrains/python/psi/impl/stubs/PyClassElementType.java index 5b584b2d2822..e0d93eb86f5f 100644 --- a/python/src/com/jetbrains/python/psi/impl/stubs/PyClassElementType.java +++ b/python/src/com/jetbrains/python/psi/impl/stubs/PyClassElementType.java @@ -49,7 +49,7 @@ public class PyClassElementType extends PyStubElementType final PyStringLiteralExpression docStringExpression = psi.getDocStringExpression(); return new PyClassStubImpl(psi.getName(), parentStub, superClasses.toArray(new PyQualifiedName[superClasses.size()]), - ((PyClassImpl)psi).getOwnSlots(), + psi.getOwnSlots(), PyPsiUtils.strValue(docStringExpression), getStubElementType()); } diff --git a/python/testData/inspections/PyUnresolvedReferencesInspection/slots.py b/python/testData/inspections/PyUnresolvedReferencesInspection/slots.py index 6929ebccd548..793903c2338b 100644 --- a/python/testData/inspections/PyUnresolvedReferencesInspection/slots.py +++ b/python/testData/inspections/PyUnresolvedReferencesInspection/slots.py @@ -8,7 +8,7 @@ class C(B): pass c = C() -c.bar = 1 +c.bar = 1 def test_slots_with_dict(): class C(object):