From 1dfa08e124b40e24d49fc6f534e0ff29c94e4c51 Mon Sep 17 00:00:00 2001 From: Semyon Proshev Date: Fri, 10 Nov 2017 22:57:37 +0300 Subject: [PATCH] Collect metaclass members while calculating class member names (PY-21408) --- .../python/psi/types/PyClassTypeImpl.java | 27 ++++++++++++++ .../ClassMetaAttrsAgainstStructural.py | 37 +++++++++++++++++++ .../PyTypeCheckerInspectionTest.java | 5 +++ 3 files changed, 69 insertions(+) create mode 100644 python/testData/inspections/PyTypeCheckerInspection/ClassMetaAttrsAgainstStructural.py diff --git a/python/src/com/jetbrains/python/psi/types/PyClassTypeImpl.java b/python/src/com/jetbrains/python/psi/types/PyClassTypeImpl.java index 3e5fdf02eb42..1d17b87cfc8c 100644 --- a/python/src/com/jetbrains/python/psi/types/PyClassTypeImpl.java +++ b/python/src/com/jetbrains/python/psi/types/PyClassTypeImpl.java @@ -662,6 +662,9 @@ public class PyClassTypeImpl extends UserDataHolderBase implements PyClassType { @NotNull @Override public Set 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 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 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 typeInstanceAttributes = ((PyClassType)typeType).getPyClass().getInstanceAttributes(); + return ContainerUtil.map2SetNotNull(typeInstanceAttributes, PyTargetExpression::getName); + } + + return Collections.emptySet(); + } + private void addOwnClassMembers(PsiElement expressionHook, Set namesAlready, boolean suppressParentheses, diff --git a/python/testData/inspections/PyTypeCheckerInspection/ClassMetaAttrsAgainstStructural.py b/python/testData/inspections/PyTypeCheckerInspection/ClassMetaAttrsAgainstStructural.py new file mode 100644 index 000000000000..d3b014b102fc --- /dev/null +++ b/python/testData/inspections/PyTypeCheckerInspection/ClassMetaAttrsAgainstStructural.py @@ -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(A1) + + +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(A2()) diff --git a/python/testSrc/com/jetbrains/python/inspections/PyTypeCheckerInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/PyTypeCheckerInspectionTest.java index cb9e1f605b54..0a7dd0d0f99d 100644 --- a/python/testSrc/com/jetbrains/python/inspections/PyTypeCheckerInspectionTest.java +++ b/python/testSrc/com/jetbrains/python/inspections/PyTypeCheckerInspectionTest.java @@ -446,4 +446,9 @@ public class PyTypeCheckerInspectionTest extends PyInspectionTestCase { public void testMatchingOpenFunctionCallTypesPy2() { doMultiFileTest(); } + + // PY-21408 + public void testClassMetaAttrsAgainstStructural() { + runWithLanguageLevel(LanguageLevel.PYTHON30, this::doTest); + } }