From 5c920951f0d979173d4fe88b29a93b58c10607b6 Mon Sep 17 00:00:00 2001 From: Semyon Proshev Date: Thu, 3 Nov 2016 17:09:40 +0300 Subject: [PATCH] PY-19702 Fixed: Unresolved metaclass attributes in children In case of definition resolve attribute in metaclass definition and instance. In case of instance check if metaclass is PyClass and look for attribute in instance attributes. Same with completion. --- .../python/psi/types/PyClassTypeImpl.java | 84 ++++++++++++++++--- .../metaclassAttribute.py | 12 +++ .../jetbrains/python/Py3CompletionTest.java | 52 ++++++++++++ ...Py3UnresolvedReferencesInspectionTest.java | 5 ++ 4 files changed, 140 insertions(+), 13 deletions(-) create mode 100644 python/testData/inspections/PyUnresolvedReferencesInspection3K/metaclassAttribute.py diff --git a/python/src/com/jetbrains/python/psi/types/PyClassTypeImpl.java b/python/src/com/jetbrains/python/psi/types/PyClassTypeImpl.java index f609a68f64b8..1fa26e2e302d 100644 --- a/python/src/com/jetbrains/python/psi/types/PyClassTypeImpl.java +++ b/python/src/com/jetbrains/python/psi/types/PyClassTypeImpl.java @@ -38,6 +38,7 @@ import com.jetbrains.python.psi.*; import com.jetbrains.python.psi.impl.PyBuiltinCache; import com.jetbrains.python.psi.impl.PyResolveResultRater; import com.jetbrains.python.psi.impl.ResolveResultList; +import com.jetbrains.python.psi.impl.references.PyReferenceImpl; import com.jetbrains.python.psi.resolve.CompletionVariantsProcessor; import com.jetbrains.python.psi.resolve.PyResolveContext; import com.jetbrains.python.psi.resolve.PyResolveProcessor; @@ -243,13 +244,10 @@ public class PyClassTypeImpl extends UserDataHolderBase implements PyClassType { } } - if (inherited && isDefinition() && myClass.isNewStyleClass(context)) { - final PyClassLikeType typeType = getMetaClassType(context, true); - if (typeType != null) { - List typeMembers = typeType.resolveMember(name, location, direction, resolveContext); - if (typeMembers != null && !typeMembers.isEmpty()) { - return typeMembers; - } + if (inherited) { + final List typeMembers = resolveMetaClassMember(name, location, direction, resolveContext); + if (typeMembers != null) { + return typeMembers; } } @@ -280,6 +278,46 @@ public class PyClassTypeImpl extends UserDataHolderBase implements PyClassType { return Collections.emptyList(); } + @Nullable + private List resolveMetaClassMember(@NotNull String name, + @Nullable PyExpression location, + @NotNull AccessDirection direction, + @NotNull PyResolveContext resolveContext) { + final TypeEvalContext context = resolveContext.getTypeEvalContext(); + if (!myClass.isNewStyleClass(context)) { + return null; + } + + final PyClassLikeType typeType = getMetaClassType(context, true); + if (typeType == null) { + return null; + } + + if (isDefinition()) { + final List typeMembers = typeType.resolveMember(name, location, direction, resolveContext); + if (!ContainerUtil.isEmpty(typeMembers)) { + return typeMembers; + } + + final List typeInstanceMembers = + typeType.toInstance().resolveMember(name, location, direction, resolveContext); + + if (!ContainerUtil.isEmpty(typeInstanceMembers)) { + return typeInstanceMembers; + } + } + else if (typeType instanceof PyClassType) { + final List typeInstanceAttributes = ((PyClassType)typeType).getPyClass().getInstanceAttributes(); + + if (!ContainerUtil.isEmpty(typeInstanceAttributes)) { + return ContainerUtil + .map(typeInstanceAttributes, member -> new RatedResolveResult(PyReferenceImpl.getRate(member, context), member)); + } + } + + return null; + } + private Ref findProperty(String name, AccessDirection direction, boolean inherited, @@ -560,16 +598,36 @@ public class PyClassTypeImpl extends UserDataHolderBase implements PyClassType { } } - if (isDefinition() && myClass.isNewStyleClass(typeEvalContext)) { - final PyClassLikeType typeType = getMetaClassType(typeEvalContext, true); - if (typeType != null) { - Collections.addAll(ret, typeType.getCompletionVariants(prefix, location, context)); - } - } + Collections.addAll(ret, getMetaClassCompletionVariants(prefix, location, context, typeEvalContext)); return ret.toArray(); } + @NotNull + private Object[] getMetaClassCompletionVariants(@Nullable String prefix, + @Nullable PsiElement location, + @NotNull ProcessingContext processingContext, + @NotNull TypeEvalContext typeEvalContext) { + if (!myClass.isNewStyleClass(typeEvalContext)) { + return ArrayUtil.EMPTY_OBJECT_ARRAY; + } + + final PyClassLikeType typeType = getMetaClassType(typeEvalContext, true); + if (typeType == null) { + return ArrayUtil.EMPTY_OBJECT_ARRAY; + } + + if (isDefinition()) { + return typeType.getCompletionVariants(prefix, location, processingContext); + } + else if (typeType instanceof PyClassType) { + final List typeInstanceAttributes = ((PyClassType)typeType).getPyClass().getInstanceAttributes(); + return ContainerUtil.map2Array(typeInstanceAttributes, LookupElementBuilder::create); + } + + return ArrayUtil.EMPTY_OBJECT_ARRAY; + } + @Override public void visitMembers(@NotNull final Processor processor, final boolean inherited, diff --git a/python/testData/inspections/PyUnresolvedReferencesInspection3K/metaclassAttribute.py b/python/testData/inspections/PyUnresolvedReferencesInspection3K/metaclassAttribute.py new file mode 100644 index 000000000000..9d64bd1ad047 --- /dev/null +++ b/python/testData/inspections/PyUnresolvedReferencesInspection3K/metaclassAttribute.py @@ -0,0 +1,12 @@ +class Meta(type): + def __init__(self, what, bases, dict): + self.meta_attr = "attr" + super().__init__(what, bases, dict) + + +class A(metaclass=Meta): + pass + + +print(A.meta_attr) +print(A().meta_attr) \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/Py3CompletionTest.java b/python/testSrc/com/jetbrains/python/Py3CompletionTest.java index 2af14073d216..2bedfc9b4521 100644 --- a/python/testSrc/com/jetbrains/python/Py3CompletionTest.java +++ b/python/testSrc/com/jetbrains/python/Py3CompletionTest.java @@ -205,6 +205,58 @@ public class Py3CompletionTest extends PyTestCase { runWithLanguageLevel(LanguageLevel.PYTHON35, this::doTest); } + // PY-19702 + public void testMetaclassAttributeOnDefinition() { + final List suggested = doTestByText("class Meta(type):\n" + + " def __init__(self, what, bases, dict):\n" + + " self.meta_attr = \"attr\"\n" + + " super().__init__(what, bases, dict)\n" + + "class A(metaclass=Meta):\n" + + " pass\n" + + "print(A.)"); + + assertNotNull(suggested); + assertContainsElements(suggested, "meta_attr"); + } + + // PY-19702 + public void testMetaclassAttributeOnInstance() { + final List suggested = doTestByText("class Meta(type):\n" + + " def __init__(self, what, bases, dict):\n" + + " self.meta_attr = \"attr\"\n" + + " super().__init__(what, bases, dict)\n" + + "class A(metaclass=Meta):\n" + + " pass\n" + + "print(A().)"); + + assertNotNull(suggested); + assertContainsElements(suggested, "meta_attr"); + } + + public void testMetaclassMethodOnDefinition() { + final List suggested = doTestByText("class Meta(type):\n" + + " def meta_method(cls):\n" + + " pass\n" + + "class A(metaclass=Meta):\n" + + " pass\n" + + "print(A.)"); + + assertNotNull(suggested); + assertContainsElements(suggested, "meta_method"); + } + + public void testMetaclassMethodOnInstance() { + final List suggested = doTestByText("class Meta(type):\n" + + " def meta_method(cls):\n" + + " pass\n" + + "class A(metaclass=Meta):\n" + + " pass\n" + + "print(A().)"); + + assertNotNull(suggested); + assertDoesntContain(suggested, "meta_method"); + } + @Override protected String getTestDataPath() { return super.getTestDataPath() + "/completion"; diff --git a/python/testSrc/com/jetbrains/python/inspections/Py3UnresolvedReferencesInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/Py3UnresolvedReferencesInspectionTest.java index 1d0a91458282..a864796fe868 100644 --- a/python/testSrc/com/jetbrains/python/inspections/Py3UnresolvedReferencesInspectionTest.java +++ b/python/testSrc/com/jetbrains/python/inspections/Py3UnresolvedReferencesInspectionTest.java @@ -95,6 +95,11 @@ public class Py3UnresolvedReferencesInspectionTest extends PyTestCase { doTest(); } + // PY-19702 + public void testMetaclassAttribute() { + doTest(); + } + public void testMetaclassStub() { doMultiFileTest("a.py"); final Project project = myFixture.getProject();