PY-27604 Exclude instantiated metaclasses from ancestor types of a class

but properly take them into account in PyClassType#getMetaClassType().

This check was implemented there instead of PyClass#getMetaClassType()
itself, since the former method already looks up for explicitly declared
metaclasses in ancestors unlike PyClassImpl that considers only those
specified directly in the class definition or its containing module.

Moreover, there are actual usages that expects such behavior from
PyClass#getMetaClassType(), e.g. PyAbstractClassInspection.
This commit is contained in:
Mikhail Golubev
2018-01-11 12:45:08 +03:00
parent f7da96db03
commit cbb5fe587a
5 changed files with 62 additions and 9 deletions
@@ -291,6 +291,11 @@ public interface PyClass extends PsiNameIdentifierOwner, PyStatement, PyDocStrin
//TODO: Add "addMetaClass" or move methods out of here
@NotNull
default List<PyClassLikeType> getAncestorTypesWithMetaClassInstances(@NotNull TypeEvalContext context) {
return getAncestorTypes(context);
}
/**
* Returns the type representing the metaclass of the class if it is explicitly set, null otherwise.
* <p/>
@@ -1423,6 +1423,12 @@ public class PyClassImpl extends PyBaseElementImpl<PyClassStub> implements PyCla
@NotNull
@Override
public List<PyClassLikeType> getAncestorTypes(@NotNull final TypeEvalContext context) {
return ContainerUtil.filter(getAncestorTypesWithMetaClassInstances(context), type -> type == null || type.isDefinition());
}
@Override
@NotNull
public List<PyClassLikeType> getAncestorTypesWithMetaClassInstances(@NotNull TypeEvalContext context) {
return PyUtil.getParameterizedCachedValue(this, context, contextArgument -> {
List<PyClassLikeType> ancestorTypes;
if (isNewStyleClass(contextArgument)) {
@@ -2,7 +2,6 @@
package com.jetbrains.python.psi.types;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.intellij.codeInsight.completion.CompletionUtil;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.lookup.LookupElementBuilder;
@@ -388,7 +387,7 @@ public class PyClassTypeImpl extends UserDataHolderBase implements PyClassType {
if (!inherited) {
return as(myClass.getMetaClassType(context), PyClassLikeType.class);
}
final List<PyClassLikeType> metaClassTypes = getAllExplicitMetaClassTypes(context);
final List<PyClassLikeType> metaClassTypes = getAllPossibleMetaClassTypes(context);
final PyClassLikeType mostDerivedMeta = getMostDerivedClassType(metaClassTypes, context);
return mostDerivedMeta != null ? mostDerivedMeta : PyBuiltinCache.getInstance(myClass).getObjectType("type");
}
@@ -431,17 +430,23 @@ public class PyClassTypeImpl extends UserDataHolderBase implements PyClassType {
private static final class NotDerivedClassTypeException extends RuntimeException {
}
private List<PyClassLikeType> getAllExplicitMetaClassTypes(@NotNull TypeEvalContext context) {
@NotNull
private List<PyClassLikeType> getAllPossibleMetaClassTypes(@NotNull TypeEvalContext context) {
final List<PyClassLikeType> results = Lists.newArrayList();
final PyClassLikeType ownMeta = getMetaClassType(context, false);
if (ownMeta != null) {
results.add(ownMeta);
}
for (PyClassLikeType ancestor : myClass.getAncestorTypes(context)) {
for (PyClassLikeType ancestor : myClass.getAncestorTypesWithMetaClassInstances(context)) {
if (ancestor != null) {
final PyClassLikeType ancestorMeta = ancestor.getMetaClassType(context, false);
if (ancestorMeta != null) {
results.add(ancestorMeta);
if (!ancestor.isDefinition()) {
results.add(ancestor.toClass());
}
else {
final PyClassLikeType ancestorMeta = ancestor.getMetaClassType(context, false);
if (ancestorMeta != null) {
results.add(ancestorMeta);
}
}
}
}
@@ -0,0 +1,10 @@
class Meta(type):
pass
class Base(metaclass=Meta):
pass
class MyClass(Base):
pass
@@ -20,6 +20,8 @@ import com.jetbrains.python.fixtures.PyTestCase;
import com.jetbrains.python.psi.LanguageLevel;
import com.jetbrains.python.psi.PyClass;
import com.jetbrains.python.psi.types.PyClassLikeType;
import com.jetbrains.python.psi.types.PyClassType;
import com.jetbrains.python.psi.types.PyType;
import com.jetbrains.python.psi.types.TypeEvalContext;
import org.jetbrains.annotations.NotNull;
@@ -120,6 +122,14 @@ public class PyClassMROTest extends PyTestCase {
assertOrderedEquals(classNames, Arrays.asList(mro));
}
public void assertMetaClass(@NotNull PyClass cls, @NotNull String name) {
final TypeEvalContext context = TypeEvalContext.deepCodeInsight(cls.getProject());
final PyType metaClassType = cls.getType(context).getMetaClassType(context, true);
assertInstanceOf(metaClassType, PyClassType.class);
assertTrue(((PyClassType)metaClassType).isDefinition());
assertEquals(name, metaClassType.getName());
}
// PY-20026
public void testDuplicatedBaseClasses() {
assertMRO(getClass("MyClass"), "Base", "object");
@@ -127,12 +137,29 @@ public class PyClassMROTest extends PyTestCase {
// PY-27656
public void testDirectlyInstantiatedMetaclassAncestor() {
runWithLanguageLevel(LanguageLevel.PYTHON30, () -> assertMRO(getClass("MyClass"), "Meta", "object"));
runWithLanguageLevel(LanguageLevel.PYTHON30, () -> {
final PyClass pyClass = getClass("MyClass");
assertMRO(pyClass, "object");
assertMetaClass(pyClass, "Meta");
});
}
// PY-27656
public void testMetaClassDeclaredThroughAncestor() {
runWithLanguageLevel(LanguageLevel.PYTHON30, () -> {
final PyClass pyClass = getClass("MyClass");
assertMRO(pyClass, "Base", "object");
assertMetaClass(pyClass, "Meta");
});
}
// PY-20026
public void testUnresolvedMetaClassAncestors() {
runWithLanguageLevel(LanguageLevel.PYTHON30, () -> assertMRO(getClass("CompositeFieldMeta"), "type", "object"));
runWithLanguageLevel(LanguageLevel.PYTHON30, () -> {
final PyClass pyClass = getClass("CompositeFieldMeta");
assertMRO(pyClass, "object");
assertMetaClass(pyClass, "type");
});
}
@NotNull