Inline __slots__ invalidation logic into PyClass (PY-29268)

This commit is contained in:
Semyon Proshev
2018-05-29 17:24:39 +03:00
parent 74d1d6b18b
commit 54898c22c6
6 changed files with 42 additions and 13 deletions
@@ -266,7 +266,13 @@ public interface PyClass extends PsiNameIdentifierOwner, PyStatement, PyDocStrin
boolean isSubclass(@NotNull String superClassQName, @Nullable TypeEvalContext context);
/**
* Returns the aggregated list of names defined in __slots__ attributes of the class and its ancestors.
* Returns the aggregated list of names defined in `__slots__` attributes of the class and its ancestors.
* <p>
* Returned value is `null` if class or at least one of its ancestor does not follow the next conditions:
* <ul>
* <li>it should be a new style class</li>
* <li>its `__slots__` should exist and should not contain `__dict__`</li>
* </ul>
*
* @param context (will be used default if null)
*/
@@ -101,7 +101,7 @@ class PyDunderSlotsInspection : PyInspection() {
}
private fun attributeIsWritableInPy2(cls: PyClass, name: String): Boolean {
val slots = PyUtil.deactivateSlots(cls, cls.getSlots(myTypeEvalContext), myTypeEvalContext)
val slots = cls.getSlots(myTypeEvalContext)
return slots == null ||
slots.contains(name) && cls.findClassAttribute(name, true, myTypeEvalContext) == null ||
cls.findProperty(name, true, myTypeEvalContext) != null
@@ -1,6 +1,7 @@
// 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.impl;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.intellij.codeInsight.completion.CompletionUtil;
import com.intellij.lang.ASTNode;
@@ -259,24 +260,27 @@ public class PyClassImpl extends PyBaseElementImpl<PyClassStub> implements PyCla
@Override
@Nullable
public List<String> getSlots(@Nullable TypeEvalContext context) {
final List<String> ownSlots = getOwnSlots();
if (ownSlots == null) {
return null;
}
final Set<String> result = new LinkedHashSet<>();
final Set<String> result = new LinkedHashSet<>(ownSlots);
final PyClassType currentType = new PyClassTypeImpl(this, true);
final TypeEvalContext contextToUse = context != null ? context : TypeEvalContext.codeInsightFallback(getProject());
for (PyClass cls : getAncestorClasses(context)) {
for (PyClassLikeType type : Iterables.concat(Collections.singletonList(currentType), getAncestorTypes(contextToUse))) {
if (!(type instanceof PyClassType)) return null;
final PyClass cls = ((PyClassType)type).getPyClass();
if (PyUtil.isObjectClass(cls)) {
continue;
}
final List<String> ancestorSlots = cls.getOwnSlots();
if (ancestorSlots == null) {
if (!cls.isNewStyleClass(contextToUse)) return null;
final List<String> ownSlots = cls.getOwnSlots();
if (ownSlots == null || ownSlots.contains(PyNames.DICT)) {
return null;
}
result.addAll(ancestorSlots);
result.addAll(ownSlots);
}
return new ArrayList<>(result);
@@ -747,7 +747,7 @@ public class PyClassTypeImpl extends UserDataHolderBase implements PyClassType {
final TypeEvalContext context =
(expressionHook != null ? TypeEvalContext.codeCompletion(myClass.getProject(), myClass.getContainingFile()) : null);
processor.setAllowedNames(PyUtil.deactivateSlots(myClass, myClass.getSlots(context), context));
processor.setAllowedNames(myClass.getSlots(context));
myClass.processInstanceLevelDeclarations(processor, expressionHook);
for (LookupElement le : processor.getResultList()) {
+1 -1
View File
@@ -1,2 +1,2 @@
class A:
class A(object):
__slots__ = ['foo', 'bar']
@@ -269,6 +269,25 @@ public class PyDunderSlotsInspectionTest extends PyInspectionTestCase {
);
}
// PY-29268
public void testWriteToNewStyleInheritedFromOldStyle() {
doTestByText("class A:\n" +
" __slots__ = ['a']\n" +
"\n" +
"class B(A, object):\n" +
" __slots__ = ['b']\n" +
"\n" +
"B().c = 1");
}
// PY-29268
public void testWriteToNewStyleInheritedFromUnknown() {
doTestByText("class B(A, object):\n" +
" __slots__ = ['b']\n" +
"\n" +
"B().c = 1");
}
private void doTestPy2() {
runWithLanguageLevel(LanguageLevel.PYTHON26, this::doTest);
}