From bf73dcef67996821db6eb491c69f8cf6614c5db2 Mon Sep 17 00:00:00 2001 From: Semyon Proshev Date: Fri, 30 Mar 2018 18:26:14 +0300 Subject: [PATCH] Simplify determining if class type attribute is writable (PY-27866) Don't consider the following attribute as writable in Python 3+: ``` class A: attr = "attr" __slots__ = ("attr") A().attr ``` --- .../python/psi/types/PyClassTypeImpl.java | 65 ++----------------- ...nmentAndOwnWithAttrAndInheritedSlotsPy3.py | 2 +- .../classAttrAssignmentAndSlotsWithAttrPy3.py | 2 +- ...nmentAndOwnAndInheritedWithAttrSlotsPy3.py | 2 +- 4 files changed, 8 insertions(+), 63 deletions(-) diff --git a/python/src/com/jetbrains/python/psi/types/PyClassTypeImpl.java b/python/src/com/jetbrains/python/psi/types/PyClassTypeImpl.java index 5f793de070fa..d17b757c7b72 100644 --- a/python/src/com/jetbrains/python/psi/types/PyClassTypeImpl.java +++ b/python/src/com/jetbrains/python/psi/types/PyClassTypeImpl.java @@ -1,7 +1,6 @@ // Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.jetbrains.python.psi.types; -import com.google.common.collect.Iterables; import com.intellij.codeInsight.completion.CompletionUtil; import com.intellij.codeInsight.lookup.LookupElement; import com.intellij.codeInsight.lookup.LookupElementBuilder; @@ -865,68 +864,14 @@ public class PyClassTypeImpl extends UserDataHolderBase implements PyClassType { @Override public boolean isAttributeWritable(@NotNull String name, @NotNull TypeEvalContext context) { - if (isDefinition() || PyUtil.isObjectClass(getPyClass())) return true; + final PyClass cls = getPyClass(); - /* - The only difference between Py2 and Py3+ is that the following case is considered as writable in Py3+: + if (isDefinition() || PyUtil.isObjectClass(cls)) return true; - class A: - attr = "attr" - __slots__ = ("attr") - - A().attr - - Py3+ raises ValueError about conflict between __slots__ and class variable. - This case is handled in com.jetbrains.python.inspections.PyDunderSlotsInspection.Visitor.processSlot. - */ - if (LanguageLevel.forElement(getPyClass()).isPython2()) { - return attributeIsWritableInPy2(name, context); - } - else { - return attributeIsWritableInPy3(name, context); - } - } - - private boolean attributeIsWritableInPy2(@NotNull String name, @NotNull TypeEvalContext context) { - final List slots = getPyClass().getSlots(context); + final List slots = cls.getSlots(context); return slots == null || - slots.contains(name) && getPyClass().findClassAttribute(name, true, context) == null || - getPyClass().findProperty(name, true, context) != null; - } - - private boolean attributeIsWritableInPy3(@NotNull String name, @NotNull TypeEvalContext context) { - boolean classAttrIsFound = false; - boolean slotIsFound = false; - - for (PyClassLikeType type : Iterables.concat(Collections.singletonList(this), getAncestorTypes(context))) { - if (!(type instanceof PyClassType)) return true; - - final PyClass cls = ((PyClassType)type).getPyClass(); - if (PyUtil.isObjectClass(cls)) { - continue; - } - - if (!cls.isNewStyleClass(context)) return true; - - final List ownSlots = cls.getOwnSlots(); - if (ownSlots == null || ownSlots.contains(PyNames.DICT)) { - return true; - } - - if (cls.findProperty(name, false, context) != null) { - return true; - } - - if (!classAttrIsFound) { - classAttrIsFound = cls.findClassAttribute(name, false, context) != null; - if (ownSlots.contains(name)) { - if (classAttrIsFound) return true; - slotIsFound = true; - } - } - } - - return slotIsFound && !classAttrIsFound; + slots.contains(name) && cls.findClassAttribute(name, true, context) == null || + cls.findProperty(name, true, context) != null; } @Nullable diff --git a/python/testData/inspections/PyDunderSlotsInspection/classAttrAssignmentAndOwnWithAttrAndInheritedSlotsPy3.py b/python/testData/inspections/PyDunderSlotsInspection/classAttrAssignmentAndOwnWithAttrAndInheritedSlotsPy3.py index 9bf8cbb3654c..f332c7ec6d13 100644 --- a/python/testData/inspections/PyDunderSlotsInspection/classAttrAssignmentAndOwnWithAttrAndInheritedSlotsPy3.py +++ b/python/testData/inspections/PyDunderSlotsInspection/classAttrAssignmentAndOwnWithAttrAndInheritedSlotsPy3.py @@ -9,5 +9,5 @@ C.attr = 'spam' print(C.attr) c = C() -c.attr = 'spam' +c.attr = 'spam' print(c.attr) \ No newline at end of file diff --git a/python/testData/inspections/PyDunderSlotsInspection/classAttrAssignmentAndSlotsWithAttrPy3.py b/python/testData/inspections/PyDunderSlotsInspection/classAttrAssignmentAndSlotsWithAttrPy3.py index 3db9a4206fe4..7a94d542b0cf 100644 --- a/python/testData/inspections/PyDunderSlotsInspection/classAttrAssignmentAndSlotsWithAttrPy3.py +++ b/python/testData/inspections/PyDunderSlotsInspection/classAttrAssignmentAndSlotsWithAttrPy3.py @@ -6,5 +6,5 @@ Foo.attr = 'spam' print(Foo.attr) foo = Foo() -foo.attr = 'spam' +foo.attr = 'spam' print(foo.attr) \ No newline at end of file diff --git a/python/testData/inspections/PyDunderSlotsInspection/inheritedClassAttrAssignmentAndOwnAndInheritedWithAttrSlotsPy3.py b/python/testData/inspections/PyDunderSlotsInspection/inheritedClassAttrAssignmentAndOwnAndInheritedWithAttrSlotsPy3.py index 301371fbf75e..964788a791ce 100644 --- a/python/testData/inspections/PyDunderSlotsInspection/inheritedClassAttrAssignmentAndOwnAndInheritedWithAttrSlotsPy3.py +++ b/python/testData/inspections/PyDunderSlotsInspection/inheritedClassAttrAssignmentAndOwnAndInheritedWithAttrSlotsPy3.py @@ -9,5 +9,5 @@ C.attr = 'spam' print(C.attr) c = C() -c.attr = 'spam' +c.attr = 'spam' print(c.attr) \ No newline at end of file