From 5ab55667c876ceee6768d4366b2e23badf2089cf Mon Sep 17 00:00:00 2001 From: Semyon Proshev Date: Thu, 28 Apr 2016 21:21:49 +0300 Subject: [PATCH] PY-12773 Fixed: Misleading warning for attribute assignment when a class has __slots__ Update PyPropertyAccessInspection to check writing to class attribute in case of __slots__ in class --- .../PyPropertyAccessInspection.java | 65 +++++++- .../classAttrAssignmentAndInheritedSlots.py | 13 ++ ...ssAttrAssignmentAndOwnAndInheritedSlots.py | 13 ++ ...tAndOwnAndInheritedWithAttrAndDictSlots.py | 13 ++ ...signmentAndOwnAndInheritedWithAttrSlots.py | 13 ++ ...signmentAndOwnAndInheritedWithDictSlots.py | 13 ++ ...AttrAssignmentAndOwnSlotsAndEmptyParent.py | 13 ++ ...nmentAndOwnWithAttrAndInheritedSlotsPy2.py | 13 ++ ...nmentAndOwnWithAttrAndInheritedSlotsPy3.py | 15 ++ ...signmentAndOwnWithDictAndInheritedSlots.py | 13 ++ ...AndOwnWithDictAndInheritedWithAttrSlots.py | 13 ++ .../classAttrAssignmentAndSlots.py | 10 ++ .../classAttrAssignmentAndSlotsWithAttrPy2.py | 10 ++ .../classAttrAssignmentAndSlotsWithAttrPy3.py | 12 ++ .../classAttrAssignmentAndSlotsWithDict.py | 10 ++ ...tedClassAttrAssignmentAndInheritedSlots.py | 14 ++ ...ssAttrAssignmentAndOwnAndInheritedSlots.py | 13 ++ ...tAndOwnAndInheritedWithAttrAndDictSlots.py | 17 ++ ...nmentAndOwnAndInheritedWithAttrSlotsPy2.py | 13 ++ ...nmentAndOwnAndInheritedWithAttrSlotsPy3.py | 15 ++ ...signmentAndOwnAndInheritedWithDictSlots.py | 13 ++ ...inheritedClassAttrAssignmentAndOwnSlots.py | 13 ++ ...tAndOwnWithAttrAndDictAndInheritedSlots.py | 13 ++ ...signmentAndOwnWithAttrAndInheritedSlots.py | 13 ++ ...AndOwnWithAttrAndInheritedWithDictSlots.py | 13 ++ ...signmentAndOwnWithDictAndInheritedSlots.py | 13 ++ ...AndOwnWithDictAndInheritedWithAttrSlots.py | 17 ++ .../PyPropertyAccessInspectionTest.java | 150 +++++++++++++++++- 28 files changed, 546 insertions(+), 10 deletions(-) create mode 100644 python/testData/inspections/PyPropertyAccessInspection/classAttrAssignmentAndInheritedSlots.py create mode 100644 python/testData/inspections/PyPropertyAccessInspection/classAttrAssignmentAndOwnAndInheritedSlots.py create mode 100644 python/testData/inspections/PyPropertyAccessInspection/classAttrAssignmentAndOwnAndInheritedWithAttrAndDictSlots.py create mode 100644 python/testData/inspections/PyPropertyAccessInspection/classAttrAssignmentAndOwnAndInheritedWithAttrSlots.py create mode 100644 python/testData/inspections/PyPropertyAccessInspection/classAttrAssignmentAndOwnAndInheritedWithDictSlots.py create mode 100644 python/testData/inspections/PyPropertyAccessInspection/classAttrAssignmentAndOwnSlotsAndEmptyParent.py create mode 100644 python/testData/inspections/PyPropertyAccessInspection/classAttrAssignmentAndOwnWithAttrAndInheritedSlotsPy2.py create mode 100644 python/testData/inspections/PyPropertyAccessInspection/classAttrAssignmentAndOwnWithAttrAndInheritedSlotsPy3.py create mode 100644 python/testData/inspections/PyPropertyAccessInspection/classAttrAssignmentAndOwnWithDictAndInheritedSlots.py create mode 100644 python/testData/inspections/PyPropertyAccessInspection/classAttrAssignmentAndOwnWithDictAndInheritedWithAttrSlots.py create mode 100644 python/testData/inspections/PyPropertyAccessInspection/classAttrAssignmentAndSlots.py create mode 100644 python/testData/inspections/PyPropertyAccessInspection/classAttrAssignmentAndSlotsWithAttrPy2.py create mode 100644 python/testData/inspections/PyPropertyAccessInspection/classAttrAssignmentAndSlotsWithAttrPy3.py create mode 100644 python/testData/inspections/PyPropertyAccessInspection/classAttrAssignmentAndSlotsWithDict.py create mode 100644 python/testData/inspections/PyPropertyAccessInspection/inheritedClassAttrAssignmentAndInheritedSlots.py create mode 100644 python/testData/inspections/PyPropertyAccessInspection/inheritedClassAttrAssignmentAndOwnAndInheritedSlots.py create mode 100644 python/testData/inspections/PyPropertyAccessInspection/inheritedClassAttrAssignmentAndOwnAndInheritedWithAttrAndDictSlots.py create mode 100644 python/testData/inspections/PyPropertyAccessInspection/inheritedClassAttrAssignmentAndOwnAndInheritedWithAttrSlotsPy2.py create mode 100644 python/testData/inspections/PyPropertyAccessInspection/inheritedClassAttrAssignmentAndOwnAndInheritedWithAttrSlotsPy3.py create mode 100644 python/testData/inspections/PyPropertyAccessInspection/inheritedClassAttrAssignmentAndOwnAndInheritedWithDictSlots.py create mode 100644 python/testData/inspections/PyPropertyAccessInspection/inheritedClassAttrAssignmentAndOwnSlots.py create mode 100644 python/testData/inspections/PyPropertyAccessInspection/inheritedClassAttrAssignmentAndOwnWithAttrAndDictAndInheritedSlots.py create mode 100644 python/testData/inspections/PyPropertyAccessInspection/inheritedClassAttrAssignmentAndOwnWithAttrAndInheritedSlots.py create mode 100644 python/testData/inspections/PyPropertyAccessInspection/inheritedClassAttrAssignmentAndOwnWithAttrAndInheritedWithDictSlots.py create mode 100644 python/testData/inspections/PyPropertyAccessInspection/inheritedClassAttrAssignmentAndOwnWithDictAndInheritedSlots.py create mode 100644 python/testData/inspections/PyPropertyAccessInspection/inheritedClassAttrAssignmentAndOwnWithDictAndInheritedWithAttrSlots.py diff --git a/python/src/com/jetbrains/python/inspections/PyPropertyAccessInspection.java b/python/src/com/jetbrains/python/inspections/PyPropertyAccessInspection.java index 45f39e1fc895..dd3f89a619dd 100644 --- a/python/src/com/jetbrains/python/inspections/PyPropertyAccessInspection.java +++ b/python/src/com/jetbrains/python/inspections/PyPropertyAccessInspection.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2014 JetBrains s.r.o. + * Copyright 2000-2016 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. @@ -22,14 +22,18 @@ import com.intellij.psi.PsiElement; import com.intellij.psi.PsiElementVisitor; import com.intellij.util.containers.HashMap; import com.jetbrains.python.PyBundle; +import com.jetbrains.python.PyNames; import com.jetbrains.python.inspections.quickfix.PyCreatePropertyQuickFix; import com.jetbrains.python.psi.*; +import com.jetbrains.python.psi.resolve.PyResolveContext; import com.jetbrains.python.psi.types.PyClassType; import com.jetbrains.python.psi.types.PyType; import com.jetbrains.python.toolbox.Maybe; import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NotNull; +import java.util.List; + /** * Checks that properties are accessed correctly. * User: dcheryasov @@ -64,16 +68,17 @@ public class PyPropertyAccessInspection extends PyInspection { @Override public void visitPyReferenceExpression(PyReferenceExpression node) { super.visitPyReferenceExpression(node); - checkExpression(node); + checkPropertyExpression(node); } @Override public void visitPyTargetExpression(PyTargetExpression node) { super.visitPyTargetExpression(node); - checkExpression(node); + checkPropertyExpression(node); + checkAttributeExpression(node); } - private void checkExpression(PyQualifiedExpression node) { + private void checkPropertyExpression(PyQualifiedExpression node) { final PyExpression qualifier = node.getQualifier(); if (qualifier != null) { final PyType type = myTypeEvalContext.getType(qualifier); @@ -122,5 +127,57 @@ public class PyPropertyAccessInspection extends PyInspection { } } + private void checkAttributeExpression(@NotNull PyTargetExpression target) { + final String targetName = target.getName(); + final PyExpression qualifier = target.getQualifier(); + + if (targetName == null || qualifier == null) { + return; + } + + final PyType qualifierType = myTypeEvalContext.getType(qualifier); + + if (qualifierType instanceof PyClassType) { + final PyClassType qualifierClassType = (PyClassType)qualifierType; + + if (!qualifierClassType.isDefinition()) { + final PyClass qualifierClass = qualifierClassType.getPyClass(); + + PyUtil + .multiResolveTopPriority(target.getReference(PyResolveContext.noImplicits().withTypeEvalContext(myTypeEvalContext))) + .stream() + .filter(PyTargetExpression.class::isInstance) + .map(declaration -> ((PyTargetExpression)declaration).getContainingClass()) + .filter(declaringClass -> declaringClass != null && !attributeIsWritable(qualifierClass, declaringClass, target)) + .findFirst() + .ifPresent( + cls -> registerProblem(target, String.format("'%s' object attribute '%s' is read-only", qualifierClass.getName(), targetName)) + ); + } + } + } + + private boolean attributeIsWritable(@NotNull PyClass qualifierClass, + @NotNull PyClass declaringClass, + @NotNull PyTargetExpression target) { + return attributeIsWritableInClass(qualifierClass, declaringClass, target) || + qualifierClass + .getAncestorClasses(myTypeEvalContext) + .stream() + .filter(ancestorClass -> !PyUtil.isObjectClass(ancestorClass)) + .anyMatch(ancestorClass -> attributeIsWritableInClass(ancestorClass, declaringClass, target)); + } + + private static boolean attributeIsWritableInClass(@NotNull PyClass cls, + @NotNull PyClass declaringClass, + @NotNull PyTargetExpression target) { + final List ownSlots = cls.getOwnSlots(); + + return ownSlots == null || + ownSlots.contains(PyNames.DICT) || + (LanguageLevel.forElement(target).isPy3K() && + cls.equals(declaringClass) && + ownSlots.contains(target.getName())); + } } } diff --git a/python/testData/inspections/PyPropertyAccessInspection/classAttrAssignmentAndInheritedSlots.py b/python/testData/inspections/PyPropertyAccessInspection/classAttrAssignmentAndInheritedSlots.py new file mode 100644 index 000000000000..72d6e058e9c0 --- /dev/null +++ b/python/testData/inspections/PyPropertyAccessInspection/classAttrAssignmentAndInheritedSlots.py @@ -0,0 +1,13 @@ +class B(object): + __slots__ = ['foo', 'bar'] + + +class C(B): + attr = 'baz' + +C.attr = 'spam' +print(C.attr) + +c = C() +c.attr = 'spam' +print(c.attr) \ No newline at end of file diff --git a/python/testData/inspections/PyPropertyAccessInspection/classAttrAssignmentAndOwnAndInheritedSlots.py b/python/testData/inspections/PyPropertyAccessInspection/classAttrAssignmentAndOwnAndInheritedSlots.py new file mode 100644 index 000000000000..1eb3a75cc54c --- /dev/null +++ b/python/testData/inspections/PyPropertyAccessInspection/classAttrAssignmentAndOwnAndInheritedSlots.py @@ -0,0 +1,13 @@ +class B(object): + __slots__ = ['f', 'b'] + +class C(B): + attr = 'baz' + __slots__ = ['foo', 'bar'] + +C.attr = 'spam' +print(C.attr) + +c = C() +c.attr = 'spam' +print(c.attr) \ No newline at end of file diff --git a/python/testData/inspections/PyPropertyAccessInspection/classAttrAssignmentAndOwnAndInheritedWithAttrAndDictSlots.py b/python/testData/inspections/PyPropertyAccessInspection/classAttrAssignmentAndOwnAndInheritedWithAttrAndDictSlots.py new file mode 100644 index 000000000000..9dcf334b4dcb --- /dev/null +++ b/python/testData/inspections/PyPropertyAccessInspection/classAttrAssignmentAndOwnAndInheritedWithAttrAndDictSlots.py @@ -0,0 +1,13 @@ +class B(object): + __slots__ = ['attr', 'b', '__dict__'] + +class C(B): + attr = 'baz' + __slots__ = ['foo', 'bar'] + +C.attr = 'spam' +print(C.attr) + +c = C() +c.attr = 'spam' +print(c.attr) \ No newline at end of file diff --git a/python/testData/inspections/PyPropertyAccessInspection/classAttrAssignmentAndOwnAndInheritedWithAttrSlots.py b/python/testData/inspections/PyPropertyAccessInspection/classAttrAssignmentAndOwnAndInheritedWithAttrSlots.py new file mode 100644 index 000000000000..78e2dd7756a7 --- /dev/null +++ b/python/testData/inspections/PyPropertyAccessInspection/classAttrAssignmentAndOwnAndInheritedWithAttrSlots.py @@ -0,0 +1,13 @@ +class B(object): + __slots__ = ['attr', 'b'] + +class C(B): + attr = 'baz' + __slots__ = ['foo', 'bar'] + +C.attr = 'spam' +print(C.attr) + +c = C() +c.attr = 'spam' +print(c.attr) \ No newline at end of file diff --git a/python/testData/inspections/PyPropertyAccessInspection/classAttrAssignmentAndOwnAndInheritedWithDictSlots.py b/python/testData/inspections/PyPropertyAccessInspection/classAttrAssignmentAndOwnAndInheritedWithDictSlots.py new file mode 100644 index 000000000000..e87b16ff9b99 --- /dev/null +++ b/python/testData/inspections/PyPropertyAccessInspection/classAttrAssignmentAndOwnAndInheritedWithDictSlots.py @@ -0,0 +1,13 @@ +class B(object): + __slots__ = ['f', 'b', '__dict__'] + +class C(B): + attr = 'baz' + __slots__ = ['foo', 'bar'] + +C.attr = 'spam' +print(C.attr) + +c = C() +c.attr = 'spam' +print(c.attr) \ No newline at end of file diff --git a/python/testData/inspections/PyPropertyAccessInspection/classAttrAssignmentAndOwnSlotsAndEmptyParent.py b/python/testData/inspections/PyPropertyAccessInspection/classAttrAssignmentAndOwnSlotsAndEmptyParent.py new file mode 100644 index 000000000000..8ed2e603f3e8 --- /dev/null +++ b/python/testData/inspections/PyPropertyAccessInspection/classAttrAssignmentAndOwnSlotsAndEmptyParent.py @@ -0,0 +1,13 @@ +class B(object): + pass + +class C(B): + attr = 'baz' + __slots__ = ['foo', 'bar'] + +C.attr = 'spam' +print(C.attr) + +c = C() +c.attr = 'spam' +print(c.attr) \ No newline at end of file diff --git a/python/testData/inspections/PyPropertyAccessInspection/classAttrAssignmentAndOwnWithAttrAndInheritedSlotsPy2.py b/python/testData/inspections/PyPropertyAccessInspection/classAttrAssignmentAndOwnWithAttrAndInheritedSlotsPy2.py new file mode 100644 index 000000000000..85db2e99ba2e --- /dev/null +++ b/python/testData/inspections/PyPropertyAccessInspection/classAttrAssignmentAndOwnWithAttrAndInheritedSlotsPy2.py @@ -0,0 +1,13 @@ +class B(object): + __slots__ = ['f', 'b'] + +class C(B): + attr = 'baz' + __slots__ = ['attr', 'bar'] + +C.attr = 'spam' +print(C.attr) + +c = C() +c.attr = 'spam' +print(c.attr) \ No newline at end of file diff --git a/python/testData/inspections/PyPropertyAccessInspection/classAttrAssignmentAndOwnWithAttrAndInheritedSlotsPy3.py b/python/testData/inspections/PyPropertyAccessInspection/classAttrAssignmentAndOwnWithAttrAndInheritedSlotsPy3.py new file mode 100644 index 000000000000..8642e35ade02 --- /dev/null +++ b/python/testData/inspections/PyPropertyAccessInspection/classAttrAssignmentAndOwnWithAttrAndInheritedSlotsPy3.py @@ -0,0 +1,15 @@ +class B(object): + __slots__ = ['f', 'b'] + +# ValueError: 'attr' in __slots__ conflicts with class variable +# This is not responsibility of current inspection +class C(B): + attr = 'baz' + __slots__ = ['attr', 'bar'] + +C.attr = 'spam' +print(C.attr) + +c = C() +c.attr = 'spam' +print(c.attr) \ No newline at end of file diff --git a/python/testData/inspections/PyPropertyAccessInspection/classAttrAssignmentAndOwnWithDictAndInheritedSlots.py b/python/testData/inspections/PyPropertyAccessInspection/classAttrAssignmentAndOwnWithDictAndInheritedSlots.py new file mode 100644 index 000000000000..466f91e4f013 --- /dev/null +++ b/python/testData/inspections/PyPropertyAccessInspection/classAttrAssignmentAndOwnWithDictAndInheritedSlots.py @@ -0,0 +1,13 @@ +class B(object): + __slots__ = ['f', 'b'] + +class C(B): + attr = 'baz' + __slots__ = ['foo', 'bar', '__dict__'] + +C.attr = 'spam' +print(C.attr) + +c = C() +c.attr = 'spam' +print(c.attr) \ No newline at end of file diff --git a/python/testData/inspections/PyPropertyAccessInspection/classAttrAssignmentAndOwnWithDictAndInheritedWithAttrSlots.py b/python/testData/inspections/PyPropertyAccessInspection/classAttrAssignmentAndOwnWithDictAndInheritedWithAttrSlots.py new file mode 100644 index 000000000000..f26130ca3d2b --- /dev/null +++ b/python/testData/inspections/PyPropertyAccessInspection/classAttrAssignmentAndOwnWithDictAndInheritedWithAttrSlots.py @@ -0,0 +1,13 @@ +class B(object): + __slots__ = ['attr', 'b'] + +class C(B): + attr = 'baz' + __slots__ = ['foo', 'bar', '__dict__'] + +C.attr = 'spam' +print(C.attr) + +c = C() +c.attr = 'spam' +print(c.attr) \ No newline at end of file diff --git a/python/testData/inspections/PyPropertyAccessInspection/classAttrAssignmentAndSlots.py b/python/testData/inspections/PyPropertyAccessInspection/classAttrAssignmentAndSlots.py new file mode 100644 index 000000000000..a85d00ca0e4e --- /dev/null +++ b/python/testData/inspections/PyPropertyAccessInspection/classAttrAssignmentAndSlots.py @@ -0,0 +1,10 @@ +class Foo(object): + attr = 'baz' + __slots__ = ['foo', 'bar'] + +Foo.attr = 'spam' +print(Foo.attr) + +foo = Foo() +foo.attr = 'spam' +print(foo.attr) \ No newline at end of file diff --git a/python/testData/inspections/PyPropertyAccessInspection/classAttrAssignmentAndSlotsWithAttrPy2.py b/python/testData/inspections/PyPropertyAccessInspection/classAttrAssignmentAndSlotsWithAttrPy2.py new file mode 100644 index 000000000000..0653c4b0004f --- /dev/null +++ b/python/testData/inspections/PyPropertyAccessInspection/classAttrAssignmentAndSlotsWithAttrPy2.py @@ -0,0 +1,10 @@ +class Foo(object): + attr = 'baz' + __slots__ = ['attr', 'bar'] + +Foo.attr = 'spam' +print(Foo.attr) + +foo = Foo() +foo.attr = 'spam' +print(foo.attr) \ No newline at end of file diff --git a/python/testData/inspections/PyPropertyAccessInspection/classAttrAssignmentAndSlotsWithAttrPy3.py b/python/testData/inspections/PyPropertyAccessInspection/classAttrAssignmentAndSlotsWithAttrPy3.py new file mode 100644 index 000000000000..d8f34b6bc76b --- /dev/null +++ b/python/testData/inspections/PyPropertyAccessInspection/classAttrAssignmentAndSlotsWithAttrPy3.py @@ -0,0 +1,12 @@ +# ValueError: 'attr' in __slots__ conflicts with class variable +# This is not responsibility of current inspection +class Foo(object): + attr = 'baz' + __slots__ = ['attr', 'bar'] + +Foo.attr = 'spam' +print(Foo.attr) + +foo = Foo() +foo.attr = 'spam' +print(foo.attr) \ No newline at end of file diff --git a/python/testData/inspections/PyPropertyAccessInspection/classAttrAssignmentAndSlotsWithDict.py b/python/testData/inspections/PyPropertyAccessInspection/classAttrAssignmentAndSlotsWithDict.py new file mode 100644 index 000000000000..82d160d099e7 --- /dev/null +++ b/python/testData/inspections/PyPropertyAccessInspection/classAttrAssignmentAndSlotsWithDict.py @@ -0,0 +1,10 @@ +class Foo(object): + attr = 'baz' + __slots__ = ['foo', 'bar', '__dict__'] + +Foo.attr = 'spam' +print(Foo.attr) + +foo = Foo() +foo.attr = 'spam' +print(foo.attr) \ No newline at end of file diff --git a/python/testData/inspections/PyPropertyAccessInspection/inheritedClassAttrAssignmentAndInheritedSlots.py b/python/testData/inspections/PyPropertyAccessInspection/inheritedClassAttrAssignmentAndInheritedSlots.py new file mode 100644 index 000000000000..b6aed45a7bd3 --- /dev/null +++ b/python/testData/inspections/PyPropertyAccessInspection/inheritedClassAttrAssignmentAndInheritedSlots.py @@ -0,0 +1,14 @@ +class B(object): + attr = 'baz' + __slots__ = ['foo', 'bar'] + + +class C(B): + pass + +C.attr = 'spam' +print(C.attr) + +c = C() +c.attr = 'spam' +print(c.attr) \ No newline at end of file diff --git a/python/testData/inspections/PyPropertyAccessInspection/inheritedClassAttrAssignmentAndOwnAndInheritedSlots.py b/python/testData/inspections/PyPropertyAccessInspection/inheritedClassAttrAssignmentAndOwnAndInheritedSlots.py new file mode 100644 index 000000000000..8e5ca20cf8d0 --- /dev/null +++ b/python/testData/inspections/PyPropertyAccessInspection/inheritedClassAttrAssignmentAndOwnAndInheritedSlots.py @@ -0,0 +1,13 @@ +class B(object): + attr = 'baz' + __slots__ = ['f', 'b'] + +class C(B): + __slots__ = ['foo', 'bar'] + +C.attr = 'spam' +print(C.attr) + +c = C() +c.attr = 'spam' +print(c.attr) \ No newline at end of file diff --git a/python/testData/inspections/PyPropertyAccessInspection/inheritedClassAttrAssignmentAndOwnAndInheritedWithAttrAndDictSlots.py b/python/testData/inspections/PyPropertyAccessInspection/inheritedClassAttrAssignmentAndOwnAndInheritedWithAttrAndDictSlots.py new file mode 100644 index 000000000000..ecc7cafebcb4 --- /dev/null +++ b/python/testData/inspections/PyPropertyAccessInspection/inheritedClassAttrAssignmentAndOwnAndInheritedWithAttrAndDictSlots.py @@ -0,0 +1,17 @@ +# Py3: OK +# Py2: +# ValueError: 'attr' in __slots__ conflicts with class variable +# This is not responsibility of current inspection +class B(object): + attr = 'baz' + __slots__ = ['f', 'attr', '__dict__'] + +class C(B): + __slots__ = ['foo', 'bar'] + +C.attr = 'spam' +print(C.attr) + +c = C() +c.attr = 'spam' +print(c.attr) \ No newline at end of file diff --git a/python/testData/inspections/PyPropertyAccessInspection/inheritedClassAttrAssignmentAndOwnAndInheritedWithAttrSlotsPy2.py b/python/testData/inspections/PyPropertyAccessInspection/inheritedClassAttrAssignmentAndOwnAndInheritedWithAttrSlotsPy2.py new file mode 100644 index 000000000000..36f56fa84033 --- /dev/null +++ b/python/testData/inspections/PyPropertyAccessInspection/inheritedClassAttrAssignmentAndOwnAndInheritedWithAttrSlotsPy2.py @@ -0,0 +1,13 @@ +class B(object): + attr = 'baz' + __slots__ = ['attr', 'b'] + +class C(B): + __slots__ = ['foo', 'bar'] + +C.attr = 'spam' +print(C.attr) + +c = C() +c.attr = 'spam' +print(c.attr) \ No newline at end of file diff --git a/python/testData/inspections/PyPropertyAccessInspection/inheritedClassAttrAssignmentAndOwnAndInheritedWithAttrSlotsPy3.py b/python/testData/inspections/PyPropertyAccessInspection/inheritedClassAttrAssignmentAndOwnAndInheritedWithAttrSlotsPy3.py new file mode 100644 index 000000000000..4ae21acb1d24 --- /dev/null +++ b/python/testData/inspections/PyPropertyAccessInspection/inheritedClassAttrAssignmentAndOwnAndInheritedWithAttrSlotsPy3.py @@ -0,0 +1,15 @@ +# ValueError: 'attr' in __slots__ conflicts with class variable +# This is not responsibility of current inspection +class B(object): + attr = 'baz' + __slots__ = ['attr', 'b'] + +class C(B): + __slots__ = ['foo', 'bar'] + +C.attr = 'spam' +print(C.attr) + +c = C() +c.attr = 'spam' +print(c.attr) \ No newline at end of file diff --git a/python/testData/inspections/PyPropertyAccessInspection/inheritedClassAttrAssignmentAndOwnAndInheritedWithDictSlots.py b/python/testData/inspections/PyPropertyAccessInspection/inheritedClassAttrAssignmentAndOwnAndInheritedWithDictSlots.py new file mode 100644 index 000000000000..c295bcfc83d5 --- /dev/null +++ b/python/testData/inspections/PyPropertyAccessInspection/inheritedClassAttrAssignmentAndOwnAndInheritedWithDictSlots.py @@ -0,0 +1,13 @@ +class B(object): + attr = 'baz' + __slots__ = ['f', 'b', '__dict__'] + +class C(B): + __slots__ = ['foo', 'bar'] + +C.attr = 'spam' +print(C.attr) + +c = C() +c.attr = 'spam' +print(c.attr) \ No newline at end of file diff --git a/python/testData/inspections/PyPropertyAccessInspection/inheritedClassAttrAssignmentAndOwnSlots.py b/python/testData/inspections/PyPropertyAccessInspection/inheritedClassAttrAssignmentAndOwnSlots.py new file mode 100644 index 000000000000..504c97db721a --- /dev/null +++ b/python/testData/inspections/PyPropertyAccessInspection/inheritedClassAttrAssignmentAndOwnSlots.py @@ -0,0 +1,13 @@ +class B(object): + attr = 'baz' + + +class C(B): + __slots__ = ['foo', 'bar'] + +C.attr = 'spam' +print(C.attr) + +c = C() +c.attr = 'spam' +print(c.attr) \ No newline at end of file diff --git a/python/testData/inspections/PyPropertyAccessInspection/inheritedClassAttrAssignmentAndOwnWithAttrAndDictAndInheritedSlots.py b/python/testData/inspections/PyPropertyAccessInspection/inheritedClassAttrAssignmentAndOwnWithAttrAndDictAndInheritedSlots.py new file mode 100644 index 000000000000..00bcbc187dee --- /dev/null +++ b/python/testData/inspections/PyPropertyAccessInspection/inheritedClassAttrAssignmentAndOwnWithAttrAndDictAndInheritedSlots.py @@ -0,0 +1,13 @@ +class B(object): + attr = 'baz' + __slots__ = ['f', 'b'] + +class C(B): + __slots__ = ['attr', 'bar', '__dict__'] + +C.attr = 'spam' +print(C.attr) + +c = C() +c.attr = 'spam' +print(c.attr) \ No newline at end of file diff --git a/python/testData/inspections/PyPropertyAccessInspection/inheritedClassAttrAssignmentAndOwnWithAttrAndInheritedSlots.py b/python/testData/inspections/PyPropertyAccessInspection/inheritedClassAttrAssignmentAndOwnWithAttrAndInheritedSlots.py new file mode 100644 index 000000000000..307acdbdb1c8 --- /dev/null +++ b/python/testData/inspections/PyPropertyAccessInspection/inheritedClassAttrAssignmentAndOwnWithAttrAndInheritedSlots.py @@ -0,0 +1,13 @@ +class B(object): + attr = 'baz' + __slots__ = ['f', 'b'] + +class C(B): + __slots__ = ['attr', 'bar'] + +C.attr = 'spam' +print(C.attr) + +c = C() +c.attr = 'spam' +print(c.attr) \ No newline at end of file diff --git a/python/testData/inspections/PyPropertyAccessInspection/inheritedClassAttrAssignmentAndOwnWithAttrAndInheritedWithDictSlots.py b/python/testData/inspections/PyPropertyAccessInspection/inheritedClassAttrAssignmentAndOwnWithAttrAndInheritedWithDictSlots.py new file mode 100644 index 000000000000..21844e6b7da6 --- /dev/null +++ b/python/testData/inspections/PyPropertyAccessInspection/inheritedClassAttrAssignmentAndOwnWithAttrAndInheritedWithDictSlots.py @@ -0,0 +1,13 @@ +class B(object): + attr = 'baz' + __slots__ = ['f', 'b', '__dict__'] + +class C(B): + __slots__ = ['attr', 'bar'] + +C.attr = 'spam' +print(C.attr) + +c = C() +c.attr = 'spam' +print(c.attr) \ No newline at end of file diff --git a/python/testData/inspections/PyPropertyAccessInspection/inheritedClassAttrAssignmentAndOwnWithDictAndInheritedSlots.py b/python/testData/inspections/PyPropertyAccessInspection/inheritedClassAttrAssignmentAndOwnWithDictAndInheritedSlots.py new file mode 100644 index 000000000000..f1b67dca0faf --- /dev/null +++ b/python/testData/inspections/PyPropertyAccessInspection/inheritedClassAttrAssignmentAndOwnWithDictAndInheritedSlots.py @@ -0,0 +1,13 @@ +class B(object): + attr = 'baz' + __slots__ = ['f', 'b'] + +class C(B): + __slots__ = ['foo', 'bar', '__dict__'] + +C.attr = 'spam' +print(C.attr) + +c = C() +c.attr = 'spam' +print(c.attr) \ No newline at end of file diff --git a/python/testData/inspections/PyPropertyAccessInspection/inheritedClassAttrAssignmentAndOwnWithDictAndInheritedWithAttrSlots.py b/python/testData/inspections/PyPropertyAccessInspection/inheritedClassAttrAssignmentAndOwnWithDictAndInheritedWithAttrSlots.py new file mode 100644 index 000000000000..b8ed2806bc6a --- /dev/null +++ b/python/testData/inspections/PyPropertyAccessInspection/inheritedClassAttrAssignmentAndOwnWithDictAndInheritedWithAttrSlots.py @@ -0,0 +1,17 @@ +# Py2: OK +# Py3: +# ValueError: 'attr' in __slots__ conflicts with class variable +# This is not responsibility of current inspection +class B(object): + attr = 'baz' + __slots__ = ['f', 'attr'] + +class C(B): + __slots__ = ['foo', 'bar', '__dict__'] + +C.attr = 'spam' +print(C.attr) + +c = C() +c.attr = 'spam' +print(c.attr) \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/inspections/PyPropertyAccessInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/PyPropertyAccessInspectionTest.java index 3ee4b542b4a5..21101df33545 100644 --- a/python/testSrc/com/jetbrains/python/inspections/PyPropertyAccessInspectionTest.java +++ b/python/testSrc/com/jetbrains/python/inspections/PyPropertyAccessInspectionTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2013 JetBrains s.r.o. + * Copyright 2000-2016 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. @@ -23,15 +23,153 @@ import com.jetbrains.python.psi.LanguageLevel; */ public class PyPropertyAccessInspectionTest extends PyTestCase { public void testTest() { - doTest(); + doTestPy2(); } - public void testOverrideAssignment() { // PY-2313 - doTest(); + // PY-2313 + public void testOverrideAssignment() { + doTestPy2(); } - private void doTest() { - setLanguageLevel(LanguageLevel.PYTHON26); + // PY-12773 + public void testClassAttrAssignmentAndSlots() { + doTestPy2(); + } + + // PY-12773 + public void testClassAttrAssignmentAndSlotsWithDict() { + doTestPy2(); + } + + // PY-12773 + public void testClassAttrAssignmentAndSlotsWithAttrPy2() { + doTestPy2(); + } + + // PY-12773 + public void testClassAttrAssignmentAndSlotsWithAttrPy3() { + doTestPy3(); + } + + // PY-12773 + public void testInheritedClassAttrAssignmentAndInheritedSlots() { + doTestPy2(); + } + + // PY-12773 + public void testClassAttrAssignmentAndInheritedSlots() { + doTestPy2(); + } + + // PY-12773 + public void testInheritedClassAttrAssignmentAndOwnSlots() { + doTestPy2(); + } + + // PY-12773 + public void testClassAttrAssignmentAndOwnSlotsAndEmptyParent() { + doTestPy2(); + } + + // PY-12773 + public void testClassAttrAssignmentAndOwnAndInheritedSlots() { + doTestPy2(); + } + + // PY-12773 + public void testClassAttrAssignmentAndOwnWithDictAndInheritedSlots() { + doTestPy2(); + } + + // PY-12773 + public void testClassAttrAssignmentAndOwnWithDictAndInheritedWithAttrSlots() { + doTestPy2(); + } + + // PY-12773 + public void testClassAttrAssignmentAndOwnAndInheritedWithDictSlots() { + doTestPy2(); + } + + // PY-12773 + public void testClassAttrAssignmentAndOwnWithAttrAndInheritedSlotsPy2() { + doTestPy2(); + } + + // PY-12773 + public void testClassAttrAssignmentAndOwnWithAttrAndInheritedSlotsPy3() { + doTestPy3(); + } + + // PY-12773 + public void testClassAttrAssignmentAndOwnAndInheritedWithAttrSlots() { + doTestPy2(); + } + + // PY-12773 + public void testClassAttrAssignmentAndOwnAndInheritedWithAttrAndDictSlots() { + doTestPy2(); + } + + // PY-12773 + public void testInheritedClassAttrAssignmentAndOwnAndInheritedSlots() { + doTestPy2(); + } + + // PY-12773 + public void testInheritedClassAttrAssignmentAndOwnWithAttrAndInheritedSlots() { + doTestPy2(); + } + + // PY-12773 + public void testInheritedClassAttrAssignmentAndOwnWithDictAndInheritedSlots() { + doTestPy2(); + } + + // PY-12773 + public void testInheritedClassAttrAssignmentAndOwnWithAttrAndDictAndInheritedSlots() { + doTestPy2(); + } + + // PY-12773 + public void testInheritedClassAttrAssignmentAndOwnAndInheritedWithAttrSlotsPy2() { + doTestPy2(); + } + + // PY-12773 + public void testInheritedClassAttrAssignmentAndOwnAndInheritedWithAttrSlotsPy3() { + doTestPy3(); + } + + // PY-12773 + public void testInheritedClassAttrAssignmentAndOwnAndInheritedWithDictSlots() { + doTestPy2(); + } + + // PY-12773 + public void testInheritedClassAttrAssignmentAndOwnAndInheritedWithAttrAndDictSlots() { + doTestPy2(); + } + + // PY-12773 + public void testInheritedClassAttrAssignmentAndOwnWithAttrAndInheritedWithDictSlots() { + doTestPy2(); + } + + // PY-12773 + public void testInheritedClassAttrAssignmentAndOwnWithDictAndInheritedWithAttrSlots() { + doTestPy2(); + } + + private void doTestPy2() { + runWithLanguageLevel(LanguageLevel.PYTHON26, this::doTestPy); + } + + private void doTestPy3() { + runWithLanguageLevel(LanguageLevel.PYTHON30, this::doTestPy); + } + + private void doTestPy() { myFixture.configureByFile("inspections/PyPropertyAccessInspection/" + getTestName(true) + ".py"); myFixture.enableInspections(PyPropertyAccessInspection.class); myFixture.checkHighlighting(true, false, false);