mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user