Move PyPropertyAccessInspection functionality which relates to __slots__ to PyDunderSlotsInspection

This commit is contained in:
Semyon Proshev
2016-10-02 19:21:27 +03:00
parent c15c99d00a
commit 92e2aadcc1
35 changed files with 335 additions and 282 deletions
@@ -21,6 +21,8 @@ import com.intellij.psi.PsiElementVisitor
import com.jetbrains.python.PyNames
import com.jetbrains.python.psi.*
import com.jetbrains.python.psi.impl.PyPsiUtils
import com.jetbrains.python.psi.resolve.PyResolveContext
import com.jetbrains.python.psi.types.PyClassType
class PyDunderSlotsInspection : PyInspection() {
@@ -31,6 +33,8 @@ class PyDunderSlotsInspection : PyInspection() {
private class Visitor(holder: ProblemsHolder, session: LocalInspectionToolSession) : PyInspectionVisitor(holder, session) {
override fun visitPyClass(node: PyClass?) {
super.visitPyClass(node)
if (node != null && LanguageLevel.forElement(node).isAtLeast(LanguageLevel.PYTHON30)) {
val slots = findSlotsValue(node)
@@ -47,6 +51,14 @@ class PyDunderSlotsInspection : PyInspection() {
}
}
override fun visitPyTargetExpression(node: PyTargetExpression?) {
super.visitPyTargetExpression(node)
if (node != null) {
checkAttributeExpression(node)
}
}
private fun findSlotsValue(pyClass: PyClass): PyExpression? {
val target = pyClass.findClassAttribute(PyNames.SLOTS, false, myTypeEvalContext) as? PyTargetExpression
val value = target?.findAssignedValue()
@@ -61,6 +73,57 @@ class PyDunderSlotsInspection : PyInspection() {
registerProblem(slot, "'$name' in __slots__ conflicts with class variable")
}
}
private fun checkAttributeExpression(target: PyTargetExpression) {
val targetName = target.name
val qualifier = target.qualifier
if (targetName == null || qualifier == null) {
return
}
val qualifierType = myTypeEvalContext.getType(qualifier)
if (qualifierType is PyClassType && !qualifierType.isDefinition) {
val reference = target.getReference(PyResolveContext.noImplicits().withTypeEvalContext(myTypeEvalContext))
val qualifierClass = qualifierType.pyClass
val classWithReadOnlyAttr = PyUtil
.multiResolveTopPriority(reference)
.asSequence()
.filterIsInstance<PyTargetExpression>()
.map { declaration -> declaration.containingClass }
.filterNotNull()
.find { declaringClass -> !attributeIsWritable(qualifierClass, declaringClass, targetName) }
if (classWithReadOnlyAttr != null) {
registerProblem(target, "'${qualifierClass.name}' object attribute '$targetName' is read-only")
}
}
}
private fun attributeIsWritable(qualifierClass: PyClass, declaringClass: PyClass, targetName: String): Boolean {
return attributeIsWritableInClass(qualifierClass, declaringClass, targetName) ||
qualifierClass
.getAncestorClasses(myTypeEvalContext)
.asSequence()
.filter { ancestorClass -> !PyUtil.isObjectClass(ancestorClass) }
.any { ancestorClass -> attributeIsWritableInClass(ancestorClass, declaringClass, targetName) }
}
private fun attributeIsWritableInClass(cls: PyClass, declaringClass: PyClass, targetName: String): Boolean {
val ownSlots = cls.ownSlots
if (ownSlots == null || ownSlots.contains(PyNames.DICT)) {
return true
}
if (!cls.equals(declaringClass) || !ownSlots.contains(targetName)) {
return false
}
return LanguageLevel.forElement(declaringClass).isAtLeast(LanguageLevel.PYTHON30) ||
declaringClass.findClassAttribute(targetName, false, myTypeEvalContext) == null
}
}
}
@@ -22,18 +22,14 @@ 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
@@ -75,7 +71,6 @@ public class PyPropertyAccessInspection extends PyInspection {
public void visitPyTargetExpression(PyTargetExpression node) {
super.visitPyTargetExpression(node);
checkPropertyExpression(node);
checkAttributeExpression(node);
}
private void checkPropertyExpression(PyQualifiedExpression node) {
@@ -126,59 +121,5 @@ public class PyPropertyAccessInspection extends PyInspection {
registerProblem(node, message, new PyCreatePropertyQuickFix(dir));
}
}
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, targetName))
.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 String targetName) {
return attributeIsWritableInClass(qualifierClass, declaringClass, targetName) ||
qualifierClass
.getAncestorClasses(myTypeEvalContext)
.stream()
.filter(ancestorClass -> !PyUtil.isObjectClass(ancestorClass))
.anyMatch(ancestorClass -> attributeIsWritableInClass(ancestorClass, declaringClass, targetName));
}
private boolean attributeIsWritableInClass(@NotNull PyClass cls, @NotNull PyClass declaringClass, @NotNull String targetName) {
final List<String> ownSlots = cls.getOwnSlots();
if (ownSlots == null || ownSlots.contains(PyNames.DICT)) {
return true;
}
if (!cls.equals(declaringClass) || !ownSlots.contains(targetName)) {
return false;
}
return LanguageLevel.forElement(declaringClass).isAtLeast(LanguageLevel.PYTHON30) ||
declaringClass.findClassAttribute(targetName, false, myTypeEvalContext) == null;
}
}
}
@@ -0,0 +1,13 @@
class B(object):
__slots__ = ['f', 'b']
class C(B):
attr = 'baz'
__slots__ = [<warning descr="'attr' in __slots__ conflicts with class variable">'attr'</warning>, 'bar']
C.attr = 'spam'
print(C.attr)
c = C()
c.attr = 'spam'
print(c.attr)
@@ -0,0 +1,10 @@
class Foo(object):
attr = 'baz'
__slots__ = [<warning descr="'attr' in __slots__ conflicts with class variable">'attr'</warning>, 'bar']
Foo.attr = 'spam'
print(Foo.attr)
foo = Foo()
foo.attr = 'spam'
print(foo.attr)
@@ -1,7 +1,3 @@
# 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__']
@@ -0,0 +1,13 @@
class B(object):
attr = 'baz'
__slots__ = ['f', <warning descr="'attr' in __slots__ conflicts with class variable">'attr'</warning>, '__dict__']
class C(B):
__slots__ = ['foo', 'bar']
C.attr = 'spam'
print(C.attr)
c = C()
c.attr = 'spam'
print(c.attr)
@@ -1,8 +1,6 @@
# ValueError: 'attr' in __slots__ conflicts with class variable
# This is not responsibility of current inspection
class B(object):
attr = 'baz'
__slots__ = ['attr', 'b']
__slots__ = [<warning descr="'attr' in __slots__ conflicts with class variable">'attr'</warning>, 'b']
class C(B):
__slots__ = ['foo', 'bar']
@@ -1,7 +1,3 @@
# 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']
@@ -0,0 +1,13 @@
class B(object):
attr = 'baz'
__slots__ = ['f', <warning descr="'attr' in __slots__ conflicts with class variable">'attr'</warning>]
class C(B):
__slots__ = ['foo', 'bar', '__dict__']
C.attr = 'spam'
print(C.attr)
c = C()
c.attr = 'spam'
print(c.attr)
@@ -1,15 +0,0 @@
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)
@@ -1,12 +0,0 @@
# 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)
@@ -0,0 +1,217 @@
/*
* 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.python.inspections;
import com.intellij.openapi.application.WriteAction;
import com.intellij.openapi.vfs.VirtualFile;
import com.jetbrains.python.fixtures.PyTestCase;
import com.jetbrains.python.psi.LanguageLevel;
import java.io.IOException;
import java.io.UncheckedIOException;
public class PyDunderSlotsInspectionTest extends PyTestCase {
// PY-12773
public void testClassAttrAssignmentAndSlots() {
doTestPy2();
doTestPy3();
}
// PY-12773
public void testClassAttrAssignmentAndSlotsWithDict() {
doTestPy2();
doTestPy3();
}
// PY-12773
public void testClassAttrAssignmentAndSlotsWithAttrPy2() {
doTestPy2();
}
// PY-12773
public void testClassAttrAssignmentAndSlotsWithAttrPy3() {
doTestPy3();
}
// PY-12773
public void testInheritedClassAttrAssignmentAndInheritedSlots() {
doTestPy2();
doTestPy3();
}
// PY-12773
public void testClassAttrAssignmentAndInheritedSlots() {
doTestPy2();
doTestPy3();
}
// PY-12773
public void testInheritedClassAttrAssignmentAndOwnSlots() {
doTestPy2();
doTestPy3();
}
// PY-12773
public void testClassAttrAssignmentAndOwnSlotsAndEmptyParent() {
doTestPy2();
doTestPy3();
}
// PY-12773
public void testClassAttrAssignmentAndOwnAndInheritedSlots() {
doTestPy2();
doTestPy3();
}
// PY-12773
public void testClassAttrAssignmentAndOwnWithDictAndInheritedSlots() {
doTestPy2();
doTestPy3();
}
// PY-12773
public void testClassAttrAssignmentAndOwnWithDictAndInheritedWithAttrSlots() {
doTestPy2();
doTestPy3();
}
// PY-12773
public void testClassAttrAssignmentAndOwnAndInheritedWithDictSlots() {
doTestPy2();
doTestPy3();
}
// PY-12773
public void testClassAttrAssignmentAndOwnWithAttrAndInheritedSlotsPy2() {
doTestPy2();
}
// PY-12773
public void testClassAttrAssignmentAndOwnWithAttrAndInheritedSlotsPy3() {
doTestPy3();
}
// PY-12773
public void testClassAttrAssignmentAndOwnAndInheritedWithAttrSlots() {
doTestPy2();
doTestPy3();
}
// PY-12773
public void testClassAttrAssignmentAndOwnAndInheritedWithAttrAndDictSlots() {
doTestPy2();
doTestPy3();
}
// PY-12773
public void testInheritedClassAttrAssignmentAndOwnAndInheritedSlots() {
doTestPy2();
doTestPy3();
}
// PY-12773
public void testInheritedClassAttrAssignmentAndOwnWithAttrAndInheritedSlots() {
doTestPy2();
doTestPy3();
}
// PY-12773
public void testInheritedClassAttrAssignmentAndOwnWithDictAndInheritedSlots() {
doTestPy2();
doTestPy3();
}
// PY-12773
public void testInheritedClassAttrAssignmentAndOwnWithAttrAndDictAndInheritedSlots() {
doTestPy2();
doTestPy3();
}
// PY-12773
public void testInheritedClassAttrAssignmentAndOwnAndInheritedWithAttrSlotsPy2() {
doTestPy2();
}
// PY-12773
public void testInheritedClassAttrAssignmentAndOwnAndInheritedWithAttrSlotsPy3() {
doTestPy3();
}
// PY-12773
public void testInheritedClassAttrAssignmentAndOwnAndInheritedWithDictSlots() {
doTestPy2();
doTestPy3();
}
// PY-12773
public void testInheritedClassAttrAssignmentAndOwnAndInheritedWithAttrAndDictSlotsPy2() {
doTestPy2();
}
// PY-12773
public void testInheritedClassAttrAssignmentAndOwnAndInheritedWithAttrAndDictSlotsPy3() {
doTestPy3();
}
// PY-12773
public void testInheritedClassAttrAssignmentAndOwnWithAttrAndInheritedWithDictSlots() {
doTestPy2();
doTestPy3();
}
// PY-12773
public void testInheritedClassAttrAssignmentAndOwnWithDictAndInheritedWithAttrSlotsPy2() {
doTestPy2();
}
// PY-12773
public void testInheritedClassAttrAssignmentAndOwnWithDictAndInheritedWithAttrSlotsPy3() {
doTestPy3();
}
// PY-19956
public void testWriteToAttrInSlots() {
doTestPy2();
doTestPy3();
}
private void doTestPy2() {
runWithLanguageLevel(LanguageLevel.PYTHON26, this::doTestPy);
}
private void doTestPy3() {
runWithLanguageLevel(LanguageLevel.PYTHON30, this::doTestPy);
}
private void doTestPy() {
final String path = "inspections/PyDunderSlotsInspectionTest/" + getTestName(true) + ".py";
final VirtualFile file = myFixture.getTempDirFixture().getFile(path);
if (file != null) {
try {
WriteAction.run(() -> file.delete(this));
}
catch (IOException e) {
throw new UncheckedIOException(e);
}
}
myFixture.configureByFile(path);
myFixture.enableInspections(PyDunderSlotsInspection.class);
myFixture.checkHighlighting(true, false, false);
}
}
@@ -15,205 +15,25 @@
*/
package com.jetbrains.python.inspections;
import com.intellij.openapi.application.WriteAction;
import com.intellij.openapi.vfs.VirtualFile;
import com.jetbrains.python.fixtures.PyTestCase;
import com.jetbrains.python.psi.LanguageLevel;
import java.io.IOException;
import java.io.UncheckedIOException;
/**
* @author yole
*/
public class PyPropertyAccessInspectionTest extends PyTestCase {
public void testTest() {
doTestPy2();
doTest();
}
// PY-2313
public void testOverrideAssignment() {
doTestPy2();
doTest();
}
// PY-12773
public void testClassAttrAssignmentAndSlots() {
doTestPy2();
doTestPy3();
}
// PY-12773
public void testClassAttrAssignmentAndSlotsWithDict() {
doTestPy2();
doTestPy3();
}
// PY-12773
public void testClassAttrAssignmentAndSlotsWithAttrPy2() {
doTestPy2();
}
// PY-12773
public void testClassAttrAssignmentAndSlotsWithAttrPy3() {
doTestPy3();
}
// PY-12773
public void testInheritedClassAttrAssignmentAndInheritedSlots() {
doTestPy2();
doTestPy3();
}
// PY-12773
public void testClassAttrAssignmentAndInheritedSlots() {
doTestPy2();
doTestPy3();
}
// PY-12773
public void testInheritedClassAttrAssignmentAndOwnSlots() {
doTestPy2();
doTestPy3();
}
// PY-12773
public void testClassAttrAssignmentAndOwnSlotsAndEmptyParent() {
doTestPy2();
doTestPy3();
}
// PY-12773
public void testClassAttrAssignmentAndOwnAndInheritedSlots() {
doTestPy2();
doTestPy3();
}
// PY-12773
public void testClassAttrAssignmentAndOwnWithDictAndInheritedSlots() {
doTestPy2();
doTestPy3();
}
// PY-12773
public void testClassAttrAssignmentAndOwnWithDictAndInheritedWithAttrSlots() {
doTestPy2();
doTestPy3();
}
// PY-12773
public void testClassAttrAssignmentAndOwnAndInheritedWithDictSlots() {
doTestPy2();
doTestPy3();
}
// PY-12773
public void testClassAttrAssignmentAndOwnWithAttrAndInheritedSlotsPy2() {
doTestPy2();
}
// PY-12773
public void testClassAttrAssignmentAndOwnWithAttrAndInheritedSlotsPy3() {
doTestPy3();
}
// PY-12773
public void testClassAttrAssignmentAndOwnAndInheritedWithAttrSlots() {
doTestPy2();
doTestPy3();
}
// PY-12773
public void testClassAttrAssignmentAndOwnAndInheritedWithAttrAndDictSlots() {
doTestPy2();
doTestPy3();
}
// PY-12773
public void testInheritedClassAttrAssignmentAndOwnAndInheritedSlots() {
doTestPy2();
doTestPy3();
}
// PY-12773
public void testInheritedClassAttrAssignmentAndOwnWithAttrAndInheritedSlots() {
doTestPy2();
doTestPy3();
}
// PY-12773
public void testInheritedClassAttrAssignmentAndOwnWithDictAndInheritedSlots() {
doTestPy2();
doTestPy3();
}
// PY-12773
public void testInheritedClassAttrAssignmentAndOwnWithAttrAndDictAndInheritedSlots() {
doTestPy2();
doTestPy3();
}
// PY-12773
public void testInheritedClassAttrAssignmentAndOwnAndInheritedWithAttrSlotsPy2() {
doTestPy2();
}
// PY-12773
public void testInheritedClassAttrAssignmentAndOwnAndInheritedWithAttrSlotsPy3() {
doTestPy3();
}
// PY-12773
public void testInheritedClassAttrAssignmentAndOwnAndInheritedWithDictSlots() {
doTestPy2();
doTestPy3();
}
// PY-12773
public void testInheritedClassAttrAssignmentAndOwnAndInheritedWithAttrAndDictSlots() {
doTestPy2();
doTestPy3();
}
// PY-12773
public void testInheritedClassAttrAssignmentAndOwnWithAttrAndInheritedWithDictSlots() {
doTestPy2();
doTestPy3();
}
// PY-12773
public void testInheritedClassAttrAssignmentAndOwnWithDictAndInheritedWithAttrSlots() {
doTestPy2();
doTestPy3();
}
// PY-19956
public void testWriteToAttrInSlots() {
doTestPy2();
doTestPy3();
}
private void doTestPy2() {
runWithLanguageLevel(LanguageLevel.PYTHON26, this::doTestPy);
}
private void doTestPy3() {
runWithLanguageLevel(LanguageLevel.PYTHON30, this::doTestPy);
}
private void doTestPy() {
final String path = "inspections/PyPropertyAccessInspection/" + getTestName(true) + ".py";
final VirtualFile file = myFixture.getTempDirFixture().getFile(path);
if (file != null) {
try {
WriteAction.run(() -> file.delete(this));
}
catch (IOException e) {
throw new UncheckedIOException(e);
}
}
myFixture.configureByFile(path);
private void doTest() {
setLanguageLevel(LanguageLevel.PYTHON26);
myFixture.configureByFile("inspections/PyPropertyAccessInspection/" + getTestName(true) + ".py");
myFixture.enableInspections(PyPropertyAccessInspection.class);
myFixture.checkHighlighting(true, false, false);
}