Collect metaclass members while calculating class member names (PY-21408)

This commit is contained in:
Semyon Proshev
2017-11-16 14:37:55 +03:00
parent d270f9f48d
commit 1dfa08e124
3 changed files with 69 additions and 0 deletions
@@ -662,6 +662,9 @@ public class PyClassTypeImpl extends UserDataHolderBase implements PyClassType {
@NotNull
@Override
public Set<String> getMemberNames(boolean inherited, @NotNull TypeEvalContext context) {
// PyNamedTupleType.getMemberNames provide names that we are not able to visit,
// so this method could not be replaced with PyClassLikeType.visitMembers
final Set<String> result = new LinkedHashSet<>();
for (PyFunction function : myClass.getMethods()) {
@@ -692,11 +695,35 @@ public class PyClassTypeImpl extends UserDataHolderBase implements PyClassType {
result.addAll(ancestorType.getMemberNames(false, context));
}
}
result.addAll(getMetaClassMemberNames(context));
}
return result;
}
@NotNull
private Set<String> getMetaClassMemberNames(@NotNull TypeEvalContext context) {
if (!myClass.isNewStyleClass(context)) {
return Collections.emptySet();
}
final PyClassLikeType typeType = getMetaClassType(context, true);
if (typeType == null) {
return Collections.emptySet();
}
if (isDefinition()) {
return typeType.getMemberNames(true, context);
}
else if (typeType instanceof PyClassType) {
final List<PyTargetExpression> typeInstanceAttributes = ((PyClassType)typeType).getPyClass().getInstanceAttributes();
return ContainerUtil.map2SetNotNull(typeInstanceAttributes, PyTargetExpression::getName);
}
return Collections.emptySet();
}
private void addOwnClassMembers(PsiElement expressionHook,
Set<String> namesAlready,
boolean suppressParentheses,
@@ -0,0 +1,37 @@
class B1(type):
meta_attr = "meta_attr"
class A1(metaclass=B1):
pass
def print_A1(a):
print(a.__name__)
print(a.meta_attr)
def print_unknown(a):
print(a.unknown)
print_A1(A1)
print_unknown(<warning descr="Type 'Type[A1]' doesn't have expected attribute 'unknown'">A1</warning>)
class B2(type):
def __init__(self, what, bases, dict):
self.meta_attr = "meta_attr"
super().__init__(what, bases, dict)
class A2(metaclass=B2):
pass
def print_A2(a):
print(a.meta_attr)
print_A2(A2())
print_unknown(<warning descr="Type 'A2' doesn't have expected attribute 'unknown'">A2()</warning>)
@@ -446,4 +446,9 @@ public class PyTypeCheckerInspectionTest extends PyInspectionTestCase {
public void testMatchingOpenFunctionCallTypesPy2() {
doMultiFileTest();
}
// PY-21408
public void testClassMetaAttrsAgainstStructural() {
runWithLanguageLevel(LanguageLevel.PYTHON30, this::doTest);
}
}